Aller au contenu principal

Guardrails : valider entrées et sorties

Qu’est-ce qu’un guardrail ?

Un guardrail est une couche de validation qui s’interpose entre l’utilisateur et le modèle (en entrée) ou entre le modèle et l’utilisateur (en sortie). Contrairement à la modération qui détecte le contenu offensant, les guardrails valident la conformité métier : le message est-il dans le périmètre attendu ? La réponse contient-elle des informations interdites ? L’action demandée est-elle autorisée ?

En 2026, les guardrails sont devenus un composant standard des architectures d’agents IA, indispensables dès qu’un modèle a accès à des outils ou des données sensibles.

Architecture d’un système de guardrails

Le pipeline de validation

from dataclasses import dataclass
from enum import Enum

class Decision(Enum):
    AUTORISER = "autoriser"
    BLOQUER = "bloquer"
    MODIFIER = "modifier"

@dataclass
class ResultatValidation:
    decision: Decision
    raison: str
    contenu_modifie: str | None = None

class Guardrail:
    """Classe de base pour un guardrail."""

    def valider(self, contenu: str, contexte: dict) -> ResultatValidation:
        raise NotImplementedError

class PipelineGuardrails:
    """Enchaîne plusieurs guardrails en séquence."""

    def __init__(self):
        self.guardrails_entree: list[Guardrail] = []
        self.guardrails_sortie: list[Guardrail] = []

    def ajouter_entree(self, guardrail: Guardrail):
        self.guardrails_entree.append(guardrail)

    def ajouter_sortie(self, guardrail: Guardrail):
        self.guardrails_sortie.append(guardrail)

    def valider_entree(self, message: str, contexte: dict) -> ResultatValidation:
        for g in self.guardrails_entree:
            resultat = g.valider(message, contexte)
            if resultat.decision == Decision.BLOQUER:
                return resultat
            if resultat.decision == Decision.MODIFIER:
                message = resultat.contenu_modifie
        return ResultatValidation(Decision.AUTORISER, "Toutes les validations passées")

    def valider_sortie(self, reponse: str, contexte: dict) -> ResultatValidation:
        for g in self.guardrails_sortie:
            resultat = g.valider(reponse, contexte)
            if resultat.decision == Decision.BLOQUER:
                return resultat
            if resultat.decision == Decision.MODIFIER:
                reponse = resultat.contenu_modifie
        return ResultatValidation(Decision.AUTORISER, "OK", reponse)

Guardrail de périmètre (Topic Guard)

class GuardrailPerimetre(Guardrail):
    """Vérifie que le message reste dans le périmètre autorisé."""

    def __init__(self, sujets_autorises: list[str], sujets_interdits: list[str]):
        self.sujets_autorises = sujets_autorises
        self.sujets_interdits = sujets_interdits

    def valider(self, contenu: str, contexte: dict) -> ResultatValidation:
        contenu_lower = contenu.lower()

        # Vérifier les sujets interdits
        for sujet in self.sujets_interdits:
            if sujet.lower() in contenu_lower:
                return ResultatValidation(
                    Decision.BLOQUER,
                    f"Sujet hors périmètre détecté : {sujet}"
                )

        return ResultatValidation(Decision.AUTORISER, "Dans le périmètre")

Guardrail de longueur et de format

class GuardrailFormat(Guardrail):
    """Valide le format et la longueur des messages."""

    def __init__(self, max_tokens: int = 4000, max_lignes: int = 100):
        self.max_tokens = max_tokens
        self.max_lignes = max_lignes

    def valider(self, contenu: str, contexte: dict) -> ResultatValidation:
        # Estimation grossière des tokens (1 token ~ 4 caractères en français)
        tokens_estimes = len(contenu) // 4
        if tokens_estimes > self.max_tokens:
            return ResultatValidation(
                Decision.BLOQUER,
                f"Message trop long : ~{tokens_estimes} tokens (max {self.max_tokens})"
            )

        nb_lignes = contenu.count("\n") + 1
        if nb_lignes > self.max_lignes:
            return ResultatValidation(
                Decision.BLOQUER,
                f"Trop de lignes : {nb_lignes} (max {self.max_lignes})"
            )

        return ResultatValidation(Decision.AUTORISER, "Format valide")

