Aller au contenu

Référence de configuration

Module : piighost.config

Un fichier de configuration décrit un pipeline entier de façon déclarative. piighost le lit en TOML ou en JSON, selon le suffixe du fichier. Il le valide ensuite avec Pydantic, puis construit le pipeline que le fichier décrit. Cette page documente chaque section et chaque type de composant.

from piighost.config import load_config, load_pipeline, load_thread_pipeline

L'extra config est requis (pip install "piighost[config]"). Il tire pydantic-settings. Les clés inconnues sont rejetées, donc une faute de frappe échoue à la validation au lieu d'être ignorée. Un type de composant peut demander son propre extra, nommé dans la colonne Extra du tableau qui le documente.


Points d'entrée

FonctionRenvoieConstruitMémoire
load_config(path)PipelineConfigrien, valide seulementquelconque
load_pipeline(path)AnonymizationPipelineun pipeline sans étatrejette une section [memory]
load_thread_pipeline(path)ThreadAnonymizationPipelineun pipeline de conversationrequiert une section [memory]

load_config analyse et valide un fichier en PipelineConfig sans construire de composant, donc aucun modèle ne charge. load_pipeline construit un AnonymizationPipeline sans état et lève ConfigError si le fichier déclare une section [memory], car une mémoire décrit un pipeline de conversation. load_thread_pipeline construit un ThreadAnonymizationPipeline et lève ConfigError si le fichier ne déclare aucune section [memory].

from piighost.config import load_pipeline, load_thread_pipeline

stateless = load_pipeline("pipeline.toml")  # no [memory]
thread = load_thread_pipeline("thread.toml")  # has [memory]

Format de fichier

Le suffixe choisit le parseur. Un suffixe .json est lu en JSON, quelle que soit sa casse. Tout autre suffixe est lu en TOML. Les deux formats portent le même schéma. Une section est une table TOML ou un objet JSON.

[detector]
type = "regex"
patterns = { EMAIL = '[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}' }

[linker]
type = "exact"

[anonymizer.placeholder]
type = "redact"
{
  "detector": { "type": "regex", "patterns": { "EMAIL": "[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}" } },
  "linker": { "type": "exact" },
  "anonymizer": { "placeholder": { "type": "redact" } }
}

Surcharges par l'environnement

Chaque clé de premier niveau accepte une surcharge par une variable d'environnement préfixée PIIGHOST_, qu'elle porte un scalaire ou une section entière. PIIGHOST_NAME surcharge le scalaire name. PIIGHOST_DETECTOR surcharge la section [detector] avec un objet JSON. Si ce n'est pas du JSON valide, la variable est rejetée comme erreur de validation. Les surcharges se superposent au fichier clé par clé. Une valeur d'environnement l'emporte sur celle du fichier, et les clés qu'elle omet gardent la valeur du fichier.

export PIIGHOST_NAME="local-en"
export PIIGHOST_DETECTOR='{"type": "exact", "values": {"Patrick": "PERSON"}}'

Aucun délimiteur d'imbrication n'est configuré. Une variable comme PIIGHOST_DETECTOR__TYPE ne nomme donc aucun champ. Elle est ignorée sans erreur au lieu d'atteindre la clé type. Une section se surcharge uniquement par son objet JSON.

Les secrets ne sont jamais lus depuis le fichier. Chacun est lu depuis sa propre variable d'environnement à la construction, et une variable manquante lève ConfigError depuis build().

SecretVariableFormatUtilisé par
Poivre de hachagePIIGHOST_HASH_PEPPERtoute chaîne non vide[memory.hasher]
Clé de chiffrementPIIGHOST_CIPHER_KEYbase64 de 16, 24 ou 32 octets[memory.cipher]
Clé de modérationMISTRAL_API_KEYclé d'API Mistral[guard] type moderation
URL de base de donnéesla valeur de url_env, PIIGHOST_DATABASE_URL par défautune URL SQLAlchemy async[memory] type sqlalchemy

