LLM Guard: el traductor jurado con cuaderno de equivalencias — anatomía, scanners y su integración con Langfuse, vLLM y LiteLLM

Este post es deep-dive de una sola pieza dentro de la capa cubierta en el post sobre guardrails y safety LLM. Aquel mapea las cuatro líneas de defensa (input, retrieval, tool, output) y el catálogo OSS 2026 a vista de pájaro; éste baja al ras de LLM Guard porque su patrón Anonymize/Deanonymize, su modelo de scanners composables y sus cuatro modos de despliegue merecen tratamiento propio. Las analogías que se construyeron arriba (cocina HACCP, cuatro CCP) siguen valiendo: este post amplía el zoom sobre la herramienta que ocupa el cinturón de PII y de scanners individuales dentro de esa arquitectura.

TL;DR

LLM Guard es la herramienta OSS (MIT, Protect AI) que materializa la capa de guardrails LLM con un modelo radicalmente distinto al de NeMo Guardrails y al de Guardrails AI: en lugar de un DSL declarativo (Colang) o de un framework de validators con LLM-as-judge externos, ofrece un catálogo de detectores compactos especializados —15 input scanners, 21 output scanners— componibles como pipeline Python, con un mecanismo único distintivo: el patrón Anonymize → LLM → Deanonymize con Vault. El Vault es un almacén centralizado del mapping entre entidades reales (John Doe, 12345678X) y placeholders ([REDACTED_PERSON_1], [REDACTED_DNI_1]); en input, las entidades se redactan y el mapping se guarda; el LLM nunca ve datos personales reales; en output, el Deanonymize scanner restituye los originales antes de devolver la respuesta al usuario. Este post desmonta: la anatomía interna (Vault + scanners + orquestador con fail_fast y caché TTL), los cuatro patrones de despliegue con sus matemáticas (librería in-process, API FastAPI, sidecar OTel sobre vLLM, plugin de AI Gateway — LiteLLM, Envoy AI Gateway, Kong AI Gateway), los diagramas de integración con Langfuse (vía OTel HTTP exporter de LLM Guard + langfuse.score() desde el AI Gateway), las matemáticas con benchmarks del proyecto (Anonymize en 177 ms CPU → 128 ms ONNX-CPU → 125 ms GPU FP16 → 38 ms GPU+ONNX, escalado x4.6 cuando combinas ONNX + GPU), el patrón ONNX como aceleración por defecto sin GPU dedicada, la comparativa con NeMo Guardrails (DSL Colang declarativo orientado a flujo conversacional) y Guardrails AI (validators tipo contrato JSON con judges externos), la aplicación a hardware on-premise (qué scanners aguantan CPU, cuáles necesitan GPU compartida) y las siete trampas operativas específicas de la herramienta.

La analogía: el traductor jurado con cuaderno de equivalencias

Cliente"Mi DNI es 12345678X,¿paga IVA?"Traductor (Anonymize)redacta entidades sensibles+ inscribe en cuadernoLLMrecibe texto saneado:"Mi DNI es [DNI_1], ¿paga IVA?"Traductor (Deanonymize)restituye originalesdesde el cuadernoCliente reciberespuesta con"12345678X" restituidoVault (cuaderno compartido)[PERSON_1] = "Marta García"[DNI_1] = "12345678X"[IBAN_1] = "ES91 2100 0418..."guarda mappingconsulta para restituirEl LLM nunca ve la PII original. El cuaderno (Vault) es el único punto que conoce la equivalencia.

Un traductor jurado serio que trabaja con documentos sensibles —un contrato laboral, una historia clínica, una declaración fiscal— no envía el texto crudo al traductor automático que tiene en la nube. Lleva un cuaderno de equivalencias abierto sobre la mesa. Cuando recibe el documento original, abre el cuaderno y va apuntando: “Marta García” → [PERSONA-1], “12345678X” → [DNI-1], “ES91 2100 0418…” → [IBAN-1]. Sustituye cada aparición en el texto por su etiqueta y pasa el texto anonimizado al servicio de traducción. El servicio devuelve una traducción que sigue conteniendo las etiquetas. El traductor abre de nuevo el cuaderno, restituye cada etiqueta por su valor original, y entrega al cliente la traducción final con la PII intacta. Para el servicio de traducción, esos datos personales nunca existieron: sólo vio placeholders.