Guardrail anti-fuite de données (sortie)

import re

class GuardrailAntiExfiltration(Guardrail):
    """Empêche la fuite de données sensibles dans les sorties."""

    def __init__(self):
        self.patterns_sensibles = {
            "cle_api": r"(sk|pk|api[_-]?key)[_-][a-zA-Z0-9]{16,}",
            "email_interne": r"[a-zA-Z0-9.]+@(entreprise|internal|corp)\.\w+",
            "ip_privee": r"\b(10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})\b",
            "chemin_serveur": r"(/data/|/etc/|/home/|C:\\Users\\)",
        }

    def valider(self, contenu: str, contexte: dict) -> ResultatValidation:
        for type_donnee, pattern in self.patterns_sensibles.items():
            if re.search(pattern, contenu):
                # Masquer au lieu de bloquer
                contenu_nettoye = re.sub(pattern, "[MASQUÉ]", contenu)
                return ResultatValidation(
                    Decision.MODIFIER,
                    f"Données sensibles masquées : {type_donnee}",
                    contenu_nettoye,
                )
        return ResultatValidation(Decision.AUTORISER, "Aucune fuite détectée")

Exemple complet d’intégration

from openai import OpenAI

def creer_assistant_securise():
    """Crée un assistant avec un pipeline complet de guardrails."""
    client = OpenAI()
    pipeline = PipelineGuardrails()

    # Guardrails d'entrée
    pipeline.ajouter_entree(GuardrailFormat(max_tokens=2000))
    pipeline.ajouter_entree(GuardrailPerimetre(
        sujets_autorises=["produits", "commandes", "livraison", "retours"],
        sujets_interdits=["politique", "religion", "concurrent"],
    ))

    # Guardrails de sortie
    pipeline.ajouter_sortie(GuardrailAntiExfiltration())

    def traiter(message: str) -> str:
        contexte = {"role_utilisateur": "client"}

        # Validation entrée
        check_entree = pipeline.valider_entree(message, contexte)
        if check_entree.decision == Decision.BLOQUER:
            return f"Je ne peux pas traiter cette demande : {check_entree.raison}"

        message_valide = check_entree.contenu_modifie or message

        # Appel au modèle
        response = client.chat.completions.create(
            model="gpt-5.4",
            messages=[
                {"role": "system", "content": "Assistant service client Acme Corp."},
                {"role": "user", "content": message_valide},
            ],
        )
        reponse = response.choices[0].message.content

        # Validation sortie
        check_sortie = pipeline.valider_sortie(reponse, contexte)
        if check_sortie.decision == Decision.BLOQUER:
            return "Je ne suis pas en mesure de répondre à cette question."

        return check_sortie.contenu_modifie or reponse

    return traiter

Guardrails avancés : validation par LLM

Pour les cas complexes, un second modèle peut servir de juge :

def guardrail_llm(message: str, regle: str) -> ResultatValidation:
    """Utilise un modèle rapide pour valider la conformité."""
    client = OpenAI()
    response = client.chat.completions.create(
        model="o4-mini",
        messages=[
            {"role": "system", "content": f"""Vous êtes un validateur de conformité.
Règle à vérifier : {regle}
Répondez UNIQUEMENT par JSON : {{"conforme": true/false, "raison": "..."}}"""},
            {"role": "user", "content": f"Message à valider :\n{message}"},
        ],
    )
    import json
    resultat = json.loads(response.choices[0].message.content)
    if resultat["conforme"]:
        return ResultatValidation(Decision.AUTORISER, resultat["raison"])
    return ResultatValidation(Decision.BLOQUER, resultat["raison"])

Points clés à retenir

  • Les guardrails valident la conformité métier, pas seulement le contenu offensant
  • L’architecture en pipeline permet de combiner plusieurs couches de validation
  • Chaque guardrail a une responsabilité unique : périmètre, format, exfiltration, etc.
  • Les guardrails de sortie sont aussi importants que ceux d’entrée
  • Pour les cas complexes, un LLM rapide peut servir de validateur