Por qué el texto libre invita a la mentira
Pídale a un sistema RAG “una respuesta” y le entrega al modelo una página en blanco. Sobre un tema que saturó sus datos de entrenamiento la llenará de forma confiable; sobre su contrato, visto una vez o nunca, produce una continuación igual de fluida, igual de segura y mucho más propensa a estar equivocada. No es un defecto que se corrija con un prompt más severo. Es lo que es la generación.
La solución no es un prompt más inteligente, sino una ejecución controlada. El modelo responde solo a partir de los pasajes que tiene enfrente, con una forma tipada y una cita para cada afirmación. Entra una entrada estructurada y sale un objeto estructurado. Pida un objeto JSON en el que cada campo se valide contra la entrada, y los huecos que antes llenaba de memoria sencillamente ya no están.
Valores tipados, no cadenas
El primer paso es darle un tipo a la respuesta. En lugar de la cadena "USD 1,200 per claim", el modelo llena un Amount(value, currency, unit); en lugar de "15 March 2024", un DateValue(iso, original); en lugar de texto separado por barras verticales, una TableValue(headers, rows) de verdad. El código posterior nunca vuelve a analizar una cadena, y los campos con nombre funcionan también como instrucción: un modelo que ve street / postal_code / city / country separa por sí solo el bloque de la dirección: una llamada, una fila, un solo lugar que auditar, en lugar de cuatro idas y vueltas frágiles.
Cada valor lleva además sus propios tramos de evidencia: rangos de líneas en la fuente, no el texto de la evidencia. El modelo devuelve números de línea más una cita opcional que solo se usa como suma de verificación (el validador confirma que es una subcadena de las líneas citadas, lo que detecta un número de línea equivocado); el fragmento legible lo recupera el proceso, nunca se toma como confiable del modelo. Una respuesta en forma de lista son muchos elementos, cada uno con sus propios tramos; una sola respuesta cuya evidencia está en las páginas 5 y 23 es un elemento con dos tramos. La estructura modela ambos casos directamente.
La regla más clara: el LLM extrae, Python compara
Nunca le pida al modelo que calcule, compare o agregue cuando el resultado se puede derivar de valores extraídos. “¿La prima es mayor a un millón de dólares?” le pide al modelo que localice la cifra, interprete su moneda, la convierta y la compare, todo de una vez, con un sí o un no que borra el valor que lo produjo. Una prima de 100,000,000 JPY se vuelve “sí” o “no” según el tipo de cambio que el modelo imagine.
- Primero extraiga. Pida un
Amount(value, currency, unit), nada más. - Calcule en Python. Aplique la conversión con un tipo de cambio visible y compare contra el umbral.
- Mantenga todo auditable. Cada paso es visible y se puede repetir; si el tipo de cambio se actualiza, se recalcula sin volver a llamar al modelo.
La señal que NO debe pedirle al modelo
El esquema también puede llevar campos de retroalimentación con los que el proceso decide la ruta: answer_found frente a complete_answer_found, conflicting_evidence, una aclaración sugerida. Pero la mejor idea del artículo es el único campo que deliberadamente no le pide al modelo. Un punto limpio al final de la página 5 demuestra que terminó una oración, no que terminó una lista. El modelo no puede ver la página 6, así que un contexto que solo incluye la página 5 siempre parece completo cuando el corte es limpio.
Por eso lo completo de la respuesta lo calcula el proceso, mediante una recuperación que se traslapa una página y que el modelo nunca ve: un nuevo encabezado de sección al principio de la página 6 → la lista estaba completa; contenido que continúa → estaba truncada, hay que volver a recuperar. Es el mismo instinto que el traslape entre fragmentos, aplicado al alcance de la recuperación: se compra seguridad trayendo a propósito un poco más de lo que parece necesario. Es determinista, se basa en la estructura del documento y detecta una falla que ni el modelo ni una persona que lea solo la página 5 podrían detectar.
Lo que vuelve real el contrato: la decodificación restringida
Un esquema solo es un contrato si se hace cumplir. La decodificación restringida (el responses.parse de OpenAI con Pydantic, o enfoques basados en gramáticas como Outlines, las gramáticas de vLLM o GBNF de llama.cpp) hace que el modelo no pueda emitir una salida que no se pueda analizar. Debajo hay una escalera de confiabilidad: el modo de esquema JSON, luego “devuelva JSON válido” en el prompt (validando después), luego un vago “devuelva JSON”, que de vez en cuando envolverá el objeto en un bloque de código o le antepondrá “Aquí está la respuesta:” y romperá al consumidor. Cualquier cosa por encima del último peldaño es bastante más segura.
Pusimos a prueba la afirmación: los modelos que “razonan” son peores con JSON
El artículo hace una afirmación refutable y medible dentro de nuestro terreno: los modelos de razonamiento (“thinking”) son menos confiables con JSON estructurado, porque su traza de razonamiento contamina la salida, y la calidad del JSON no se correlaciona con el tamaño del modelo. Así que lo medimos, 5 corridas por modelo, pidiéndole a cada uno un solo objeto JSON estricto (cuatro claves tipadas, sin prosa, sin markdown) con suficiente presupuesto de tokens para que un modelo de razonamiento pueda terminar de razonar y todavía emitir el JSON. Dos columnas, y la diferencia entre ellas es toda la historia:
| Modelo | Tipo | JSON estricto | Extraíble | Traza <think> |
|---|---|---|---|---|
| DeepSeek-R1 · 671B MoE | con razonamiento | 0/5 | 5/5 | 5/5 |
| DeepSeek-V3.1 · 671B MoE | sin razonamiento | 5/5 | 5/5 | 0/5 |
| QwQ-32B · 32B | con razonamiento | 0/5 | 5/5 | 5/5 |
| Qwen2.5-72B · 72B | sin razonamiento | 5/5 | 5/5 | 0/5 |
| gpt-oss-20b · 20B | sin razonamiento | 5/5 | 5/5 | 0/5 |
| gpt-oss-120b · 120B | sin razonamiento | 5/5 | 5/5 | 0/5 |
| GPT-4o · frontier | sin razonamiento | 5/5 | 5/5 | 0/5 |
- La afirmación se sostiene, con precisión. Los dos modelos de razonamiento (DeepSeek-R1, QwQ-32B) obtuvieron 0/5 en JSON estricto, y aun así 5/5 en extraíble. Conocen los valores; no los entregan limpios, porque la traza
<think>va antes del objeto y un analizador estricto se atraganta. - El tamaño no se correlaciona. Un modelo de 20B y uno de 671B obtuvieron los dos 5/5 en estricto; las dos fallas son las de los modelos de razonamiento (uno de 671B y uno de 32B). La variable es el modo de razonamiento, no la cantidad de parámetros.
- La solución es el contrato. Para cargas de trabajo de salida estructurada, elija un modelo sin razonamiento (o desactive el razonamiento) y deje que la decodificación restringida imponga el esquema, para que “produce JSON limpio” deje de ser una propiedad que uno espera que se cumpla.
Medido en el banco de pruebas de EyesInAI, 2026-07-05, 5× por modelo con un presupuesto de 800 tokens. “Estricto” = JSON limpio, sin nada alrededor (lo que necesita un proceso); “extraíble” = un objeto válido que se puede recuperar incluso de una traza de razonamiento. Vea la clasificación para la suite en vivo.