Esta es la operación exacta que define el carácter de LLM Guard frente al resto del ecosistema. NeMo Guardrails resuelve safety con un grafo declarativo de reglas en Colang; Guardrails AI con validators que invocan a un LLM-as-judge para verificar contratos; LLM Guard con un catálogo de detectores compactos especializados + el patrón Vault. Los tres son válidos en distintos escenarios. La elección no es de gusto: es estructural según cómo se construye el sistema y dónde está el cuello.

El traductor también revisa, claro, que el texto no contenga otros problemas además de la PII: insultos, instrucciones para reprogramarse, links a páginas hostiles, código que no debería estar ahí. Para eso tiene el resto del catálogo de scanners. Pero la firma de la casa, lo que la distingue, es ese cuaderno.

Anatomía interna de LLM Guard

Orquestador: scan_prompt() / scan_output() · fail_fast · caché TTL · timeout · OTel spansItera scanners en orden, agrega is_valid y risk_score, emite trace y métricas PrometheusInput scanners (15)Anonymize ⓥPromptInjectionToxicitySecretsTokenLimitBanTopicsBanCompetitorsBanCode / CodeSentimentGibberishLanguageInvisibleTextRegex · BanSubstringsVaultDiccionario in-memorypor sesión / request[PERSON_1]→"Marta García"[DNI_1]→"12345678X"[IBAN_1]→"ES91...".placeholder() / .get()opcional: persistenciaRedis / cliente stickyOutput scanners (21)Deanonymize ⓥSensitive (PII out)Toxicity · BiasNoRefusalRelevanceFactualConsistencyJSON validatorMaliciousURLsURLReachabilityLanguageSameReadingTimeBanCompetitorsRegex · BanSubstringsModelos backendONNX runtimemodelos cuantizadosCPU + GPU compatiblesTransformers (HF)BERT NER, distilbertdeberta, bge, etc.Presidio AnalyzerspaCy / flair / regex~50 entidades baseValidators purosregex, JSON schema,stdlib URL parsingTelemetríaOTel exportertraces (HTTP)metrics (HTTP)Prometheus/metrics endpointcounters + histogramsstructured logsstdout JSON,parseable Loki/ELKFastAPI/analyze/prompt/analyze/outputEl Vault es la pieza única: lo comparten Anonymize (input) y Deanonymize (output) en la misma request o sesión. Sin él, la PII se filtraría al LLM.

Las tres piezas estructurales son:

1. El orquestador (scan_prompt, scan_output). Recibe una lista de scanners en orden y los ejecuta secuencialmente sobre el texto. Devuelve la terna (sanitized_text, results_valid, results_score) donde:

  • sanitized_text es el texto transformado por los scanners que mutan (Anonymize, BanSubstrings con redaction).
  • results_valid es un dict {scanner_name: bool} indicando qué scanners pasaron.
  • results_score es un dict {scanner_name: float} con el risk score reportado (0 limpio, 1 violación máxima).

Soporta fail_fast=True para cortar tras el primer fail. Soporta timeout por scanner para no bloquearse en un detector lento. Cuando se expone como API FastAPI, soporta caché TTL para evitar reescanear prompts repetidos (caso de bots con preguntas idénticas).

2. El catálogo de scanners. Quince input scanners y veintiún output scanners, cada uno con su propio modelo backend y su umbral configurable:

FamiliaInputOutputBackend dominante
PIIAnonymizeDeanonymize, SensitivePresidio + BERT-NER
Inyección y jailbreakPromptInjectionDeBERTa fine-tuned (Protect AI propio)
Toxicidad y biasToxicity, SentimentToxicity, Bias, SentimentRoBERTa / BERT fine-tuned
Tópicos prohibidosBanTopics, BanCompetitorsBanTopics, BanCompetitorsZero-shot classifier BART-MNLI
Substrings y regexBanSubstrings, RegexBanSubstrings, Regexstring matching + regex
SecretsSecretsdetect-secrets (Yelp) + regex
EstructuraTokenLimit, Language, InvisibleText, GibberishJSON, Language, LanguageSame, Gibberish, ReadingTimetokenizer, lang-detect, JSON schema
CódigoBanCode, CodeBanCode, Codeclassifier de lenguaje + regex
URLsMaliciousURLs, URLReachabilityblock-list + DNS lookup
Calidad de respuestaNoRefusal, Relevance, FactualConsistencyNLI-cross-encoder + cosine similarity

