--- icon: lucide/code --- # De-identify and restore a text You have a text with confidential data, and you want to de-identify it, send it to an LLM, then restore the original values in the reply. This guide does the round-trip with the `piighost` core alone, no model and no optional dependency. The detector's patterns come from the [piighost catalog](https://catalog.piighost.dev). They are fetched every time the detector is built, which needs network access. Install the core. === "uv" ```bash uv add piighost ``` === "pip" ```bash pip install piighost ``` ## Do the round-trip A pipeline chains a detector, a linker, and an anonymizer. Only the detector is required. The linker defaults to `ExactEntityLinker` and the anonymizer to `Anonymizer(LabelCounterPlaceholderFactory())`. `anonymize` returns the de-identified text and the token assigned to each entity. `deanonymize` replays that mapping in reverse. ```python import asyncio from piighost.components.detector import RegexDetector from piighost.pipeline import AnonymizationPipeline detector = RegexDetector.from_catalog("catalog:piighost/generic") pipeline = AnonymizationPipeline(detector) async def main() -> None: result = await pipeline.anonymize("Contact alice@example.com from 192.168.1.42.") print(result.text) restored = pipeline.deanonymize(result.text, result.tokens) print(restored) asyncio.run(main()) ``` The output should be: ```text Contact <> from <>. Contact alice@example.com from 192.168.1.42. ``` `result.text` carries `<>`{ .placeholder } in place of `alice@example.com`{ .pii }. `result.tokens` maps each entity to its token. Pass it as-is to `deanonymize` to recover the original text. ## Restore an LLM reply `deanonymize` restores any text that carries the tokens, not only the one the pipeline produced. If the LLM answers with `<>`{ .placeholder }, put the real values back with the same `result.tokens` mapping. ```python async def main() -> None: result = await pipeline.anonymize("Contact alice@example.com from 192.168.1.42.") llm_reply = "I sent the message to <>." print(pipeline.deanonymize(llm_reply, result.tokens)) asyncio.run(main()) ``` The output should be: ```text I sent the message to alice@example.com. ``` ## Group repeated occurrences A value cited several times gets a single token, so the LLM keeps the thread. `ExactEntityLinker` groups occurrences by value and label. ```python from piighost.components.detector import ExactMatchDetector detector = ExactMatchDetector({"Patrick": "PERSON", "Paris": "LOCATION"}) pipeline = AnonymizationPipeline(detector) async def main() -> None: result = await pipeline.anonymize("Patrick lives in Paris. Patrick loves Paris.") print(result.text) asyncio.run(main()) ``` The output should be: ```text <> lives in <>. <> loves <>. ``` `ExactMatchDetector` detects fixed literal values. The example therefore stays reproducible without loading a model. For free text, swap it for an NER (named entity recognition) or LLM detector, see the [detectors reference](../reference/detectors.md). ## Change the token shape `LabelCounterPlaceholderFactory`, the default factory, produces `<>`{ .placeholder }. If you want another token shape, pass the pipeline an `Anonymizer` built on another factory. Here, `LabelHashPlaceholderFactory` replaces the number with a short digest. ```python import asyncio from piighost.components.anonymizer import Anonymizer from piighost.components.detector import ExactMatchDetector from piighost.components.placeholder import LabelHashPlaceholderFactory from piighost.pipeline import AnonymizationPipeline detector = ExactMatchDetector({"Patrick": "PERSON", "Marie": "PERSON"}) anonymizer = Anonymizer(LabelHashPlaceholderFactory()) pipeline = AnonymizationPipeline(detector, anonymizer=anonymizer) async def main() -> None: result = await pipeline.anonymize("Patrick, Marie, Patrick.") print(result.text) asyncio.run(main()) ``` The output should be: ```text <>, <>, <>. ``` The digest is computed from the entity's label and rank, never from the value. `Patrick`{ .pii } therefore keeps the same token at both appearances, and `Marie`{ .pii } gets another one. To restore the values, the factory must preserve identity, that is, give each value a distinct token. `LabelCounterPlaceholderFactory` does. `LabelPlaceholderFactory` does not, because it gives the same `<>`{ .placeholder } to two distinct people. See the [placeholder factories](../placeholder-factories.md) page. ## See also - [Pre-built detectors](detectors.md) to combine catalog groups and detectors. - [Pipeline reference](../reference/pipeline.md) for the optional stages. - [Extending piighost](../extending.md) to write your own components.