Phase 13 - Lesson 04

Salida Estructurada — JSON Schema, Pydantic, Zod, Decodificación Restringida

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

"Ask the model nicely to return JSON" fails 5 to 15 percent of the time, even on frontier models. Structured outputs close that gap with constrained decoding: the model is literally prevented from emitting a token that would violate the schema. OpenAI's strict mode, Anthropic's schema-typed tool use, Gemini's responseSchema, Pydantic AI's output_type, and Zod's .parse are five surface forms of the same idea. This lesson builds the schema validator and the strict-mode contract learners will use for every production extraction pipeline.

Tipo: Build Idiomas: Python (stdlib, subconjunto de JSON Schema 2020-12) Prerrequisitos: Phase 13 · 02 (inmersión profunda en llamada de funciones) Tiempo: ~75 minutos

Objetivos de Aprendizaje

  • Escribir un JSON Schema 2020-12 para un objetivo de extracción utilizando las restricciones adecuadas (enum, min/max, required, pattern).
  • Explicar por qué el strict mode y la decodificación restringida ofrecen garantías diferentes a las de "validar después de la generación".
  • Distinguir los tres modos de fallo: error de parseo, violación de esquema, rechazo del modelo.
  • Implementar un pipeline de extracción con reparación tipada y manejo de rechazo tipado.

El Problema

Un agente que lee un correo electrónico de orden de compra necesita convertir texto libre en {customer, line_items, total_usd}. Tres enfoques.

Enfoque uno: solicitar JSON por prompt. "Responde en JSON con los campos customer, line_items, total_usd". Funciona del 85 al 95 por ciento de las veces en modelos de frontera. Falla de seis maneras: llave faltante, coma al final, tipos incorrectos, campos alucinados, truncado en el límite de tokens, prosa filtrada como "Aquí está tu JSON:".

Enfoque dos: validar después de la generación. Generar libremente, parsear, validar contra el esquema, reintentar en caso de fallo. Confiable pero costoso: pagas por cada reintento, y los errores de truncado cuestan un turno adicional por ocurrencia.

Enfoque tres: decodificación restringida. El proveedor impone el esquema al momento de la decodificación. Los tokens no válidos se enmascaran de la distribución de muestreo. Se garantiza que la salida se parseará y se validará. El fallo se reduce a un solo modo: rechazo (el modelo decide que la entrada no se ajusta al esquema).

Cada proveedor de frontera en 2026 ofrece alguna forma del enfoque tres.

  • OpenAI. response_format: {type: "json_schema", strict: true} más refusal en la respuesta si el modelo rechaza la solicitud.
  • Anthropic. Imposición de esquema en las entradas de tool_use; stop_reason: "refusal" no existe, pero end_turn sin llamada a herramienta es la señal.
  • Gemini. responseSchema a nivel de solicitud; en 2026, Gemini ofrece restricciones gramaticales a nivel de token para tipos seleccionados.
  • Pydantic AI. output_type=InvoiceModel emite un RunResult estructurado tipado como InvoiceModel.
  • Zod (TypeScript). Parser en tiempo de ejecución que valida la salida del proveedor contra un esquema de Zod; se empareja con beta.chat.completions.parse de OpenAI.

El elemento común: declarar el esquema una vez, imponerlo de extremo a extremo.

El Concepto

JSON Schema 2020-12 — la lengua franca

Cada proveedor acepta JSON Schema 2020-12. Los constructos que más se utilizan:

  • type: uno de object, array, string, number, integer, boolean, null.
  • properties: mapa del nombre del campo al subesquema.
  • required: lista de nombres de campos que deben aparecer.
  • enum: conjunto cerrado de valores permitidos.
  • minimum / maximum (números), minLength / maxLength / pattern (cadenas de texto).
  • items: subesquema aplicado a cada elemento del arreglo.
  • additionalProperties: false prohíbe campos adicionales (el comportamiento predeterminado varía según el modo).

El strict mode de OpenAI añade tres requisitos: cada propiedad debe figurar en required, additionalProperties: false en todas partes y no debe haber ninguna $ref sin resolver. Si se rompen estas reglas, la API devuelve un código 400 al momento de la solicitud.

Pydantic, la integración en Python

Pydantic v2 genera JSON Schema a partir de modelos con formato de dataclass a través de model_json_schema(). Pydantic AI envuelve esto para que escribas:

class Invoice(BaseModel):
    customer: str
    line_items: list[LineItem]
    total_usd: Decimal

y el framework del agente traduce el esquema a strict mode de OpenAI, input_schema de Anthropic o responseSchema de Gemini en el extremo. La salida del modelo regresa como una instancia tipada de Invoice. Los errores de validación lanzan un ValidationError con rutas de error tipadas.

Zod, la integración en TypeScript

Zod (z.object({customer: z.string(), ...})) es el equivalente en TS. El SDK de Node de OpenAI expone zodResponseFormat(Invoice), lo que se traduce en el payload JSON Schema de la API.

Rechazos (Refusals)

El strict mode no puede obligar al modelo a responder. Si la entrada no cabe en el esquema ("el correo electrónico era un poema, no una factura"), el modelo emite un campo refusal que contiene la razón. Tu código debe manejar esto como un resultado de primera clase, no como un fallo. El rechazo también es útil como una señal de seguridad: un modelo al que se le pide extraer un número de tarjeta de crédito de un correo electrónico con contenido protegido devuelve un rechazo con la razón de seguridad adjunta.

Decodificación restringida en el ecosistema open-source

