Les garde-fous des LLM en pratique : ce qui fonctionne réellement

Contrôlez le risque, pas seulement le modèle.

Sommaire

Les LLMs sont imprévisibles. Ils hallucinent, fuient des données, génèrent du contenu nuisible ou refusent des demandes légitimes. Les garde-fous (guardrails) contraignent le comportement du modèle sans sacrifier ses capacités.

L’essentiel est de savoir quels garde-fous sont importants et lesquels ne sont que du bruit.

Les garde-fous ne visent pas à contrôler le modèle. Ils visent à maîtriser le risque.

Les garde-fous des LLMs en pratique

Validation des entrées

Le garde-fou le plus important. Une mauvaise entrée donne une mauvaise sortie, et une mauvaise entrée peut également injecter des prompts malveillants dans votre système.

Stratégie 1 : Sanitisation des prompts

Sanitisez les motifs dangereux dès le début :

import re

class PromptSanitizer:
    def __init__(self):
        self.dangerous_patterns = [
            r"ignore\s+previous\s+instructions",
            r"system\s+prompt",
            r"you\s+are\s+now\s+free",
            r"break\s+out\s+of",
        ]

    def sanitize(self, prompt: str) -> str:
        for pattern in self.dangerous_patterns:
            prompt = re.sub(pattern, "[REDACTED]", prompt, flags=re.IGNORECASE)
        return prompt

Cela n’est pas infaillible. Les entrées adverses sont créatives. Mais cela capture les cas évidents, et les cas évidents sont les plus courants.

Stratégie 2 : Limites de longueur des entrées

Les limites de longueur empêchent le gaspillage de tokens et les dépassements de délai :

class InputValidator:
    def __init__(self, max_length: int = 10000):
        self.max_length = max_length

    def validate(self, prompt: str) -> tuple[bool, str]:
        if len(prompt) > self.max_length:
            return False, f"Entrée trop longue : {len(prompt)} > {self.max_length}"
        return True, "OK"

Stratégie 3 : Filtrage du contenu

Le filtrage du contenu bloque les violations de politique. Les motifs dépendent de votre domaine :

class ContentFilter:
    def __init__(self):
        self.blocked_topics = [
            "violence", "hate speech", "self-harm",
            "sexual content", "illegal activities",
        ]

    def filter(self, prompt: str) -> tuple[bool, str]:
        prompt_lower = prompt.lower()
        for topic in self.blocked_topics:
            if topic in prompt_lower:
                return False, f"Bloqué : {topic}"
        return True, "OK"

La correspondance de chaînes simple est rapide mais imprécise. Pour la production, utilisez un modèle de classification — même un petit comme Qwen2.5-1.5B — pour détecter les violations de politique. C’est plus précis et plus difficile à contourner.

Filtrage des sorties

La sortie du modèle doit également être vérifiée. Structure, contenu et faits.

Stratégie 1 : Validation des réponses

Validez d’abord la structure. Si vous attendez du JSON, vérifiez la présence de JSON :

class ResponseValidator:
    def __init__(self):
        self.required_fields = ["answer", "confidence"]

    def validate(self, response: dict) -> tuple[bool, str]:
        for field in self.required_fields:
            if field not in response:
                return False, f"Champ manquant : {field}"
        return True, "OK"

Stratégie 2 : Filtrage du contenu

Filtrez le contenu nuisible :

class OutputFilter:
    def __init__(self):
        self.blocked_patterns = [
            r"kill\s+someone",
            r"bomb\s+recipe",
            r"hate\s+speech",
            r"self-harm",
        ]

    def filter(self, response: str) -> tuple[bool, str]:
        for pattern in self.blocked_patterns:
            if re.search(pattern, response, re.IGNORECASE):
                return False, f"Bloqué : {pattern}"
        return True, "OK"

Stratégie 3 : Vérification des faits

La vérification des faits est plus difficile. Vous ne pouvez pas valider chaque affirmation, alors choisissez celles qui comptent :

class FactChecker:
    def __init__(self):
        self.known_facts = {
            "capital of france": "Paris",
            "population of usa": "330 million",
            "speed of light": "299,792,458 m/s",
        }

    def check(self, claim: str) -> tuple[bool, str]:
        claim_lower = claim.lower()
        for fact, truth in self.known_facts.items():
            if fact in claim_lower and truth not in claim_lower:
                return False, f"Vérification des faits échouée : {fact}"
        return True, "OK"

Pour une vérification des faits réelle, vous avez besoin d’un pipeline de récupération. Vérifiez les affirmations contre une base de connaissances, pas un dictionnaire en dur.

Mécanismes de sécurité

Stratégie 1 : Limitation de débit

La limitation de débit empêche les abus :

import time
from collections import deque

class RateLimiter:
    def __init__(self, max_requests: int = 10, window: int = 60):
        self.max_requests = max_requests
        self.window = window
        self.requests = deque()

    def allow(self) -> bool:
        now = time.time()
        while self.requests and self.requests[0] < now - self.window:
            self.requests.popleft()

        if len(self.requests) >= self.max_requests:
            return False

        self.requests.append(now)
        return True

