Esquemas JSON para salidas estructuradas de LLM
Outputs estructurados con JSON Schema: cómo OpenAI, Gemini y Claude aplican un esquema válido, con pasos de uso y errores comunes.
Las salidas estructuradas restringen la respuesta de un LLM a un esquema JSON (JSON Schema) que tú proporcionas, de modo que el modelo devuelve JSON legible por máquina que coincide con tus campos, tipos y enums, en lugar de prosa libre que tendrías que parsear a mano.
Cualquiera que haya puesto en producción una funcionalidad basada en LLM conoce la alternativa: una expresión regular para eliminar bloques de markdown sueltos, un try/catch alrededor de JSON.parse y un bucle de reintentos que se dispara más a menudo de lo que te gustaría. Funciona bien hasta la mañana en que, sin avisar, deja de funcionar.
El proveedor aplica el esquema durante la generación, lo que convierte el «ojalá el modelo devuelva JSON válido» en un contrato. Esto ya funciona hoy en OpenAI, Google Gemini y Anthropic Claude. Los tres aceptan JSON Schema, por lo que el mismo esquema es portable y solo cambias el cableado de la petición, no el contrato. Este artículo cubre qué son las salidas estructuradas, cómo las gobierna JSON Schema, cómo funciona la aplicación del esquema internamente, cómo configurarlo en cada proveedor y los modos de fallo contra los que conviene protegerse.
Puntos clave
- El modo JSON solo garantiza JSON sintácticamente válido; las salidas estructuradas estrictas garantizan JSON que coincide con tu esquema concreto: la diferencia entre «se parsea» y «tiene los campos que necesitas».
- La aplicación del esquema ocurre mediante decodificación restringida (constrained decoding): en cada token, el modelo solo puede emitir continuaciones que mantengan la salida válida frente a tu esquema, de modo que la conformidad se impone durante la generación, no se comprueba después.
- OpenAI, Gemini y Claude hablan todos JSON Schema, por lo que un mismo esquema se puede portar entre proveedores; Pydantic y Zod compilan a JSON Schema, que es el flujo de trabajo que realmente usan la mayoría de los equipos.
- Incluso con el modo estricto activado, una negativa (refusal) o una respuesta truncada por longitud se devuelve con estado de éxito, pero no es JSON válido según el esquema, así que valida el objeto parseado antes de confiar en él.
- Cada proveedor soporta solo un subconjunto de JSON Schema, por lo que palabras clave como
minimum,patterno la recursión profunda pueden descartarse o rechazarse. Verifícalo en la documentación del subconjunto soportado de cada proveedor.
De texto libre a JSON con esquema garantizado
Si se les deja a su aire, los LLM emiten texto libre que rompe los parsers: añaden prosa alrededor del JSON, omiten comillas o inventan campos. Las salidas estructuradas resuelven esto restringiendo la respuesta a un JSON Schema, de modo que la salida sea legible por máquina y parseable de forma fiable.
Esto supone una mejora sobre el antiguo modo JSON. OpenAI introdujo el modo JSON en 2023 como una forma de forzar JSON válido, pero solo promete que la salida se podrá parsear, no que seguirá algún esquema que tú definas. Las salidas estructuradas estrictas cierran esa brecha al aplicar el esquema en sí. En la propia evaluación de OpenAI sobre el seguimiento de esquemas complejos, el modelo gpt-4o-2024-08-06 con Structured Outputs obtuvo un 100 %, frente a menos del 40 % del antiguo gpt-4-0613. Se trata de un benchmark específico de esos modelos, no de una garantía universal, pero ilustra el cambio de «normalmente se parsea» a «coincide con el esquema».
¿Qué papel juega JSON Schema?
Discover how at OpenReplay.com.
Un JSON Schema es un contrato declarativo para tus datos: declara tipos, campos obligatorios, enums y restricciones de valores. Lo envías junto con la petición y el proveedor restringe la generación para que coincida con él. Un esquema compacto para la extracción de un contacto tiene este aspecto:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" },
"plan_interest": {
"type": "string",
"enum": ["starter", "pro", "enterprise"]
}
},
"required": ["name", "email", "plan_interest"],
"additionalProperties": false
}
Poca gente los escribe a mano en producción. El flujo de trabajo habitual consiste en definir la estructura en Pydantic (Python) o Zod (TypeScript) y dejar que el SDK genere el JSON Schema. Los SDK de OpenAI lo soportan directamente: les pasas un objeto Pydantic o Zod y generan el esquema JSON correspondiente, convierten la respuesta de vuelta en tu objeto tipado y te exponen las negativas del modelo. Gemini añadió la misma comodidad, extendiendo el soporte de JSON Schema a todos los modelos Gemini con soporte activo, de modo que los esquemas de Pydantic y Zod funcionan sin un paso de conversión.
Si prefieres no montar a mano el esquema ni el envoltorio específico de cada proveedor, el constructor de JSON Schema de OpenReplay hace ambas cosas en el navegador. Añades campos con tipos, descripciones, enums y anidamiento, o pegas un JSON de ejemplo para inferir un punto de partida, y luego copias el resultado desde el panel de exportación, que ofrece JSON Schema, response_format de OpenAI, herramienta de función de OpenAI, Anthropic, Gemini, Zod y Pydantic. Nada de lo que escribas sale de la página.
¿Cómo funciona la aplicación del esquema?
Las salidas estructuradas funcionan porque el proveedor restringe la decodificación: en cada paso de generación, el modelo solo puede producir tokens que mantengan la salida válida frente a tu esquema. El esquema se compila en una gramática (o en una máquina de estados finitos), y los tokens que la violarían se enmascaran antes del muestreo. La propia documentación de Anthropic describe el mismo mecanismo: tu esquema se compila en una gramática y el muestreo restringido mantiene la generación dentro de ella, de modo que el modelo no tiene forma de emitir un token que rompa el esquema.
La misma idea se generaliza más allá de JSON. Para modelos locales y autoalojados, llama.cpp usa ficheros de gramática GBNF y Outlines aplica restricciones basadas en expresiones regulares y gramáticas; ambos imponen formatos arbitrarios (SQL, DSL personalizados o JSON) mediante el mismo principio de enmascarado de tokens.
Salidas estructuradas en los distintos proveedores
Los tres grandes proveedores hablan JSON Schema, así que el patrón se puede portar. Lo que cambia es el cableado de la petición y las señales de fallo.
| Proveedor | Dónde va el esquema | Dialecto del esquema | Señal de negativa / respuesta incompleta |
|---|---|---|---|
| OpenAI | response_format (Chat Completions) o text.format (Responses API), con strict: true | Subconjunto de JSON Schema | campo refusal; finish_reason: "length" |
| Gemini | responseFormat.text (mimeType + schema) dentro de generationConfig | Subconjunto de JSON Schema (incl. anyOf, $ref) | candidato truncado; rechazo de esquemas demasiado complejos |
| Claude | output_config.format, o strict: true en el input_schema de una herramienta | Subconjunto de JSON Schema | stop_reason: "refusal" / "max_tokens" |
OpenAI. Configura strict: true y pasa el esquema. La guía de OpenAI recomienda empezar los proyectos nuevos con sus modelos actuales y señala que la Responses API cambió el parámetro de sitio: usa response_format: { type: "json_schema", strict: true } en Chat Completions, o text: { format: { type: "json_schema", strict: true } } en la Responses API.
Gemini. Proporciona el esquema a través de generationConfig:
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="Extract the contact from this email...",
config={
"response_format": {
"text": {
"mime_type": "application/json",
"schema": person_schema,
}
},
},
)
Claude. Anthropic ofrece salidas estructuradas nativas, ya no un apaño con llamadas a herramientas. Hay dos funcionalidades complementarias: salidas JSON mediante output_config.format para el cuerpo de la respuesta, y uso estricto de herramientas mediante strict: true para las entradas de las herramientas, utilizables de forma independiente o conjunta. El uso estricto de herramientas garantiza que los argumentos de una llamada coincidan con su input_schema, porque ese esquema se compila en una gramática que restringe el muestreo, la misma familia de técnicas que usan OpenAI y Gemini. Ten en cuenta que la superficie de la API cambió con la disponibilidad general: el parámetro output_format pasó a output_config.format, y ya no se requieren cabeceras beta.
Trampas y buenas prácticas
El modo estricto no es una garantía de salida parseable, y el soporte de esquemas no es universal. Protégete frente a estos casos.
Mantén los esquemas planos. Las estructuras profundamente anidadas o recursivas son la causa más común de errores de tipo «esquema demasiado complejo» y de degradación del razonamiento. Claude lo expone directamente, devolviendo un 400 cuando la gramática compilada crece demasiado, y la documentación de Gemini advierte de que los esquemas muy grandes o profundamente anidados pueden ser rechazados. Los nombres de propiedad largos, los arrays grandes, los enums con muchos valores y los objetos llenos de propiedades opcionales contribuyen todos al coste. Divide las extracciones grandes en esquemas más pequeños y planos.
Gestiona las negativas y el truncamiento. Incluso con el modo estricto activado, una negativa o una respuesta truncada por longitud devuelve un estado de éxito, pero no es JSON válido según el esquema. OpenAI añadió una señal dedicada para esto: un campo refusal en la respuesta te indica que el modelo se ha negado, en lugar de devolver algo que coincida con tu esquema. No existe un límite universal de tokens de salida. Los topes varían según el modelo, y cualquier respuesta que alcance el tope a mitad de un objeto produce JSON inválido, así que dimensiona tu max_tokens para el peor caso y consulta el límite documentado de tu modelo.
Verifica el subconjunto soportado. Cada proveedor soporta solo una porción de JSON Schema en modo estricto. La guía de OpenAI es explícita: se cubre buena parte de la especificación, pero algunas partes quedan fuera, ya sea por rendimiento o por razones técnicas. Palabras clave como minimum, pattern o los valores por defecto pueden descartarse o rechazarse, así que consulta la documentación del subconjunto soportado del proveedor en lugar de dar por hecho el soporte completo de la especificación.
Valida de todos modos. Como las negativas y el truncamiento producen respuestas con estado válido pero JSON inválido, parsea y valida el objeto contra tu esquema antes de confiar en él, incluso con strict: true.
Razona primero, emite después. Restringir la salida puede reducir la calidad del razonamiento en algunas tareas. Una guía práctica sobre las salidas estructuradas de Claude lo plantea como una compensación real frente al razonamiento extendido: si una tarea se beneficia más del razonamiento del modelo que de la conformidad garantizada con el esquema, deja el razonamiento sin restringir. Un término medio práctico es dejar que el modelo razone en una fase de pensamiento y restringir solo el JSON final.
Las salidas estructuradas convierten las respuestas de un LLM en algo que puedes tratar como una API tipada, y el patrón se traslada limpiamente entre OpenAI, Gemini y Claude porque todos aceptan JSON Schema. Empieza definiendo tu estructura en Pydantic o Zod, activa el modo estricto en tu proveedor, mantén el esquema plano y envuelve el parseo en una validación que gestione las negativas y el truncamiento; después, conecta el mismo esquema al proveedor con el que despliegues.
Preguntas frecuentes
¿Cuál es la diferencia entre el modo JSON y las salidas estructuradas estrictas?
El modo JSON solo garantiza que el modelo devuelva JSON sintácticamente válido que se parsee sin errores, pero no garantiza que la salida coincida con ningún esquema concreto. Las salidas estructuradas estrictas aplican tu JSON Schema específico durante la generación, de modo que el objeto devuelto tiene los campos, tipos y enums que definiste. La distinción es «se parsea» frente a «tiene los campos que necesitas». OpenAI introdujo el modo JSON en 2023 y más tarde añadió salidas estructuradas con esquema aplicado para cerrar esa brecha.
¿Activar el modo estricto garantiza que siempre obtendré JSON válido y parseable?
No. El modo estricto restringe la generación de tokens según tu esquema durante una compleción normal, pero una negativa por motivos de seguridad o una respuesta truncada por longitud sigue devolviendo un estado de éxito mientras produce una salida que no es JSON válido según el esquema. OpenAI expone un campo refusal dedicado y un finish_reason con valor length; Claude lo señaliza con un stop_reason de refusal o max_tokens. Como estas respuestas devuelven un 200 y se facturan, deberías parsear y validar el objeto contra tu esquema antes de confiar en él.
¿Puedo reutilizar el mismo JSON Schema en OpenAI, Gemini y Claude?
En gran medida, sí. OpenAI, Google Gemini y Anthropic Claude aceptan todos JSON Schema, por lo que el mismo esquema es portable y solo cambias el cableado de la petición, no el contrato. Lo que difiere es dónde va el esquema: OpenAI usa response_format o text.format con strict en true, Gemini anida el esquema bajo responseFormat.text dentro de generationConfig, y Claude usa output_config.format o el uso estricto de herramientas. Cada proveedor soporta solo un subconjunto de JSON Schema, así que verifica las palabras clave no soportadas en la documentación del subconjunto soportado de cada proveedor antes de dar por hecha una portabilidad total.
¿Por qué se rechaza mi esquema por ser demasiado complejo y cómo lo soluciono?
Los límites de complejidad provienen de estructuras profundamente anidadas o recursivas, nombres de propiedad largos, límites de arrays grandes, enums con muchos valores u objetos con muchas propiedades opcionales. Claude devuelve un 400 cuando la gramática compilada crece demasiado, y Gemini puede rechazar esquemas muy grandes o profundamente anidados. La solución es mantener los esquemas planos y dividir las extracciones grandes en esquemas más pequeños y planos. Las estructuras profundamente anidadas son también una causa habitual de degradación del razonamiento, así que aplanarlas mejora tanto la aceptación como la calidad de la salida.