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:
| Path | Content |
|---|---|
| Original | Contact 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
| Entity | Example | Pattern |
|---|---|---|
EMAIL | user@example.com | Standard email format |
PHONE | (555) 123-4567, +1-555-123-4567 | US phone with optional country code |
SSN | 123-45-6789 | US Social Security Number |
CREDIT_CARD | 4111 1111 1111 1111 | 16-digit card with optional separators |
IBAN | DE89370400440532013000 | International Bank Account Number |
IP_ADDRESS | 192.168.1.1 | IPv4 address |
PASSPORT | AB1234567 | Alphanumeric 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
-
Use
blockfor high-sensitivity agents (financial, healthcare). Rejecting a message is safer than risking PII reaching the LLM. -
Use
redactfor general-purpose agents where users may accidentally include PII but the request can still be fulfilled without the sensitive values. -
Use
escalatein production when you want a detection audit trail without disrupting the user. Feed the events into your compliance monitoring pipeline. -
Restrict entity types to what your agent actually handles. If your agent only processes financial data, there is no point scanning for passport numbers.
-
Test against realistic data. The built-in patterns target US formats. Verify they match the PII formats used by your users.
-
Pair
PIIGuardwithContentFilter.PIIGuardcatches structured PII (emails, SSNs).ContentFiltercatches domain-specific sensitive terms (project names, internal codes, SQL keywords). -
Review detection logs regularly. Exposure patterns change over time. Audit the Analytics API output to tune your guardrail configuration.