Tokenize PII before AI document generation: keep real customer data out of the model
AI document generation has two phases: the AI decides structure and wording, then a deterministic step fills in real values. Tokenize the personal fields before the AI phase and restore the originals only at the render step. The model shapes the document from typed tokens like <NAME_uuid>, and no real customer data ever reaches the provider.
- Generating a document with AI is really two jobs: the model structures and drafts, then a template engine fills in the real values.
- Only the second job needs real customer data. The AI phase can run entirely on tokens.
- Redact the JSON payload before the AI call, keep the token-to-value map in your vault, and restore into the final render.
- This beats relying on a provider's no-training promise: the data minimisation is structural, because the real values never left your boundary.
- 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
The default implementation sends the whole record to the model and asks it to produce the finished document in one shot. That means the customer's real name, DOB, passport number, and address all travel to OpenAI or Anthropic as part of the prompt, and sit in that provider's request logs, for a step that never needed them.
{
"customer_name": "Amara Okafor",
"date_of_birth": "1990-05-12",
"passport_number": "A1234567",
"billing_address": "12 Marina Road, Lagos",
"template": "welcome_letter"
}Whether that is acceptable is usually argued on trust: the provider promises not to train on it, the DPA is signed, the retention window is short. All true, and all beside the point, because there is a design that removes the question entirely.
The pattern: tokens through the AI, originals only at render
Redact the personal fields before the AI phase. The model receives typed tokens, drafts the document around them exactly as it would around real values, and returns its output. You then restore the real values in the very last step, when your own render code assembles the final document. The provider only ever saw tokens.
- 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.
The model's job is unaffected, because a typed token carries its type: the model knows <NAME_uuid> is a name and places it correctly. What changes is that the real name never left your service.
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 } from '@raeven-co/sether';
const sether = new Sether({
detectors: [...basicDetectors, ...identityDetectors],
});
// 1. Redact the record before the AI phase
const safePayload = sether.redactSync(JSON.stringify(record));
// 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 = sether.restoreSync(draft);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)The same instance does redact and restore because the token-to-value map lives on it. If your drafting and rendering run in different processes or workers, give both the same shared vault (the interface is pluggable, Redis-backed if you need it) so the tokens resolve.
Why this beats a no-training promise
A contractual promise is a control you cannot inspect. You are trusting a log-retention policy, an access boundary, and a training pipeline you will never audit. Tokenizing before the AI phase replaces that with a structural fact: the real values were never in the request, so there is nothing in the provider's logs to leak, subpoena, or accidentally train on.
For a regulator or an auditor, "we do not send personal data to the model" is a far stronger statement than "the provider agreed not to keep it". It maps directly to data minimisation under GDPR Article 5 and to technical measures under Article 32. The legal detail is in Is it GDPR-compliant to send customer data to OpenAI?.
What the AI still does well on tokens
The worry is always that a model reasons worse without the real values. In document generation it barely matters, because the drafting job is about structure and language, not the literal identity. The model still chooses the right clauses, matches tone, handles conditionals ("if the customer is under 18, add the guardian line"), and places every field, all from typed tokens.
Quality only drops when a task genuinely needs the literal value, for example validating that an email is well-formed or computing an age from a DOB. Those are deterministic checks you should run in your own code anyway, not delegate to a language model.
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.
- Personal data under opaque keys (
"field_7") has no key signal for the identity pack to anchor on. Self-describing values like email and card are still caught anywhere; names and addresses under meaningless keys need the NER add-on. - Redaction supports data minimisation; it is not a lawful basis on its own. You still need the agreement and the purpose. It removes the exposure, not the paperwork.
- 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?
- Rarely. Document drafting is about structure, clauses, and tone, not the literal identity. Typed tokens keep their type, so the model places a name where the name belongs. Quality only drops for tasks that need the literal value, like validating an email, which you should do in your own code anyway.
- How is this different from trusting the provider not to train on my data?
- A no-training promise is a control you cannot inspect. Tokenizing before the AI phase is structural: the real values were never in the request, so there is nothing in the provider's logs to leak or train on. "We do not send personal data to the model" is a stronger claim than "the provider agreed not to keep it".
- Where do the real values get filled back in?
- At the final render step, in your own code. You restore each token to its original value as you assemble the document or PDF. The AI phase runs entirely on tokens; the originals only appear in the deterministic filling step that never leaves your infrastructure.
- 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.
Sether is free and MIT-licensed: npm i @raeven-co/sether · pip install sether. Or paste some text into the live sandbox and watch the redaction happen.
Keep reading
- How to redact PII in JSON and structured data before sending it to an LLMMost PII sent to an LLM is not prose, it is JSON: form data, API payloads, database rows. Here is how to tokenize personal fields inside JSON before OpenAI or Claude sees them, and restore them in the reply.
- PII redaction for LLM applications: the complete guide (2026)How to keep names, emails, cards, and secrets out of OpenAI, Anthropic, and other LLM providers: detection methods, reversible tokenization, streaming pitfalls, and tool comparison.
- Is it GDPR-compliant to send customer data to OpenAI?It can be, if you sign the DPA, list OpenAI as a sub-processor, handle the US transfer, and apply technical measures like redaction. Here is the honest checklist.
- Reversible PII redaction: how redact-then-restore worksWhy one-way masking breaks chat UX, how stable tokens and a local vault make redaction reversible, and when reversibility is the wrong choice.