Sections

Les clés de premier niveau d'un PipelineConfig.

SectionRequiseSignification
namenonUn nom de pipeline optionnel, un scalaire de premier niveau surchargeable par PIIGHOST_NAME
token_memo_ttlnonLe nombre de secondes pendant lesquelles la carte de jetons mémoïsée d'une conversation est gardée. Un scalaire de premier niveau, qui exige un [memory]
[detector]ouiL'étage de détection
[linker]nonLe linker d'entités, par défaut ExactEntityLinker
[anonymizer]nonL'étage de rendu, par défaut un Anonymizer avec une factory label-counter
[overlap_resolver]nonRésout les détections qui se chevauchent, par défaut ConfidenceOverlapResolver
[expander]nonRetrouve les occurrences manquées d'une valeur détectée
[entity_resolver]nonRegroupe les entités qui désignent la même chose
[guard]nonRevérifie la sortie pour des données confidentielles résiduelles
[override]nonForce ou écarte des détections via une liste à masquer et une liste à laisser en clair
[observation_redactor]nonUne factory de placeholders caviardant les charges de trace
[memory]nonLa mémoire de conversation. Sa présence fait un pipeline de conversation

[detector]

Discriminé sur type. Requis.

type = "regex"

Applique un regex par label, tiré des patterns en ligne, des groupes du catalogue listés dans catalogs, ou des deux. Les groupes fusionnent d'abord, puis les patterns en ligne. Un pattern en ligne l'emporte donc sur un pattern de catalogue de même label. Au moins un pattern en ligne ou un catalogue est requis. Au chargement, chaque pattern est validé comme un regex compilable. Il est ensuite compilé sous re.ASCII, donc \d correspond à 0-9 et \w s'arrête au premier caractère non ASCII. Un motif écrit avec \w reconnaît donc prénom@corp.com à partir de nom. Pour inclure toutes les lettres, limitez le drapeau Unicode à la classe, (?u:\w), ou nommez une plage. Le motif EMAIL de catalog:piighost/generic nomme ainsi la plage latine À-ɏ. Voir Limites pour ce que chaque choix manque.

CléTypeDéfautSignification
patternsdict[str, str]{}Correspondance label vers regex en ligne
catalogslist[str][]Références du catalogue, catalog:namespace/name avec un :selector optionnel. Une référence hub: de la 1.x est toujours acceptée. Toute autre entrée échoue à la validation
[detector]
type = "regex"
catalogs = ["catalog:piighost/generic", "catalog:piighost/fr"]
patterns = { EMPLOYEE_ID = 'EMP-[0-9]{4}' }

Un groupe est récupéré depuis le catalogue à la construction de la config, pas à sa lecture. Une référence épinglée sur un commit est récupérée une fois, puis relue depuis le cache sur disque. PIIGHOST_CATALOG_URL désigne un registre privé, et PIIGHOST_HUB_URL de la 1.x est toujours lue quand elle n'est pas posée. Les noms generic, us, eu et fr sont refusés. Voir Groupes du catalogue pour les groupes qui les remplacent.

type = "composite"

Exécute des détecteurs enfants ensemble et fusionne leurs détections.

CléTypeSignification
detectorslist[detector]Les configs de détecteurs enfants, au moins un, sous [[detector.detectors]]
[detector]
type = "composite"

[[detector.detectors]]
type = "regex"
catalogs = ["catalog:piighost/generic"]

[[detector.detectors]]
type = "exact"
values = { Patrick = "PERSON" }

type = "exact"

Trouve les occurrences de valeurs littérales, chacune associée à un label.

CléTypeSignification
valuesdict[str, str]Correspondance valeur littérale vers label, au moins une
[detector]
type = "exact"
values = { Patrick = "PERSON", Lyon = "LOCATION" }

type = "chunked"

Enveloppe un détecteur avec un splitter qui découpe un texte long en tranches qui se chevauchent.

