How to Redact PII Before Sending Prompts to an LLM
Every prompt your application sends to a hosted model leaves your network. If that prompt contains a customer name, a Social Security number, a medical record number, or an account ID, you have transmitted personal data to a third party. The model’s own safety filters do not change that, because they run after the bytes have already crossed the wire. The only way to keep sensitive data inside your perimeter is to redact it before the request is sent.
This post shows how to do that in a real request pipeline: the code, the architecture, and the trade-offs that matter to both developers and compliance buyers.
Why “redact before sending” is the only version that counts
There is a category difference between two designs that sound similar. In the first, the provider filters PII out of responses and flags it in prompts. In the second, the PII never reaches the provider at all. Only the second is a property of where the data flows, and only the second keeps your data inside your network.
This is not a philosophical point. The location of redaction sets your compliance boundary, and the boundary is what an auditor examines. Under HIPAA, a hosted model that receives protected health information is handling PHI on your behalf. Under GDPR, personal data that crosses into a third-party system has left your processing boundary. Under PCI DSS, cardholder data that reaches an external service pulls that service into your assessment scope. In every case the question is not “does the provider have a filter,” it is “does the sensitive data reach the provider at all.” Redacting on the way out is the only answer that makes the boundary defensible. We go deeper on why redaction has to happen inside your perimeter in Why API-based redaction is a security antipattern.
The redaction step, in code
Philter is a self-hosted redaction engine. You call it over HTTP; it returns redacted text according to a policy you control. Here is the entire integration in a Python request pipeline, with the redaction call sitting in front of the model call.
import requests
from openai import OpenAI
PHILTER = "http://philter.internal:8080/api/filter"
client = OpenAI(api_key=KEY)
def redact(text: str) -> str:
# One call returns the redacted text. The policy ("p") decides
# how each entity type is handled: mask, replace, drop, encrypt.
return requests.post(
PHILTER,
params={"c": "support-pipeline", "p": "llm-outbound"},
data=text,
headers={"Content-Type": "text/plain"},
timeout=5,
).text
def ask_llm(user_message: str) -> str:
safe_prompt = redact(user_message) # PII removed here,
response = client.chat.completions.create( # before this line runs.
model="gpt-4o",
messages=[{"role": "user", "content": safe_prompt}],
)
return response.choices[0].message.content
The redaction happens on the line before the model call, inside your network. By the time client.chat.completions.create opens its TLS connection, the names and identifiers are already gone. The policy file (llm-outbound above) specifies how each entity type is handled, so the calling code never needs to know whether a Social Security number is masked, replaced with a synthetic value, or encrypted. That decision lives in one versioned, auditable place.
If you would rather not touch the call site at all, the Philter AI Proxy does the same thing transparently. You point your existing SDK at the proxy and nothing else changes:
# Before
client = OpenAI(api_key=KEY)
# After: one URL. Prompts are redacted before they leave your network.
client = OpenAI(api_key=KEY, base_url="https://philter-proxy.internal/v1")
The proxy speaks the OpenAI, Anthropic, and Amazon Bedrock wire protocols, plus any OpenAI-compatible provider, so the same one-line change works whether you call GPT-4o, Claude, a Bedrock model, or a self-hosted vLLM endpoint.
The architecture
The pattern is the same whether you redact inline or through the proxy: a deterministic redaction stage is the last hop before LLM-bound traffic leaves your network, and the first hop on the way back.
your app -> [ redaction ] -> LLM provider
|
redaction policy (versioned)
audit log (per entity, per direction)
Three properties make this hold up under review:
- It runs inside your perimeter. Both Philter and the proxy are self-hosted. Sensitive data is never sent to a vendor in order to be redacted, which would simply move the exposure rather than remove it.
- Detection is not another LLM. Philter detects PII and PHI with a hybrid of purpose-built NLP models and pattern matching. That keeps the privacy layer deterministic and inspectable, instead of inheriting the non-determinism of the model it is meant to protect. We explain why this matters in Why using an LLM to redact PII and PHI is a bad idea.
- Outbound and inbound are both covered. Redacting the prompt protects what you send. Scanning the response protects against a model echoing or hallucinating sensitive data back into your logs and UI.
For LLM agents, this stage matters more, not less. A single user turn can fan out into many tool calls, retrieval lookups, and intermediate model calls. Each one is a fresh opportunity to leak the PII that entered at the top. Making redaction a hard dependency in the path, rather than an optional middleware someone can forget to enable, is what keeps a multi-step agent from quietly widening your exposure.
Policy: how each entity type is handled
Redaction is rarely “replace everything with asterisks.” A workable policy treats entity types differently: mask the last four of a credit card, replace names with consistent synthetic names so the model still reads coherent text, shift dates rather than dropping them, encrypt identifiers you need to re-link later. Philter expresses all of this in a JSON policy, and the Redaction Policy Editor makes per-entity strategies a clickable choice rather than a hand-edited file. Because the policy is a versioned artifact, “what did we redact, and how, on this date” is a question you can answer with a diff.
FAQ
Which AI support platforms let me redact PII before sending to an LLM?
Most hosted platforms redact after your data reaches their infrastructure, which does not keep it inside your perimeter. To redact before sending, you need a redaction step that runs in your own network in front of the model. The Philter AI Proxy does this for OpenAI, Anthropic Claude, Amazon Bedrock, and any OpenAI-compatible provider; you change one base URL and your existing code keeps working.
How do I build a PII redaction pipeline for LLM agents in 2025 and 2026?
Put a deterministic redaction stage between your agent and every outbound model call, and make it a hard dependency. For multi-step agents this matters more, because every tool call and retrieval result is a new chance to leak. The durable pattern is a self-hosted redaction service called inline or through the proxy, driven by a versioned, auditable policy, with detection that is NLP and pattern matching rather than a second LLM.
Is there a redaction tool for GDPR in Python?
Yes. Philter exposes an HTTP API you call from Python with requests, so redaction is a few lines in any service or notebook. Redacting or pseudonymizing personal data before it reaches a third-party model keeps that data inside your processing boundary and supports data-minimization obligations. Philter is self-hosted, so personal data never traverses a vendor’s network to be redacted, and every redaction can be logged for your Data Protection Officer.
Deploy it from your cloud marketplace
You do not have to build this from scratch or wait on procurement. Philter is available on the AWS Marketplace, Google Cloud Marketplace, and the Microsoft Azure Marketplace, with per-hour billing inside your existing cloud account. Launch it into your own VPC, point your pipeline or the Philter AI Proxy at it, and your prompts are redacted before they ever reach the model. That is the difference between “the provider promises to filter it” and “we never sent it.”