Strona główna / Artykuły

Redakcja danych PII i PCI w zapytaniu, kontekście oraz odpowiedzi w grafie RAG

Cenzura danych PII i PCI w trzech miejscach w grafie RAG: przed wyszukiwaniem, przed zapytaniem i przed interfejsem użytkownika, z testami kontrolnymi dla przypadków czystych danych, danych kartowych i numerów SSN.

Wydawca: REACTAPP.TOP8 min czytania1908 słów

Źródła: Adding PII/PCI Guardrails to the RAG Pipeline

Ochrona zapytania i odpowiedzi — redakcja danych PII i PCI

Procesy RAG, które indeksują harmonogramy opłat, dokumenty kont lub transkrypcje rozmów z obsługą klienta, w końcu mogą napotkać numery ubezpieczenia społecznego, numery kart PAN oraz imiona w zapytaniu użytkownika lub w pobranych fragmentach tekstu. Zasady bezpieczeństwa muszą obejmować obie strony: należy usunąć wrażliwe dane z wpływającego zapytania, oczyścić pobrane fragmenty przed ich użyciem w promptzie oraz wyeliminować zagrożenia z odpowiedzi modelu przed jej pokazaniem w interfejsie użytkownika.

Dobre wieści

Azure AI Language (oraz powiązane funkcje do ochrony danych PII) potrafi wykrywać powszechne typy entytetów z przyjętą dokładnością, co stanowi pierwszą linię obrony. Połączenie tego z politykami dotyczącymi uprawnień do dostępu do danych w kodzie aplikacji pokrywa większość przypadków demonstracyjnych oraz wiele sytuacji w środowisku produkcyjnym — szczególnie gdy dodatkowo stosuje się zasady bezpieczeństwa portalu Foundry.

Zmiany w kodzie względem poprzedniej implementacji

Rozszerz graf o trzy punkty kontrolne:

  1. Zaawansowany filtr zapytań — wykrywanie i maskowanie danych osobowych/PCI w tekście użytkownika przed umieszczaniem go w wynikach wyszukiwania
  2. Zaawansowany filtr kontekstu — maskowanie elementów wewnątrz pobranych fragmentów tekstu
  3. Zaawansowany filtr odpowiedzi — ostatnia weryfikacja wygenerowanej odpowiedzi
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..

Zachowuj strukturyzowane zapisy tego, co zostało zamaskowane (typ elementu + pozycja), ale nigdy same tajne dane, w celach audytowych.

Egzekucja: czy rzeczywiście działa

Proste zapytanie bez danych osobowych/PCI

Oczekuj tekstu niezmienionego po przesłaniu go przez wszystkie trzy filtry oraz normalnej odpowiedzi z wyników wyszukiwania.

Zapytanie zawierające fałszywe dane PCI

Zainstaluj testowy PAN; upewnij się, że mechanizm ochrony zapytań maskuje go przed wyszukiwaniem, a logi pokazują wykrycie typu CreditCard bez przechowywania cyfr.

Zapytanie zawierające fałszywe dane osobowe (SSN)

Upewnij się, że wartości w kształcie SSN są w podobny sposób zaszyfrowywane.

Wyodrębnianie imienia właściciela konta z pobranego tekstu

Nawet jeśli pytanie użytkownika jest czyste, pobrany tekst PDF może zawierać imiona. Mechanizm ochrony kontekstu musi je zamaskować, zanim model czatowy zobaczy ten fragment; mechanizm odpowiedzi wykrywa ponowne generowanie imienia przez model.