CléTypeDéfautSignification
detectordetectorLe détecteur exécuté sur chaque tranche, sous [detector.detector]
chunk_sizeint1000Taille maximale d'une tranche, supérieure à 0
chunk_overlapint100Chevauchement entre tranches, inférieur à chunk_size
[detector]
type = "chunked"
chunk_size = 2000
chunk_overlap = 200

[detector.detector]
type = "spacy"
model = "en_core_web_sm"

Détecteurs à modèle

Chacun nécessite son propre extra, et tous sauf presidio nécessitent un modèle. labels accepte une liste ou une map {emitted: internal}. max_concurrency plafonne les inférences concurrentes. None les laisse illimitées.

typeExtraClés
gliner2gliner2model (requis), labels (requis), threshold (défaut 0.5), max_concurrency, max_chars
spacyspacymodel (requis), labels, max_concurrency
transformerstransformersmodel (requis), labels, threshold (défaut 0.0), aggregation_strategy (défaut simple), max_concurrency, max_chars
presidiopresidiolabels, language (défaut en), threshold (défaut 0.0)
llmllmmodel (requis), labels (requis), prompt, provider
[detector]
type = "gliner2"
model = "fastino/gliner2-multi-v1"
labels = ["PERSON", "LOCATION"]
threshold = 0.5
max_chars = 2000

Les détecteurs gliner2 et transformers acceptent max_chars, le plus long texte qu'une inférence voit. Un texte plus long est découpé en morceaux qui se chevauchent. Chaque morceau est analysé séparément, puis les spans sont replacés dans le texte d'origine. Sans cette clé, le texte entier part au modèle en une fois. Un modèle à fenêtre courte tronque alors ce texte, et un long document peut épuiser la mémoire.

Le détecteur transformers passe aggregation_strategy à sa pipeline de classification de tokens, qui regroupe les sous-tokens en entités entières.

Le détecteur presidio ne prend aucune clé model, car le chemin par configuration construit l'AnalyzerEngine anglais par défaut de Presidio avec ses reconnaisseurs par défaut. Une autre langue, un reconnaisseur sur mesure ou un moteur NLP sur mesure passent par le chemin programmatique, en construisant le moteur et en le passant à PresidioDetector.

Le détecteur llm lit l'identifiant de son fournisseur depuis la variable d'environnement propre au fournisseur, jamais depuis le fichier.


[linker]

Optionnel. Par défaut ExactEntityLinker. Un seul linker existe, donc type le nomme sans discriminer une union.

typeSignification
exactRegroupe les détections par valeur, les mêmes mots quelles que soient leurs espaces et leur casse
[linker]
type = "exact"

[anonymizer]

Optionnel. Par défaut un Anonymizer avec une factory label-counter. Quand il est présent, il porte une table [anonymizer.placeholder] qui choisit la factory de placeholders, discriminée sur type.

typeJetonClés
redact<<REDACT>>
label<<PERSON>>
label_counter<<PERSON:1>>
label_hash<<PERSON:a1b2c3d4>>hash_length (défaut 8, au moins 1)
maskP***visible (défaut 1, 0 ou plus), mask_char (défaut *, exactement un caractère)
[anonymizer.placeholder]
type = "label_counter"

Le middleware a besoin d'une factory délimitée, c'est-à-dire redact, label, label_counter ou label_hash. La factory mask produit P***, qui ne garde aucun délimiteur et n'a pas de reconnaisseur.


[overlap_resolver]

Optionnel dans le fichier, mais l'étage tourne dans tous les cas. Omettre la section construit un ConfidenceOverlapResolver. Il n'existe aucun moyen supporté de désactiver l'étage, car l'étage de rendu suppose des spans disjoints.

typeSignification
confidenceGarde la détection la plus confiante quand deux se chevauchent
mergeGarde l'union des détections qui se chevauchent, avec le label de la plus confiante. À confiance égale, c'est le label de la plus large
[overlap_resolver]
type = "merge"