Cada scanner se importa y se instancia individualmente, con su umbral propio:

from llm_guard.input_scanners import Anonymize, PromptInjection, Toxicity, Secrets
from llm_guard.vault import Vault

vault = Vault()
scanners = [
    Anonymize(vault, threshold=0.5),
    PromptInjection(threshold=0.85),
    Toxicity(threshold=0.7),
    Secrets(),
]

3. El Vault. Pieza única no encontrada en NeMo Guardrails ni en Guardrails AI con el mismo modelo. Es un diccionario in-memory por sesión o request que guarda el mapping placeholder → valor_original. Lo escribe el scanner Anonymize en input y lo lee el scanner Deanonymize en output. Si el Vault es compartido entre múltiples requests del mismo usuario, el mapping persiste (útil para conversaciones multi-turno). Si es por request, se descarta tras la respuesta.

El Vault básico es dict Python; para entornos distribuidos con múltiples pods, se sustituye por un Redis sticky (mismo usuario → mismo pod) o por un Vault custom que lea/escriba a un Redis externo, descartado tras un TTL. Esto es operacional, no de la librería core.

El flujo Anonymize → LLM → Deanonymize en detalle

El patrón canónico de uso de LLM Guard se descompone en seis pasos exactos:

1. Recibir prompt del usuario:
   "Mi nombre es Marta García y mi IBAN es ES9121000418450200051332,
    ¿podéis revisar el cargo del 14 de marzo?"

2. scan_prompt() con [Anonymize(vault), PromptInjection(), Toxicity()]
   → Anonymize redacta entidades y las guarda en vault:
       vault["[REDACTED_PERSON_1]"] = "Marta García"
       vault["[REDACTED_IBAN_1]"] = "ES9121000418450200051332"
   → PromptInjection comprueba que no haya jailbreak (no lo hay)
   → Toxicity comprueba que no haya insultos (no los hay)
   → results_valid = {Anonymize: True, PromptInjection: True, Toxicity: True}
   → sanitized_prompt:
   "Mi nombre es [REDACTED_PERSON_1] y mi IBAN es [REDACTED_IBAN_1],
    ¿podéis revisar el cargo del 14 de marzo?"

3. Llamar al LLM con sanitized_prompt:
   → vLLM recibe el prompt sin PII real
   → genera respuesta:
   "Sí, [REDACTED_PERSON_1], voy a revisar el cargo en la cuenta
    [REDACTED_IBAN_1]. ¿Puedes confirmar el importe?"

4. scan_output() con [Deanonymize(vault), Toxicity(), Relevance(), Sensitive()]
   → Deanonymize sustituye placeholders por valores del vault:
       [REDACTED_PERSON_1] → "Marta García"
       [REDACTED_IBAN_1]   → "ES9121000418450200051332"
   → Toxicity comprueba que la respuesta no sea ofensiva
   → Relevance comprueba que responde al prompt
   → Sensitive comprueba que no aparezca PII no autorizada
     (en este caso, la PII restituida está autorizada porque la trajo
      el propio usuario y la firma el Vault → la regla aplica solo a
      PII nueva inventada por el LLM)
   → sanitized_response:
   "Sí, Marta García, voy a revisar el cargo en la cuenta
    ES9121000418450200051332. ¿Puedes confirmar el importe?"

5. Devolver al usuario sanitized_response.

6. Si la sesión sigue, el vault persiste y los próximos turnos reutilizan
   los mismos placeholders. Cuando termina la sesión, el vault se descarta.

Tres detalles que importan operativamente:

  • Las entidades persistentes ([REDACTED_PERSON_1] para “Marta García”) se mantienen constantes durante la sesión. Si el usuario menciona otra persona (“hablé con Juan Pérez”), Anonymize asignará [REDACTED_PERSON_2]. La coherencia inter-turno la asegura el Vault.
  • El LLM nunca ve los datos originales durante la sesión. Esto es la propiedad clave para casos donde el LLM se sirve desde un modelo en cloud o cuando se loguea el prompt (Langfuse, OTel) sin acceso confidencial.
  • El logging de LLM Guard registra los placeholders, no los valores originales. Para auditoría con valores originales hace falta una capa adicional (acceso al Vault con permisos privilegiados) — esto es por diseño, no por defecto.

