Guard-rails per LLM nella Pratica: Cosa Funziona Davvero

Controlla il rischio, non solo il modello.

Indice

I LLM sono imprevedibili. Hallucinate, perdono dati, generano contenuti dannosi o rifiutano richieste legittime. I guardrails (meccanismi di sicurezza) vincolano il comportamento del modello senza sacrificare le sue capacità.

La chiave è sapere quali guardrails sono importanti e quali sono solo rumore.

I guardrails non servono a controllare il modello. Servono a controllare il rischio.

LLM guardrails in practice

Validazione dell’input

Il guardrail più importante. Un input scadente produce un output scadente, e un input scadente può anche iniettare prompt nel tuo sistema.

Strategia 1: Sanificazione del Prompt

Sanifica precocemente i pattern pericolosi:

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

Questo non è a prova di proiettile. Gli input adversariali sono creativi. Ma cattura quelli evidenti, e quelli evidenti sono i più comuni.

Strategia 2: Limiti di Lunghezza dell’Input

I limiti di lunghezza prevengono lo spreco di token e i timeout:

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"Input troppo lungo: {len(prompt)} > {self.max_length}"
        return True, "OK"

Strategia 3: Filtraggio dei Contenuti

Il filtraggio dei contenuti blocca le violazioni delle policy. I pattern qui dipendono dal tuo dominio:

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"Blocked: {topic}"
        return True, "OK"

La semplice corrispondenza di stringhe è veloce ma imprecisa. Per la produzione, utilizza un modello classificatore — anche uno piccolo come qwen3-1.7b — per rilevare le violazioni delle policy. È più accurato e più difficile da eludere.

Filtraggio dell’output

Anche l’output del modello ha bisogno di essere verificato. Struttura, contenuti e fatti.

Strategia 1: Validazione della Risposta

Valida prima la struttura. Se ti aspetti JSON, controlla per 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"Missing field: {field}"
        return True, "OK"

Strategia 2: Filtraggio dei Contenuti

Filtra i contenuti dannosi:

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"Blocked: {pattern}"
        return True, "OK"

Strategia 3: Verifica dei Fatti

La verifica dei fatti è più difficile. Non puoi validare ogni affermazione, quindi scegli quelle che contano:

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"Fact check failed: {fact}"
        return True, "OK"

Per una vera verifica dei fatti, hai bisogno di un pipeline di recupero. Controlla le affermazioni contro una base di conoscenza, non un dizionario hardcoded.

Meccanismi di sicurezza

Strategia 1: Rate Limiting

Il rate limiting previene l’abuso:

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

Strategia 2: Budget dei Token

Il budget dei token limita i costi per richiesta:

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"Token limit exceeded: {token_count} > {self.max_tokens}"
        return True, "OK"

Strategia 3: Gestione della Finestra di Contesto

La gestione della finestra di contesto previene l’overflow:

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)

Il trimming a finestra scorrevole è semplice ma perde il contesto iniziale. Approcci migliori utilizzano la sommatoria o la compressione basata sull’attenzione, ma questi aggiungono latenza.

Conformità

I sistemi enterprise hanno bisogno di guardrails di conformità. Due che contano di più:

Pattern 1: Residenza dei Dati

Residenza dei dati — assicurati che i dati rimangano entro i confini geografici richiesti:

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"Region not allowed: {region}"
        return True, "OK"

Pattern 2: Audit Logging

Audit logging — registra tutte le interazioni con il modello:

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")

I log di audit sono critici per il debugging e la conformità. Rendili strutturati, solo in appende e archiviati in modo sicuro.

Metterli insieme

Pattern 1: Guardrails Semplici

Un pipeline di guardrails semplici:

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"Error: {message}"

        response = self.call_model(prompt)

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

        return response

Pattern 2: Guardrails Avanzati

I guardrails avanzati aggiungono sanificazione, rate limiting e budget dei token:

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"Error: {message}"

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

        if not self.rate_limiter.allow():
            return "Error: Rate limit exceeded"

        response = self.call_model(prompt)

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

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

        return response

Quando i guardrails contano

I guardrails contano quando stai costruendo sistemi rivolti agli utenti, gestendo dati sensibili o operando in produzione. Contano anche quando hai requisiti di conformità — GDPR, HIPAA, SOC 2.

Non contano quando stai prototipando, usando modelli solo per strumenti interni o non gestendo dati sensibili. Saltali finché non ne hai bisogno.

Il compromesso è sempre capacità versus sicurezza. Più guardrails significano meno fallimenti ma anche meno capacità. Trova l’equilibrio che funziona per il tuo sistema.

I guardrails coprono la conversazione del modello, ma si fermano al punto in cui un agente chiama un tool o passa il lavoro a un altro agente. Una volta che deployi strumenti MCP o agenti A2A, hai anche bisogno di sicurezza del protocollo — identità, autorizzazione scoped, limiti di delega e audit trail per chi può agire per conto di chi. Questo livello è coperto in A2A and MCP Agent Security: Identity, Delegation, and Audit Trails.

Compromessi

Strategia Sicurezza Capacità Latenza
Nessun guardrail Più bassa Più alta Più bassa
Validazione dell’input Alta Media Bassa
Filtraggio dell’output Alta Media Bassa
Meccanismi di sicurezza Più alta Più bassa Più alta
Conformità Più alta Più bassa Più alta

Correlati

Iscriviti

Ricevi nuovi articoli su sistemi, infrastruttura e ingegneria AI.