PII Protection

This guide covers how to prevent personally identifiable information (PII) from reaching LLM providers. You will set up detection, choose a response action, filter specific entity types, and connect detections to your audit pipeline.

Prerequisites: You have an agent running. If not, start with First Agent.

Why you need this

When users talk to an AI agent, they often include sensitive data they may not intend to share: email addresses in support tickets, phone numbers in contact requests, SSNs in financial documents, card numbers in payment conversations. Without a guardrail, that data goes straight to the LLM provider's API and its training pipeline.

PIIGuard intercepts messages on both the input and output paths before anything reaches the model.


Quick setup

Add PIIGuard to any agent:

from sagewai.engines.universal import UniversalAgent
from sagewai.safety.pii import PIIGuard

agent = UniversalAgent(
    name="support-agent",
    model="gpt-4o",
    guardrails=[PIIGuard(action="redact")],
)

This detects and redacts all 7 built-in entity types on both input and output paths.


Actions

PIIGuard supports five actions. Choose based on your compliance requirements.

block — reject the message entirely

PIIGuard(action="block")

When PII is detected, the agent raises GuardrailViolationError. The message never reaches the LLM. Use block when you cannot process PII under any circumstances.

from sagewai.safety.guardrails import GuardrailViolationError

try:
    response = await agent.chat("My SSN is 123-45-6789")
except GuardrailViolationError as e:
    print(f"Blocked: {e}")

redact — replace PII with labels

PIIGuard(action="redact")

When PII is detected, action="redact" currently raises a GuardrailViolationError — it does not forward the scrubbed message to the LLM. To get pass-through behaviour with PII replaced, call guard.detect() and guard.redact() directly and then decide what to forward:

guard = PIIGuard(action="redact")
findings = guard.detect(message)
if findings:
    clean_message = guard.redact(message)
    # Forward clean_message to the agent instead of the original
    response = await agent.chat(clean_message)
else:
    response = await agent.chat(message)

The redacted form replaces each detected value with a labelled placeholder:

PathContent
OriginalContact me at john@example.com or 555-123-4567
After guard.redact()Contact me at [REDACTED_EMAIL] or [REDACTED_PHONE]

warn — log and allow through

PIIGuard(action="warn")

The violation is logged but the message is sent to the LLM unchanged. Use warn during development or when you need an audit trail without disrupting the user.

escalate — emit an event and allow through

PIIGuard(action="escalate")

Emits a GUARDRAIL_ESCALATION event. The message is sent to the LLM unchanged. Wire up a handler to forward detections to your compliance system.

from sagewai.core.events import AgentEvent

async def handle_escalation(event: AgentEvent, data: dict):
    if event == AgentEvent.GUARDRAIL_ESCALATION:
        print(f"PII escalation: {data}")
        # Forward to your compliance system or alert queue

agent.on_event(handle_escalation)

log_only — detect and log silently

PIIGuard(action="log_only")

Behaves like warn: detects, logs, and passes the message through without modification.


Entity types

PIIGuard detects all 7 entity types by default. Narrow the scope when you only care about a subset.

from sagewai.safety.pii import PIIGuard, PIIEntityType

# Block only financial PII
financial_guard = PIIGuard(
    action="block",
    entity_types=[
        PIIEntityType.CREDIT_CARD,
        PIIEntityType.IBAN,
        PIIEntityType.SSN,
    ],
)

# Redact only contact information
contact_guard = PIIGuard(
    action="redact",
    entity_types=[
        PIIEntityType.EMAIL,
        PIIEntityType.PHONE,
    ],
)

Supported entity types

EntityExamplePattern
EMAILuser@example.comStandard email format
PHONE(555) 123-4567, +1-555-123-4567US phone with optional country code
SSN123-45-6789US Social Security Number
CREDIT_CARD4111 1111 1111 111116-digit card with optional separators
IBANDE89370400440532013000International Bank Account Number
IP_ADDRESS192.168.1.1IPv4 address
PASSPORTAB1234567Alphanumeric passport number

Standalone detection

Use PIIGuard outside an agent to scan or clean text directly:

from sagewai.safety.pii import PIIGuard

guard = PIIGuard(action="redact")

text = "Send invoice to john@acme.com, call 555-123-4567, card 4111-1111-1111-1111"

# Detect
findings = guard.detect(text)
for entity_type, matched_text in findings:
    print(f"  {entity_type.value}: {matched_text}")
# EMAIL: john@acme.com
# PHONE: 555-123-4567
# CREDIT_CARD: 4111-1111-1111-1111

# Redact
clean = guard.redact(text)
print(clean)
# "Send invoice to [REDACTED_EMAIL], call [REDACTED_PHONE], card [REDACTED_CARD]"

Batch processing

documents = ["Doc 1 with email@test.com", "Doc 2 with 555-0123", "Doc 3 clean"]

for doc in documents:
    findings = guard.detect(doc)
    if findings:
        print(f"PII found: {len(findings)} entities")
        clean_doc = guard.redact(doc)
        # Forward clean_doc instead of the original

Combining with other guardrails

List guardrails in the order you want them to run. Put PIIGuard first so PII is stripped before other guardrails see the text.

from sagewai.safety.guardrails import ContentFilter, TokenBudgetGuard
from sagewai.safety.hallucination import HallucinationGuard

agent = UniversalAgent(
    name="production-agent",
    model="gpt-4o",
    guardrails=[
        # 1. Strip PII before anything else touches the message
        PIIGuard(action="redact", entity_types=[
            PIIEntityType.EMAIL,
            PIIEntityType.SSN,
            PIIEntityType.CREDIT_CARD,
        ]),
        # 2. Block known injection patterns
        ContentFilter(blocklist=["DROP TABLE", "DELETE FROM"]),
        # 3. Warn when output lacks grounding
        HallucinationGuard(threshold=0.3, action="warn"),
        # 4. Cut off runaway cost
        TokenBudgetGuard(max_usd=2.0),
    ],
)

Monitoring PII events

Track detections through the Analytics API:

from sagewai.admin.analytics import AnalyticsStore

store = AnalyticsStore()

# PIIGuard calls this automatically when action is "escalate" or "log_only"
store.record_guardrail_event(
    agent_name="support-agent",
    event_type="pii",
    entity_types=["EMAIL", "PHONE"],
    count=2,
)

# Query exposure metrics
risks = store.get_risks(agent_name="support-agent")
print(f"PII events: {risks['pii_events']}")
print(f"Total guardrail events: {risks['total_events']}")

Best practices

  1. Use block for high-sensitivity agents (financial, healthcare). Rejecting a message is safer than risking PII reaching the LLM.

  2. Use redact for general-purpose agents where users may accidentally include PII but the request can still be fulfilled without the sensitive values.

  3. Use escalate in production when you want a detection audit trail without disrupting the user. Feed the events into your compliance monitoring pipeline.

  4. Restrict entity types to what your agent actually handles. If your agent only processes financial data, there is no point scanning for passport numbers.

  5. Test against realistic data. The built-in patterns target US formats. Verify they match the PII formats used by your users.

  6. Pair PIIGuard with ContentFilter. PIIGuard catches structured PII (emails, SSNs). ContentFilter catches domain-specific sensitive terms (project names, internal codes, SQL keywords).

  7. Review detection logs regularly. Exposure patterns change over time. Audit the Analytics API output to tune your guardrail configuration.