Guardrail per LLM nella pratica: cosa funziona davvero
Controlla il rischio, non solo il modello.
I modelli linguistici di grandi dimensioni (LLM) sono imprevedibili. Possono generare allucinazioni, perdere dati, produrre contenuti dannosi o rifiutare richieste legittime. Le misure di sicurezza (guardrails) vincolano il comportamento del modello senza comprometterne le capacità.
La chiave è sapere quali misure di sicurezza sono importanti e quali sono solo rumore.
Le misure di sicurezza non riguardano il controllo del modello. Riguardano il controllo del rischio.

Validazione dell’input
La misura di sicurezza più importante. Un input errato produce un output errato, e un input errato può anche iniettare prompt nel tuo sistema.
Strategia 1: Sanitizzazione dei Prompt
Sanitizzare 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 è invincibile. Gli input avversari 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 delle stringhe è veloce ma imprecisa. Per la produzione, usa un modello classificatore — anche uno piccolo come Qwen2.5-1.5B — per rilevare le violazioni delle policy. È più accurato e più difficile da eludere.
Filtraggio dell’output
Anche l’output del modello deve essere verificato. Struttura, contenuti e fatti.
Strategia 1: Validazione della Risposta
Valida prima la struttura. Se ti aspetti JSON, controlla il 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 una pipeline di recupero. Verifica le affermazioni contro una base di conoscenza, non un dizionario hardcoded.
Meccanismi di Sicurezza
Strategia 1: Limitazione della Frequenza (Rate Limiting)
La limitazione della frequenza 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: Gestione del Budget Token
La gestione del budget 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 usano la sommariizzazione o la compressione basata sull’attenzione, ma questi aggiungono latenza.
Conformità
I sistemi aziendali richiedono misure di sicurezza per la conformità. Due che contano di più:
Pattern 1: Residenzialità dei Dati
Residenzialità 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: Registrazione degli Audit
Registrazione degli audit — 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 append e archiviati in modo sicuro.
Unire il tutto
Pattern 1: Misure di Sicurezza Semplici
Una pipeline di misure di sicurezza 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: Misure di Sicurezza Avanzate
Le misure di sicurezza avanzate aggiungono sanitizzazione, limitazione della frequenza e budget 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 not 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 le misure di sicurezza contano
Le misure di sicurezza contano quando stai costruendo sistemi front-end per 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ù misure di sicurezza significano meno fallimenti ma anche meno capacità. Trova l’equilibrio che funziona per il tuo sistema.
Compromessi
| Strategia | Sicurezza | Capacità | Latenza |
|---|---|---|---|
| Nessuna misura di sicurezza | 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
- Strategie di Routing dei Modelli — routing basato sulle capacità, consapevole dei costi e della latenza
- Ottimizzazione dei Costi per i Sistemi LLM — gestione del budget token, modelli di fallback, caching
- Progettazione di Sistemi Multi-Modello — architettura per più modelli
- Architettura LLM — pilastro del design del sistema: routing, costi, misure di sicurezza e orchestrazione