Guardrails para LLMs en la práctica: qué funciona realmente
«Controla el riesgo, no solo el modelo».
Los LLMs son impredecibles. Alucinan, filtran datos, generan contenido dañino o rechazan solicitudes legítimas. Las barreras de seguridad (guardrails) restringen el comportamiento del modelo sin sacrificar su capacidad.
La clave es saber qué barreras importan y cuáles son solo ruido.
Las barreras de seguridad no se trata de controlar el modelo. Se trata de controlar el riesgo.

Validación de entrada
La barrera de seguridad más importante. Una mala entrada produce una mala salida, y una mala entrada también puede inyectar prompts en tu sistema.
Estrategia 1: Sanitización de Prompts
Sanitiza patrones peligrosos temprano:
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, "[REDACTADO]", prompt, flags=re.IGNORECASE)
return prompt
Esto no es a prueba de balas. Las entradas adversarias son creativas. Pero captura las evidentes, y las evidentes son las más comunes.
Estrategia 2: Límites de longitud de entrada
Los límites de longitud previenen el desperdicio de tokens y los tiempos de espera (timeouts):
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"Entrada demasiado larga: {len(prompt)} > {self.max_length}"
return True, "OK"
Estrategia 3: Filtrado de contenido
El filtrado de contenido bloquea violaciones de políticas. Los patrones aquí dependen de tu dominio:
class ContentFilter:
def __init__(self):
self.blocked_topics = [
"violencia", "discurso de odio", "autolesiones",
"contenido sexual", "actividades ilegales",
]
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"Bloqueado: {topic}"
return True, "OK"
La coincidencia de cadenas simple es rápida pero imprecisa. Para producción, utiliza un modelo clasificador —incluso uno pequeño como Qwen2.5-1.5B— para detectar violaciones de políticas. Es más preciso y más difícil de evadir.
Filtrado de salida
La salida del modelo también necesita verificación. Estructura, contenido y hechos.
Estrategia 1: Validación de respuestas
Valida la estructura primero. Si esperas JSON, verifica el 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"Campo faltante: {field}"
return True, "OK"
Estrategia 2: Filtrado de contenido
Filtra contenido dañino:
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"Bloqueado: {pattern}"
return True, "OK"
Estrategia 3: Verificación de hechos
La verificación de hechos es más difícil. No puedes validar cada afirmación, así que elige las que importan:
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"Verificación de hecho fallida: {fact}"
return True, "OK"
Para una verificación de hechos real, necesitas una tubería de recuperación (retrieval pipeline). Verifica las afirmaciones contra una base de conocimientos, no contra un diccionario codificado.
Mecanismos de seguridad
Estrategia 1: Limitación de tasa (Rate Limiting)
La limitación de tasa previene el 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
Estrategia 2: Presupuesto de tokens
El presupuesto de tokens limita los costos por solicitud:
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"Límite de tokens excedido: {token_count} > {self.max_tokens}"
return True, "OK"
Estrategia 3: Gestión de la ventana de contexto
La gestión de la ventana de contexto previene el desbordamiento:
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)
El recorte de ventana deslizante es simple pero pierde contexto inicial. Enfoques mejores utilizan resumen o compresión basada en atención, pero esos añaden latencia.
Cumplimiento
Los sistemas empresariales necesitan barreras de seguridad de cumplimiento. Dos que importan más:
Patrón 1: Residencia de datos
Residencia de datos: asegúrate de que los datos permanezcan dentro de los límites geográficos requeridos:
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"Región no permitida: {region}"
return True, "OK"
Patrón 2: Registro de auditoría
Registro de auditoría: registra todas las interacciones del modelo:
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")
Los registros de auditoría son críticos para la depuración y el cumplimiento. Hazlos estructurados, solo para agregar (append-only) y almacenados de forma segura.
Poniéndolo todo junto
Patrón 1: Barreras de seguridad simples
Una tubería de barreras de seguridad simples:
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
Patrón 2: Barreras de seguridad avanzadas
Las barreras de seguridad avanzadas añaden sanitización, limitación de tasa y presupuestos de 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"Error: {message}"
valid, message = self.content_filter.filter(prompt)
if not valid:
return f"Error: {message}"
if not self.rate_limiter.allow():
return "Error: Límite de tasa excedido"
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
Cuando las barreras de seguridad importan
Las barreras de seguridad importan cuando estás construyendo sistemas orientados al usuario, manejando datos sensibles o ejecutando en producción. También importan cuando tienes requisitos de cumplimiento: GDPR, HIPAA, SOC 2.
No importan cuando estás prototipando, usando modelos solo para herramientas internas o no manejando datos sensibles. Omitelas hasta que las necesites.
El compromiso siempre es capacidad versus seguridad. Más barreras de seguridad significan menos fallos, pero también menos capacidades. Encuentra el equilibrio que funcione para tu sistema.
Compromisos
| Estrategia | Seguridad | Capacidad | Latencia |
|---|---|---|---|
| Sin barreras de seguridad | Más baja | Más alta | Más baja |
| Validación de entrada | Alta | Media | Baja |
| Filtrado de salida | Alta | Media | Baja |
| Mecanismos de seguridad | Más alta | Más baja | Más alta |
| Cumplimiento | Más alta | Más baja | Más alta |
Relacionado
- Estrategias de enrutamiento de modelos — enrutamiento basado en capacidad, consciente del costo y de la latencia
- Optimización de costos para sistemas LLM — presupuesto de tokens, modelos de respaldo, caché
- Diseño de sistemas multimodelo — arquitectura para múltiples modelos
- Arquitectura de LLM — pilar de diseño de sistemas: enrutamiento, costos, barreras de seguridad y orquestación