Aller au contenu

Référence des garde-fous

Module : piighost.components.guard

Un garde-fou est le dernier étage, optionnel, du pipeline. Il revérifie le texte dé-identifié pour y chercher des valeurs confidentielles résiduelles. S'il en trouve, le pipeline lève PIIRemainingError plutôt que de renvoyer une fuite. Chaque garde-fou satisfait le port AnyGuardRail, un async def check(self, text: str) -> GuardVerdict. Il renvoie un GuardVerdict indiquant si des valeurs confidentielles semblent subsister et comment il le sait. Contrairement aux autres étages, les garde-fous ne partagent aucun template Base*, parce que leurs mécanismes de vérification n'ont pas de squelette commun. L'un réexécute un détecteur local, un autre appelle une API externe.

Le garde-fou classifie, il ne décide pas. Il rapporte un verdict. Le pipeline transforme un verdict signalé en exception, et votre code choisit comment réagir.

from piighost.components.guard import (
    DetectorGuardRail,
    LLMGuardRail,
    ModerationGuardRail,
)

Brancher un garde-fou dans un pipeline

AnonymizationPipeline prend un argument guard optionnel, désactivé par défaut. Une fois défini, le garde-fou s'exécute sur la sortie rendue après dé-identification, et le pipeline lève PIIRemainingError si le garde-fou signale quelque chose d'inattendu.

from piighost.catalog import pull
from piighost.components.detector import ExactMatchDetector, RegexDetector
from piighost.components.guard import DetectorGuardRail
from piighost.exceptions import PIIRemainingError
from piighost.pipeline import AnonymizationPipeline

# The primary detector only knows the literal name. The guard re-runs a broader
# email and phone regex over the short output to catch structured PII it missed.
guard_detector = RegexDetector(
    {**pull("catalog:piighost/generic"), **pull("catalog:piighost/us")}
)
pipeline = AnonymizationPipeline(
    ExactMatchDetector({"Emma Doe": "PERSON"}),
    guard=DetectorGuardRail(guard_detector),
)

try:
    result = await pipeline.anonymize("Emma Doe, reachable at emma@acme.com.")
except PIIRemainingError as error:
    print(error)  # Anonymized text still contains PII: ['EMAIL']
    print(error.detections)  # the residual detections behind the flag

La version exécutable est examples/guard_rail.py. Ce script utilise aussi un garde-fou seul. Il appelle await guard.check(text) et lit le verdict sans lever d'exception. La version à modèle local est examples/guard_rail_local_model.py.

DetectorGuardRail

Réexécute un détecteur sur la sortie dé-identifiée et signale tout ce qu'il y trouve encore, en portant les détections résiduelles sur le verdict.

DetectorGuardRail(detector: AnyDetector)

Ce garde-fou n'a de valeur qu'avec un détecteur différent de celui du pipeline. Réexécuter le même ne trouve rien, puisque le pipeline a déjà dé-identifié tout ce qu'il détecte. Un détecteur plus puissant ou complémentaire, exécuté en seconde passe peu coûteuse sur la courte sortie dé-identifiée, rattrape ce que le détecteur primaire a manqué. Les placeholders synthétiques n'ont pas la forme de vraies valeurs, donc un détecteur conçu pour de vraies valeurs les laisse tranquilles. DetectorGuardRail ne requiert aucun extra.

Un modèle local comme garde

Le détecteur complémentaire est souvent un modèle, parce que les formes qu'un regex attrape bien sont justement celles que la passe primaire a déjà prises. Ce qui passe au travers, c'est un nom, une adresse, une raison sociale :

DetectorGuardRail(
    Gliner2Detector(
        model="fastino/GLiNER2-Guardrails-PII-Multi",
        labels=["person", "address"],
        threshold=0.5,
    )
)

Ce garde localise ce qui a fuité. C'est ce qu'un garde adossé à un détecteur apporte de plus qu'un classifieur. Pour un verdict au niveau du texte issu du même checkpoint, sans spans et en une seule passe, voir Gliner2GuardRail.

LLMGuardRail

Enveloppe un LLMDetector configuré avec un prompt de garde qui dit au modèle d'ignorer les placeholders et de ne signaler que les PII résiduelles en clair, puis rapporte un verdict.

LLMGuardRail(
    model: BaseChatModel | str,
    labels: list[str] | dict[str, str],
    prompt: str | None = None,
    provider: str | None = None,
    prefix: str = "<<",
    suffix: str = ">>",
    fail_open: bool = False,
)

Un modèle str est chargé comme celui de LLMDetector. Une instance déjà chargée est utilisée telle quelle. Un prompt personnalisé doit contenir un placeholder {labels}. Quand aucun prompt personnalisé n'est fourni, prefix et suffix (par défaut << et >>) façonnent les exemples de placeholder du prompt par défaut pour qu'ils correspondent aux délimiteurs que le pipeline émet. Une sortie que le garde ne sait pas lire lève UnreadableOutputError au lieu de déclarer le texte propre, sauf avec fail_open=True. Ce comportement est le même que pour LLMDetector. Requiert piighost[llm].

