Référence Détecteurs¶
Module : piighost.components.detector
Un détecteur est l'étage de détection d'un pipeline. Il lit un texte et renvoie les PII qu'il y trouve. Tout détecteur satisfait le port AnyDetector et renvoie une liste de Detection, quel que soit le backend qu'il enveloppe.
from piighost.components.detector import (
ChunkedDetector,
CompositeDetector,
ExactMatchDetector,
LLMDetector,
RegexDetector,
)
from piighost.components.detector.ner import (
Gliner2Detector,
Gliner2PiiDetector,
PresidioDetector,
SpacyDetector,
TransformersDetector,
)
Chaque détecteur NER a besoin de son propre extra (gliner2, spacy, transformers, presidio). LLMDetector a besoin de l'extra llm et d'un paquet fournisseur.
AnyDetector (protocole)¶
Le port que tout détecteur implémente. Une seule méthode asynchrone, donc une implémentation peut attendre une I/O comme un serveur de modèle ou une API LLM sans bloquer le pipeline.
detect renvoie les détections dans un ordre quelconque. Les chevauchements et les doublons sont résolus par les étages suivants du pipeline, pas par le détecteur.
Detection¶
Chaque détecteur renvoie une liste de Detection, un dataclass gelé qui porte l'emplacement de la correspondance, le texte trouvé, son label et sa confiance.
| Attribut | Type | Description |
|---|---|---|
span |
Span |
L'emplacement de la détection, en intervalle semi-ouvert |
text |
str |
La sous-chaîne trouvée |
label |
str |
La catégorie de PII, par exemple PERSON ou EMAIL |
confidence |
float |
La confiance du détecteur, dans l'intervalle fermé 0 à 1 |
RegexDetector¶
Trouve les PII en appliquant un pattern regex par label. Chaque pattern est compilé une fois à la construction, sous re.ASCII, donc \d et les autres classes de forme ne correspondent qu'à l'ASCII. Un caractère Unicode ressemblant à un chiffre, comme un chiffre arabo-indien, ne correspond pas, car un format de PII utilise des chiffres ASCII. detect émet une détection par correspondance sans chevauchement, à une confiance fixe de 1.0.
Il ne porte aucun validateur de somme de contrôle, donc il correspond sur la forme seule. Une valeur structurée abîmée par un OCR est conservée plutôt que rejetée, car rejeter une vraie valeur reviendrait à la laisser fuiter.
Constructeur¶
| Paramètre | Type | Description |
|---|---|---|
patterns |
dict[str, str] |
Correspondance d'un label de PII vers le pattern regex à appliquer (requis) |
from piighost.components.detector import RegexDetector
detector = RegexDetector({"EMAIL": r"[\w.+-]+@[\w.-]+\.\w{2,}"})
detections = await detector.detect("write to alice@example.com")
# [Detection(span=Span(9, 26), text="alice@example.com", label="EMAIL", confidence=1.0)]
from_hub¶
Construit un détecteur à partir des regex que porte une référence du hub piighost. Le hub est un registre de regex de dé-identification testées, adressées par namespace/name et un sélecteur optionnel : un tag, ou les huit caractères hexadécimaux d'un commit.
| Paramètre | Type | Description |
|---|---|---|
ref |
str |
Une référence, namespace/name avec un :selector optionnel et un préfixe hub: optionnel. Sans sélecteur, elle résout vers latest (requis) |
hub |
str \| None |
Origine du hub à interroger. Par défaut PIIGHOST_HUB_URL, puis le hub public |
from piighost.components.detector import RegexDetector
detector = RegexDetector.from_hub("piighost/logs:fd79aec6")
detections = await detector.detect("mail me at a@b.co from 10.0.0.1")
Une référence épinglée sur un commit est immuable : la réponse est mise en cache sous ~/.cache/piighost/hub et relue depuis le disque aux appels suivants. Une référence pointant vers un tag ou vers latest bouge, elle est donc récupérée à chaque fois : servir une version périmée détecterait silencieusement moins que ce que l'appelant a demandé.
L'appel lève une sous-classe de HubError (piighost.hub) si la référence ne se parse pas, si le hub est injoignable, ou si la référence résout vers autre chose qu'un détecteur regex simple. Ce dernier cas couvre une référence portant un détecteur modèle : n'en prendre que les regex détecterait moins que ce que la référence promet, donc l'appel échoue plutôt que d'en rendre la moitié.
Il n'utilise que la bibliothèque standard, donc l'installation de base n'a besoin d'aucun extra.
CompositeDetector¶
Fait tourner plusieurs détecteurs sur le même texte et fusionne leurs détections. Il est lui-même un AnyDetector, donc il se compose avec le pipeline sans changement. Il exécute chaque enfant en parallèle et concatène leurs résultats dans l'ordre des enfants. Il ne déduplique pas. Chevauchements et doublons passent à l'étage de résolution de spans.
Constructeur¶
| Paramètre | Type | Description |
|---|---|---|
detectors |
list[AnyDetector] |
Les détecteurs enfants à exécuter, dans l'ordre (requis) |
from piighost.components.detector import CompositeDetector, RegexDetector
from piighost.components.detector.ner import Gliner2Detector
email_detector = RegexDetector({"EMAIL": r"[\w.+-]+@[\w.-]+\.\w{2,}"})
person_detector = Gliner2Detector(model="fastino/gliner2-multi-v1", labels=["PERSON"])
detector = CompositeDetector([email_detector, person_detector])
ExactMatchDetector¶
Trouve les occurrences en mot entier de valeurs littérales configurées. Il parcourt le texte pour chaque valeur et émet une détection par occurrence à une confiance de 1.0. La correspondance se fait sur des frontières de mot, donc une valeur ne se déclenche pas à l'intérieur d'un mot plus long (Ann ne correspond pas dans Anne), et elle est insensible à la casse par défaut, donc une valeur correspond quelle que soit sa casse tandis que la détection garde le texte tel qu'il apparaît. Il ne porte aucun modèle et aucune dépendance optionnelle, ce qui en fait le détecteur de choix pour exercer le pipeline dans les tests.
Constructeur¶
| Paramètre | Type | Description |
|---|---|---|
values |
dict[str, str] |
Correspondance d'une valeur littérale vers le label de PII à émettre pour elle (requis) |
case_sensitive |
bool |
Si la correspondance respecte la casse. False par défaut |
from piighost.components.detector import ExactMatchDetector
detector = ExactMatchDetector({"Patrick": "PERSON", "Lyon": "LOCATION"})
detections = await detector.detect("Patrick lives in Lyon")
ChunkedDetector¶
Fait tourner un détecteur enveloppé sur chaque morceau d'un texte long. C'est un décorateur, lui-même un AnyDetector. Il découpe le texte en morceaux qui se chevauchent, exécute le détecteur enveloppé sur chacun, et reprojette chaque détection sur le texte original. Les détections strictement identiques produites par le chevauchement sont supprimées. Conflits de label et confiances différentes passent à l'étage de résolution de spans.
Constructeur¶
| Paramètre | Type | Description |
|---|---|---|
detector |
AnyDetector |
Le détecteur exécuté sur chaque morceau (requis) |
splitter |
AnySplitter \| None |
Le splitter, ou None pour un RecursiveCharacterTextSplitter par défaut |
from piighost.components.detector import ChunkedDetector
from piighost.components.detector.ner import SpacyDetector
spacy_detector = SpacyDetector(model="en_core_web_sm")
detector = ChunkedDetector(spacy_detector)
LLMDetector¶
Détecte les PII avec un modèle de chat LangChain via une sortie structurée. A besoin de l'extra llm et d'un paquet fournisseur. On demande au modèle d'extraire des paires (text, label) contre un schéma dont le champ label est contraint aux labels configurés. Chaque valeur extraite est ensuite localisée dans le texte source par recherche sur frontière de mot, donc une valeur inventée par le modèle mais absente du texte ne donne rien. labels est requis, puisque le schéma en est construit. Le texte source est enveloppé dans des balises <text_to_analyze> et le prompt système ordonne au modèle de traiter le contenu balisé comme des données, jamais comme des instructions, donc une tentative d'injection de prompt dans le texte ne peut pas orienter l'extraction.
Constructeur¶
LLMDetector(
model: BaseChatModel | str,
labels: list[str] | dict[str, str],
prompt: str | None = None,
provider: str | None = None,
confidence: float = 1.0,
)
| Paramètre | Type | Description |
|---|---|---|
model |
BaseChatModel \| str |
Un modèle de chat chargé, ou un nom chargé avec init_chat_model (requis) |
labels |
list[str] \| dict[str, str] |
Les labels à extraire, liste ou map {emitted: internal} (requis) |
prompt |
str \| None |
Un prompt système personnalisé, ou None pour celui par défaut |
provider |
str \| None |
Le fournisseur passé à init_chat_model quand model est un nom |
confidence |
float |
Confiance portée sur chaque détection, 1.0 par défaut, pour qu'un détecteur LLM puisse être départagé face à un détecteur NER à la résolution des chevauchements |
Un prompt personnalisé doit contenir un placeholder {labels} et, selon le format f-string de LangChain, doubler toute autre accolade littérale en {{ ou }}.
from piighost.components.detector import LLMDetector
detector = LLMDetector(
model="gpt-5.6-terra",
labels=["PERSON", "EMAIL"],
provider="openai",
)
Détecteurs NER¶
Les détecteurs adossés à un modèle étendent BaseNERDetector, qui gère la correspondance et le filtrage des labels (voir plus bas). Chacun a besoin de son propre extra et prend un modèle chargé ou un nom de modèle à charger, sauf PresidioDetector, qui prend un AnalyzerEngine construit.
Gliner2Detector¶
Un modèle GLiNER2 zero-shot. A besoin de l'extra gliner2. labels est requis, car GLiNER2 est interrogé avec les labels internes. Un model en str est chargé avec GLiNER2.from_pretrained.
Gliner2Detector(
model: GLiNER2 | str,
labels: list[str] | dict[str, str],
threshold: float = 0.5,
max_concurrency: int | None = None,
max_chars: int | None = None,
auto_chunk: bool = True,
)
| Paramètre | Type | Description |
|---|---|---|
model |
GLiNER2 \| str |
Un modèle chargé, ou un nom chargé avec from_pretrained (requis) |
labels |
list[str] \| dict[str, str] |
Les labels à interroger, liste ou map {emitted: internal} (requis) |
threshold |
float |
La confiance à partir de laquelle une entité est conservée |
max_concurrency |
int \| None |
Plafond d'inférences concurrentes, ou None pour sans limite |
max_chars |
int \| None |
Limite en caractères vue par une seule inférence, ou None pour aucune limite |
auto_chunk |
bool |
Si un texte plus long que max_chars est découpé et reprojeté, sinon lève TextTooLongError |
Gliner2PiiDetector¶
Un Gliner2Detector prêt à l'emploi sur le modèle GLiNER2 de fastino affiné pour les PII, avec une map de labels préréglée, donc ni identifiant de modèle ni argument labels n'est requis. Le préréglage couvre la taxonomie du modèle, des noms et coordonnées aux identifiants, données de paiement, identité numérique, secrets et dates sensibles. Passez labels pour restreindre ou étendre l'ensemble, ou model pour injecter une instance chargée, par exemple dans un test, afin qu'aucun poids ne soit téléchargé.
Gliner2PiiDetector(
model: GLiNER2 | str | None = None,
labels: list[str] | dict[str, str] | None = None,
threshold: float = 0.5,
max_concurrency: int | None = None,
max_chars: int | None = None,
auto_chunk: bool = True,
)
| Paramètre | Type | Description |
|---|---|---|
model |
GLiNER2 \| str \| None |
Un modèle chargé ou un nom, ou None pour le modèle PII préréglé |
labels |
list[str] \| dict[str, str] \| None |
Les labels à interroger, ou None pour la map de labels PII préréglée |
threshold |
float |
La confiance à partir de laquelle une entité est conservée |
max_concurrency |
int \| None |
Plafond d'inférences concurrentes, ou None pour sans limite |
max_chars |
int \| None |
Limite en caractères vue par une seule inférence, ou None pour aucune limite |
auto_chunk |
bool |
Si un texte plus long que max_chars est découpé et reprojeté, sinon lève TextTooLongError |
SpacyDetector¶
Un modèle NER spaCy. A besoin de l'extra spacy. labels est optionnel. Omis, chaque entité produite par spaCy est conservée avec son label spaCy. Un model en str est chargé avec spacy.load.
SpacyDetector(
model: Language | str,
labels: list[str] | dict[str, str] | None = None,
max_concurrency: int | None = None,
)
| Paramètre | Type | Description |
|---|---|---|
model |
Language \| str |
Un modèle chargé, ou un nom chargé avec spacy.load (requis) |
labels |
list[str] \| dict[str, str] \| None |
Les labels à mapper et filtrer, ou None pour garder chaque label natif |
max_concurrency |
int \| None |
Plafond d'inférences concurrentes, ou None pour sans limite |
TransformersDetector¶
Un pipeline de classification de tokens Hugging Face. A besoin de l'extra transformers. labels est optionnel, gardé natif s'il est omis. Un pipeline en str est chargé comme un pipeline ner. Une entité qui score sous threshold est rejetée.
TransformersDetector(
pipeline: TokenClassificationPipeline | str,
labels: list[str] | dict[str, str] | None = None,
threshold: float = 0.0,
max_concurrency: int | None = None,
aggregation_strategy: str = "simple",
max_chars: int | None = None,
auto_chunk: bool = True,
)
| Paramètre | Type | Description |
|---|---|---|
pipeline |
TokenClassificationPipeline \| str |
Un pipeline construit, ou un nom de modèle chargé comme pipeline ner (requis) |
labels |
list[str] \| dict[str, str] \| None |
Les labels à mapper et filtrer, ou None pour garder chaque label natif |
threshold |
float |
Le score sous lequel une entité détectée est rejetée |
max_concurrency |
int \| None |
Plafond d'inférences concurrentes, ou None pour sans limite |
aggregation_strategy |
str |
Comment les sous-tokens sont regroupés en entités entières, appliqué seulement à la construction depuis un nom de modèle. Un pipeline injecté garde la sienne. "simple" par défaut |
max_chars |
int \| None |
Limite en caractères vue par une seule inférence, ou None pour aucune limite |
auto_chunk |
bool |
Si un texte plus long que max_chars est découpé et reprojeté, sinon lève TextTooLongError |
PresidioDetector¶
Enveloppe un AnalyzerEngine de Presidio pour réutiliser ses recognizers. A besoin de l'extra presidio. L'analyzer est injecté, car un moteur est assemblé d'un moteur NLP et d'un registre de recognizers, pas chargé depuis un nom. labels est optionnel, gardé natif quand il est omis. Une entité scorant sous threshold est écartée par Presidio.
PresidioDetector(
analyzer: AnalyzerEngine,
labels: list[str] | dict[str, str] | None = None,
language: str = "en",
threshold: float = 0.0,
max_concurrency: int | None = None,
)
| Paramètre | Type | Description |
|---|---|---|
analyzer |
AnalyzerEngine |
Un analyzer Presidio construit (requis) |
labels |
list[str] \| dict[str, str] \| None |
Les labels à mapper et filtrer, ou None pour garder chaque type natif |
language |
str |
Le code de langue passé à analyze |
threshold |
float |
Le score sous lequel une entité est écartée |
max_concurrency |
int \| None |
Plafond d'inférences concurrentes, ou None pour sans limite |
Depuis une config, le type de détecteur presidio construit l'AnalyzerEngine anglais par défaut de Presidio. Pour une autre langue ou des recognizers custom, construisez le moteur vous-même et utilisez PresidioDetector directement.
Gestion des textes longs¶
Gliner2Detector et TransformersDetector prennent max_chars avec auto_chunk (défaut True). Un texte plus long que max_chars est découpé en morceaux qui se chevauchent, scannés séparément, puis reprojetés sur le texte original. Avec auto_chunk désactivé, un texte au-delà de la limite lève TextTooLongError à la place. max_chars vaut None par défaut, donc il n'y a pas de limite et le texte entier est scanné en une passe. SpacyDetector et PresidioDetector ne les exposent pas.
Correspondance des labels¶
BaseNERDetector normalise l'argument labels en une map externe vers interne, puis mappe et filtre les détections produites par le modèle. Il distingue le label qu'un modèle utilise nativement du label émis dans Detection.label.
- Une liste,
["PERSON", "LOCATION"], mappe chaque label vers lui-même. - Une map,
{"PERSON": "PER"}, prend le label émis comme clé et le label natif du modèle comme valeur, donc une détection que le modèle étiquettePERest émise enPERSON. Un label natif absent des valeurs de la map est rejeté. Noneou une map vide n'applique aucune correspondance, donc chaque détection est gardée avec le label donné par le modèle.
Deux labels externes mappant vers un même label interne lèvent LabelMappingError, car la recherche inverse serait ambiguë.
from piighost.components.detector.ner import TransformersDetector
detector = TransformersDetector(
pipeline="dslim/bert-base-NER",
labels={"PERSON": "PER", "LOCATION": "LOC"},
)
Catalogues de patterns¶
Ensembles de patterns regex réutilisables pour RegexDetector. Chaque catalogue est un dict[str, str] simple qui associe un label de PII à un pattern regex. Les patterns correspondent sur la forme seule, sans validation de somme de contrôle.
from piighost.components.detector.patterns import (
EU_PATTERNS,
FR_PATTERNS,
GENERIC_PATTERNS,
US_PATTERNS,
)
Passez un catalogue à un RegexDetector, ou fusionnez-en plusieurs par fusion de dict, un pattern en ligne sur le même label prenant le dessus.
from piighost.components.detector import RegexDetector
from piighost.components.detector.patterns import FR_PATTERNS, GENERIC_PATTERNS
detector = RegexDetector({**GENERIC_PATTERNS, **FR_PATTERNS})
| Catalogue | Import | Labels |
|---|---|---|
| Générique | GENERIC_PATTERNS |
EMAIL, URL, IPV4, CREDIT_CARD |
| US | US_PATTERNS |
US_SSN, US_PHONE, US_ZIP |
| EU | EU_PATTERNS |
IBAN |
| France | FR_PATTERNS |
FR_PHONE, FR_IBAN, FR_NIR, FR_SIRET |
Chaque pattern de catalogue est testé contre le backtracking catastrophique, de sorte qu'une entrée adverse ne peut pas transformer un scan en déni de service.
Les labels de GENERIC_PATTERNS ne dépendent d'aucun pays. Les autres sont préfixés (US_, FR_) pour ne pas se confondre quand les catalogues sont fusionnés. EU_PATTERNS porte l'IBAN ISO 13616 partagé entre les États membres. Pour des numéros propres à un pays, utilisez un catalogue par pays.
Tirer les catalogues depuis une config¶
Une config de détecteur regex tire les catalogues via catalogs. Une entrée est soit un nom prédéfini, parmi generic, us, eu, fr, soit une référence de hub écrite hub:namespace/name avec un :selector optionnel. Les catalogues fusionnent dans l'ordre, puis les patterns en ligne, donc un pattern en ligne l'emporte sur un pattern de catalogue sur le même label. Une config de détecteur regex a besoin d'au moins un pattern en ligne ou un catalogue.
[detector]
type = "regex"
catalogs = ["generic", "fr"]
[detector.patterns]
INTERNAL_ID = "EMP-\\d{6}"
Une référence de hub nomme un catalogue relu au lieu d'en porter une copie : la config reste courte et les patterns restent auditables à leur source :
Un catalogue de hub est récupéré à la construction de la config, pas à sa lecture, et une référence épinglée sur un commit est ensuite mise en cache sur disque. Définissez PIIGHOST_HUB_URL pour interroger un registre privé. Un nom inconnu ou une référence malformée échoue au chargement plutôt que sous forme d'URL invalide plus tard.
Voir aussi¶
- Référence Pipeline pour le pipeline qui pilote le détecteur.
- Détecteurs prêts à l'emploi pour composer les catalogues en pratique.
- Configuration TOML pour la construction déclarative.
- Étendre PIIGhost pour écrire son propre détecteur.
- Référence des modèles de données pour la forme complète d'une
Detection.