Cuatro modos de despliegue

Modo 1 — Librería Python in-process

El más simple: pip install llm-guard, importar los scanners en el código de la aplicación, llamar a scan_prompt/scan_output directamente. Los modelos se cargan en el proceso. La ventaja es latencia mínima; la desventaja es que cada réplica de la aplicación carga sus propios modelos en memoria.

# en el servidor de la app
from llm_guard import scan_prompt, scan_output
from llm_guard.input_scanners import Anonymize, PromptInjection, Toxicity
from llm_guard.output_scanners import Deanonymize, Toxicity as OutToxicity, Relevance
from llm_guard.vault import Vault

vault = Vault()
input_scanners = [Anonymize(vault), PromptInjection(), Toxicity()]
output_scanners = [Deanonymize(vault), OutToxicity(), Relevance()]

# en el handler de la request
sanitized_prompt, valid_in, score_in = scan_prompt(input_scanners, user_prompt)
if not all(valid_in.values()):
    return error_response(score_in)

response = vllm_client.complete(sanitized_prompt)

sanitized_resp, valid_out, score_out = scan_output(output_scanners, sanitized_prompt, response)
if not all(valid_out.values()):
    return error_response(score_out)

return sanitized_resp

Encaja con el patrón A (sidecar) del post de guardrails cuando la app y el sidecar comparten proceso. Y con el patrón C (in-process) si la app es directamente la capa de inferencia.

Modo 2 — API FastAPI propia

El proyecto incluye un servidor FastAPI listo (llm-guard-api) que expone los scanners detrás de dos endpoints REST:

POST /analyze/prompt
  body: {"prompt": "...", "scanners": [...] (opcional)}
  response: {"sanitized_prompt": "...", "is_valid": bool, "scanners": {scanner: {is_valid, risk_score}}}

POST /analyze/output
  body: {"prompt": "...", "output": "...", "scanners": [...]}
  response: análoga

Configuración por config/scanners.yml con variables de entorno (SCAN_FAIL_FAST, CACHE_MAX_SIZE, CACHE_TTL, SCAN_PROMPT_TIMEOUT…). Lleva métricas Prometheus en /metrics y traces OTel HTTP exporter por defecto.

Encaja con el patrón B (servicio centralizado tras AI Gateway) del post de guardrails.

Modo 3 — Sidecar OTel sobre el pod del motor de inferencia

Para deployments de vLLM en Kubernetes, una variante del modo 2 es desplegar la API de LLM Guard como sidecar container en el mismo pod del vLLM, hablando por localhost. El AI Gateway delante invoca al sidecar antes y después de la inferencia. El OTel collector del nodo agrega los spans de vLLM con los spans gen_ai.guardrail.* de LLM Guard automáticamente porque comparten trace_id propagado por baggage HTTP.

Esto encaja con el patrón A (sidecar) del post de guardrails, pero con la disciplina de la API REST para no acoplar lenguaje (el AI Gateway puede ser Envoy en C++, LLM Guard en Python).

Modo 4 — Plugin dentro de un AI Gateway

Tres AI Gateways soportan LLM Guard como plugin nativo en 2026:

  • LiteLLM Proxy (MIT, BerriAI) — plugin llm_guard activable en config con guardrails: ["llm_guard"]. Llama internamente a la API.
  • Envoy AI Gateway (CNCF, Apache 2.0) — filtro ai-guardrails con backend pluggable apuntando al servicio LLM Guard.
  • Kong AI Gateway (Apache 2.0) — plugin ai-proxy con post-procesador que invoca LLM Guard.

En los tres casos, el AI Gateway es el punto único de entrada de la app cliente al LLM; el gateway llama a LLM Guard antes/después de pasar al motor de inferencia. Ventaja: lock-in cero en el código de la aplicación; cambiar de LLM Guard a NeMo Guardrails es cambiar el plugin del gateway, no reescribir la app. Desventaja: el hop adicional añade latencia (típicamente 5-15 ms intra-cluster).

Integración gráfica con Langfuse, vLLM y el stack OTel

