Guardrails para LLMs en la práctica: qué funciona realmente

«Controla el riesgo, no solo el modelo».

Índice

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.

Barreras de seguridad de LLM en la práctica

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

Suscribirse

Recibe nuevas publicaciones sobre sistemas, infraestructura e ingeniería de IA.