#41600·litellm

[Feature]: Presidio output_parse_pii — derive placeholders from the value so tokens stay stable across requests

Author: weltmaisterCreated Sep 17, 2026Updated Sep 17, 2026

The Feature

With output_parse_pii: true, the Presidio guardrail builds its unmask table in request_data["metadata"]["pii_tokens"] during the pre_call hook and numbers placeholders with a counter that starts over on every request. Unmasking is then a literal string replace against that table (PresidioPII._unmask_pii_text, called from _process_response_for_pii).

That makes tokens meaningful only inside the single request that produced them. In a multi-turn or agentic conversation the message list grows between requests, so the same value is assigned a different index each time:

request 1 (3 messages):   Alice Brenner -> <PERSON_2>
request 2 (7 messages):   Alice Brenner -> <PERSON_5>

Whenever the model emits a placeholder it picked up from earlier context rather than from the current request's masked text — quoting its own previous turn, echoing a tool result the client stored, or copying a block it was asked to revise — the token is not a key in the current table. _unmask_pii_text finds no match and passes it through verbatim. The client receives <PERSON_2> as literal text.

This is worse than a cosmetic glitch for agentic use. Once such a string is written to a file, a wiki page or a vector store, it is permanently unresolvable: nothing records what it stood for, and on the next read it is no longer PII, so it is never masked or restored again. We hit exactly this — a knowledge-base agent persisted placeholders into its notes, and the original values are not recoverable.

Request: derive the placeholder suffix deterministically from the entity value instead of from a per-request counter, e.g. the first few hex characters of a keyed hash of (entity_type, value):

Alice Brenner  ->  <PERSON_a91c4f>     # identical in every request

A stable derivation means:

  • a token stays valid across requests, so late or quoted placeholders still resolve;
  • two different values can never collide onto one token;
  • the mapping does not have to be carried anywhere — it can be recomputed.

A config flag (presidio_stable_tokens: true, defaulting to the current behaviour) would keep this backwards compatible for anyone parsing the <TYPE_N> shape.

Motivation, pitch

Masking that cannot be reliably reversed is worse than no masking for agent workloads: it silently corrupts the content the agent produces, and the damage is permanent once persisted. Deterministic tokens remove the entire class of failure without changing the guardrail's protection properties — the provider still only ever sees an opaque placeholder.

Related: #31959 describes the same root cause within a single request (per-message counters colliding). A value-derived token fixes that case too, and additionally covers the cross-request case, which no within-request numbering fix can address.

LiteLLM version

1.99.1

Are you a ML Ops Team?

No

Twitter / LinkedIn details

No response