App clientechatbot · backend · agenteAI GatewayLiteLLM · Envoy AI · Kong AILLM Guard APIscan_prompt + scan_outputvLLMmotor inferencia + adapterVault Redismapping placeholder→PII1: prompt2: pre-scan3: inferenciavault R/WOTel Collector (DaemonSet)recibe spans gen_ai.* ygen_ai.guardrail.* desde:vLLM, LLM Guard, AI Gatewayspans OTel HTTPLangfuse/api/public/otel ingestion+ /api/public/scores+ datasets + sessionsOTLPTempo / Jaegertrace storageVictoriaMetricsmétricas PromGrafanadatasourceTempo + VM+ LangfusePlano scoring de Langfuse: el AI Gateway postea langfuse.score(trace_id, name="guardrail.PromptInjection", value=risk_score)por cada scanner ejecutado; eso permite a Langfuse construir dashboards de "% bloqueos por categoría" y series temporalesscores HTTPTres planos de telemetría se mezclan: traces (OTel → Tempo + Langfuse), métricas (Prometheus → VictoriaMetrics), scores (Langfuse SDK).Grafana los une por trace_id; Langfuse los une por session_id + trace_id propagado.El Vault Redis tiene su propio plano de datos y NO se exporta a observabilidad — la PII original nunca sale de él.

Las tres rutas de integración con Langfuse que importan operativamente:

Ruta A — OTel HTTP exporter de LLM Guard. LLM Guard tiene exporter OTel HTTP nativo. Configurando OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://langfuse.cluster/api/public/otel, los spans gen_ai.guardrail.* que emite cada scanner llegan directamente a Langfuse y aparecen como spans hijos del span LLM principal (siempre que el trace_id se propague vía baggage HTTP desde el AI Gateway). Esta es la ruta canónica en 2026.

Ruta B — Langfuse scoring API desde el AI Gateway. El AI Gateway (LiteLLM, Envoy AI, Kong AI), al recibir la respuesta de LLM Guard con los risk_score por scanner, emite una llamada langfuse.score(trace_id, name="guardrail.PromptInjection", value=0.87, comment="blocked") por cada scanner. En Langfuse aparece como scores enganchados al mismo trace que la inferencia. Permite dashboards “bloqueos por categoría” y series temporales por scanner. Es complementaria a la ruta A: la A trae los spans, la B trae el score numérico fácil de agregar en SQL.

Ruta C — Sessions de Langfuse + Vault metadata. En modo conversacional, el AI Gateway propaga langfuse_session_id al Vault como su clave. Cuando un usuario tiene una sesión multi-turno, Langfuse muestra la traza completa de la sesión, con los placeholders que se reutilizan turno a turno. La PII original sigue sin viajar a Langfuse — sólo los placeholders y sus categorías.

El OTel Collector del nodo es el pegamento: recibe spans de vLLM (por OpenLLMetry o instrumentación nativa), de LLM Guard (por su exporter OTel) y del AI Gateway (instrumentación HTTP estándar), los une por trace_id, y los envía paralelamente a Langfuse (vía OTLP HTTP) y a Tempo/Jaeger. Las métricas Prometheus de LLM Guard van a VictoriaMetrics por scraping normal. Grafana ofrece la vista unificada para investigación cross-trace; Langfuse ofrece la vista LLM-céntrica con sessions y scores. El post sobre tracing OTel GenAI detalla la mecánica completa del Collector.

Las matemáticas que importan

Latencia por scanner — los números reales

El proyecto publica benchmarks reproducibles. Para el scanner Anonymize (input length 317 chars, batch 5), los datos de referencia son:

PlataformaBackendLatencia avgp99QPS
AWS m5.xlarge (CPU)Transformers177 ms326 ms1.789
AWS m5.xlarge (CPU)ONNX runtime128 ms180 ms2.464
AWS r6a.xlarge (AMD CPU)Transformers244 ms284 ms1.298
AWS g5.xlarge (NVIDIA A10G)Transformers FP16125 ms498 ms2.532
AWS g5.xlarge (A10G)ONNX + GPU38 ms99 ms8.317

Tres observaciones operativas:

  1. ONNX siempre gana. Incluso en CPU, ONNX baja el avg de 177 a 128 ms (factor 1,4×). En GPU con ONNX, baja de 177 a 38 ms (factor 4,6×). La regla práctica: siempre exportar el modelo del scanner a ONNX antes de producción. La preview del SaaS oficial lo usa por defecto.
  2. GPU sin ONNX no rinde tanto como uno espera. Una A10G sin ONNX (125 ms) es comparable a m5.xlarge con ONNX (128 ms). La GPU sola no compensa si el grafo de inferencia no está optimizado. El binomio relevante es ONNX + GPU.
  3. La latencia p99 sin ONNX explota. En GPU sin ONNX, el p99 de 498 ms triplica el avg de 125 ms — colas y batching producen tail latencies altas. Con ONNX, el ratio p99/avg cae a 2,6× (99/38), mucho más predecible.