Las implementaciones de pesos abiertos (open-weights) utilizan tres técnicas.

  1. Decodificación basada en gramática (outlines, guidance, lm-format-enforcer): construye un autómata finito determinista a partir del esquema; en cada paso, enmascara los logits de los tokens que violarían la FSM.
  2. Enmascaramiento de logits con un parser JSON: ejecuta un parser JSON en tiempo real en sincronía con el modelo; en cada paso, calcula el conjunto de tokens siguientes válidos.
  3. Decodificación especulativa con un verificador: un modelo de borrador económico propone tokens, y el verificador impone el esquema.

Los proveedores comerciales eligen una de estas técnicas detrás de escena. El estado del arte en 2026 es más rápido que la generación de texto plano para salidas estructuradas cortas y tiene aproximadamente la misma velocidad para salidas largas.

Los tres modos de fallo

  1. Error de parseo. La salida no es un JSON válido. No puede ocurrir bajo strict mode. Aún puede ocurrir en proveedores no estrictos.
  2. Violación de esquema. La salida se parsea pero viola el esquema. No puede ocurrir bajo strict mode. Es común fuera de él.
  3. Rechazo. El modelo declina la respuesta. Debe manejarse como un resultado tipado.

Estrategia de reintentos

Cuando estás fuera del strict mode (uso de herramientas de Anthropic, OpenAI no estricto, Gemini más antiguo), el patrón de recuperación es:

generate -> parse -> validate -> if fail, inject error and retry, max 3x

Un reintento suele ser suficiente. Tres reintentos capturan las inconsistencias de modelos más débiles. Más de tres es señal de un esquema deficiente: el modelo no puede satisfacerlo para algunas entradas, y el prompt o el esquema necesitan corregirse.

Soporte para modelos pequeños

La decodificación restringida funciona en modelos pequeños. Un modelo abierto de 3B parámetros con imposición gramatical supera a un modelo de 70B parámetros con prompting puro en tareas estructuradas. Esta es la razón principal por la que las salidas estructuradas son importantes para producción: desacoplan la confiabilidad del tamaño del modelo.

Úsalo

code/main.py incluye un validador mínimo de JSON Schema 2020-12 en stdlib (types, required, enum, min/max, pattern, items, additionalProperties). Envuelve un esquema de Invoice y ejecuta una salida de LLM simulada a través del validador, demostrando las rutas de error de parseo, violación de esquema y rechazo. Reemplaza la salida simulada con la respuesta real de cualquier proveedor en producción.

Qué observar:

  • El validador devuelve una lista [ValidationError] tipada con ruta y mensaje. Esa es la estructura que deseas presentar en el prompt de reintento.
  • La rama de rechazo NO realiza reintentos. Registra el evento y devuelve un rechazo tipado. La Phase 14 · 09 utiliza los rechazos como una señal de seguridad.
  • La verificación additionalProperties: false se activa con la entrada de prueba adversarial, lo que demuestra por qué el strict mode cierra la puerta a campos alucinados.

Impleméntalo

Esta lección produce outputs/skill-structured-output-designer.md. Dado un objetivo de extracción de texto libre (facturas, tickets de soporte, currículums, etc.), la skill produce un JSON Schema 2020-12 que es compatible con strict-mode y un modelo de Pydantic correspondiente, con el manejo de rechazo y de reintento tipados ya preparados.

Ejercicios

  1. Ejecuta code/main.py. Agrega un cuarto caso de prueba donde total_usd sea un número negativo. Confirma que el validador lo rechaza con la ruta de restricción minimum.

  2. Extiende el validador para soportar oneOf con un discriminador. El caso común: line_item es un producto o un servicio, etiquetado por kind. El strict mode tiene reglas sutiles aquí; consulta la guía de salidas estructuradas de OpenAI.

  3. Escribe el mismo esquema Invoice como un BaseModel de Pydantic y compara la salida de model_json_schema() con tu esquema hecho a mano. Identifica el único campo que Pydantic establece de forma predeterminada y que la versión hecha a mano omite.

  4. Mide las tasas de rechazo. Construye diez entradas que no deberían ser extraíbles (la letra de una canción, una demostración matemática, un correo electrónico en blanco) y ejecútalas a través de un proveedor real con strict mode. Cuenta los rechazos frente a las salidas alucinadas. Esta es tu verdad de referencia (ground truth) para reintentos conscientes del rechazo.

  5. Lee la guía de salidas estructuradas de OpenAI de principio a fin. Identifica el único constructo que prohíbe explícitamente en strict mode que el JSON Schema plano permite. Luego diseña un esquema que utilice el constructo prohibido de manera no esencial y refactorízalo para que sea compatible con strict mode.

Key Terms

Término Lo que la gente dice Lo que realmente significa
JSON Schema 2020-12 "La especificación del esquema" Dialeto de esquema borrador de la IETF que todos los proveedores modernos entienden
Strict mode "Esquema garantizado" Flag de OpenAI que impone el esquema a través de decodificación restringida
Constrained decoding "Enmascaramiento de logit" Imposición en tiempo de decodificación que enmascara los siguientes tokens inválidos
Refusal "El modelo declina" Resultado tipado cuando la entrada no cabe en el esquema
Parse error "JSON no válido" La salida no se parseó como JSON; imposible bajo strict
Schema violation "Forma incorrecta" Parseado pero violó tipos / requeridos / enums / rangos
additionalProperties: false "No se permiten extras" Prohíbe campos desconocidos; requerido en strict de OpenAI
Pydantic BaseModel "Salida tipada" Clase de Python que emite y valida JSON Schema
Zod schema "Tipo de salida TypeScript" Esquema en tiempo de ejecución de TS para la validación de salida del proveedor
Grammar enforcement "Decodificación restringida open-source" Enmascaramiento de logit basado en FSM, como en outlines / guidance

Lecturas Adicionales

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).