merge cache chaque caractère qu'un détecteur a relevé. Avec confidence, une regex à confiance 1.0 qui a trouvé Wirth l'emporte sur un modèle qui a trouvé Loni M. Wirth. Loni M. part alors en clair. Choisissez merge quand une fuite coûte plus qu'un mot voisin masqué, comme dans un document traité par des règles et un modèle ensemble.


[expander]

Optionnel, et désactivé quand il est omis. Un seul expander existe, donc type le nomme sans discriminer une union.

typeClésSignification
word_boundarycase_sensitive (défaut false)Retrouve les autres occurrences entières d'une valeur détectée, quelles que soient les espaces entre ses mots
[expander]
type = "word_boundary"
case_sensitive = false

[entity_resolver]

Optionnel. Discriminé sur type.

typeExtraClésSignification
mergeUnit les entités qui partagent des détections
separateGarde chaque entité distincte
fuzzyfuzzythreshold (défaut 0.85)Regroupe les entités au-dessus d'une similarité de Jaro-Winkler
[entity_resolver]
type = "fuzzy"
threshold = 0.85

[guard]

Optionnel. Discriminé sur type. Revérifie la sortie dé-identifiée pour des données confidentielles résiduelles et la refuse quand il en subsiste.

typeExtraRevérifie avec
detectorUn détecteur réexécuté sur la sortie
llmllmUn modèle de chat à qui l'on demande la PII résiduelle
moderationmistralUn modèle de modération Mistral qui note la sortie
gliner2gliner2Un modèle garde-fou GLiNER2 local qui classe la sortie

type = "detector"

Réexécute un détecteur sur la sortie. Porte une config imbriquée [guard.detector].

[guard]
type = "detector"

[guard.detector]
type = "regex"
patterns = { EMAIL = '[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}' }

type = "llm"

Demande à un modèle de chat de trouver une PII résiduelle.

CléTypeSignification
modelstrL'identifiant du modèle de chat (requis)
labelslist ou dictLes labels à chercher (requis)
promptstrUn prompt qui remplace celui par défaut, ou omis
providerstrLe fournisseur, ou omis pour l'inférer du modèle

type = "moderation"

Note la sortie avec un modèle de modération Mistral. L'identifiant est lu depuis MISTRAL_API_KEY à la construction, et build() lève ConfigError quand il est absent.

CléTypeDéfautSignification
modelstrmistral-moderation-latestLe modèle de modération
thresholdfloat0.5Le score de catégorie au-dessus duquel le texte est signalé

type = "gliner2"

Classe la sortie avec un modèle garde-fou GLiNER2 qui tourne dans le processus, sans aucun identifiant. Le checkpoint est téléchargé à la première construction, puis lu depuis le cache Hugging Face.

CléTypeDéfautSignification
modelstrfastino/GLiNER2-Guardrails-PII-MultiLe checkpoint GLiNER2 avec lequel le garde-fou classe
taskstrresponse_safetyLa tâche de classification lue dans la réponse du modèle
labelslist["safe", "unsafe"]Les réponses entre lesquelles la tâche choisit, deux au moins. La réponse refusée vient en dernier
thresholdfloat0.5La confiance à partir de laquelle une réponse refusée signale la sortie
[guard]
type = "gliner2"
threshold = 0.5

[override]

Optionnel. Force des détections via une liste à masquer, dont les valeurs sont toujours masquées, et en écarte via une liste à laisser en clair, dont les valeurs restent toujours en clair. Chaque liste est une config de détecteur, [override.deny_list] et [override.allow_list], toutes deux optionnelles. Les clés de la 1.x, whitelist, blacklist et leurs stratégies, sont refusées au chargement avec la clé qui les remplace, voir Passer à la 2.0.