Para una capa de guardrails con cinco scanners ejecutados secuencialmente (Anonymize, PromptInjection, Toxicity, Secrets, BanTopics), la suma del p99 es lo que determina el budget de la línea 1 (input). Cinco scanners a ~100 ms p99 cada uno = 500 ms p99 acumulado — fuera de presupuesto para chat interactivo. Con ONNX bajamos a ~50 ms cada uno = 250 ms p99 — manejable. Con fail_fast=True, el tiempo esperado es menor (el más probable es que pasen los más baratos y fallen los caros sólo si se ejecutan).

Para un cálculo más fino, la latencia esperada del pipeline con fail_fast es:

[ \mathbb{E}[L] = \sum_{i=1}^{N} L_i \cdot \prod_{j=1}^{i-1} p_j ]

donde (L_i) es la latencia del scanner (i) y (p_j) la probabilidad de que el scanner (j) devuelva válido. En tráfico bien comportado (la mayoría de prompts pasan todos los scanners), (\prod p_j \approx 1) y la fórmula colapsa a la suma directa. En tráfico adversarial, los scanners más rápidos al principio del pipeline cortan antes y la latencia esperada baja drásticamente.

Coste computacional por scanner

El tamaño del modelo backend determina el coste y la posibilidad de correr en CPU vs requerir GPU:

ScannerModelo backend típicoParámetrosVRAM FP16 / ONNX-INT8CPU viable
Anonymize (BERT-NER)dslim/bert-base-NER110 M220 MB / 55 MBSí (con ONNX)
Anonymize (BERT-large)dslim/bert-large-NER335 M670 MB / 170 MBSí pero lento (~500 ms CPU)
PromptInjectionDeBERTa-v3-base fine-tuned184 M370 MB / 90 MBSí (con ONNX)
Toxicityunitary/toxic-bert110 M220 MB / 55 MB
Sentimentdistilbert-sst267 M130 MB / 35 MB
Gibberishsmall distilbert67 M130 MB / 35 MB
BanTopicsBART-MNLI zero-shot407 M815 MB / 200 MBLento en CPU (~400 ms)
Bias (output)RoBERTa-bias125 M250 MB / 65 MB
FactualConsistencycross-encoder/nli-deberta184 M370 MB / 90 MB
Relevancesentence-transformers110 M220 MB / 55 MB
TokenLimit, Regex, JSON, BanSubstrings, Secrets(sin modelo)0Trivial

Patrón razonable on-premise: scanners sin modelo (TokenLimit, Regex, BanSubstrings, Secrets) corren en CPU sin pestañear. Anonymize, PromptInjection, Toxicity, Sentiment, Relevance corren cómodamente en CPU con ONNX-INT8 con ~50-150 ms p99. BanTopics y los basados en cross-encoder grandes (FactualConsistency) son los candidatos a vivir en una GPU compartida si quieres p99 < 100 ms.

Throughput de la API en cluster

Una instancia de la API FastAPI con 4 workers Uvicorn sobre un nodo con 8 vCPUs alcanza ~600-1.200 RPS sobre un pipeline típico de 5 scanners en CPU + ONNX. Para escalar:

  • Horizontal: replicar pods detrás de un Service ClusterIP — escalado lineal porque los scanners son stateless (excepto el Vault, que es por sesión y se externaliza a Redis si se quiere sticky o compartido).
  • Vertical con GPU: 1 H100 sirve ~5.000-10.000 RPS con todos los scanners en ONNX-GPU. Es overkill para la mayoría de deployments excepto en multi-tenant con miles de QPS sostenidos.

La regla práctica del post sobre guardrails (1 GPU guardrails por 4-8 GPUs LLM) se mantiene aquí: con cluster 4×H100 SXM sirviendo Llama 70B en TP=4, una L4 o RTX 4090 dedicada al servicio LLM Guard cubre la carga.

Comparativa con NeMo Guardrails y Guardrails AI

Las tres herramientas resuelven el mismo problema desde tres modelos arquitectónicos distintos. La elección entre ellas no es de calidad —las tres están maduras—, es de encaje con el resto del stack:

