Course Content
Applied AI Engineering: From Prompt to Production
9 sections · 29 lessons
Privacy and personal data
During a review, an HR partner asked one question that the team could not answer on the spot: "If an employee tells PolicyPal about their pregnancy, where does that sentence end up, and for how long?"
The honest answer, after an afternoon of checking, was six places. The model provider received it. The application log kept it for 90 days. The trace kept it as an attribute, for as long as traces are kept. The review queue might show it to a reviewer. If the question was ever sampled into the eval set, it would be stored in the repository permanently. And someone had once exported a slice of the eval set to a spreadsheet to share with HR.
None of these were decisions. They were defaults. Privacy in an AI system is mostly about replacing defaults with decisions: what data flows where, for what purpose, for how long and who can see it.
Map the flow first
You cannot protect data you have not located. The team drew the map, and then decided each row.
| Place | What it holds | Retention | Who can read |
|---|---|---|---|
| Model provider | The prompt, including the question | Per the data-processing agreement; no training | Provider under contract |
| Text store | Question and answer, redacted | 30 days | On-call engineers, access logged |
| Traces and logs | Ids, scores, tokens, cost; no text | 90 days | Engineering |
| Answer cache | Policy answers only; nothing personal | 24 hours | The service |
| Review queue | Sampled answers, redacted | Until reviewed, then deleted | Two named HR reviewers |
| Eval set and router training data | Selected cases, redacted and checked by a person | Permanent, in the repository | Engineering |
Some choices on this map come from earlier sections: no text in traces (Section 7), no personal answers in the cache (Section 7), no diagnosis extracted from certificates (Section 8). The new work is redaction and retention. The spreadsheet was deleted, and exporting eval data now requires a request.
The legal frame depends on where you operate. Harbourline is covered by India's Digital Personal Data Protection Act, 2023 for its Indian staff and by UK data protection law for its Manchester office. Both expect the same broad habits: collect data for a stated purpose, keep only what that purpose needs, protect it, delete it when it is no longer needed, and respond to people's requests about their data. This is not legal advice; involve your privacy or legal team early, and bring them the map, because it answers most of their questions.
Redact with Presidio
Redaction replaces personal data in text with a placeholder. PolicyPal uses Microsoft's open-source Presidio, which combines named-entity recognition for things like names with pattern recognisers for things like email addresses, and lets you add your own patterns. Harbourline adds three: its employee ID format, and two common Indian identifiers.
1# policypal/pii.py2from presidio_analyzer import AnalyzerEngine, Pattern, PatternRecognizer3from presidio_anonymizer import AnonymizerEngine45analyzer = AnalyzerEngine() # uses a spaCy English model for names6for entity, regex, score in [7 ("EMPLOYEE_ID", r"\bHL-\d{6}\b", 0.9),8 ("IN_PAN", r"\b[A-Z]{5}\d{4}[A-Z]\b", 0.8),9 ("IN_AADHAAR", r"\b\d{4}\s?\d{4}\s?\d{4}\b", 0.6),10]:11 analyzer.registry.add_recognizer(PatternRecognizer(12 supported_entity=entity, patterns=[Pattern(entity.lower(), regex, score)]))13anonymizer = AnonymizerEngine()1415ENTITIES = ["PERSON", "EMAIL_ADDRESS", "PHONE_NUMBER", "EMPLOYEE_ID", "IN_PAN", "IN_AADHAAR"]1617def redact(text: str) -> str:18 found = analyzer.analyze(text=text, language="en", entities=ENTITIES, score_threshold=0.5)19 return anonymizer.anonymize(text=text, analyzer_results=found).textBy default, each detected span is replaced by its entity type in angle brackets. A message becomes:
before: My manager Rahul Verma (HL-104233) refused my leave. Call me on +91 98450 12345.after: My manager <PERSON> (<EMPLOYEE_ID>) refused my leave. Call me on <PHONE_NUMBER>.Redaction runs on everything before it is stored: the text store, the review queue and any case promoted to the eval set. It also runs on the question before it goes to the model, because a policy answer never needs a name, an ID or a phone number, and it costs about 15 milliseconds. It does not remove the content of the question. "My wife is pregnant; how much paternity leave do I get?" needs the pregnancy to be answered. Minimisation means removing what the purpose does not need, not everything personal.
Redaction is imperfect, and you should measure how imperfect. On 300 hand-labelled messages, Presidio with the small spaCy model found 100% of employee IDs and emails, but only 91% of person names; Indian names and names written in lower case were missed most often. A larger spaCy model raised names to 95%, at about three times the latency. Because redaction leaks a little, it is one layer among several: short retention, logged access and no text in traces are what protect the names it misses.
Permissions belong in retrieval
Not every policy is for everyone. Harbourline's index includes an executive compensation policy, HR's internal guidance on disciplinary cases and IT's security incident runbooks. An early version relied on the system prompt: "Do not reveal content from restricted documents." A red-team attempt got a summary of the disciplinary guidance in three messages.
The fix is the same pattern as the country filter from Section 3. Each chunk carries an audience field from the policy register, and retrieval removes chunks the user may not see before ranking, using the groups from their login.
1import numpy as np23def allowed_mask(meta, user) -> np.ndarray:4 country_ok = np.isin(meta.country, [user.country, "Global"])5 audience_ok = np.array(["all" in aud or bool(set(aud) & user.groups) for aud in meta.audience])6 return country_ok & audience_okA chunk the model never receives cannot be leaked, summarised or hinted at, whatever the question. Test it the way the red team did: ask for restricted content from an ordinary account and check that zero restricted chunks were retrieved, not merely that the answer looks safe.
Rights and retention
People have the right to ask what you hold about them and, in many cases, to have it deleted. PolicyPal's design makes that tractable. The text store is keyed by user id, so an access or deletion request is a single query. Traces hold no text. The eval set and training data are redacted and checked by a person before they are saved, so they no longer identify anyone, and they are not in scope for an individual request. Retention is enforced by jobs that delete expired rows nightly, not by a policy document that says they will be.
Check your understanding
0 of 3 answered
1.Why does PolicyPal redact names before sending a question to the model, but keep a detail like a pregnancy?
2.An early version protected restricted documents with a prompt rule. Why is filtering them out of retrieval better?
3.Presidio found 91% of person names in testing. What is the right response?