Gliner2GuardRail

Classe la sortie dé-identifiée avec un modèle de garde GLiNER2 qui tourne dans le processus, et signale le verdict quand la réponse revient unsafe avec assez de confiance.

Gliner2GuardRail(
    model: GLiNER2 | str = "fastino/GLiNER2-Guardrails-PII-Multi",
    task: str = "response_safety",
    labels: tuple[str, ...] = ("safe", "unsafe"),
    threshold: float = 0.5,
)

C'est ModerationGuardRail sans l'appel d'API, et cette différence est tout l'intérêt. Le texte qu'un garde examine est celui qui contient encore ce qui a fuité. L'envoyer à un tiers est donc une drôle de forme pour la dernière étape d'un pipeline de dé-identification. Le modèle par défaut fait 300M de paramètres, couvre sept langues, et fait modération de sûreté et extraction de PII en une seule passe.

Un modèle str est chargé avec GLiNER2.from_pretrained, et une instance déjà chargée est utilisée telle quelle. Un même checkpoint peut ainsi servir à ce garde et à un Gliner2Detector. La paire labels est lue par position, et la réponse refusée vient en dernier. Une autre tâche du même modèle se lit donc de la même façon. Par exemple, task="response_refusal" avec labels=("compliance", "refusal") signale un refus. Requiert piighost[gliner2].

from piighost.components.detector import RegexDetector
from piighost.components.guard import Gliner2GuardRail
from piighost.pipeline import AnonymizationPipeline

pipeline = AnonymizationPipeline(
    RegexDetector({"EMAIL": r"[\w.+-]+@[\w.-]+\.\w{2,}"}),
    guard=Gliner2GuardRail(),
)

await pipeline.anonymize("Write to a@b.co about the invoice.")
# Write to <<EMAIL:1>> about the invoice.

await pipeline.anonymize("Write to John Doe, 12 rue des Lilas, 75008 Paris.")
# PIIRemainingError: A guard flagged residual PII (score 0.997)

Gliner2GuardRail rend un verdict au niveau du texte, donc il ne localise rien. detections reste vide et seul score est renseigné. Associez-le à un DetectorGuardRail s'il vous faut savoir quelle valeur a fuité. Les placeholders qu'émet le pipeline ne le déclenchent pas. Par exemple, <<EMAIL:1>> est classé safe à 0,989. La version exécutable est examples/guard_rail_local_model.py.

ModerationGuardRail

Classifie les PII résiduelles avec le modèle de modération de Mistral, en lisant le score de la catégorie PII et en signalant le verdict quand il atteint le seuil.

ModerationGuardRail(
    client: Mistral,
    model: str = "mistral-moderation-latest",
    threshold: float = 0.5,
)

Ce garde-fou classe le texte, il ne détecte pas de valeurs. Il attrape donc des PII qu'un pipeline basé sur la détection ne peut pas localiser. En contrepartie, il rend un verdict au niveau du texte, sans spans. Requiert piighost[mistral].

GuardVerdict et PIIRemainingError

check renvoie un GuardVerdict(flagged: bool, score: float | None, detections: tuple[Detection, ...]) gelé. Le détail dépend du garde-fou. C'est un score depuis un modèle de modération, ou les détections résiduelles depuis un détecteur. Les deux sont optionnels.

Quand un garde-fou signale des valeurs confidentielles, le pipeline lève PIIRemainingError (une sous-classe de GuardError, elle-même une PIIGhostError). Son message nomme les labels fuités ou le score. Son attribut detections contient les détections résiduelles. Il reste vide pour un garde-fou basé sur un score, qui ne localise rien.

Configurer un garde-fou depuis un fichier

Une section [guard] ajoute l'étage, et son champ type choisit le garde-fou.

[guard]
type = "detector"

[guard.detector]
type = "regex"
catalogs = ["catalog:piighost/generic", "catalog:piighost/us"]
typeChampsExtra
detector[guard.detector] (une config de détecteur)
gliner2model (défaut fastino/GLiNER2-Guardrails-PII-Multi), task, labels, thresholdgliner2
llmmodel, labels, prompt (optionnel), provider (optionnel)llm
moderationmodel (défaut mistral-moderation-latest), threshold (défaut 0.5)mistral

Le garde-fou de modération lit MISTRAL_API_KEY dans l'environnement à la construction. Il lève ConfigError si la variable est absente. Chaque clé [guard] est dans la référence de configuration.

Voir aussi

  • Pipeline : où l'étage garde-fou se place dans l'exécution.
  • Détecteurs : les détecteurs qu'un DetectorGuardRail réexécute.
  • Sécurité : ce qu'un garde-fou protège et ne protège pas.