ai document generation · templates · pii redaction · architecture · privacy by design
Tokenize PII before AI document generation: keep real customer data out of the model
Separate AI drafting from sensitive field insertion. Use typed replacements, validate the draft and restore known mappings at document rendering.
In this article
- AI document generation is two jobs, not one
- The common mistake: real values through the AI phase
- The pattern: tokens through the AI, originals only at render
- Wiring it with Sether
- How this complements provider controls
- What the AI still does well on tokens
- Limitations, honestly
- Frequently asked questions
- Comments
Sether™ / Field notes
ai document generation
Write the document with stand-ins.
Restore authorised originals at the output boundary.
Many document workflows can separate drafting from filling sensitive fields. Replace supported personal values before the model call, keep the mapping in your application and restore recognised replacements at rendering. Validate the draft, fields and final output. This withholds matched originals; unnecessary context, missed values and destination escaping still need attention.
- Generating a document with AI is really two jobs: the model structures and drafts, then a template engine fills in the real values.
- A drafting step often needs field roles rather than literal identities. Derive any required facts deliberately instead of assuming the model can infer them from tokens.
- Redact the JSON payload before the AI call, keep the token-to-value map in your vault, and restore into the final render.
- Pre-request minimisation complements provider retention and contract controls; it does not remove every identifying detail or obligation.
- Sether does the redact and restore in TypeScript and Python, including PII that lives in JSON keys.
AI document generation is two jobs, not one
When a product generates a document with AI, whether that is a contract, a letter, a filled-in template, or a PDF, it feels like one action. Under the hood it is almost always two. First, a model decides structure and wording: which clauses apply, how to phrase a paragraph, what tone fits. Second, a deterministic step drops the actual values into place: this customer's name, this date of birth, this address.
Those two jobs have very different data needs. The drafting job needs to know that a name goes here and an address goes there. It does not need to know that the name is Amara Okafor. The filling job needs the real values, but it is your own code running on your own infrastructure, not a call to a third-party model. Once you see the split, the privacy design almost writes itself.
The common mistake: real values through the AI phase
A one-shot implementation can send a complete customer record even when drafting needs only a subset of its fields. Review that payload first. Provider handling and retention depend on the service and settings; avoiding unnecessary disclosure is a separate design decision.
{
"customer_name": "Amara Okafor",
"date_of_birth": "1990-05-12",
"passport_number": "A1234567",
"billing_address": "12 Marina Road, Lagos",
"template": "welcome_letter"
}
Provider terms, retention and access controls still matter for what you send. A two-stage design lets you reduce that payload before those controls apply, while keeping final field insertion under your own application’s rules.
The pattern: tokens through the AI, originals only at render
Replace supported personal fields before the AI phase. Let the model draft around the resulting references, then check the draft and restore known replacements when your own code renders the document. Review the remaining context too: a distinctive case description can identify someone even after names are removed.
- Redact the payload. Personal fields become tokens: <NAME_uuid>, <DOB_uuid>, <ADDRESS_uuid>. The token-to-value map stays in your vault.
- Call the model with the tokenized payload. It structures and drafts the document, referring to <NAME_uuid> wherever the name belongs.
- Restore. Your render step swaps each token back to its original value as it produces the final document or PDF.
Tokens carry field hints, not the facts inside the originals. The model can omit a token or put it in the wrong place. Validate the output against a template or schema and calculate sensitive business decisions in your own code instead of expecting tokenization to preserve every aspect of reasoning.
Wiring it with Sether
Because document pipelines pass structured records, most of the personal data is in JSON values whose keys name them (customer_name, date_of_birth). Sether's identity pack anchors on those keys, so a single redact call tokenizes the whole payload. The how and why of that key-anchoring is in How to redact PII in JSON and structured data before an LLM.
import {
Sether,
basicDetectors,
identityDetectors,
redactSync,
restoreSync,
} from '@raeven-co/sether';
const sether = new Sether({
detectors: [...basicDetectors, ...identityDetectors],
});
// 1. Redact the record before the AI phase
const safePayload = redactSync(JSON.stringify(record), {
detectors: sether.detectors,
vault: sether.vault,
});
// 2. The model drafts the document from tokens only
const draft = await callModel(safePayload); // provider sees <NAME_...>, never the name
// 3. Restore real values as you render the final document
const finalDocument = restoreSync(draft, { vault: sether.vault });
from sether import Sether, basic_detectors, identity_detectors
import json
sether = Sether(detectors=[*basic_detectors, *identity_detectors])
safe_payload = sether.redact_sync(json.dumps(record))
draft = call_model(safe_payload) # provider sees tokens only
final_document = sether.restore_sync(draft)
If the draft is JSON, for example template fields for a document API, restore it with restoreJson (npm 0.8.0) or restore_json (PyPI 0.4.0) instead. Plain text restore puts originals back unescaped, so a value containing a quote could change the structure. What changed in 0.8.0.
Both stages need access to the matching retained vault. One instance is the simplest arrangement. Separate workers need a compatible shared mapping strategy you operate; the TypeScript lookup contract is synchronous, so an ordinary async Redis client is not a drop-in vault. Cover the intended rendering delay, isolate conversations and decide what happens if an entry expires.
How this complements provider controls
A provider’s training and retention controls describe what happens to received data. Pre-request replacement changes which matched values are sent in the first place. These controls can complement each other; neither establishes that the remaining document context is anonymous.
Use a precise statement in your review: “these fields are replaced before the provider request, these fields remain, and known replacements restore only at render.” That is more useful than “we never send personal data” when the surrounding record has not been assessed. For legal context, see Is it GDPR-compliant to send customer data to OpenAI?.
What the AI still does well on tokens
A model may help draft structure and wording without a literal name or address. Conditions need explicit facts, though. If a document requires an under-18 clause, your code can compute the required age category locally and provide that minimal fact; the model cannot infer age from an opaque DOB token. Validate the resulting clause before rendering.
Evaluate quality on your actual templates. Test missing, repeated and misplaced fields, not only natural wording. Email-format checks, date calculations, recipient validation and authorization should be deterministic steps rather than assumptions about what the model understood.
A useful fictional fixture is a welcome letter with a name, an email and a locally computed age category. Assert that the model request contains replacements and the category, the reply contains only allowed fields, and the final renderer escapes restored text for its destination. JSON-safe restore protects JSON syntax; it does not also escape HTML, SQL or a PDF template.
| Stage | Keep the responsibility here |
|---|---|
| Before the model | Select fields, compute required facts and replace supported values. |
| After drafting | Validate required fields, token placement and document rules. |
| At rendering | Restore known mappings and escape for the destination format. |
| On failure | Stop or request review rather than inserting guessed originals. |
Limitations, honestly
- This works because document pipelines separate drafting from filling. If your generation genuinely fuses the two and the model must emit the finished value inline, you keep the token in the draft and restore at output, which is the pattern above; but a design that truly needs the model to reason over the real value is out of scope for redaction.
- Opaque keys provide no identity-class cue. Other detectors may recognize self-describing values, while names or addresses can need schema-specific handling, known terms or evaluated NER.
- Redaction can reduce disclosed detail; it does not itself determine lawfulness or remove obligations for the remaining context.
- Keep redact and restore on a shared vault if they run in different processes, or the tokens will not resolve at render time.
You can watch a JSON record tokenize and restore in the live sandbox, read the full API at /docs, and see both packages at github.com/raeven-co/sether. The concepts underneath are in the complete guide to PII redaction for LLM apps.
Frequently asked questions
Does the document quality suffer if the AI only sees tokens?
It can. Field tokens may preserve enough structure for some templates, but the model can omit or misplace them and cannot infer facts hidden inside a value. Test representative templates, derive necessary facts locally and validate the draft before rendering.
How is this different from trusting the provider not to train on my data?
They address different stages. Provider controls govern received data; tokenization withholds matched originals from that edited request. Remaining context can still identify someone, so keep the provider review and avoid a blanket no-personal-data claim.
Where do the real values get filled back in?
Restore recognised replacements at the final render step in code you operate, while the matching mapping is available. Validate the output first and escape originals for the destination format. Restoring text is not authorization to generate or send a document.
Does this handle PII that sits in JSON fields?
Yes. Document pipelines pass structured records, so Sether's identity pack anchors on JSON keys like customer_name and date_of_birth, tokenizing the whole payload in one redact call while keeping the output valid JSON.
Written by Godfrey Lebo
Founder of Sether. Ships production software for a living and reads every comment below. More about the team.
Comments
Emails and phone numbers in comments are hidden automatically. We take our own medicine.
No comments yet. Ask a question or share how you handle this. First comment gets our undivided attention.
Join the conversation
Questions, corrections and war stories are all welcome. Be kind, stay on topic.