CléValeursDéfautSignification
[override.deny_list]détecteurUn détecteur dont les hits sont toujours masqués, forcés dans l'ensemble
[override.allow_list]détecteurUn détecteur dont les hits restent toujours en clair, en invalidant les détections qu'ils recouvrent
allow_list_strategyexact, value, overlapvalueComment un hit de la liste à laisser en clair invalide une détection. value exige la même valeur, quelles que soient ses espaces et sa casse. exact exige le même span et le même label. overlap invalide tout span en chevauchement
deny_list_strategyrespect_provenance, forcerespect_provenanceSi un hit de la liste à masquer laisse en clair une valeur introduite par l'assistant, ou la dé-identifie quand même
conflict_strategydeny_list_wins, allow_list_wins, raisedeny_list_winsQui l'emporte quand les deux listes se contredisent. raise refuse la collision avec ConflictingOverrideError
[override]
allow_list_strategy = "value"

[override.deny_list]
type = "regex"
patterns = { CODENAME = 'ACME-[A-Z]+' }

[override.allow_list]
type = "exact"
values = { "public@corp.com" = "EMAIL" }

[observation_redactor]

Optionnel. Une config de factory de placeholders, avec les mêmes valeurs de type que [anonymizer.placeholder]. Elle caviarde les charges envoyées à un backend de traçage, pour qu'une trace porte des jetons et pas des valeurs brutes.

[observation_redactor]
type = "label"

Sans cette section, le texte en clair et les valeurs détectées sont tracés. Un traceur actif émet alors un PIIGhostSecurityWarning. Le drapeau trace_clear_text du pipeline fait taire cet avertissement, mais il n'a aucune clé dans un fichier de configuration. Un pipeline construit depuis un fichier ne peut donc pas assumer le traçage en clair. Passer trace_clear_text=True au pipeline est le chemin programmatique.


[memory]

Optionnel. Sa présence fait du pipeline un ThreadAnonymizationPipeline qui garde un état par conversation. Discriminé sur type.

Le scalaire token_memo_ttl va avec cette section, mais il se pose au premier niveau, parce qu'il borne la carte de jetons mémoïsée du pipeline et non le store. Le poser sans [memory] lève une erreur, parce qu'un pipeline sans état ne mémoïse rien. Déploiement multi-instance explique pourquoi il compte sur un déploiement multi-worker.

typeExtraStockage
in_memoryLocal au processus, perdu au redémarrage
redisredisPersistant, partagé entre workers
sqlalchemysqlalchemyDurable, dans une base SQL

type = "in_memory"

Un stockage local au processus, perdu au redémarrage et non partagé entre workers.

CléTypeDéfautSignification
max_threadsint10000Plafond de conversations gardées, éviction LRU au-delà (au moins 1)
ttlfloat86400Durée d'inactivité, en secondes, après laquelle une conversation expire (supérieur à 0). Elle n'est retirée qu'au prochain accès
[memory]
type = "in_memory"

type = "redis"

Un stockage persistant et multi-worker. En option, il indexe chaque message stocké avec un hacheur et chiffre chaque valeur stockée avec un cipher.

CléTypeDéfautSignification
urlstrL'URL de connexion Redis (requis)
namespacestrpiighostLe préfixe de clé isolant les clés de cette librairie
ttlintNoneSecondes de vie d'un message stocké, ou omis pour garder jusqu'à l'éviction
[memory.hasher]hacheurOptionnel (les deux ou aucun). Le hacheur qui indexe chaque message
[memory.cipher]cipherOptionnel (les deux ou aucun). Le cipher qui chiffre chaque valeur

Configurez les deux, [memory.hasher] et [memory.cipher], ou aucun. Sans aucun, le backend stocke la correspondance en clair et émet un avertissement. Avec un seul, build() lève ConfigError.

Le hacheur, [memory.hasher], est discriminé sur type.

typeExtraClésSignification
sha256HMAC-SHA256, un condensé rapide à clé
argon2argon2time_cost (défaut 2), memory_cost (défaut 19456), parallelism (défaut 1), hash_length (défaut 32)Argon2id, un condensé lent et gourmand en mémoire

