Référence Pipeline¶
Un pipeline enchaîne les étages qui transforment un texte en texte dé-identifié, et l'inverse. AnonymizationPipeline traite un seul texte, sans mémoire d'un appel à l'autre. ThreadAnonymizationPipeline traite une conversation, en gardant un token par valeur sur tous les messages d'un thread.
Les deux renvoient une Anonymization, le texte dé-identifié associé au token qui a remplacé chaque entité.
Dé-identification, pas anonymisation
Les pipelines par défaut gardent la correspondance entre une valeur et son token pour pouvoir restaurer la valeur. C'est une pseudonymisation réversible. Le mot anonymisation reste réservé à une suppression irréversible.
AnonymizationPipeline¶
Module : piighost.pipeline
Dé-identifie un seul texte à travers les étages, dans l'ordre. Détecter les PII, appliquer l'override du serveur, résoudre les spans qui se chevauchent, retrouver les occurrences manquées, grouper les détections en entités, résoudre les conflits d'entités, remplacer par des tokens, puis revérifier avec un guard. Chaque appel à anonymize() est indépendant.
Constructeur¶
AnonymizationPipeline(
detector: AnyDetector,
linker: AnyEntityLinker | None = None,
anonymizer: AnyAnonymizer | None = None,
overlap_resolver: AnyOverlapResolver | None = None,
expander: AnyDetectionExpander | None = None,
entity_resolver: AnyEntityResolver | None = None,
guard: AnyGuardRail | None = None,
observation_redactor: AnyPlaceholderFactory | None = None,
override: AnyDetectionOverride | None = None,
trace_clear_text: bool = False,
)
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
detector |
AnyDetector |
requis | Détecteur d'entités async |
linker |
AnyEntityLinker \| None |
None |
Groupe les détections en entités. Par défaut ExactEntityLinker() |
anonymizer |
AnyAnonymizer \| None |
None |
Moteur de remplacement et sa placeholder factory. Par défaut Anonymizer(LabelCounterPlaceholderFactory()) |
overlap_resolver |
AnyOverlapResolver \| None |
None |
Résout les détections qui se chevauchent. Par défaut ConfidenceOverlapResolver(), car l'étape de rendu a besoin de spans disjoints |
expander |
AnyDetectionExpander \| None |
None |
Ajoute les occurrences manquées d'une valeur détectée. Désactivé quand None |
entity_resolver |
AnyEntityResolver \| None |
None |
Réconcilie les entités en conflit. Désactivé quand None |
guard |
AnyGuardRail \| None |
None |
Revérifie la sortie pour de la PII résiduelle. Désactivé quand None |
observation_redactor |
AnyPlaceholderFactory \| None |
None |
Placeholder factory remplaçant les valeurs en clair dans les payloads d'observation. None trace le texte en clair, ce qui permet aux traces de servir de jeux d'annotation. Avec un tracer actif et sans redactor, le constructeur émet un PIIGhostSecurityWarning sauf si trace_clear_text=True l'acquitte |
override |
AnyDetectionOverride \| None |
None |
Whitelist et blacklist du serveur imposées à chaque ensemble de détections. Désactivé quand None |
trace_clear_text |
bool |
False |
Acquitte le traçage en clair de l'observation pour supprimer l'avertissement de sécurité quand aucun observation_redactor n'est défini |
Les composants sont des protocoles
AnyDetector, AnyEntityLinker, AnyAnonymizer, AnyOverlapResolver, AnyDetectionExpander, AnyEntityResolver, AnyGuardRail, AnyDetectionOverride. Toute implémentation du protocole est acceptée. Voir Étendre PIIGhost.
Méthodes¶
anonymize(text) -> Anonymization (async)¶
Exécute le pipeline complet et renvoie le texte dé-identifié avec le token utilisé pour chaque entité.
Lève PIIRemainingError quand un guard configuré signale de la PII restée dans la sortie.
result = await pipeline.anonymize("Patrick lives in Paris.")
# result.text == "<<PERSON:1>> lives in <<LOCATION:1>>."
deanonymize(text, tokens) -> str¶
Renvoie le texte avec chaque token connu remplacé par la valeur de son entité. tokens est la correspondance issue d'une Anonymization, lue à l'envers. Les tokens absents de la correspondance sont laissés intacts.
La restauration n'est sans ambiguïté que si les tokens préservent l'identité, car deux entités partageant un même token se confondent en une seule valeur.
ThreadAnonymizationPipeline¶
Module : piighost.pipeline
Dé-identifie chaque message d'une conversation avec des tokens stables sur tout le thread. Une valeur vue dans un premier message puis à nouveau plus tard porte le même token, car les tokens sont assignés sur l'union des détections de tous les messages, pas sur un message seul. Les détections de chaque message sont mises en cache dans la mémoire, donc renvoyer un message évite la détection.
Le composant en plus est une mémoire de conversation, memory, le stockage par thread des détections de chaque message.
Constructeur¶
ThreadAnonymizationPipeline(
detector: AnyDetector,
linker: AnyEntityLinker | None = None,
anonymizer: AnyAnonymizer | None = None,
memory: AnyConversationMemory | None = None,
overlap_resolver: AnyOverlapResolver | None = None,
expander: AnyDetectionExpander | None = None,
entity_resolver: AnyEntityResolver | None = None,
guard: AnyGuardRail | None = None,
observation_redactor: AnyPlaceholderFactory | None = None,
override: AnyDetectionOverride | None = None,
trace_clear_text: bool = False,
)
En plus de tous les paramètres de AnonymizationPipeline :
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
memory |
AnyConversationMemory \| None |
None |
Stockage par thread des détections de chaque message. Par défaut InMemoryConversationMemory() pour un seul processus, passez RedisConversationMemory pour un backend partagé |
Méthodes¶
anonymize(text, thread_id, role=MessageRole.USER) -> Anonymization (async)¶
Détecte les entités du message, les enregistre dans la mémoire de thread_id, puis dé-identifie avec des tokens assignés sur tout le thread. Le token d'une valeur reste le même d'un message à l'autre.
Le thread_id est requis. Il n'y a pas de défaut partagé, donc deux appelants ne peuvent pas tomber dans un même thread et laisser fuir mutuellement leurs PII. role date les valeurs que le message introduit. Une valeur introduite d'abord par l'assistant est laissée en clair, car ce n'est pas une PII de l'utilisateur.
Lève PIIRemainingError quand un guard configuré signale de la PII restée dans la sortie.
a1 = await pipeline.anonymize("Patrick lives in Paris.", thread_id="user-A")
a2 = await pipeline.anonymize("Patrick wrote to Marie.", thread_id="user-A")
# Patrick keeps <<PERSON:1>> across both turns.
anonymize_corrected(text, thread_id, detections) -> Anonymization (async)¶
Redé-identifie un message utilisateur avec un ensemble de détections corrigé par un humain. L'ensemble corrigé remplace les détections de ce message dans la mémoire, puis le message est dé-identifié avec des tokens cohérents sur le thread. La détection ne relance pas. Cela ne concerne que les propres messages d'un utilisateur, donc la correction est enregistrée comme un message utilisateur.
L'ensemble corrigé est stocké tel quel, sans résolution de chevauchement ni recherche d'occurrences, car l'humain fait autorité sur lui. Un override configuré s'applique encore, donc les listes du serveur priment sur la correction.
detection = Detection(span=Span(0, 5), text="Marie", label="PERSON", confidence=1.0)
detections = [detection]
result = await pipeline.anonymize_corrected("Marie called.", "user-A", detections)
deanonymize(text, thread_id) -> str (async)¶
Renvoie le texte avec chaque token du thread remplacé par sa valeur. Les tokens du thread sont reconstruits depuis sa mémoire, donc tout texte qui les porte est restauré, y compris une réponse du modèle que le pipeline n'a jamais dé-identifiée.
reply = await pipeline.deanonymize("Message sent to <<PERSON:2>>.", thread_id="user-A")
# reply == "Message sent to Marie."
thread_token_map(thread_id) -> dict[str, str] (async)¶
Renvoie la correspondance placeholder vers valeur du thread, dérivée du cache, pour qu'un appelant puisse résoudre tout un flux d'un coup plutôt que de restaurer token par token. Un token que le thread n'a jamais émis est absent de la correspondance.
forget_thread(thread_id) -> Forgotten (async)¶
Efface la mémoire d'un thread et renvoie un Forgotten indiquant ce qui a été supprimé. Oublier un thread inconnu ne supprime rien et rapporte zéro.
La correspondance de tokens mémoïsée du thread part avec lui. Ce mémo garde les valeurs du thread en clair, donc effacer le store seul les laisserait vivantes dans le processus. Les autres threads gardent le leur. L'appel n'atteint que le processus où il tourne, donc sur un déploiement multi-worker posez token_memo_ttl au constructeur pour borner la fenêtre sur les autres, comme décrit dans Déploiement multi-instance. Un cache reste debout, celui des motifs de frontière de mot, partagé par tout le processus et indexé sur le fragment cherché, donc porteur de valeurs venant de tous les threads. Videz-le avec clear_boundary_cache quand une demande d'effacement couvre tout le processus.
from piighost.text import clear_boundary_cache
await pipeline.forget_thread("user-A")
clear_boundary_cache()
recognizer (propriété)¶
La grammaire des tokens que ce pipeline émet, une BaseDelimitedPlaceholderFactory, ou None. Une factory à délimiteurs est son propre recognizer, car ses tokens portent une grammaire retrouvable. Une factory sans grammaire, comme un masque, n'a pas de recognizer.
Ports¶
Deux protocoles typent un pipeline là où un appelant, comme le middleware, doit l'accepter sans dépendre d'une classe concrète. Les deux sont génériques sur ce que les tokens émis préservent, donc un consommateur peut exiger un pipeline dont les tokens préservent l'identité et rejeter celui dont les tokens ne la préservent pas.
AnyPipeline¶
Un composant qui dé-identifie un seul texte et sait le restaurer.
class AnyPipeline(Protocol[PreservationT_co]):
async def anonymize(self, text: str) -> Anonymization[PreservationT_co]: ...
def deanonymize(self, text: str, tokens: Mapping[Entity, str]) -> str: ...
AnyThreadPipeline¶
Un pipeline scopé par thread, local ou distant. Il dé-identifie chaque message d'un thread, redé-identifie un message corrigé, restaure tout texte portant les tokens du thread, oublie un thread en entier, et expose la grammaire de ses tokens.
class AnyThreadPipeline(Protocol[PreservationT_co]):
async def anonymize(
self, text: str, thread_id: str, role: MessageRole = MessageRole.USER
) -> Anonymization[PreservationT_co]: ...
async def anonymize_corrected(
self, text: str, thread_id: str, detections: list[Detection]
) -> Anonymization[PreservationT_co]: ...
async def deanonymize(self, text: str, thread_id: str) -> str: ...
async def forget_thread(self, thread_id: str) -> Forgotten: ...
@property
def recognizer(self) -> BaseDelimitedPlaceholderFactory | None: ...
BaseAnonymizationPipeline¶
Module : piighost.pipeline
La machinerie partagée que les deux pipelines étendent. Elle tient les composants d'étage et les étapes communes à tous les pipelines. Les étages optionnels de chevauchement, de recherche d'occurrences et de résolution d'entités, la vérification du guard, et les payloads d'observation. Les pipelines concrets ajoutent leur propre anonymize, sur un texte seul ou sur une conversation.
Construire depuis une configuration¶
Module : piighost.config
load_pipeline et load_thread_pipeline lisent un fichier de configuration, TOML ou JSON selon son suffixe, et renvoient un pipeline construit. Une mémoire configurée fait de la configuration un pipeline de thread. Les deux loaders imposent cette distinction.
load_pipeline(path)renvoie unAnonymizationPipeline. Il lèveConfigErrorquand la configuration déclare une mémoire.load_thread_pipeline(path)renvoie unThreadAnonymizationPipeline. Il lèveConfigErrorquand la configuration ne déclare pas de mémoire.
from piighost.config import load_pipeline, load_thread_pipeline
pipeline = load_pipeline("pipeline.toml")
thread_pipeline = load_thread_pipeline("thread.toml")
Ce package a besoin de l'extra config. Voir la référence Configuration TOML pour le format du fichier.
Exemple complet¶
import asyncio
from gliner2 import GLiNER2
from piighost.components.anonymizer import Anonymizer
from piighost.components.detector.ner.gliner2 import Gliner2Detector
from piighost.components.linker import ExactEntityLinker
from piighost.components.placeholder import LabelCounterPlaceholderFactory
from piighost.conversation_memory import InMemoryConversationMemory
from piighost.pipeline import ThreadAnonymizationPipeline
model = GLiNER2.from_pretrained("fastino/gliner2-multi-v1")
detector = Gliner2Detector(model=model, threshold=0.5, labels=["PERSON", "LOCATION"])
factory = LabelCounterPlaceholderFactory()
anonymizer = Anonymizer(factory)
linker = ExactEntityLinker()
memory = InMemoryConversationMemory()
pipeline = ThreadAnonymizationPipeline(
detector=detector,
linker=linker,
anonymizer=anonymizer,
memory=memory,
)
async def main():
result = await pipeline.anonymize("Patrick is in Lyon.", thread_id="user-A")
print(result.text) # <<PERSON:1>> is in <<LOCATION:1>>.
original = await pipeline.deanonymize(result.text, thread_id="user-A")
print(original) # Patrick is in Lyon.
asyncio.run(main())
Voir aussi¶
- Référence Anonymizer pour l'
Anonymizer, son résultatAnonymizationet le portAnyAnonymizer. - Architecture pour l'agencement des étages.
- Configuration TOML pour la construction déclarative.