En vivo 18.11 · Cohorte 318 nov, 19:00 MSK — taller práctico gratuito de agentes IA · Cohorte 3 en vivo inicia 23.11Taller 18 nov →Cohorte 3 (23.11)
IndustriaAnálisis · En radarMachine Learning Mastery→ original

El esquema pasa, pero el importe de la factura es 10 veces menor: cómo validar el structured output de un LLM

El modelo devolvió un JSON perfectamente válido: todas las claves presentes, tipos correctos, la cita de la factura es auténtica. Solo que el importe era 12 500 céntimos en lugar de 125 000, es decir, 125 euros en lugar de 1 250. Ni el parser ni JSON Schema detectan algo así. El autor describe tres capas de protección: un contrato estricto de Pydantic con verificación contra el texto original, reintentos limitados y constrained decoding.

Procesado por IA desde Machine Learning Mastery; editado por Hamidun News
El esquema pasa, pero el importe de la factura es 10 veces menor: cómo validar el structured output de un LLM
Fuente: Machine Learning Mastery. Collage: Hamidun News.
Escuchar artículo
Ficha técnica y cifras clave
ParámetroDatos
Fuente originalMachine Learning Mastery ↗
Editor jefe
Dato clave #1El modo strict de Pydantic impide que la cadena "125000" se convierta en silencio en un número
Dato clave #2Reintentos solo para shape_error, con un máximo de tres intentos
Dato clave #3Compare el número de la respuesta con la cita del documento original

Aquí tenemos una factura de cuatro líneas: número 2026-0417, pagador Northwind Traders, «Total due: EUR 1,250.00», pagadera en un plazo de 30 días. Le pide al modelo que extraiga el número, la divisa, el importe en céntimos y la línea de la que tomó el importe. La respuesta llega así: importe 12 500, divisa EUR, cita «Total due: EUR 1,250.00».

El JSON es válido, todas las claves están presentes, los tipos son correctos, EUR está en la lista permitida y la cita realmente aparece en la factura. Pero 12 500 céntimos son 125 euros, y la factura dice 1 250. Es un error de diez veces y ningún parser lo notará: 12500 es un entero perfectamente válido. Solo se puede detectar comparando con el texto original, y esa comparación hay que escribirla uno mismo.

Machine Learning Mastery ha publicado un análisis que muestra cómo construir una defensa en tres capas. El structured output ya es una función estándar: la Responses API de OpenAI acepta un esquema JSON o un modelo Pydantic, y vLLM hace lo mismo en un servidor local mediante xgrammar. La mayoría de los tutoriales terminan en el momento en que «el JSON se parseó». El autor sostiene que es justo ahí donde empieza todo.

El contrato y la verificación contra la fuente

La primera capa es un contrato que pertenece a la aplicación, no al prompt. En el ejemplo es un modelo Invoice con cuatro campos. Dos parámetros de configuración hacen el trabajo principal. El modo strict=True desactiva las conversiones implícitas de Pydantic: la cadena "125000" en un campo entero produce un error int_type en lugar de convertirse en silencio en un número. Lo mismo ocurre con el booleano True, que de otro modo pasaría a ser 1. El parámetro extra="forbid" convierte una clave sobrante en un error, y no en basura ignorada.

Otro detalle: tres campos admiten null, pero ninguno tiene valor por defecto. El modelo puede decir honestamente «esto no aparece en la factura», pero no puede callarse ni debe inventar un número solo por no escribir null. El dinero se guarda en céntimos como int, para que 1 250,00 no se convierta en 1249,9999.

Después, la función accept() da cuatro pasos. Primero, el parseo y la comprobación de la forma, que es el único paso que puede devolver shape_error. Luego, la comprobación de que la cita aparece literalmente en la fuente. A continuación, se contrastan el número de factura y la divisa con el texto. Por último, el importe se compara con las cifras de la cita: «1,250.00» sin separadores da 125000, el modelo envió 12500 y el objeto se rechaza. Hay cuatro resultados posibles: accepted, needs_review, rejected y shape_error. Si la factura no indica el importe, el resultado es needs_review, es decir, decide una persona, no un cero inventado.

Cuándo reintentar y cuándo no