Le cipher, [memory.cipher], a un seul type.

typeExtraSignification
aesgcmcryptoChiffrement authentifié AES-GCM des valeurs stockées

Le hacheur lit son poivre depuis PIIGHOST_HASH_PEPPER et le cipher lit sa clé base64 depuis PIIGHOST_CIPHER_KEY, tous deux à la construction. Une valeur manquante ou mal formée lève ConfigError.

[memory]
type = "redis"
url = "redis://localhost:6379/0"
namespace = "piighost"
ttl = 3600

[memory.hasher]
type = "argon2"

[memory.cipher]
type = "aesgcm"

type = "sqlalchemy"

Un stockage durable et multi-worker adossé à n'importe quelle base supportée par SQLAlchemy (SQLite, PostgreSQL, ...). Il lit l'URL de la base depuis une variable d'environnement plutôt que le fichier de config, pour que l'URL et son mot de passe restent hors du gestionnaire de versions. Un hacheur et un cipher optionnels protègent les valeurs stockées exactement comme pour Redis.

CléTypeDéfautSignification
url_envstrPIIGHOST_DATABASE_URLLa variable d'environnement contenant l'URL async de la base
table_namestrpiighost_conversation_messagesLa table stockant les messages par conversation
[memory.hasher]hacheurOptionnel (les deux ou aucun). Le hacheur qui indexe chaque message
[memory.cipher]cipherOptionnel (les deux ou aucun). Le cipher qui chiffre chaque valeur

Configurez les deux, [memory.hasher] et [memory.cipher], ou aucun, exactement comme pour Redis. Sans aucun, le backend stocke la correspondance en clair et émet un avertissement. Avec un seul, build() lève ConfigError.

L'URL doit utiliser un driver async, par exemple postgresql+asyncpg://... ou sqlite+aiosqlite://.... Une variable d'environnement manquante lève ConfigError à la construction. Appelez await memory.create_schema() une fois au démarrage pour créer la table.

[memory]
type = "sqlalchemy"
url_env = "PIIGHOST_DATABASE_URL"
table_name = "piighost_conversation_messages"

[memory.hasher]
type = "argon2"

[memory.cipher]
type = "aesgcm"

Exemple complet

Les clés de examples/config/pipeline.toml, un pipeline sans état qui tire un groupe du catalogue, ajoute un pattern en ligne, et active plusieurs étages optionnels. Le fichier lui-même porte les mêmes clés avec un commentaire sur chaque étage.

[detector]
type = "regex"
catalogs = ["catalog:piighost/generic"]
patterns = { EMPLOYEE_ID = 'EMP-[0-9]{4}' }

[overlap_resolver]
type = "confidence"

[expander]
type = "word_boundary"

[entity_resolver]
type = "fuzzy"
threshold = 0.85

[linker]
type = "exact"

[anonymizer.placeholder]
type = "label_counter"

[override.deny_list]
type = "regex"
patterns = { CODENAME = 'ACME-[A-Z]+' }

[guard]
type = "detector"

[guard.detector]
type = "regex"
patterns = { EMAIL = '[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}' }

[observation_redactor]
type = "label"

Le même contenu en JSON, choisi par un suffixe .json, est équivalent. Une table devient un objet, une table en ligne devient un objet imbriqué, et un tableau de tables devient un tableau d'objets.


Erreurs

ErreurLevée quand
ConfigFileErrorLe fichier est absent, illisible, ou du TOML ou JSON invalide
ConfigValidationErrorLes données analysées échouent à la validation du schéma
ConfigErrorUn secret manque à la construction, ou le mauvais point d'entrée est utilisé pour la mémoire déclarée

ConfigFileError et ConfigValidationError sont des sous-classes de ConfigError, donc attraper ConfigError couvre les trois. Les classes vivent dans piighost.exceptions, donc un appelant peut les attraper sans l'extra config.


Voir aussi