DimensiónLLM GuardNeMo GuardrailsGuardrails AI
Modelo conceptualPipeline de scanners compactosGrafo declarativo Colang (flujo conversacional)Validators tipo contrato JSON
Detección dominanteModelos ML especializados (BERT, DeBERTa) por categoríaReglas + LLM-as-judgeValidators heurísticos + LLM-as-judge externo
PII workflowAnonymize + Vault + DeanonymizeVía Presidio integrado, sin Vault built-inValidators de PII, sin restitución automática
LicenciaMITApache 2.0Apache 2.0 (+ Hub paid)
LenguajePythonPython + Colang DSLPython
Madurez APIAPI FastAPI built-in, OTel built-inServer FastAPI built-in, OTel parcialAPI server externo
Despliegue clusterLib + API + sidecar + plugin gatewaysLib + serverLib + server + Hub SaaS
Latencia típica (5 scanners ONNX-GPU)50-200 ms100-500 ms (más si hay LLM judge)100-300 ms (depende del validator)
Cuándo brillaApps con PII fuerte, multi-tenant con sesiones, requisitos GDPR/HIPAASistemas conversacionales con flujos definidos, agentes con dialog policyApps con contratos JSON estrictos, structured output con validación adicional
Cuándo no encajaSi necesitas dialog policy declarativaSi quieres detectores compactos sin LLM judgeSi quieres Vault y Deanonymize automático

Los tres son complementarios en deployments grandes. Un patrón maduro en 2026 es:

  • NeMo Guardrails orquesta el flujo de diálogo (qué tools puede invocar el agente, en qué orden, con qué cooldowns).
  • LLM Guard ocupa la línea de PII + scanners compactos en input y output, con su Vault haciendo el trabajo sucio de anonimización.
  • Guardrails AI valida outputs estructurados (JSON Schema, function calling) con sus validators.

La separación de responsabilidades evita el solapamiento y permite cambiar piezas sin reescribir todo. Las tres exponen API FastAPI y emiten spans OTel; el AI Gateway las orquesta secuencialmente.

Aplicado a hardware on-premise

En la RTX 4090 (24 GB)

Una 4090 dedicada al pod del servicio LLM Guard sirve cómodamente el pipeline completo en producción media:

  • Anonymize (BERT-NER ONNX-INT8): ~50 MB VRAM.
  • PromptInjection (DeBERTa ONNX-INT8): ~90 MB.
  • Toxicity, Sentiment, Gibberish: ~150 MB total.
  • BanTopics (BART-MNLI ONNX-INT8): ~200 MB.
  • Bias, Relevance, FactualConsistency (output): ~250 MB total.

Total ~750 MB. Resto de la VRAM ociosa o aprovechable para batching agresivo. Throughput sostenido a 3.000-6.000 RPS sobre el pipeline completo. Para deployments con < 500 RPS sostenidos, la 4090 está sub-utilizada y se puede compartir con otra carga (embeddings de RAG, reranker BGE).

Sobra capacidad por orden de magnitud. Patrón razonable:

  • 3 H100 sirviendo el LLM principal en TP=3 (Llama 70B FP8).
  • 1 H100 dividida en MIG instances (1g.10gb o similar) — una porción para LLM Guard (~10 GB MIG es más que suficiente), otra para el reranker, otra para embeddings.

Throughput agregado para LLM Guard a esa escala: 15.000-30.000 RPS. Sobra para multi-tenant grande con sesiones largas.

Las trampas operativas específicas

Trampa 1 — Vault sin TTL. El Vault crece sin freno si no se limpia. En modo lib in-process por request, no hay problema (el objeto se destruye). En modo servicio centralizado con Redis, falta poner TTL y el Redis se llena. Trampa silenciosa que se descubre cuando el pod de Redis OOM-killea en producción a las 6 semanas.

Trampa 2 — Vault no compartido entre pods + AI Gateway sin sticky session. Si el AI Gateway distribuye round-robin entre múltiples pods de LLM Guard, el Vault local de un pod no sabe del mapping creado por otro. Resultado: en el turno 2 de una sesión, el Deanonymize no encuentra los placeholders del turno 1 y deja [REDACTED_PERSON_1] literal en la respuesta. Solución: Vault Redis compartido o sticky session por user_id.

