Agentes & IA
Structured outputs con salidas reales: qué errores cometen los LLM al devolver JSON y cuándo reparar o reintentar
1200 salidas reales de Llama, Qwen y Gemma: reparar en local evita del 0 % al 100 % de los reintentos según el modelo. Con la mezcla real, el 59,6 % del gasto extra.
- Fecha
- 20 oct 2026
- Lectura
- 9 min
- Resultado
- 1200 salidas reales • Ahorro del 59,6 % con la mezcla real • De 0 % a 76 % de pedidos correctos con salida restringida en Llama 3.2
Con salidas reales, lo que ahorras reparando el JSON en local en vez de reintentar es casi exactamente el porcentaje de tus fallos que son de sintaxis. En 600 salidas libres de cuatro modelos abiertos, reparar evitó el 100 % de los reintentos en Gemma 3 y Qwen 2.5, el 58,7 % en Llama 3.1 y solo el 1,3 % en Llama 3.2, que en vez de datos devolvía el esquema. Con la mezcla real de errores, el ahorro en gasto extra fue del 59,6 %. Y la generación restringida al esquema eliminó todos los fallos de sintaxis y llevó a Llama 3.2 del 0 % al 76 % de pedidos correctos.
Esta es la segunda parte de reparar el JSON en local o reintentar, que usaba fallos sintéticos repartidos a partes iguales. En X nos preguntaron con razón qué pasa con salidas reales y si un tipo de error aparece mucho más que los demás. Aquí está la respuesta medida.
Montaje
- 4 modelos abiertos en local con Ollama: Llama 3.2 (3B), Qwen 2.5 (3B), Gemma 3 (4B) y Llama 3.1 (8B).
- Tarea: extraer el pedido (cliente, líneas con SKU y cantidad, entrega, urgencia, notas) de 30 correos en español, con 5 semillas por correo y modelo, a temperatura 0,7.
- Generación libre: se pide «solo un JSON válido» con el esquema en las instrucciones y se guarda cada salida tal cual.
- Reparar primero:
json_repairy validación contra el esquema. Si no queda válida, reintento real con el modelo, midiendo sus tokens y su tiempo. - Generación restringida: el mismo correo con el JSON Schema en el parámetro
formatde Ollama, que restringe la generación con una gramática (el mismo principio que Outlines). - Pedido correcto: coinciden email, empresa, ciudad, fecha, urgencia y todas las líneas (SKU y cantidad) con el correo original.
Qué errores salen de verdad
| Modelo | Válidas sin tocar | Fallos | Error dominante |
|---|---|---|---|
| Gemma 3 (4B) | 0 | 150 | JSON dentro de un bloque de markdown (150) |
| Qwen 2.5 (3B) | 83 | 67 | Cierra la lista de líneas con } en vez de ] (67) |
| Llama 3.1 (8B) | 58 | 92 | Texto alrededor del JSON (55) y negativas a responder (32) |
| Llama 3.2 (3B) | 1 | 149 | Devuelve el esquema en vez de los datos (145) |
Ninguna salida se cortó por límite de tokens con este tamaño de respuesta. Los errores que no se pueden reparar fueron otros:
- Esquema copiado: el modelo devuelve el JSON Schema, o el esquema y los datos mezclados con otra estructura. Es un JSON válido, pero inservible.
- Negativas: Llama 3.1 contestó 32 veces «Lo siento, pero no puedo cumplir con esa solicitud», probablemente porque los correos llevan nombres y emails.
Qué se repara en local
| Tipo de fallo | Casos | Reparados en local |
|---|---|---|
| Bloque de markdown | 151 | 151 |
| Cierre equivocado | 67 | 67 |
| Texto alrededor del JSON | 58 | 55 |
| Sin JSON (negativas) | 32 | 0 |
| Esquema copiado | 147 | 0 |
| JSON válido que no cumple el esquema | 3 | 0 |
Reparar primero frente a reintentar siempre
| Modelo | Reintentos evitados | Tokens por 1000 llamadas, reintentando siempre | Tokens por 1000 llamadas, reparando primero | Ahorro |
|---|---|---|---|---|
| Gemma 3 | 100 % | 2.132.127 | 922.687 | 56,7 % |
| Qwen 2.5 | 100 % | 1.325.753 | 806.880 | 39,1 % |
| Llama 3.1 | 58,7 % | 1.319.813 | 950.607 | 28,0 % |
| Llama 3.2 | 1,3 % | 2.190.933 | 2.173.353 | 0,8 % |
«Reintentando siempre» es el patrón habitual de reintentos de Pydantic, Instructor o la corrección automática de formato en n8n: cualquier salida que no valga tal cual se vuelve a pedir.
La pregunta del reto: ¿y si un tipo de error domina?
Con la tasa de reparación medida para cada tipo, el ahorro sobre el gasto extra de los reintentos depende de cuál domine:
| Si todos tus fallos fueran… | Ahorro reparando primero |
|---|---|
| Bloques de markdown | 100 % |
| Cierres equivocados | 100 % |
| Texto alrededor del JSON | 94,8 % |
| La mezcla real de este experimento | 59,6 % |
| Esquema copiado | 0 % |
| Negativas sin JSON | 0 % |
Dicho de otra forma: el ahorro es tu porcentaje de fallos de sintaxis. Si tu modelo falla por formato, reparar en local elimina casi todo el gasto extra. Si falla por contenido (copia el esquema, se niega, pone datos donde no van), reparar no sirve y hay que reintentar o, mejor, restringir la generación. Y si una respuesta se corta por límite de tokens, como vimos en la primera parte, faltan datos: también se reintenta.
Generación restringida al esquema
| Modelo | Pedidos correctos, libre y reparando | Pedidos correctos, restringida | Tiempo por llamada, libre | Tiempo por llamada, restringida |
|---|---|---|---|---|
| Gemma 3 | 96,7 % | 96,7 % | 10.087,4 ms | 8.415 ms |
| Qwen 2.5 | 85,3 % | 85,3 % | 7.018,9 ms | 6.100,1 ms |
| Llama 3.1 | 64,7 % | 84,7 % | 6.977,5 ms | 7.011 ms |
| Llama 3.2 | 0 % | 76 % | 6.416,8 ms | 4.456,3 ms |
Las 600 salidas restringidas fueron JSON válido y cumplieron el esquema. No hay fallos de sintaxis que reparar ni reintentos, y en estos modelos no fue más lenta. Lo que no arregla es el contenido: si el modelo se equivoca de ciudad o de cantidad, se equivoca igual dentro de un JSON perfecto.
Qué significa
- Mide tu mezcla de errores antes de decidir. Con un modelo que falla por formato,
json_repair(o un parser como el de BAML) te ahorra prácticamente todos los reintentos. Con uno que falla por contenido, no te ahorra nada. - Si controlas la inferencia o tu proveedor la ofrece, usa la salida restringida al esquema. Quita los errores de sintaxis de raíz y, en los modelos pequeños, mejora mucho el contenido.
- Valida siempre contra el esquema y compara con lo esperado. Un JSON válido no es un pedido correcto: Gemma 3 y Qwen 2.5 quedaron en el 96,7 % y el 85,3 % de pedidos correctos aun con el JSON perfecto.
- Cuidado con los modelos que se niegan. Si tus datos llevan nombres y emails, algunos modelos se niegan a procesarlos; eso no se repara, se cambia de modelo o de instrucciones.
Limitaciones
- Modelos abiertos pequeños en local. Los modelos comerciales grandes fallan mucho menos en formato; la mezcla de errores será distinta, y eso es justo lo que este experimento enseña a medir.
- Un solo tipo de tarea y 30 correos sintéticos en español, con 5 semillas cada uno a temperatura 0,7.
- Coste de los reintentos que no hicieron falta: en Gemma 3 y Qwen 2.5 todo se reparó y no hubo reintentos reales, así que el coste de «reintentar siempre» se estima con la misma fórmula (reenviar el correo, la salida rota y el error, y generar de nuevo). En los 185 reintentos reales de los dos Llama, el coste real quedó entre un 0,941 y un 1,287 de esa estimación (mediana por modelo), así que el ahorro real en Gemma y Qwen podría ser algo mayor.
- Tiempos de un solo portátil con otras aplicaciones abiertas.
- No se midió BAML ni Outlines como librerías; la salida restringida usa la gramática de Ollama, que aplica el mismo principio.
Cómo repetirlo
Código, entorno, las 1200 salidas en bruto y el análisis: yeswelab-laboratorio/structured-outputs-salidas-reales-errores-json-llm en GitHub. Si lo repites con otros modelos, abre un issue con tu mezcla de errores.
Relacionado
- Primera parte: reparar el JSON en local o reintentar (fallos sintéticos)
- Durable execution en agentes: reintentos sin efectos duplicados
- Harness de agentes de IA: OpenClaw, Hermes Agent, Claude Code, Codex y goose
¿Quieres saber cuál es la mezcla de errores de tus agentes y cuánto te cuesta? Encárganos el experimento.