Por qué las salidas estructuradas superan al formato JSON basado en prompts
Todo desarrollador que ha integrado un LLM en una pipeline ha escrito un prompt como "responde solo con JSON válido, sin ningún otro texto".
Busca en todas las páginas de la documentación
Todo desarrollador que ha integrado un LLM en una pipeline ha escrito un prompt como "responde solo con JSON válido, sin ningún otro texto".
Esa instrucción usualmente funciona.
"Usualmente" es el problema.
Las salidas estructuradas resuelven esto moviendo la garantía de JSON del prompt a la propia solicitud de la API, utilizando un parámetro llamado output_config.format. En lugar de pedirle amablemente a Claude que formatee su respuesta de cierta manera, tú declaras la forma exacta que requieres, y la API restringe la generación para que la respuesta se ajuste a ella. Esta página explica por qué esa distinción importa, qué cambia en la forma en que construyes con la API de Claude una vez que confías en ella, y dónde terminan sus garantías.
output_config.format restringe la respuesta de Claude en el momento de la generación para que coincida con un JSON Schema que tú proporcionas, en lugar de depender de la redacción del prompt para solicitar JSON.max_tokens antes de que el JSON se cierre.Antes de que las salidas estructuradas existieran como una característica a nivel de solicitud, el patrón estándar para obtener JSON de un LLM se basaba completamente en el prompt.
Escribías un prompt de sistema instruyendo al modelo a "solo generar JSON válido que coincida con esta forma", quizás incluías un ejemplo, y luego analizabas cualquier texto que regresara con json.loads().
Esto funciona la mayor parte del tiempo, que es exactamente por qué es arriesgado.
El modelo está generando texto token por token, y "responder solo con JSON" es una preferencia que usualmente respeta, no una regla que no puede romper.
Bajo ciertas entradas, especialmente conversaciones más largas, instrucciones ambiguas o datos de casos extremos, el modelo podría agregar una explicación de una línea antes del JSON, envolver el objeto en una cerca de código markdown, usar un nombre de campo que es similar pero no exactamente lo que pediste, o dejar un valor completamente fuera.
Cada una de esas salidas todavía "parece" que el modelo intentó cooperar, pero cada una de ellas rompe una llamada ingenua a json.loads().
Las salidas estructuradas invierten esta relación.
En lugar de describir la forma deseada en prosa, la describes con un JSON Schema, una especificación legible por máquina de los campos, tipos y propiedades requeridas exactas que debe tener tu respuesta.
Pasas ese esquema en el campo output_config.format de tu solicitud, y la API restringe el proceso de generación en sí para que el contenido devuelto se ajuste al esquema.
El prompt ya no realiza el trabajo de aplicación.
Un modelo mental simple: el formato basado en prompts es como pedirle a un colega que complete un formulario correctamente describiéndole el formulario con palabras. Las salidas estructuradas son como entregarle el formulario real, con campos que solo aceptan el tipo correcto de entrada. Uno se basa en la interpretación; el otro restringe el espacio de posibles respuestas antes de que ocurra la generación.
from anthropic import Anthropic
client = Anthropic()
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"is_urgent": {"type": "boolean"},
},
"required": ["name", "is_urgent"],
"additionalProperties": False,
}
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
output_config={"format": {"type": "json_schema", "schema": schema}},
messages=[{"role": "user", "content": "Extract the ticket subject and urgency."}],
)Observa lo que falta en el prompt: no hay "responde solo con JSON", no hay instrucciones de formato, no hay salida de ejemplo. El esquema hace ese trabajo.
La diferencia práctica entre los dos enfoques se manifiesta más claramente en cómo se comportan los fallos.
Con JSON basado en prompts, un fallo es un fallo de análisis completo. Tu código llama a json.loads() en una cadena que podría contener prosa inicial, comentarios finales o una cerca de markdown, y si algo de eso está presente, el análisis falla y no tienes nada utilizable. Entonces te encuentras escribiendo lógica defensiva de eliminación de cadenas: expresiones regulares para encontrar el primer { y el último }, eliminando cercas ```json, capturando y reintentando en JSONDecodeError. Ese código defensivo es un impuesto que pagas en cada integración, para siempre, porque la garantía subyacente nunca mejoró; simplemente mejoraste en la limpieza después de ella.
Con output_config.format, la restricción se aplica durante la generación, no después. La API está aplicando el esquema token por token a medida que se produce la respuesta, por lo que una respuesta exitosa es válida según el esquema por construcción, no por buena suerte. Esto elimina una categoría completa de errores de análisis de tu código, porque ya no existe un resultado de "el modelo casi lo hizo bien" que manejar; la respuesta satisface el esquema, o sucedió algo más (truncamiento, una negativa o un error genuino de la API) y lo manejas como su propia condición distinta y detectable en lugar de como texto ruidoso.
Aquí es también donde el asistente de nivel superior del SDK de Python importa. client.messages.create(...) aún devuelve contenido de respuesta sin procesar que extraerías y deserializarías tú mismo. client.messages.parse(...) va un paso más allá: devuelve un objeto ya validado y analizado, comúnmente respaldado por un modelo Pydantic que defines junto con el esquema. La distinción es la misma que la de output_config.format en sí: mover el trabajo de "escribir código para verificar esto" a "dejar que el framework garantice esto".
from pydantic import BaseModel
from anthropic import Anthropic
class Ticket(BaseModel):
name: str
is_urgent: bool
client = Anthropic()
response = client.messages.parse(
model="claude-sonnet-5",
max_tokens=1024,
output_config={"format": {"type": "json_schema", "schema": Ticket.model_json_schema()}},
messages=[{"role": "user", "content": "Ticket: server down, please help fast!"}],
)
ticket = response.parsed_output # una instancia de Ticket validada, no una cadena sin procesarHay una sutileza que vale la pena internalizar aquí: la aplicación del esquema ocurre en la capa de la API, pero no es magia que borre todos los modos de fallo. Dos condiciones aún requieren tu propio manejo. Primero, la respuesta puede ser truncada si alcanza max_tokens antes de que el objeto JSON se cierre; el texto parcial no será JSON válido, aunque la API estuviera intentando seguir fielmente tu esquema. Segundo, no todos los constructos que podrías querer expresar en JSON Schema son compatibles con el motor de aplicación de la API; hay límites en los tipos de campo, el manejo de enumeraciones y ciertas restricciones de esquema, lo que significa que el diseño del esquema en sí se convierte en una habilidad con su propio material de referencia (ver Relacionados a continuación) en lugar de "simplemente escribe cualquier JSON Schema que quieras".
Una vez que confías en las salidas estructuradas, la forma de tu código de integración cambia de una manera que se acumula en una base de código, no solo en una única solicitud.
El formato JSON basado en prompts empuja la lógica de validación hacia abajo y la duplica: cada sitio de llamada que espera JSON de vuelta necesita su propio análisis, su propio manejo de errores, su propia verificación de "el modelo realmente siguió las instrucciones esta vez". Las salidas estructuradas centralizan esa garantía en la definición del esquema. Cambia el esquema una vez, y cada respuesta que lo usa se valida de la misma manera, por la misma capa de aplicación, con los mismos modos de fallo.
Esto es más importante precisamente en los lugares donde el JSON basado en prompts es más arriesgado: pipelines de producción con volumen, donde una baja tasa de fallos de análisis de un solo dígito es invisible en las pruebas pero costosa a escala; y sistemas posteriores (bases de datos, otros servicios, componentes de UI) que se bloquearán o corromperán el estado con una entrada mal formada en lugar de simplemente mostrar una cadena fea a un humano.
Vale la pena ser preciso sobre lo que significa "garantía" aquí. output_config.format garantiza que la respuesta, si se genera con éxito, se ajusta a tu esquema. No garantiza que la respuesta sea correcta en el sentido de ser fácticamente correcta o completa; el modelo aún puede poner un valor plausible pero incorrecto en un campo correctamente tipado. Las salidas estructuradas resuelven un problema de fiabilidad sintáctica, no un problema de precisión semántica. Todavía necesitas tu propia validación, revisión humana o verificaciones posteriores para la corrección; simplemente ya no necesitas defenderte de una sintaxis mal formada además de eso.
| Enfoque | Fortaleza | Debilidad | Mejor ajuste |
|---|---|---|---|
| Solicitud JSON basada en prompt | No hay superficie de API que aprender, funciona con cualquier modelo | Probabilístico; la salida mal formada requiere análisis defensivo y reintentos | Prototipos rápidos, uso manual de bajo volumen |
output_config.format (sin procesar) | Garantiza JSON válido según el esquema a nivel de API | Aún analizas el texto devuelto tú mismo | Cuando quieres la garantía pero no un objeto tipado |
client.messages.parse() con Pydantic | Esquema garantizado y un objeto tipado validado en una sola llamada | Requiere definir modelos Pydantic; capa de conveniencia específica del SDK de Python | La mayoría de las integraciones de Python en producción que extraen datos estructurados |
max_tokens antes de que el modelo termine de escribir el JSON, lo que se devuelve es una cadena incompleta, aunque el modelo estuviera cooperando con el esquema todo el tiempo que estuvo generando.output_config) en las mismas llamadas messages.create() / messages.parse() que ya utilizas.client.messages.create() todavía devuelve el contenido de la respuesta como texto; la garantía es que el texto es JSON válido según el esquema, no que ya esté deserializado.client.messages.parse() en su lugar si quieres un objeto ya analizado y validado.create() devuelve la respuesta sin procesar; tú extraes el contenido del texto y lo analizas tú mismo.parse() valida la respuesta contra tu esquema (a menudo un modelo Pydantic) y devuelve un objeto analizado listo para usar.max_tokens antes de que el objeto JSON se cierre, lo que trunca la salida a mitad de la estructura.output_config.format restringe la respuesta completa del mensaje en sí, ya sea que haya o no una herramienta involucrada, y los dos se pueden combinar para que tanto la respuesta como los parámetros de cualquier llamada a herramienta estén garantizados como válidos.client.messages.create().client.messages.parse() porque Model.model_json_schema() genera el esquema por ti y obtienes una instancia validada.output_config.format es lo que aplica la forma.Versiones de la pila: Escrito para la línea de modelos de Claude actual a partir de junio de 2026: Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (el predeterminado) y Claude Haiku 4.5, y el SDK oficial de Python
anthropic(última versión 0.x). Los nombres de los modelos, las versiones del SDK y los precios cambian rápidamente; verifica las especificaciones actuales en platform.claude.com/docs antes de confiar en ellas.
Revisado por Chris St. John·Última actualización: 15 jul 2026