Trampa 3 — Modelos no exportados a ONNX en producción. Se despliega con la config por defecto (Transformers) y la latencia es 3-5× peor que la que reportan los benchmarks. Equipo asume que LLM Guard “es lento”. La solución es exportar a ONNX (built-in en el proyecto) y configurar recognizer_conf con la ruta al .onnx del modelo.

Trampa 4 — fail_fast=False con muchos scanners. Sin fail_fast, todos los scanners corren siempre, incluso si el primero ya bloqueó. Latencia 3-5× peor en tráfico adversarial. Para producción, salvo razón explícita (querer métricas completas por scanner aun bloqueando), fail_fast=True es el default razonable.

Trampa 5 — cache_ttl infinito + prompts con PII variable. Si la caché de la API guarda el sanitized_prompt indefinidamente, dos sesiones distintas con misma estructura de prompt pero diferentes PII pueden colidir si la clave de caché no incluye el Vault hash. Hay que verificar que la clave de caché incluya o bien el contenido completo (sin PII) o un hash del prompt original.

Trampa 6 — Logs estructurados con PII original. Los logs stdout JSON de LLM Guard registran por defecto sólo placeholders. Pero si se añaden hooks custom para debug, es fácil filtrar la PII original al log. Auditoría regulatoria (RGPD, ENS) detecta esto y es incumplimiento. Disciplina: nunca añadir hooks que lean del Vault sin permiso explícito.

Trampa 7 — scan_output sin prompt original. El método scan_output espera (prompt, output) para validadores que comparan ambos (Relevance, LanguageSame, FactualConsistency). Si se le pasa sólo el output, esos scanners fallan silenciosamente o devuelven is_valid=True por defecto. Hay que conservar el sanitized_prompt en el AI Gateway y pasarlo al scan_output.

Cuándo elegir LLM Guard (y cuándo no)

Elegir LLM Guard cuando:

  • El requisito de anonimización PII con restitución automática está en la lista. Es la razón #1 para usarlo. Banca, salud, asesoría legal, RRHH — cualquier caso con PII fuerte que no debe llegar al LLM aunque éste sea local.
  • Quieres un pipeline pythonic sin DSL nuevo. Si el equipo es Python-puro y prefiere componer scanners como objetos antes que aprender Colang.
  • El stack ya tiene un AI Gateway (LiteLLM, Envoy AI, Kong AI) y se integra como plugin sin tocar la app.
  • Necesitas OTel y Prometheus built-in sin instrumentación adicional.

No elegir LLM Guard cuando:

  • El sistema es un agente conversacional con flujos de diálogo complejos (políticas, fallbacks, escalado a humano). Ahí NeMo Guardrails con Colang es estructuralmente mejor.
  • La capa de safety se reduce a validar outputs estructurados (JSON, function calling). Guardrails AI con sus validators es más natural.
  • Tu latencia budget es ultra-agresivo (< 30 ms para toda la capa). Habrá que reducir scanners y aceptar cobertura menor; quizás un único PromptGuard 2 + Presidio en sidecar (patrón del post de guardrails) sea más simple.
  • No quieres cargar con el peso operativo del Vault distribuido (Redis, TTL, sticky session). Para sistemas pequeños sin requerimiento fuerte de PII, sobra-dimensiona.

Lo que no hemos cubierto (próximos posts)

  • Custom scanners: cómo escribir tu propio scanner cuando ninguno del catálogo encaja (regex compleja de dominio, classifier fine-tuned propio). El proyecto admite scanners custom heredando de InputScanner / OutputScanner con tres métodos.
  • Integración con SLSA / supply chain: cómo firmar el contenedor de LLM Guard con cosign, attestations SLSA, y verificación en cluster antes de admitirlo. Tema operativo de seguridad de supply chain (OWASP LLM03).
  • Red teaming contra LLM Guard: técnicas conocidas que evaden detectores (homoglyphs, Unicode confusables, encoding base64 dentro del prompt). El proyecto publica un suite de tests adversariales para hacer benchmarking propio. Cómo se monta como gate continuo en CI.
  • Benchmark comparativo con Bedrock Guardrails y Azure AI Content Safety: F1 por categoría sobre tráfico real cruzando tres deployments distintos. El post de OSS vs hyperscalers tiene la comparativa estratégica; falta el comparativo técnico de detección.

Referencias

Ver también