Stratégie 2 : Budgétisation des tokens

La budgétisation des tokens plafonne les coûts par requête :

class TokenBudget:
    def __init__(self, max_tokens: int = 1000):
        self.max_tokens = max_tokens

    def validate(self, response: str) -> tuple[bool, str]:
        token_count = len(response.split())
        if token_count > self.max_tokens:
            return False, f"Limite de tokens dépassée : {token_count} > {self.max_tokens}"
        return True, "OK"

Stratégie 3 : Gestion de la fenêtre de contexte

La gestion de la fenêtre de contexte empêche le débordement :

class ContextManager:
    def __init__(self, max_context: int = 4096):
        self.max_context = max_context
        self.context = []

    def add(self, message: str):
        self.context.append(message)
        self.trim()

    def trim(self):
        while len(" ".join(self.context)) > self.max_context:
            self.context.pop(0)

Le troncissement par fenêtre glissante est simple mais perd le contexte initial. De meilleures approches utilisent la résumation ou la compression basée sur l’attention, mais celles-ci ajoutent de la latence.

Conformité

Les systèmes d’entreprise ont besoin de garde-fous de conformité. Deux qui comptent le plus :

Motif 1 : Résidentialité des données

Résidentialité des données — s’assurer que les données restent dans les limites géographiques requises :

class DataResidency:
    def __init__(self, allowed_regions: list[str]):
        self.allowed_regions = allowed_regions

    def validate(self, region: str) -> tuple[bool, str]:
        if region not in self.allowed_regions:
            return False, f"Région non autorisée : {region}"
        return True, "OK"

Motif 2 : Journalisation d’audit

Journalisation d’audit — consigner toutes les interactions avec le modèle :

import json
from datetime import datetime

class AuditLogger:
    def __init__(self, log_file: str = "audit.log"):
        self.log_file = log_file

    def log(self, request: dict, response: dict):
        entry = {
            "timestamp": datetime.now().isoformat(),
            "request": request,
            "response": response,
        }
        with open(self.log_file, "a") as f:
            f.write(json.dumps(entry) + "\n")

Les journaux d’audit sont critiques pour le débogage et la conformité. Rendez-les structurés, en append-only (ajout uniquement) et stockés de manière sécurisée.

Mettre tout ensemble

Motif 1 : Garde-fous simples

Un pipeline de garde-fous simple :

class SimpleGuardrails:
    def __init__(self):
        self.input_validator = InputValidator(max_length=10000)
        self.output_filter = OutputFilter()

    def process(self, prompt: str) -> str:
        valid, message = self.input_validator.validate(prompt)
        if not valid:
            return f"Erreur : {message}"

        response = self.call_model(prompt)

        valid, message = self.output_filter.filter(response)
        if not valid:
            return f"Erreur : {message}"

        return response

Motif 2 : Garde-fous avancés

Les garde-fous avancés ajoutent la sanitisation, la limitation de débit et la budgétisation des tokens :

class AdvancedGuardrails:
    def __init__(self):
        self.sanitizer = PromptSanitizer()
        self.input_validator = InputValidator(max_length=10000)
        self.content_filter = ContentFilter()
        self.output_filter = OutputFilter()
        self.rate_limiter = RateLimiter(max_requests=10)
        self.token_budget = TokenBudget(max_tokens=1000)

    def process(self, prompt: str) -> str:
        prompt = self.sanitizer.sanitize(prompt)

        valid, message = self.input_validator.validate(prompt)
        if not valid:
            return f"Erreur : {message}"

        valid, message = self.content_filter.filter(prompt)
        if not valid:
            return f"Erreur : {message}"

        if not self.rate_limiter.allow():
            return "Erreur : Limite de débit dépassée"

        response = self.call_model(prompt)

        valid, message = self.output_filter.filter(response)
        if not valid:
            return f"Erreur : {message}"

        valid, message = self.token_budget.validate(response)
        if not valid:
            return f"Erreur : {message}"

        return response

Quand les garde-fous sont importants

Les garde-fous sont importants lorsque vous construisez des systèmes orientés utilisateurs, gérez des données sensibles ou fonctionnez en production. Ils sont également importants lorsque vous avez des exigences de conformité — RGPD, HIPAA, SOC 2.

Ils ne sont pas importants lorsque vous prototyperez, utilisez des modèles uniquement pour des outils internes ou ne gérez pas de données sensibles. Ignorez-les jusqu’à ce que vous en ayez besoin.

Le compromis est toujours capacité versus sécurité. Plus de garde-fous signifient moins d’échecs mais aussi moins de capacités. Trouvez l’équilibre qui fonctionne pour votre système.

Compromis

Stratégie Sécurité Capacité Latence
Aucun garde-fou La plus basse La plus élevée La plus basse
Validation des entrées Élevée Moyenne Basse
Filtrage des sorties Élevée Moyenne Basse
Mécanismes de sécurité La plus élevée La plus basse La plus élevée
Conformité La plus élevée La plus basse La plus élevée

Liens connexes

S'abonner

Recevez de nouveaux articles sur les systèmes, l'infrastructure et l'ingénierie IA.