Редактирование PII и PCI в запросе, контексте и ответе в графе RAG
Замена данных PII и PCI в трех точках графа RAG: перед поиском, перед запросом и перед интерфейсом, с проверками на качество обработки для случаев карт, номеров социального страхования и других данных.
Защита как запроса, так и ответа — маскировка персональных данных
Пайплайны RAG, которые индексируют тарифные таблицы, документы счетов или записи обслуживания клиентов, со временем могут столкнуться с номерами социального страхования, данными карт PAN и именами в запросе пользователя или в полученном контексте. Меры защиты должны действовать в обоих направлениях: маскировать конфиденциальные данные во входящем запросе, очищать полученный контекст перед его использованием и обрабатывать ответ модели перед отображением в интерфейсе.
Хорошие новости
Servis Azure AI Language (и связанные с ним функции обработки персональных данных) может с высокой точностью определять распространенные типы сущностей, что служит первой линией защиты. Сочетание этого с правилами обработки полей из списка разрешенных в коде приложения покрывает большинство демонстрационных случаев и многие производственные сценарии — особенно когда дополнительный уровень защиты обеспечивают механизмы Foundry в портале.
Изменения в коде по сравнению с предыдущей реализацией
Расширьте график с тремя точками контроля:
- Защитник запросов — обнаруживайте/маскируйте персональные и финансовые данные в тексте пользователя перед вставкой/поиском
- Защитник контекста — маскируйте сущности в полученных фрагментах текста
- Защитник ответов — проводите окончательную проверку сгенерированного ответа
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..
Ведите структурированные журналы записей о том, что было маскировано (тип сущности + смещение), но никогда не храните сами секретные данные, для целей аудита.
Выполнение: действительно ли это работает
Простой запрос без персональных или финансовых данных
Ожидайте, что текст пройдет через все три механизма защиты без изменений, и будет получен обычный ответ на поиск.
Запрос с фиктивными данными типа PCI
Установите тестовый PAN; убедитесь, что механизм защиты запросов маскирует его перед поиском, и что в логах отображается обнаружение данных класса CreditCard без сохранения цифр.
Запрос с фальшивыми персональными данными (SSN)
Убедитесь, что значения, имитирующие SSN, также маскируются аналогичным образом.
Извлечение имени владельца счета из полученного контекста
Даже если вопрос пользователя чистый, полученный текст PDF может содержать имена. Механизм защиты контекста должен маскировать их до того, как модель чата увидит этот фрагмент; механизм защиты ответов предотвращает повторное генерирование имени моделью.
Практические рекомендации
- Сохраняйте набор «золотых» промптов с чистыми данными, данными типа PCI и PII, а также вариантами с разным регистром
- Предпочитайте маскировку удалению, чтобы структура предложений оставалась читаемой
- Никогда не записывайте сырые данные PAN/SSN «для отладки»
- Сочетайте это с правилами портала Foundry для категорий, связанных с утечками данных или вредоносным поведением, которые не контролируются слоем Python
Механизмы защиты PII/PCI не являются дополнительными улучшениями для демонстрации RAG — они являются частью условий работы системы по поиску информации. Каждый запрос, контекст и ответ должны иметь четкие точки проверки, тесты и логи, подтверждающие, что произошла обработка с информацией без сохранения секретных данных.
При интеграции необходимо блокировать работу в случае сбоев детекторов для маршрутов с высоким уровнем риска (возвращать безопасную ошибку вместо передачи непроверенного контекста). Для внутренних инструментов с низким уровнем риска блокировку следует вводить только при наличии явных показателей превышения порогов и достижении установленного значения. Документируйте, какой режим используется в каждой среде.
При интеграции приостановите работу в случае сбоев детекторов на маршрутах высокого риска (возвращайте безопасную ошибку вместо трансляции непроверенного контекста). Для внутренних инструментов с низким риском разрешите работу только при наличии высокого показателя метрики и достижении порога уведомлений. Задокументируйте, какой режим используется в каждой среде.
При интеграции приостановите работу в случае сбоев детекторов на маршрутах высокого риска (возвращайте безопасную ошибку вместо трансляции непроверенного контекста). Для внутренних инструментов с низким риском разрешите работу только при наличии высокого показателя метрики и достижения порога уведомлений. Задокументируйте, какой режим используется в каждой среде.
При интеграции приостановите работу в случае сбоев детекторов на маршрутах высокого риска (возвращайте безопасную ошибку вместо трансляции непроверенного контекста). Для внутренних инструментов с низким риском разрешите работу только при наличии высокого показателя метрики и достижения порога уведомлений. Задокументируйте, какой режим используется в каждой среде.
При интеграции приостановите работу в случае сбоев детекторов на маршрутах высокого риска (возвращайте безопасную ошибку вместо трансляции непроверенного контекста). Для внутренних инструментов с низким риском разрешите работу только при наличии высокого показателя метрики и достижении порога уведомлений. Задокументируйте, какой режим используется в каждой среде.
При интеграции приостановите работу в случае сбоев детекторов на маршрутах высокого риска (возвращайте безопасную ошибку вместо трансляции непроверенного контекста). Для внутренних инструментов с низким риском разрешите работу только при наличии высокого показателя метрики и достижения порога уведомлений. Задокументируйте, какой режим используется в каждой среде.
При интеграции приостановите работу в случае сбоев детекторов на маршрутах высокого риска (возвращайте безопасную ошибку вместо трансляции непроверенного контекста). Для внутренних инструментов с низким риском разрешите работу только при наличии высокого показателя метрики и достижения порога уведомлений. Задокументируйте, какой режим используется в каждой среде.
При интеграции при отказе детекторов на маршрутах с высоким уровнем риска необходимо перейти на режим закрытых сбоев (возвращать безопасную ошибку вместо передачи непроверенного контекста). Для внутренних инструментов с низким уровнем риска переход на режим открытых сбоев допускается только при наличии высоких показателей метрик и достижении порога уведомлений. Необходимо задокументировать, какой режим используется в каждой среде.
При интеграции при отказе детекторов на маршрутах с высоким уровнем риска необходимо перейти на режим закрытых сбоев (возвращать безопасную ошибку вместо передачи непроверенного контекста). Для внутренних инструментов с низким уровнем риска переход на режим открытых сбоев допускается только при наличии высоких показателей метрик и достижения порога уведомлений. Необходимо задокументировать, какой режим используется в каждой среде.
При интеграции при отказе детекторов на маршрутах с высоким уровнем риска необходимо перейти на режим закрытых сбоев (возвращать безопасную ошибку вместо передачи непроверенного контекста). Для внутренних инструментов с низким уровнем риска переход на режим открытых сбоев допускается только при наличии высоких показателей метрик и достижения порога уведомлений. Необходимо задокументировать, какой режим используется в каждой среде.
При интеграции приостановите работу в случае сбоев детекторов на маршрутах высокого риска (возвращайте безопасную ошибку вместо трансляции непроверенного контекста). Для внутренних инструментов с низким риском разрешите работу только при наличии явных показателей и достижении порога уведомлений. Задокументируйте, какой режим используется в каждой среде.
При интеграции приостановите работу в случае сбоев детекторов на маршрутах высокого риска (возвращайте безопасную ошибку вместо трансляции непроверенного контекста). Для внутренних инструментов с низким риском разрешите работу только при наличии явных показателей и достижения порога уведомлений. Задокументируйте, какой режим используется в каждой среде.