Microsoft Presidio · PII detection + redaction
Presidio + Leanroute
Presidio is the open-source PII anonymization library Microsoft maintains. It uses ML-backed recognizers for names, addresses, government IDs, account numbers, IPs, and more across many locales. Run it in your application code before calls hit Leanroute and you get real PII handling without relying on regex.
When to use this
- You have a compliance requirement (HIPAA-adjacent, PDPA, GDPR) that demands PII be stripped before traversing third-party APIs.
- The PII in your prompts is varied — mix of names, account numbers, ID formats, contact info — and you don't want to hand-write regex for each.
- You need locale-specific recognizers (Singapore NRIC, Indian Aadhaar, UK NHS numbers).
Leanroute's built-in redact patterns cover the simpler cases (a known string, an obvious format). For anything ML-shaped, Presidio is the right answer.
Install
pip install presidio-analyzer presidio-anonymizer
python -m spacy download en_core_web_lgThe spaCy model is what powers the named-entity recognition. Larger models = better detection; the en_core_web_lg is a good default (560MB). Locale models exist for English, Spanish, French, German, Italian, Portuguese, Chinese, Japanese, and more.
Chain Presidio in front of Leanroute
The cleanest pattern is application-level: detect + redact in your own code, then call Leanroute with the redacted prompt. Your application owns the PII boundary; Leanroute just dispatches.
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
from openai import OpenAI
analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()
# Point the OpenAI SDK at Leanroute — Presidio runs locally before we dispatch.
leanroute = OpenAI(
api_key="gw_live_YOUR_KEY",
base_url="https://api.leanroute.dev/v1",
)
def chat(prompt: str, model: str = "anthropic/claude-sonnet-4-6") -> str:
# 1. Detect PII entities in the prompt
findings = analyzer.analyze(text=prompt, language="en")
# 2. Anonymize — Presidio replaces matches with <ENTITY_TYPE> tokens
anonymized = anonymizer.anonymize(text=prompt, analyzer_results=findings).text
# 3. Dispatch the redacted prompt through Leanroute
response = leanroute.chat.completions.create(
model=model,
messages=[{"role": "user", "content": anonymized}],
)
return response.choices[0].message.contentPresidio replaces detected PII with tokens like <PERSON>, <EMAIL_ADDRESS>, <PHONE_NUMBER> by default. You can configure custom replacements per entity type if you want them to round-trip through a mapping table.
Optional: Presidio as a proxy service
For multi-language teams or when several services need the same redaction policy, run Presidio as an HTTP service and put it behind a reverse proxy that forwards to Leanroute. Microsoft ships Docker images for both presidio-analyzer and presidio-anonymizer; a small Express/Hono shim in front of them gives you a single endpoint that anonymizes-then-forwards.
See microsoft.github.io/presidio/samples/docker for the reference setup.
Caveats
- No detection is 100%. Test on real samples of your traffic before claiming PII compliance. False negatives happen.
- Latency cost is real. First analyzer call loads the spaCy model (~1–3s); subsequent calls are 10–50ms. Run the analyzer in a long-lived process, not per-request.
- Locale-specific recognizers need configuration. The default analyzer covers US/UK formats; add a custom recognizer or use a locale-specific one for Singapore NRIC, Indian Aadhaar, etc.
- Round-tripping redacted tokens is your job. If you need the model's response to refer back to the original PII, maintain a per-request mapping in your application and substitute on the return path. Presidio doesn't do that part.
See also: Guardrails overview · Lakera Guard · Rebuff · microsoft.github.io/presidio ↗