Uwagi operacyjne

  • Zachowuj zestaw „złotych promptów” z czystymi danymi, danymi PCI, danymi osobowymi oraz wersjami z różnej wielkości liter
  • Niech maskowanie będzie preferowane nad usuwaniem, aby struktura zdań pozostała czytelna
  • Nigdy nie rejestruj surowych danych PAN/SSN „w celach debugowania”
  • Łącz to z zasadami bezpieczeństwa portalu Foundry w przypadku kategorii obejmujących jailbreak lub szkodliwe działania, których nie obsługuje warstwa Python
  • Przegląd fałszywych pozytywów dotyczących identyfikatorów biznesowych, które przypominają tajemnice (wewnętrzne kody kont)
  • Zakończenie

    Zasady ochrony danych PII/PCI nie są dodatkowym elementem w demonstracji RAG – stanowią część umowy dotyczącej wyszukiwania informacji. Każde zapytanie, kontekst oraz odpowiedź wymagają wyraźnych punktów kontrolnych, testów oraz logów potwierdzających, że doszło do usunięcia danych bez przechowywania tajemnic.

    Podczas integracji należy natychmiast zakończyć działanie w przypadku awarii detektorów na trasach o wysokim ryzyku (zwrócić bezpieczny błąd zamiast przesyłać nieprzefiltrowany kontekst). W przypadku narzędzi wewnętrznych o niższym ryzyku należy zakończyć działanie tylko wtedy, gdy wystąpi wyraźny wskaźnik i osiągnięty zostanie próg paginacji. Należy udokumentować, który tryb jest używany w każdym środowisku.

    Podczas integracji w przypadku awarii detektorów na trasach o wysokim ryzyku należy wdrażać tryb awarii zamkniętej (zwracać bezpieczny błąd zamiast przesyłać nieprzeanalizowany kontekst). W przypadku narzędzi wewnętrznych o niższym ryzyku tryb awarii otwartej powinien być stosowany tylko przy wystąpieniu wyraźnej metryki i osiągnięciu progu powiadamiania. Należy udokumentować, jaki tryb używa każde środowisko.

    Podczas integracji w przypadku awarii detektorów na trasach o wysokim ryzyku należy wdrażać tryb awarii zamkniętej (zwracać bezpieczny błąd zamiast przesyłać nieprzeanalizowany kontekst). W przypadku narzędzi wewnętrznych o niższym ryzyku tryb awarii otwartej powinien być stosowany tylko przy wystąpieniu wyraźnej metryki i osiągnięciu progu powiadamiania. Należy udokumentować, jaki tryb używa każde środowisko.

    Podczas integracji w przypadku awarii detektorów na trasach o wysokim ryzyku należy wdrażać tryb awarii zamkniętej (zwracać bezpieczny błąd zamiast przesyłać nieprzeanalizowany kontekst). W przypadku narzędzi wewnętrznych o niższym ryzyku tryb awarii otwartej powinien być stosowany tylko przy wystąpieniu wyraźnej metryki i osiągnięciu progu powiadamiania. Należy udokumentować, jaki tryb używa każde środowisko.

    Podczas integracji w przypadku awarii detektorów na trasach o wysokim ryzyku należy wdrażać tryb awarii zamkniętej (zwracać bezpieczny błąd zamiast przesyłać nieprzeanalizowany kontekst). W przypadku narzędzi wewnętrznych o niższym ryzyku tryb awarii otwartej powinien być stosowany tylko przy wystąpieniu wyraźnej metryki i osiągnięciu progu powiadamiania. Należy udokumentować, jaki tryb używa każde środowisko.

    Podczas integracji w przypadku awarii detektorów na trasach o wysokim ryzyku należy wdrażać tryb awarii zamkniętej (zwracać bezpieczny błąd zamiast przesyłać nieprzeanalizowany kontekst). W przypadku narzędzi wewnętrznych o niższym ryzyku tryb awarii otwartej powinien być stosowany tylko przy wystąpieniu wyraźnej metryki i osiągnięciu progu powiadamiania. Należy udokumentować, jaki tryb używa każde środowisko.

    Podczas integracji w przypadku awarii detektorów na trasach o wysokim ryzyku należy wdrażać tryb awarii zamkniętej (zwracać bezpieczny błąd zamiast przesyłać nieprzeanalizowany kontekst). W przypadku narzędzi wewnętrznych o niższym ryzyku tryb awarii otwartej powinien być stosowany tylko przy wystąpieniu wyraźnej metryki i osiągnięciu progu powiadamiania. Należy udokumentować, jaki tryb używa każde środowisko.

    Podczas integracji w przypadku awarii detektorów na trasach o wysokim ryzyku należy wdrażać tryb awarii zamkniętej (zwracać bezpieczny błąd zamiast przesyłać nieprzeanalizowany kontekst). W przypadku narzędzi wewnętrznych o niższym ryzyku tryb awarii otwartej powinien być stosowany tylko przy wystąpieniu wyraźnej metryki i osiągnięciu progu powiadamiania. Należy udokumentować, jaki tryb używa każde środowisko.

    Podczas integracji w przypadku awarii detektorów na trasach o wysokim ryzyku należy wdrażać tryb awarii zamkniętej (zwracać bezpieczny błąd zamiast przesyłać nieprzeanalizowany kontekst). W przypadku narzędzi wewnętrznych o niższym ryzyku tryb awarii otwartej powinien być stosowany tylko przy wystąpieniu wyraźnej metryki i osiągnięciu progu powiadamiania. Należy udokumentować, jaki tryb używa każde środowisko.

    Podczas integracji w przypadku awarii detektorów na trasach o wysokim ryzyku należy wdrażać tryb awarii zamkniętej (zwracać bezpieczny błąd zamiast przesyłać nieprzeanalizowany kontekst). W przypadku narzędzi wewnętrznych o niższym ryzyku tryb awarii otwartej powinien być stosowany tylko przy wystąpieniu wyraźnej metryki i osiągnięciu progu powiadamiania. Należy udokumentować, jaki tryb używa każde środowisko.

    Podczas integracji należy w przypadku awarii detektorów na trasach o wysokim ryzyku używać trybu awarii zamkniętej (zwracać bezpieczny błąd zamiast przesyłać nieprzeanalizowany kontekst). W przypadku narzędzi wewnętrznych o niższym ryzyku należy używać trybu awarii otwartej tylko przy wystąpieniu wyraźnej metryki i osiągnięciu progu powiadamiania. Należy udokumentować, który tryb jest używany w każdym środowisku.

    Podczas integracji należy w przypadku awarii detektorów na trasach o wysokim ryzyku używać trybu awarii zamkniętej (zwracać bezpieczny błąd zamiast przesyłać nieprzeanalizowany kontekst). W przypadku narzędzi wewnętrznych o niższym ryzyku należy używać trybu awarii otwartej tylko przy wystąpieniu wyraźnej metryki i osiągnięciu progu powiadamiania. Należy udokumentować, który tryb jest używany w każdym środowisku.