Edición de PII y PCI en la consulta, el contexto y la respuesta en un grafo RAG
Redacción de PII y PCI en tres puntos de un grafo RAG: antes de la búsqueda, antes del prompt y antes de la interfaz de usuario, con pruebas de control para casos de datos limpios, tarjetas y números de seguro social.
Protección de la consulta y la respuesta: redacción de PII y PCI
Los pipelines RAG que indexan horarios de tarifas, documentos de cuentas o transcripciones de soporte eventualmente encontrarán números de Seguro Social, códigos PAN de tarjetas y nombres tanto en la consulta del usuario como en el contexto recuperado. Las medidas de seguridad deben aplicarse en ambas direcciones: redactar las partes sensibles en la pregunta recibida, limpiar los fragmentos recuperados antes de que ingresen al prompt y sanitizar la respuesta del modelo antes de que llegue a la interfaz de usuario.
Buenas noticias
Azure AI Language (y las funciones relacionadas con PII) puede detectar tipos comunes de entidades con una precisión útil como primera línea de defensa. Combinar esto con políticas de campos permitidos en el código de la aplicación cubre la mayoría de los casos de demostración y muchos casos en producción, especialmente cuando las medidas de seguridad de Portal Foundry añaden una segunda capa de protección.
Cambios en el código respecto a la implementación anterior
Amplíe el gráfico con tres puntos de control:
- Protección contra consultas — detecte/redacte información personal identificable/PCI en el texto del usuario antes de incrustarlo o buscarlo
- Protección del contexto — redacte las entidades dentro de los fragmentos recuperados
- Protección de las respuestas — revisión final de la respuesta generada
mkdir AISearchGRManualVectorization && cd AISearchGRManualVectorization && \
uv init .
uv add langgraph langchain-core langchain-openai langchain-text-splitters \
azure-ai-documentintelligence azure-core \
langchain-azure-ai azure-search-documents \
azure-ai-textanalytics \
python-dotenv
# Add all files from git repo or clone repository
@dataclass
class RedactionResult:
text: str
pii_found: bool = False
pci_found: bool = False
categories: List[str] = field(default_factory=list)
error: Optional[str] = None
def redact_text(text: str) -> RedactionResult:
if not text or not text.strip():
return RedactionResult(text=text)
client = _get_client()
results = client.recognize_pii_entities([text], language="en")
result = results[0]
if result.is_error:
return RedactionResult(text=text, error=result.error.message)
categories = [e.category for e in result.entities]
return RedactionResult(
text=result.redacted_text,
pii_found=len(categories) > 0,
pci_found=any(c in PCI_CATEGORIES for c in categories),
categories=categories,
)
def redact_query_node(state: ChatFileSearchState) -> ChatFileSearchState:
if not state.get("query"):
return state
result = guardrails.redact_text(state["query"])
if result.pii_found:
kind = "PCI" if result.pci_found else "PII"
print(f"[guardrail] {kind} detected and redacted in query")
state["query"] = result.text
return state
def redact_response_node(state: ChatFileSearchState) -> ChatFileSearchState:
if not state.get("answer"):
return state
result = guardrails.redact_text(state["answer"])
if result.pii_found:
kind = "PCI" if result.pci_found else "PII"
print(f"[guardrail] {kind} detected and redacted in response")
state["answer"] = result.text
return state
def generate_node(state: ChatFileSearchState) -> ChatFileSearchState:
if not state.get("query"):
state["answer"] = None
return state
if not state["retrieved"]:
context = "No relevant content was found in the uploaded files."
else:
context = "\n\n---\n\n".join(
f"[{doc.metadata.get('file_name', 'unknown')}] {doc.page_content}"
for doc in state["retrieved"]
)
context = guardrails.redact_text(context).text # <-- checkpoint 2
prompt = (
"Answer the question using only the context below, which comes from "
"files the user uploaded to this chat. If the answer isn't in the "
"context, say you don't know - don't make anything up.\n\n"
"Format the answer in markdown: use a short bolded summary line "
"first, then bullet points or a table for any figures, dates, or "
"line items. Keep prose minimal - prefer structure over paragraphs.\n\n"
f"Context:\n{context}\n\n"
f"Question: {state['query']}"
)
response = llm.invoke(prompt)
state["answer"] = response.content
return state
graph.add_edge("ingest", "redact_query")
graph.add_edge("redact_query", "retrieve")
graph.add_edge("retrieve", "generate")
graph.add_edge("generate", "redact_response")
graph.add_edge("redact_response", END)
┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────────┐ ┌─────┐
│ ingest ├─────►│ redact_query ├─────►│ retrieve ├─────►│ generate ├─────►│ redact_response ├─────►│ END │
└──────────┘ └──────────────┘ └──────────┘ └──────────┘ └─────────────────┘ └─────┘
uv run python -m aisearchgrmanualvectorization.main \
--session demo1 \
--query "What's the overdraft protection fee?"
Q: What's the overdraft protection fee?
A: **Overdraft protection transfer fee: $10.00 per occurrence**
- Overdraft protection transfers from a linked savings account are subject to a $10.00 fee per occurrence (as stated in the Overdraft Protection disclosure).
- If no linked savings account, insufficient funds fee: $34.00 per item (up to four items per day).
Sources:
- sample_bank_statement.pdf (chunk 7): #### Important Account Disclosures ##### Overdraft Protection If a transaction is presented that ...
- sample_bank_statement.pdf (chunk 8): ##### Interest Rate & Annual Percentage Yield This account earns interest at a variable rate, curre...
- sample_bank_statement.pdf (chunk 9): ##### Monthly Maintenance Fee A $12.00 monthly maintenance fee applies unless one of the following ...
- sample_bank_statement.pdf (chunk 3): <td>-$10.44</td> <td>$8,096.52</td> </tr> <tr> <td>02/07/2026</td> <td>Greenfield Grocers</td> <td>D...
uv run python -m aisearchgrmanualvectorization.main \
--session demo1 \
--query "My card number is 4111 1111 1111 1111, what's the late payment fee on my loan?"
[guardrail] PCI detected and redacted in query
Q: My card number is 4111 1111 1111 1111, what's the late payment fee on my loan?
A: **Short answer: Late payment fee is $35.00 or 5% of the payment, whichever is less.**
- Late Payment Fee: $35.00 or 5% of the payment amount, whichever is less
- Delinquent threshold: payment not received within 15 calendar days of its Scheduled Payment Date
- Default threshold: payment unpaid 30 calendar days past Scheduled Payment Date
Sources:
- sample_loan_agreement.pdf (chunk 7): ### 6. Late Payments and Default A payment not received within 15 calendar days of its Scheduled Pa...
- sample_loan_agreement.pdf (chunk 1): ## 2. Key Loan Terms <table> <tr> <th>Principal Amount</th> <th>$18,000.00</th> <th>Loan Term</th>...
- sample_loan_agreement.pdf (chunk 6): A complete 48-month amortization schedule is available from the Lender upon request. Figures above a...
- sample_bank_statement.pdf (chunk 9): ##### Monthly Maintenance Fee A $12.00 monthly maintenance fee applies unless one of the following ...
uv run python -m aisearchgrmanualvectorization.main \
--session demo1 \
--query "My SSN is 123-45-6789 - can you also tell me the monthly payment on the loan?"
[guardrail] PII detected and redacted in query
Q: My SSN is 123-45-6789 - can you also tell me the monthly payment on the loan?
A: **Monthly payment is $454.37**
- Monthly Payment: $454.37
Sources:
- sample_loan_agreement.pdf (chunk 1): ## 2. Key Loan Terms <table> <tr> <th>Principal Amount</th> <th>$18,000.00</th> <th>Loan Term</th>...
- sample_loan_agreement.pdf (chunk 3): "Scheduled Payment Date" means the 15th calendar day of each month during the Loan Term, or the next...
- sample_loan_agreement.pdf (chunk 6): A complete 48-month amortization schedule is available from the Lender upon request. Figures above a...
- sample_loan_agreement.pdf (chunk 4): <table> <tr> <th>Payment #</th> <th>Payment Amount</th> <th>Interest</th> <th>Principal</th> <th>Rem...
uv run python -m aisearchgrmanualvectorization.main \
--session demo1 \
--query "Who is the account holder on the bank statement?"
[guardrail] PII detected and redacted in query
Q: Who is the account holder on the bank statement?
A: **I don't know — the statement redacts that name.**
- The document shows "**************" in place of the name (redacted).
- No unredacted name or identifying information for that field appears in the provided context.
Sources:
- sample_bank_statement.pdf (chunk 0): # SAMPLE STATEMENT - SYNTHETIC TEST DATA - NOT A REAL FINANCIAL DOCUMENT # Meridian Trust Bank (fi...
- sample_bank_statement.pdf (chunk 6): <!-- PageBreak --> <table> <tr> <th>Date</th> <th>Description</th> <th>Type</th> <th>Amount</th> <...
- sample_bank_statement.pdf (chunk 10): ##### Error Resolution & Your Rights In case of errors or questions about electronic transfers, tel...
- sample_bank_statement.pdf (chunk 8): ##### Interest Rate & Annual Percentage Yield This account earns interest at a variable rate, curre..
Guarde registros estructurados de lo que se redactó (tipo de entidad + posición), nunca los datos secretos en bruto, para fines de auditoría.
Ejecución: ¿funciona realmente?
Una consulta simple sin datos PII/PCI
Se esperará que el texto salga sin cambios tras pasar por los tres puntos de control y que se obtenga una respuesta normal de búsqueda.
Consulta que contiene datos PCI falsos
Inserte un PAN de prueba; confirme que la protección contra consultas lo enmascare antes de realizar la búsqueda, y que los registros muestren una detección de clase CreditCard sin almacenar los dígitos.
Consulta que contiene PII falsa (SSN)
Confirme que los valores con formato de SSN se censuren de manera similar.
Extraer el nombre del titular de la cuenta del contexto recuperado
Incluso si la pregunta del usuario está limpia, el texto PDF recuperado puede contener nombres. La protección de contexto debe enmascararlos antes de que el modelo de chat los vea; la protección de respuestas evita que el modelo genere nuevamente esos nombres.
Notas operativas
- Mantenga un conjunto de prompts estándar con versiones limpias, PCI, PII y en mayúsculas/minúsculas
- Preferir la enmascaramiento sobre la eliminación para que la estructura de las oraciones siga siendo legible
- Nunca registre PANs/SSNs en bruto “para depuración”
- Combínelo con las normas del portal Foundry para categorías de fugas de seguridad o daños que la capa en Python no maneja
Cierre
Las normas de protección de PII/PCI no son un detalle opcional en una demostración RAG: forman parte del contrato de recuperación de información. Tanto las consultas como el contexto y las respuestas requieren puntos de control explícitos, pruebas y registros que demuestren que se realizó la redacción sin conservar el secreto.
Al integrar, se debe fallar de forma cerrada ante interrupciones en los detectores en rutas de alto riesgo (devolver un error seguro en lugar de transmitir contexto sin revisar). En herramientas internas de menor riesgo, solo se debe fallar de forma abierta cuando se superen un umbral métrico elevado y un límite de paginación. Documente qué modo utiliza cada entorno.
Al integrar, active el fallo total en caso de interrupciones del detector en rutas de alto riesgo (devuelva un error seguro en lugar de transmitir contexto sin revisar). Para herramientas internas de menor riesgo, active el fallo parcial solo con una métrica evidente y un umbral de notificación. Documente qué modo utiliza cada entorno.
Al integrar, active el fallo total en caso de interrupciones del detector en rutas de alto riesgo (devuelva un error seguro en lugar de transmitir contexto sin revisar). Para herramientas internas de menor riesgo, active el fallo parcial solo con una métrica evidente y un umbral de notificación. Documente qué modo utiliza cada entorno.
Al integrar, active el fallo total en caso de interrupciones del detector en rutas de alto riesgo (devuelva un error seguro en lugar de transmitir contexto sin revisar). Para herramientas internas de menor riesgo, active el fallo parcial solo con una métrica evidente y un umbral de notificación. Documente qué modo utiliza cada entorno.
Al integrar, active el fallo total en caso de interrupciones del detector en rutas de alto riesgo (devuelva un error seguro en lugar de transmitir contexto sin revisar). Para herramientas internas de menor riesgo, active el fallo parcial solo con una métrica evidente y un umbral de notificación. Documente qué modo utiliza cada entorno.
Al integrar, active el fallo total en caso de interrupciones del detector en rutas de alto riesgo (devuelva un error seguro en lugar de transmitir contexto sin revisar). Para herramientas internas de menor riesgo, active el fallo parcial solo con una métrica evidente y un umbral de notificación. Documente qué modo utiliza cada entorno.
Al integrar, active el fallo total en caso de interrupciones del detector en rutas de alto riesgo (devuelva un error seguro en lugar de transmitir contexto sin revisar). Para herramientas internas de menor riesgo, active el fallo parcial solo con una métrica evidente y un umbral de notificación. Documente qué modo utiliza cada entorno.
Al integrar, active el fallo total en caso de interrupciones del detector en rutas de alto riesgo (devuelva un error seguro en lugar de transmitir contexto sin revisar). Para herramientas internas de menor riesgo, active el fallo parcial solo con una métrica evidente y un umbral de notificación. Documente qué modo utiliza cada entorno.
Al integrar, active el fallo total en caso de interrupciones del detector en rutas de alto riesgo (devuelva un error seguro en lugar de transmitir contexto sin revisar). Para herramientas internas de menor riesgo, active el fallo parcial solo con una métrica evidente y un umbral de notificación. Documente qué modo utiliza cada entorno.
Al integrar, active el fallo total en caso de interrupciones del detector en rutas de alto riesgo (devuelva un error seguro en lugar de transmitir contexto sin revisar). Para herramientas internas de menor riesgo, active el fallo parcial solo con una métrica evidente y un umbral de notificación. Documente qué modo utiliza cada entorno.
Al integrar, active el fallo total en caso de interrupciones del detector en rutas de alto riesgo (devuelva un error seguro en lugar de transmitir contexto sin revisar). Para herramientas internas de menor riesgo, active el fallo solo con una métrica evidente y un umbral de notificación. Documente qué modo utiliza cada entorno.
Al integrar, active el fallo total en caso de interrupciones del detector en rutas de alto riesgo (devuelva un error seguro en lugar de transmitir contexto sin revisar). Para herramientas internas de menor riesgo, active el fallo solo con una métrica evidente y un umbral de notificación. Documente qué modo utiliza cada entorno.