La segunda capa son los reintentos con una política estricta. Solo tiene sentido reintentar shape_error: se puede pedir al modelo que corrija la forma pasándole la lista de errores. Si la respuesta es rejected, repetir no sirve, porque el contenido de la factura no va a cambiar. Un rechazo del proveedor o un error de configuración también detienen el ciclo de inmediato. El máximo son tres intentos: el primero y no más de dos regeneraciones.

La retroalimentación se limita a cinco motivos para que el prompt no se convierta en una transcripción de todos los fallos. El esquema y el texto original no cambian entre intentos. Según las tesis del artículo, además hay que vigilar los reintentos de transporte ocultos del propio SDK; de lo contrario, el número real de peticiones será mayor que el que presupuestó.

Constrained decoding: útil, pero no una panacea

La tercera capa es el constrained decoding. El generador solo puede emitir físicamente tokens que no saquen la respuesta del esquema, por lo que toda una clase de errores de forma ni siquiera llega a la validación. Pero el esquema no expresa la regla «los céntimos deben coincidir con la cita». Un decodificador que respete el esquema a la perfección seguirá produciendo ese mismo 12 500. Un objeto válido es un pase a la siguiente comprobación, no el resultado final.

También hay limitaciones. El ejemplo es didáctico: dos divisas y una comprobación sencilla de cifras. Para formatos reales hace falta código que entienda sus separadores decimales. Si las facturas llegan escaneadas, verifique primero el texto reconocido: una cifra errónea en la entrada pasará todas las comprobaciones mientras el objeto sea internamente coherente.

La conclusión práctica es sencilla. No confíe en una validación de esquema «en verde»: guarde en la respuesta una cita como prueba y compruebe el número contra ella. Use reintentos solo donde repetir pueda ayudar y fije un límite estricto. Active el constrained decoding como ahorro en errores de forma, pero no en lugar de sus propias comprobaciones. Conviene cubrir con tests cada camino de fallo usando un cliente mock, como hace el autor.

Historia

Preguntas frecuentes sobre la noticia

¿Qué se sabe sobre El contrato y la verificación contra la fuente?

La primera capa es un contrato que pertenece a la aplicación, no al prompt. En el ejemplo es un modelo Invoice con cuatro campos. Dos parámetros de configuración hacen el trabajo principal. El modo strict=True desactiva las conversiones implícitas de Pydantic: la cadena "125000" en un campo entero produce un error int_type en lugar de convertirse en silencio en un número. Lo mismo ocurre con el booleano True, que de otro modo pasaría a ser 1.

Cuándo reintentar y cuándo no?

La segunda capa son los reintentos con una política estricta. Solo tiene sentido reintentar shape_error: se puede pedir al modelo que corrija la forma pasándole la lista de errores. Si la respuesta es rejected, repetir no sirve, porque el contenido de la factura no va a cambiar. Un rechazo del proveedor o un error de configuración también detienen el ciclo de inmediato. El máximo son tres intentos: el primero y no más de dos regeneraciones.

¿Qué se sabe sobre Constrained decoding: útil, pero no una panacea?

La tercera capa es el constrained decoding. El generador solo puede emitir físicamente tokens que no saquen la respuesta del esquema, por lo que toda una clase de errores de forma ni siquiera llega a la validación. Pero el esquema no expresa la regla «los céntimos deben coincidir con la cita». Un decodificador que respete el esquema a la perfección seguirá produciendo ese mismo 12 500. Un objeto válido es un pase a la siguiente comprobación, no el resultado final. También hay limitaciones.

ZK
Zhemal Khamidun — CPO de Alpina Digital, product manager de AlpinaGPT. Crea productos de IA desde 2019. Hamidun News es un proyecto personal que creció hasta ser un agregador en producción con más de 6.000 lectores.
✦ Unified AI Workspace · Nomad

Test Frontier LLMs & Autonomous Agents in One Workspace

Run Claude Opus 5.5, GPT-6, Gemini 3.1, and DeepSeek side-by-side in Nomad AI (nomadnet.ai) — unified model routing, deep research, and multimodal workflows under one subscription.

Try Nomad AI →Launch Nomad Agent
¿Qué te parece?
Cargando comentarios…