Mascota Jacar — leyendo contigo Un portátil cuyos ojos siguen el cursor mientras lees.
Desarrollo de Software Inteligencia Artificial

Decodificación restringida para salidas estructuradas en LLM

Decodificación restringida para salidas estructuradas en LLM

Actualizado: 2026-05-03

Pedirle a un LLM que devuelva un objeto JSON conforme a cierto esquema es una de esas tareas que parecen triviales hasta que las pones en producción a miles de documentos por hora. En cuanto el prompt se complica, el modelo empieza a cometer los errores habituales: una coma que falta, una llave que se cierra antes de tiempo, un preámbulo simpático del tipo “aquí tienes el JSON que pediste”, un bloque de Markdown rodeando la respuesta, o directamente un campo que no estaba en el esquema. Con prompting cuidadoso y temperatura baja puedes acercarte al 98% de aciertos en un modelo grande, pero ese 2% restante, multiplicado por volumen, acaba siendo el mayor consumidor de tiempo del equipo.

Puntos clave

  • La decodificación restringida garantiza matemáticamente que la salida cumple el esquema — no estadísticamente, por construcción.
  • Outlines (Python) es la referencia: funciona con Hugging Face Transformers, llama.cpp y vLLM; permite expresar restricciones como Pydantic, JSON Schema, regex o gramática Lark.
  • Instructor no es un decodificador estrictamente hablando — envuelve la API de OpenAI o Anthropic y usa Pydantic para validar y reintentar — pero es la vía pragmática para modelos cloud sin acceso al runtime de inferencia.
  • La decodificación restringida paga su peaje dentro del mismo forward pass: cada token es 10-30% más lento por el coste del enmascarado, pero nunca hay una segunda pasada. En lotes de miles de documentos, la cuenta sale claramente a favor.
  • La garantía es sintáctica, no semántica: el JSON será válido y cumplirá el esquema, pero si el modelo no entendió el documento, los valores extraídos seguirán siendo incorrectos.

Qué es la decodificación restringida

La idea es sencilla y elegante. En cada paso de generación, el modelo produce una distribución de probabilidad sobre todo el vocabulario — unos cincuenta mil tokens típicamente. En lugar de muestrear de esa distribución tal cual, se aplica una máscara: los tokens que no podrían extender una cadena válida según la gramática objetivo se ponen a probabilidad cero, y después se normaliza y muestrea solo sobre el subconjunto legal.

La consecuencia es que la salida cumple la gramática con garantía matemática, no estadística. No hay rama del árbol de generación que pueda llegar a producir un JSON roto, porque la llave que falta simplemente no es un token alcanzable.

Las tres familias de herramientas

Outlines es la referencia en el mundo Python. Funciona con Hugging Face Transformers, llama.cpp y vLLM, y permite expresar las restricciones como modelo Pydantic, JSON Schema, regex o gramática libre de contexto en notación Lark:

python
from outlines import models, generate
from pydantic import BaseModel

class Factura(BaseModel):
    numero: str
    total: float
    lineas: list[str]

modelo = models.transformers("meta-llama/Llama-3-8B-Instruct")
generador = generate.json(modelo, Factura)
factura = generador("Extrae datos de: Factura A-0012, total 128.50, dos lineas")

Guidance (Microsoft) ataca el problema con enfoque de plantilla: intercalas texto fijo con regiones generadas bajo restricciones — un select entre opciones, un gen con regex, un bloque JSON. Es especialmente cómoda cuando la salida no es un único bloque estructurado sino una conversación con estructura parcial.

Instructor envuelve el cliente de OpenAI o Anthropic y usa Pydantic para validar y reintentar. No es un decodificador estrictamente hablando, pero resuelve el mismo dolor con una filosofía diferente y es la vía pragmática cuando se trabaja con modelos cloud sin acceso al runtime.

Contra qué compites al elegir esta vía

El retry loop clásico: le pides al modelo que devuelva JSON, intentas parsearlo, y si falla vuelves a llamarlo con el error como contexto. Funciona razonablemente y es lo que hay bajo el capó de Instructor, pero cada reintento es una llamada completa al modelo — con su latencia y su coste. En tareas de extracción masiva el tiempo perdido en reintentos domina el presupuesto.

El JSON mode de OpenAI (noviembre 2023) garantiza que la salida parsea como JSON, pero no que cumpla el esquema: puedes recibir un objeto perfectamente válido sintácticamente que haya inventado campos o cambiado tipos. El function calling va un paso más allá porque acepta un esquema, pero en la práctica hasta la fecha sigue habiendo desviaciones en campos opcionales y enums no siempre respetados.

La decodificación restringida paga su peaje dentro del mismo forward pass: cada token es 10-30% más lento por el coste del enmascarado, pero nunca hay una segunda pasada. Para lotes de miles de documentos, la cuenta sale claramente a favor. Esta estrategia complementa bien el uso de ONNX Runtime para inferencia en edge o de TGI de Hugging Face en servidores propios.

Dónde compensa adoptarlo

En extracción estructurada a escala — facturas, currículos, resultados clínicos, contratos — la diferencia entre un 98% y un 100% de salidas válidas es enorme porque elimina por completo la cola de errores que hay que revisar a mano o reprocesar.

En agentes que deben emitir llamadas a herramientas, garantizar que el JSON de argumentos es siempre parseable evita que un error de formato tire abajo toda la cadena de razonamiento.

En generación de datos sintéticos con esquema fijo, la garantía permite encadenar pasos sin cinturones de seguridad intermedios.

En cambio, para chat conversacional con salidas semilibres, o cuando un modelo frontera con prompts bien escritos ya te da más del 99%, el coste de integración no se amortiza. Para esos casos, el enfoque de prompt engineering con Claude 3 o GPT-4 bien configurado es suficiente.

Dos malentendidos que conviene despejar

El primero: creer que la decodificación restringida mejora la calidad semántica de la respuesta. No lo hace. El JSON será válido y cumplirá el esquema, pero si el modelo no entendió el documento, los valores extraídos seguirán siendo incorrectos. La garantía es sintáctica, no de contenido.

El segundo: suponer que el coste de integración es trivial. Cambiar de la API de OpenAI a un runtime local con Outlines implica gestionar GPU, versiones de modelo, caché de KV y todo el aparato de servir inferencia. Para equipos pequeños sin plataforma de ML propia, Instructor sobre OpenAI sigue siendo la vía pragmática aunque se pierda la garantía formal.

El horizonte próximo

vLLM v0.4 integra Outlines de forma nativa. llama.cpp expone --grammar desde hace meses. TGI tiene su propia variante con Guidance. Los runtimes de inferencia están asumiendo la decodificación restringida como ciudadano de primera clase.

Conclusión

El mensaje para quien construya sobre LLMs es que la validación de formato debería salir del código de aplicación y entrar en la capa de generación, porque ahí es donde el coste marginal es más bajo y la garantía más fuerte. Seguir parcheando con reintentos y json.loads rodeado de try/except es una deuda técnica que envejece mal. Para extracción masiva, agentes con tool calling o generación de datos sintéticos, la decodificación restringida convierte un problema probabilístico en una garantía — y eso cambia el diseño del sistema.

¿Te ha resultado útil?
[Total: 10 · Media: 4.7]

Escrito por

CEO - Jacar Systems

Apasionado de la tecnología, la infraestructura cloud y la inteligencia artificial. Escribe sobre DevOps, IA, plataformas y software desde Madrid.