Fabriques de placeholders
Un placeholder est le jeton synthétique qui prend la place d'une valeur détectée avant que le texte n'atteigne le LLM. Au lieu d'envoyer Patrick habite à Paris au LLM, le pipeline transmet <<PERSON:1>> habite à <<LOCATION:1>>. Les valeurs originales restent dans la mémoire de conversation, le LLM ne les voit jamais.
Une placeholder factory décide de la forme de ces jetons et de la quantité d'information qu'ils transportent. Deux questions structurent le choix.
-
Le jeton est-il unique par entité ? Patrick et Marie ne doivent pas se ramener au même <<PERSON>> générique, sinon le LLM ne peut pas les distinguer. Un jeton unique par entité permet au modèle de raisonner sur les relations. La question le manager est-il la même personne que Patrick ? devient <<PERSON:1>> est-il <<PERSON:2>> ?, et elle a une réponse claire.
-
Le jeton est-il réversible et retrouvable ? Le jeton désigne-t-il une seule valeur dans la mémoire de la conversation, et peut-on le relocaliser dans un texte que le pipeline n'a pas produit ? La restauration a besoin de ces deux propriétés, qu'elle porte sur la réponse du modèle ou sur les arguments d'un outil. Si deux entités se confondent dans un même <<PERSON>>, on ne sait pas laquelle restaurer.
Six familles de factories se placent à des points différents de ce spectre, et le choix a des conséquences directes sur les ToolCallStrategy utilisables sans risque. Voir Stratégies d'appel outil pour le côté exécution.
-
Aucune information (<<REDACT>>) : un jeton constant qui ne révèle rien au LLM. Caviardage classique. Aucun raisonnement n'est possible sur les entités. Par exemple, le modèle ne peut pas voir que la valeur était une ville et décider d'appeler l'outil
get_weather. -
Type seul (<<PERSON>>, <<EMAIL>>) : le type est révélé, pas l'identité. Plusieurs personnes dans une même conversation se confondent dans le même <<PERSON>>, donc les références croisées se cassent.
-
Type + id (opaque) (<<PERSON:1>>, <<PERSON:a1b2c3d4>>) : type révélé, identité stable, jeton manifestement synthétique. Le LLM sait que <<PERSON:1>> et <<PERSON:2>> sont deux personnes différentes. Unique, donc réversible par remplacement de chaîne.
-
Id seul (<<REDACT:a1b2c3d4>>) : un hash unique par entité, sans révéler le type. Le LLM voit qu'il y a deux entités distinctes mais ignore si ce sont des personnes, des emails ou des cartes. Garde la réversibilité côté outil sans donner d'indice sémantique au modèle.
-
Valeur partielle (J******* pour Jonathan) : une partie du contenu réel reste visible, ici la première lettre et la longueur. Le LLM voit le début de la valeur, pas la valeur complète. Plus risqué côté confidentialité (fragments réels) et côté réversibilité (collisions possibles).
Détail des familles
Aucune information, destruction totale
Le jeton est un marqueur fixe, par exemple <<REDACT>>. Le LLM apprend qu'une information a été retirée mais rien sur son type, son nombre ni ses relations. La conversation perd toutes ses références internes. Un agent qui doit traiter envoyer la facture au client ne peut pas savoir si le client est celui cité plus tôt ou un nouveau.
Utile pour le caviardage d'archive, inutile dès qu'un agent doit raisonner.
- Intégrée :
RedactPlaceholderFactory(sortie <<REDACT>>, délimiteurs paramétrables). - Tag de préservation :
PreservesNothing.
Type seul, identités confondues
<<PERSON>>, <<EMAIL>>. Le LLM sait qu'il s'agit d'une personne, d'un email, d'une carte, et peut répondre aux questions qui dépendent du seul type. Mais deux personnes différentes dans la même conversation se confondent dans le même jeton.
Le mode d'échec classique est la référence croisée. La question Patrick est-il la même personne que le manager cité plus tôt ? devient <<PERSON>> est-il le même que <<PERSON>> ?, et cette question n'a pas de réponse.
- Intégrée :
LabelPlaceholderFactory(sortie <<PERSON>>). - Tag de préservation :
PreservesLabel.
Type + id (opaque)
<<PERSON:1>>, <<PERSON:a1b2c3d4>>. La chaîne n'est manifestement pas une personne, un email ou un numéro de carte, c'est un jeton. Le LLM ne peut pas la confondre avec une donnée réelle, les logs d'audit se parcourent facilement, et il y a zéro chance de collision avec une vraie valeur.
Ses délimiteurs rendent aussi le jeton retrouvable. On peut ainsi repérer un jeton que le modèle aurait inventé.
En contrepartie, un prompt ou un outil aval strict qui exige l'argument doit ressembler à un email rejettera ces jetons.
- Intégrées :
LabelCounterPlaceholderFactory(<<PERSON:1>>) etLabelHashPlaceholderFactory(<<PERSON:a1b2c3d4>>). - Tag de préservation :
PreservesLabeledIdentityOpaque.
Les deux numérotent les entités par label, dans l'ordre. La première personne devient l'ordinal 1, la deuxième 2, et un email démarre son propre compte à 1.
LabelHashPlaceholderFactory affiche cet ordinal sous forme de hash. Le hash est un sha256 de la chaîne label:ordinal, jamais de la valeur. Il ne sert qu'à donner une apparence opaque, pour que deux entités consécutives paraissent sans lien.
Id seul, identité sans type
<<REDACT:a1b2c3d4>>. Le jeton garde la forme synthétique <<...>> mais ne révèle pas le label, tout en portant un hash unique par entité. Le LLM ignore si l'entité est une personne, un email ou une carte, mais voit que <<REDACT:a1b2c3d4>> et <<REDACT:ef98abcd>> sont deux entités différentes.
C'est l'un des niveaux les plus protecteurs qui reste utilisable côté outil. Le remplacement de chaîne fonctionne, parce que le hash est unique.
- Intégrée : aucune pour cette branche.
- Tag de préservation :
PreservesIdentityOnly, prévu pour une factory que vous écrivez, un caviardage hashé sans préfixe de label. Voir la section Écrire la sienne plus bas.
Type + id (réaliste hashé)
Une factory utilisateur peut produire des valeurs qui ressemblent au format d'origine mais dont le contenu est piloté par un hash, par exemple a1b2c3d4@anonymized.local pour un email, ou Patient_a1b2c3d4 pour un nom.
Le jeton passe la validation de format de base (regex email, longueur, caractères autorisés), donc les outils et les templates de prompt aval qui attendent une valeur d'apparence réelle continuent de fonctionner. Comme le contenu est un hash, le jeton est unique et ne peut pas coïncider par hasard avec une vraie valeur existante.
- Intégrée : aucune. Voir la section Écrire la sienne plus bas pour un exemple complet.
- Tag de préservation :
PreservesLabeledIdentityHashed.
Valeur partielle, un fragment fuit
J*******, j***@mail.com, ****4567. Le jeton conserve une partie de la valeur originale, par exemple le domaine de l'email, les quatre derniers chiffres d'une carte, la première lettre d'un nom. Le LLM peut raisonner au-delà du type, l'email est sur le domaine de l'entreprise, la carte se termine en 4567, le nom commence par J. Deux compromis viennent avec.
- Des fragments réels de la valeur atteignent le LLM. Il ne peut pas reconstruire la valeur complète, mais j***@mail.com situe déjà l'utilisateur chez un fournisseur de mail connu.
- Des collisions sont possibles. Deux cartes différentes terminant par
4567se confondent dans ****4567, deux emails partageant la première lettre et le domaine deviennent identiques. Le jeton est majoritairement unique, sans garantie.
- Intégrée :
MaskPlaceholderFactory, qui garde par défaut le premier caractère de la valeur et masque le reste avec*, donc Jonathan devient J******* et jean@mail.com devient j************. Les formes j***@mail.com et ****4567 demandent une factory que vous écrivez. - Tag de préservation :
PreservesShape.
Le middleware le rejette, à la vérification de types comme à l'exécution. Un jeton ambigu ne peut pas être restauré par remplacement de chaîne, et un masque n'a pas de grammaire que le middleware sache retrouver.
Tags de préservation
Chaque factory porte un type fantôme qui résume le niveau de préservation de ses jetons. Un type fantôme est un paramètre générique qui n'existe qu'à la vérification de types, il n'influe pas sur l'exécution. C'est ce tag que le vérificateur de types lit pour valider une factory face à ses consommateurs.
Le tableau suivant donne un exemple de jeton et le tag de chaque famille.
| Famille | Exemple | Tag |
|---|---|---|
| Aucune information | <<REDACT>> | PreservesNothing |
| Type seul | <<PERSON>> | PreservesLabel |
| Type + id (opaque) | <<PERSON:1>>, <<PERSON:a1b2c3d4>> | PreservesLabeledIdentityOpaque |
| Id seul | <<REDACT:a1b2c3d4>> | PreservesIdentityOnly |
| Type + id (réaliste hashé) | a1b2c3d4@anonymized.local, Patient_a1b2c3d4 | PreservesLabeledIdentityHashed |
| Valeur partielle | J*******, ****4567 | PreservesShape |
Deux tableaux lisent ces familles sous deux angles. Le tableau Confidentialité montre ce qui fuit vers le LLM, du point de vue de l'attaquant et de la vie privée. Le tableau Exploitation montre ce que l'agent et le système peuvent faire avec le jeton, du point de vue des capacités fonctionnelles. La même réponse peut être bonne d'un côté et problématique de l'autre, et les deux tableaux rendent cette tension explicite.
Les deux tableaux partagent le même code couleur, du meilleur au problématique, détaillé dans la légende sous le second tableau.
Confidentialité (ce qui fuit vers le LLM)
| Famille | Type vu ? | Valeurs distinguées ? | Fuite de valeur ? | Collision avec une vraie valeur ? |
|---|---|---|---|---|
| Aucune information | non | non | aucune | non |
| Type seul | oui | non | aucune | non |
| Type + id (opaque) | oui | oui | aucune | non |
| Id seul | non | oui | aucune | non |
| Type + id (réaliste hashé) | oui | oui | aucune | non |
| Valeur partielle | oui | oui | partielle | risque |
Exploitation par le LLM et l'agent
| Famille | Raisonner sur le type | Suivre les références entre entités | Réversible côté outil | Jeton retrouvable |
|---|---|---|---|---|
| Aucune information | non | non | non | oui |
| Type seul | oui | non | non | oui |
| Type + id (opaque) | oui | oui | oui | oui |
| Id seul | non | oui | oui | oui |
| Type + id (réaliste hashé) | oui | oui | oui | non |
| Valeur partielle | oui | majoritairement | oui (collisions) | non |
Les tags forment une hiérarchie d'héritage que le vérificateur de types exploite via la covariance de AnyPlaceholderFactory[PreservationT_co]. Une factory taguée plus spécifiquement satisfait donc un consommateur qui en demande une plus lâche.
Trois axes indépendants organisent la taxonomie :
- Label : le jeton révèle le type.
- Identité : le jeton est unique par entité.
- Retrouvable : la factory peut retrouver son jeton dans un texte arbitraire. Un jeton délimité le permet, un jeton réaliste non.
PreservesLabeledIdentity combine label et identity par multi-héritage. Une factory <<PERSON:1>> est donc à la fois un PreservesLabel et un PreservesIdentity.
PreservesRecognizableIdentity croise l'identité et la retrouvabilité. Le middleware n'accepte que cette intersection. Un consommateur typé contre PreservesRecognizableIdentity trie les tags ainsi :
- Accepte :
PreservesIdentityOnlyetPreservesLabeledIdentityOpaque. - Rejette :
PreservesLabel,PreservesShapeetPreservesNothing, qui n'ont pas la garantie d'unicité, ainsi quePreservesLabeledIdentityHashed, qui n'est pas retrouvable.
Hiérarchie des tags de préservation. Chaque nœud porte un exemple de jeton, les nœuds abstraits servent d'intersection entre axes. Chaque flèche va d'un tag vers son parent et se lit "est un".
PreservesLabeledIdentity hérite à la fois de PreservesLabel et de PreservesIdentity. Cet héritage exprime la relation A est un B mais tous les B ne sont pas des A. Tout PreservesLabeledIdentity est aussi un PreservesLabel et un PreservesIdentity, mais un PreservesLabel n'est pas forcément un PreservesLabeledIdentity.
PreservesShape étend PreservesLabel, parce qu'un jeton masqué implique le label par son format. Il ne garantit pas l'unicité, donc il ne descend pas de PreservesIdentity.
Chaque tag est une sous-classe de str, si bien qu'un jeton est une vraie chaîne qui porte son niveau de préservation dans son propre type.
Une factory déclare le tag le plus spécifique qui correspond à ses garanties.
class LabelCounterPlaceholderFactory(
BaseCounterPlaceholderFactory
): ... # PreservesLabeledIdentityOpaque
class LabelHashPlaceholderFactory(
BaseCounterPlaceholderFactory
): ... # PreservesLabeledIdentityOpaque
class LabelPlaceholderFactory(AnyPlaceholderFactory[PreservesLabel]): ...
class MaskPlaceholderFactory(AnyPlaceholderFactory[PreservesShape]): ...
class RedactPlaceholderFactory(AnyPlaceholderFactory[PreservesNothing]): ...
# No built-in for the id-only branch nor the realistic hashed one,
# implement your own with PreservesIdentityOnly or PreservesLabeledIdentityHashed.Factories intégrées
| Factory | Style | Mécanisme | Exemple de sortie |
|---|---|---|---|
RedactPlaceholderFactory | Redact | aucun | <<REDACT>> |
LabelPlaceholderFactory | Label | aucun | <<PERSON>> |
LabelCounterPlaceholderFactory (défaut) | Label | Counter | <<PERSON:1>> |
LabelHashPlaceholderFactory | Label | Hash | <<PERSON:a1b2c3d4>> |
MaskPlaceholderFactory | Mask | partiel | J******* |
Le tag de chaque factory figure dans le tableau des familles, plus haut. Le nommage suit le schéma <Style><Mécanisme>PlaceholderFactory.
- Style : ce que le jeton préserve. Redact = rien, Label = type, Mask = valeur partielle.
- Mécanisme : comment l'unicité est obtenue. Counter = compteur séquentiel par label, Hash = sha256 de
label:ordinalrendu en hex. Absent quand non pertinent.
LabelCounterPlaceholderFactory et LabelHashPlaceholderFactory sont les valeurs sûres par défaut, réversibles et retrouvables. RedactPlaceholderFactory, LabelPlaceholderFactory et MaskPlaceholderFactory sont des outils de caviardage non réversibles. Le vérificateur de types les rejette sous le middleware, et le middleware refuse aussi le masque à la construction. Les branches id seul et réaliste hashé n'ont pas de factory intégrée. Vous les écrivez avec le tag correspondant.
Quel placeholder choisir ?
La placeholder factory est l'endroit où le compromis confidentialité / capacité d'agent est rendu explicite. Le bon choix dépend du contexte. Deux scénarios couvrent l'essentiel.
Cas 1, dé-identification ponctuelle (archivage, conformité)
Le but est de produire une version assainie d'un document, par exemple le caviardage d'un jugement, nettoyage d'un dossier RH avant archivage, export d'un jeu de données. Pas d'agent, pas d'outils, parfois même pas besoin de réversibilité.
| Besoin | Famille recommandée | Pourquoi |
|---|---|---|
| Effacer toute trace, sans réversibilité | Aucune information (<<REDACT>>) | Le plus protecteur, aucune fuite sémantique. Le document reste lisible mais le LLM ne peut rien en inférer. Factory intégrée RedactPlaceholderFactory. |
| Garder un texte lisible, le lecteur humain voit <<EMAIL>> plutôt que <<REDACT>> | Type seul (<<PERSON>>, <<EMAIL>>) | Le type aide la lecture humaine sans rien fuiter de la valeur. Factory intégrée LabelPlaceholderFactory. |
| Permettre une restauration côté serveur | Type + id (opaque) (<<PERSON:1>>) | Réversible, audit trivial, aucune collision. Factory intégrée LabelCounterPlaceholderFactory ou LabelHashPlaceholderFactory. |
| Suivre qui est qui sans révéler le type (médical, RH) | Id seul (<<REDACT:a1b2c3d4>>) | Distingue les entités sans indice sémantique. À implémenter, pas de factory intégrée. |
Cas 2, dé-identification pour un LLM ou un agent avec outils
Le LLM raisonne sur la conversation, et les outils (CRM, BDD, mail) ont besoin des vraies valeurs au moment de l'appel. Le middleware restaure par remplacement de chaîne, sur la réponse du modèle comme sur les arguments d'outil. Il exige donc un jeton unique par entité et retrouvable.
Par conséquent, seules les familles avec identité préservée et grammaire retrouvable sont compatibles, c'est-à-dire l'id seul et le type + id opaque. Les familles aucune information, type seul et valeur partielle sont rejetées à la vérification de types. Le réaliste hashé préserve l'identité mais n'est pas retrouvable, donc il ne passe pas la contrainte du middleware.
| Besoin | Famille recommandée | Pourquoi |
|---|---|---|
| Cas par défaut | Type + id (opaque) (<<PERSON:1>>, <<PERSON:a1b2c3d4>>) | Réversible, retrouvable, opaque, zéro collision. La valeur sûre. Factory intégrée LabelCounterPlaceholderFactory (compteur par conversation) ou LabelHashPlaceholderFactory (hash de l'ordinal). |
| Réduction des biais (CV, candidature) | Id seul (<<REDACT:a1b2c3d4>>) | Le LLM ne voit pas le type, donc pas le genre ni l'origine inférables d'un prénom. Distingue les candidats sans biaiser le raisonnement. À implémenter. |
| Type sensible (catégorie médicale, niveau d'habilitation) | Id seul (<<REDACT:a1b2c3d4>>) | Même raison, le type lui-même est une PII et ne doit pas atteindre le LLM. À implémenter. |
À éviter dans un agent sous middleware.
LabelPlaceholderFactoryetMaskPlaceholderFactorysont rejetées par le middleware, quelle que soit laToolCallStrategy, parce qu'elles ne garantissent pas l'unicité. Le vérificateur de types rejette les deux, et le middleware refuse aussi le masque à la construction. Utilisez-les avec le pipeline seul, hors middleware.- Une factory réaliste hashée (
PreservesLabeledIdentityHashed) préserve l'identité mais reste non retrouvable, donc le middleware ne peut pas repérer un jeton que le modèle inventerait. Réservez-la à la dé-identification hors agent, ou à un flux où un placeholder inventé par le modèle n'est pas un souci.
Le tag de préservation existe pour que ce choix soit visible par le vérificateur de types, pas enseveli dans des détails de format. Une factory taguée PreservesShape ne peut pas être branchée sur le middleware par accident, l'erreur tombe à la vérification de types, pas sur le premier appel d'outil en production.
Pourquoi PIIAnonymizationMiddleware exige une identité retrouvable
Le middleware travaille sur trois frontières, les messages d'entrée (LLM in), les messages de sortie (LLM out) et les appels d'outil. Les trois s'appuient sur la mémoire de conversation, qui garde les détections de chaque message.
Messages d'entrée et sortie. Quand abefore_model dé-identifie un message, le pipeline enregistre ses détections dans la mémoire. Quand le LLM répond, aafter_model restaure sa réponse par remplacement de chaîne. Il cherche chaque jeton connu de la conversation et le remplace par la valeur de son entité. La réponse du modèle est un texte nouveau, que le pipeline n'a jamais produit, donc il n'y a pas d'autre moyen de la restaurer.
Appels d'outil. Le LLM produit les arguments d'outil en combinant et paraphrasant les jetons qu'il vient de voir. Le middleware les restaure de la même façon, en parcourant les arguments à la recherche des jetons connus. La réponse de l'outil, elle, passe dans le pipeline de la conversation comme un message de l'utilisateur, détection comprise.
Sur les deux canaux, ce remplacement n'est non ambigu que si chaque entité a un jeton unique. Si deux entités se confondent dans <<PERSON>>, on ne sait pas quelle valeur restaurer.
Le middleware exige en plus une grammaire retrouvable, c'est-à-dire une forme de jeton qu'il sait repérer dans un texte. Une fois tous les jetons émis remplacés, tout jeton restant qui suit encore la grammaire a été inventé par le modèle et peut être refusé (voir Stratégies d'appel outil).
Le middleware restreint donc son type accepté à un pipeline dont les jetons sont PreservesRecognizableIdentity. Par covariance, ce type englobe PreservesIdentityOnly (caviardage hashé sans label) et PreservesLabeledIdentityOpaque (avec label). pyrefly rejette une factory PreservesLabel, PreservesShape, PreservesNothing ou PreservesLabeledIdentityHashed avant même que le programme ne tourne.
PIIAnonymizationMiddleware reproduit une partie de la contrainte à l'exécution. À la construction, il demande au pipeline un recognizer, l'objet qui sait retrouver ses propres jetons. Une factory délimitée est son propre recognizer. Une factory sans grammaire, comme un masque, n'en a pas, et le middleware lève alors UnrecognizableFactoryError. Cette vérification rattrape les pipelines non typés ou distants qui auraient contourné le vérificateur de types. Elle ne contrôle que la grammaire, donc une factory délimitée sans identité, comme LabelPlaceholderFactory, passe à l'exécution. Seul le vérificateur de types la rejette.
La grammaire du recognizer est bornée, ce n'est pas "n'importe quoi entre les délimiteurs". Elle se lit ainsi :
- Forme interne : un label, puis éventuellement un deux-points et un identifiant, comme <<PERSON>>, <<PERSON:1>> ou <<PERSON:a1b2c3d4>>.
- Label : une lettre ou un underscore, puis lettres, chiffres, underscores, espaces ou tirets, si bien qu'un label en plusieurs mots émis par un détecteur, comme
date of birth, reste reconnu. - Identifiant : après le deux-points, alphanumérique, un ordinal ou un digest hexadécimal.
Un contenu délimité arbitraire n'est pas un jeton. Un décalage C++ cout << x >> y ou un passage markdown ne déclenche donc jamais le garde-fou des jetons inventés.
Une réponse en streaming qui ouvre << sans le refermer est relâchée plutôt que retenue indéfiniment.
Le choix de ToolCallStrategy ne lève pas cette contrainte. Même sous PASSTHROUGH, le middleware restaure la réponse du modèle pour l'utilisateur, donc chaque jeton doit désigner une seule entité. Voir Stratégies d'appel outil.
Écrire la sienne
Héritez de AnyPlaceholderFactory[<tag>] avec le tag de préservation qui correspond à vos garanties, puis implémentez create().
Factory id seul (id sans label), PreservesIdentityOnly
import uuid
from collections.abc import Mapping
from piighost.components.placeholder import AnyPlaceholderFactory
from piighost.components.placeholder.tags import PreservesIdentityOnly
from piighost.models import Entity
class UUIDPlaceholderFactory(AnyPlaceholderFactory[PreservesIdentityOnly]):
"""Generate opaque delimited ids, e.g. <<a3f21b4c>>, no label revealed."""
def create(self, entities: list[Entity]) -> Mapping[Entity, PreservesIdentityOnly]:
tokens: dict[Entity, PreservesIdentityOnly] = {}
seen: dict[str, PreservesIdentityOnly] = {} # canonical value -> token
for entity in entities:
canonical = entity.text.lower()
if canonical not in seen:
seen[canonical] = PreservesIdentityOnly(f"<<{uuid.uuid4().hex[:8]}>>")
tokens[entity] = seen[canonical]
return tokens
# Not shown: the factory, put to work.
import asyncio
from piighost.components.anonymizer import Anonymizer
from piighost.components.detector import ExactMatchDetector
from piighost.pipeline import AnonymizationPipeline
pipeline = AnonymizationPipeline(
ExactMatchDetector({"Patrick": "PERSON", "office@example.com": "EMAIL"}),
anonymizer=Anonymizer(UUIDPlaceholderFactory()),
)
text = asyncio.run(pipeline.anonymize("Patrick writes from office@example.com.")).text
print("<<" in text and "Patrick" not in text)Le jeton est délimité, donc retrouvable, et unique par entité. Cette factory est utilisable sous PIIAnonymizationMiddleware.
Factory format crochets (label + id), PreservesLabeledIdentityOpaque
from collections import defaultdict
from collections.abc import Mapping
from piighost.components.placeholder import AnyPlaceholderFactory
from piighost.components.placeholder.tags import PreservesLabeledIdentityOpaque
from piighost.models import Entity
class BracketPlaceholderFactory(AnyPlaceholderFactory[PreservesLabeledIdentityOpaque]):
"""Generate tokens in the format [PERSON:1], [LOCATION:2], etc."""
def create(
self, entities: list[Entity]
) -> Mapping[Entity, PreservesLabeledIdentityOpaque]:
tokens: dict[Entity, PreservesLabeledIdentityOpaque] = {}
counters: dict[str, int] = defaultdict(int)
for entity in entities:
counters[entity.label] += 1
inner = f"{entity.label}:{counters[entity.label]}"
tokens[entity] = PreservesLabeledIdentityOpaque(f"[{inner}]")
return tokens
# Not shown: the factory, put to work.
import asyncio
from piighost.components.anonymizer import Anonymizer
from piighost.components.detector import ExactMatchDetector
from piighost.pipeline import AnonymizationPipeline
pipeline = AnonymizationPipeline(
ExactMatchDetector({"Patrick": "PERSON", "office@example.com": "EMAIL"}),
anonymizer=Anonymizer(BracketPlaceholderFactory()),
)
text = asyncio.run(pipeline.anonymize("Patrick writes from office@example.com.")).text
print(text)Factory réaliste hashé, PreservesLabeledIdentityHashed
Cette factory produit une valeur d'apparence réelle dont le contenu vient d'un hash de la valeur d'origine, donc unique et sans collision. Le jeton n'a pas de grammaire délimitée, donc il n'est pas retrouvable. Réservez-la hors middleware.
import hashlib
from collections.abc import Mapping
from piighost.components.placeholder import AnyPlaceholderFactory
from piighost.components.placeholder.tags import PreservesLabeledIdentityHashed
from piighost.models import Entity
class HashedEmailPlaceholderFactory(
AnyPlaceholderFactory[PreservesLabeledIdentityHashed]
):
"""Generate realistic emails like a1b2c3d4@anonymized.local."""
def create(
self, entities: list[Entity]
) -> Mapping[Entity, PreservesLabeledIdentityHashed]:
tokens: dict[Entity, PreservesLabeledIdentityHashed] = {}
for entity in entities:
digest = hashlib.sha256(entity.text.encode()).hexdigest()[:8]
tokens[entity] = PreservesLabeledIdentityHashed(
f"{digest}@anonymized.local"
)
return tokens
# Not shown: the factory, put to work.
import asyncio
from piighost.components.anonymizer import Anonymizer
from piighost.components.detector import ExactMatchDetector
from piighost.pipeline import AnonymizationPipeline
pipeline = AnonymizationPipeline(
ExactMatchDetector({"Patrick": "PERSON", "office@example.com": "EMAIL"}),
anonymizer=Anonymizer(HashedEmailPlaceholderFactory()),
)
text = asyncio.run(pipeline.anonymize("Patrick writes from office@example.com.")).text
print(text)Voir aussi
- Stratégies d'appel outil : comment le middleware utilise ces jetons.
- Étendre piighost : référence complète des protocoles et des autres points d'injection du pipeline.
- Limites : conséquences opérationnelles du choix de factory.