Up until now, this course has been about getting LLM apps and RAG systems to work. Starting this week, we shift toward making them safe to operate. Nowhere does that matter more than personally identifiable information (PII). If your chatbot, RAG assistant, or support agent handles names, phone numbers, SSNs, or medical details, you need a repeatable way to find that data, decide what to do with it, and prove you did it consistently. That's the job of Microsoft Presidio. Presidio is an open-source, MIT-licensed toolkit with two halves. The Analyzer finds PII using a combination of techniques: Named Entity Recognition (NER) models (like spaCy's en_core_web_lg) for things like PERSON and LOCATION, regex patterns for structured identifiers like phone numbers or credit cards, checksum validation for entities that have a built-in check digit (credit card numbers, some ID formats), and context words (terms like "SSN:" or "patient" near a candidate token) that boost confidence. The Anonymizer then acts on what the Analyzer finds, using operators such as replace (swap in a placeholder), mask (partially obscure, e.g. show only the last 4 digits), redact (remove entirely), hash, and encrypt (reversible, for cases where you need to restore the original later — this is what enables "deanonymize" workflows). You install it locally and for free with pip install presidio-analyzer presidio-anonymizer plus an NLP engine like spaCy. As of 2025, Presidio has added GPU device control (so you can pin the NLP engine to a GPU for throughput) and a transformer-based MedicalNERRecognizer for clinical text. A table helps organize the core building blocks:
| Component | Role | Examples |
|---|---|---|
| Analyzer | Detects PII entities in text | NER (PERSON, LOCATION), regex (PHONE_NUMBER), checksum (CREDIT_CARD), context words |
| Anonymizer | Acts on detected entities | replace, mask, redact, hash, encrypt |
| NLP engine | Powers the NER models | spaCy en_core_web_lg, transformer-based MedicalNERRecognizer (2025) |
| Custom recognizer | Extends detection to domain-specific IDs | student ID regex, internal ticket number pattern |
Misconception #1: "Presidio catches 100% of PII, so if it runs, the text is safe to release." This is false, and Microsoft says so directly in its own docs — Presidio does not guarantee full recall. Every detector, statistical or rule-based, trades off false positives against false negatives. A rare name format, an SSN written without dashes, or a phone number in an unusual international format can slip through. Treat Presidio's output as a strong first pass, not a certification of "PII-free." Misconception #2: "The default recognizers cover every kind of PII I care about." Out of the box, Presidio ships recognizers for common entities: PERSON, PHONE_NUMBER, EMAIL_ADDRESS, CREDIT_CARD, US_SSN, and similar. But your organization almost certainly has PII that's specific to it — a student ID format, an internal case number, a customer account number. None of the defaults know about those. You have to write a custom recognizer (typically a regex-based PatternRecognizer) and register it alongside the built-ins. The defaults and your custom recognizers run together, not as a replacement for each other. This is exactly why we don't stop at input scanning. Output validation treats the model's response as untrusted, the same way we treat a user's input as untrusted — this is OWASP's LLM05: Improper Output Handling. If you only validate what goes into the model and blindly trust what comes out, you're exposed to injected instructions being echoed into a downstream system, a hallucinated identifier being logged, or PII that was anonymized on the way in getting reconstructed or re-stated on the way out. Concretely, output validation means: validating the response against a schema (Guardrails AI's Guard.for_pydantic() is built exactly for this — it checks the LLM's output against a Pydantic model and can re-ask the model if it fails), running regex/type checks, applying allow/deny lists for certain terms or actions, scanning for PII in the response text itself, and re-prompting when validation fails rather than passing bad output downstream. Put together, a defensible privacy pipeline scans PII at multiple points, not just one: input arrives → analyze/anonymize it → if you're doing RAG, scan the retrieved chunks too (they can carry PII from your own knowledge base) → assemble the prompt → send to the LLM → scan the output → store only a redacted trace. Redacting only your logs is not enough, because sensitive tokens can still be echoed back to the user or acted upon by a downstream tool before they ever hit a log line. This is where LLM Guard's output scanners come in — Sensitive/PII, Deanonymize, MaliciousURLs, and NoRefusal can chain after your model call so that PII anonymized on input is either correctly restored (deanonymized) for an authorized user or blocked outright, tying together LLM05 (Improper Output Handling) and LLM02 (Sensitive Information Disclosure) into one enforceable pipeline. The throughline for this week: PII protection isn't a single library call, it's a pipeline with multiple checkpoints, tuned recognizers for your domain, and validation on both ends of the model call.