Validación y análisis de respuestas estructuradas en Python
Definir un esquema es solo la mitad del trabajo: también necesitas convertir la respuesta que recibes de vuelta en un objeto Python utilizable y saber qué hacer cuando esa respuesta no está completa.
Esta página cubre client.messages.parse(), la utilidad basada en Pydantic que realiza la validación y deserialización por ti, además del camino manual con client.messages.create() para cuando necesites más control.
Resumen
client.messages.parse() es la forma recomendada de consumir una salida estructurada en Python: pásale un esquema (típicamente de un modelo Pydantic) y te devolverá una respuesta cuyo atributo parsed_output ya es una instancia validada de ese modelo.
Esto reemplaza la secuencia manual de extraer texto de la respuesta, llamar a json.loads() y luego construir tu propio objeto a partir del diccionario.
Todavía puedes usar client.messages.create() directamente cuando quieras el contenido crudo de la respuesta, por ejemplo, para inspeccionar stop_reason antes de decidir si la salida es segura de analizar.
Ambos caminos utilizan el mismo esquema output_config.format subyacente: parse() es una capa de conveniencia, no una característica de API diferente.
Receta
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
from pydantic import BaseModel
from anthropic import Anthropic
class Invoice(BaseModel):
vendor: str
total: float
currency: str
client = Anthropic()
response = client.messages.parse(
model="claude-sonnet-5",
max_tokens=1024,
output_config={"format": {"type": "json_schema", "schema": Invoice.model_json_schema()}},
messages=[{"role": "user", "content": "Extract: Acme Corp billed $1,240.50 USD."}],
)
invoice: Invoice = response.parsed_output
print(invoice.vendor, invoice.total, invoice.currency)Cuándo recurrir a esto:
- Ya tienes (o quieres) un modelo Pydantic que representa los datos que estás extrayendo.
- Quieres un objeto tipado de vuelta, no un diccionario al que tengas que acceder por índice.
- Estás construyendo un pipeline donde el valor analizado se pasa directamente a otro código Python tipado.
- Quieres que la validación y el análisis ocurran en un solo paso en lugar de dos.
Ejemplo de trabajo
from pydantic import BaseModel, ValidationError
from anthropic import Anthropic, APIStatusError
class SupportTicket(BaseModel):
subject: str
urgency: str
customer_email: str
client = Anthropic()
def extract_ticket(raw_text: str) -> SupportTicket | None:
try:
response = client.messages.parse(
model="claude-sonnet-5",
max_tokens=1024,
output_config={
"format": {"type": "json_schema", "schema": SupportTicket.model_json_schema()}
},
messages=[{"role": "user", "content": f"Extract ticket fields from:\n\n{raw_text}"}],
)
except APIStatusError as e:
print(f"API error: {e.status_code} {e.message}")
return None
if response.stop_reason == "max_tokens":
print("Response was truncated before completing - not safe to trust.")
return None
if response.stop_reason == "refusal":
print("Claude declined to produce a response for this input.")
return None
return response.parsed_output
ticket = extract_ticket(
"From: jane@example.com\nSubject: Can't log in\n\n"
"I've been locked out of my account since this morning, please help ASAP."
)
if ticket:
print(ticket.subject, ticket.urgency, ticket.customer_email)Lo que esto demuestra:
stop_reasonse comprueba antes de confiar enresponse.parsed_output, porque una respuesta truncada o rechazada no garantiza tener un valor analizado utilizable.APIStatusErrorcaptura fallos a nivel de red/API por separado de problemas a nivel de contenido como truncamiento o rechazo.- La función devuelve
Noneen cualquier ruta de fallo en lugar de permitir que una excepción de una respuesta parcialmente formada se propague inesperadamente. response.parsed_outputsolo se lee una vez que todas las comprobaciones anteriores han pasado.
Inmersión profunda
Cómo funciona
client.messages.parse()envía la misma solicitud queclient.messages.create(), con el esquema construido a partir de tu modelo Pydantic (o diccionario crudo) pasado a través deoutput_config.format.- Una vez que la respuesta regresa, el SDK valida el texto JSON devuelto contra el esquema y construye una instancia del modelo que proporcionaste, exponiéndola como
response.parsed_output. - El objeto
responsesubyacente todavía tiene los mismos campos que unMessagenormal:stop_reason,usage,content.parsed_outputes aditivo, no un reemplazo. - La validación en esta capa confirma que la respuesta coincide con la forma descrita por el esquema; no verifica que los valores sean factualmente correctos.
create() vs. parse()
client.messages.create() | client.messages.parse() | |
|---|---|---|
| Devuelve | Message crudo con contenido de texto | Message más un parsed_output validado |
| Tú todavía haces | json.loads() y construcción manual | Nada adicional: ya es un objeto |
| Uso típico | Necesitas texto crudo o quieres control manual sobre el análisis | Tienes un modelo Pydantic y quieres el objeto finalizado |
Manejo de una respuesta que no se analizó limpiamente
import json
from anthropic import Anthropic
client = Anthropic()
schema = {
"type": "object",
"properties": {"summary": {"type": "string"}},
"required": ["summary"],
"additionalProperties": False,
}
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=64, # deliberadamente pequeño - es probable que se trunque en una respuesta larga
output_config={"format": {"type": "json_schema", "schema": schema}},
messages=[{"role": "user", "content": "Write an extremely detailed multi-paragraph summary."}],
)
raw_text = response.content[0].text
if response.stop_reason == "max_tokens":
print("Truncated - do not attempt json.loads() on this, retry with more max_tokens instead.")
else:
data = json.loads(raw_text)
print(data["summary"])- Usar
create()directamente aquí hace que la comprobación de truncamiento sea explícita y visible, lo cual es útil cuando quieres control total sobre la ruta de fallo en lugar de depender del manejo interno deparse(). - Llamar a
json.loads()en una cadena conocida como truncada generarájson.JSONDecodeError; comprobarstop_reasonprimero evita encontrar esa excepción en el flujo de control normal.
Notas de Python
from pydantic import BaseModel, field_validator
class Invoice(BaseModel):
vendor: str
total: float
@field_validator("total")
@classmethod
def total_must_be_positive(cls, v: float) -> float:
if v < 0:
raise ValueError("total must be non-negative")
return v- Los validadores de Pydantic todavía se ejecutan cuando
client.messages.parse()construyeparsed_output: una respuesta válida según el esquema que falla un validador personalizado genera unapydantic.ValidationError, que es un modo de fallo distinto de una respuesta de API mal formada. - Esto es útil para restricciones que la capa de JSON Schema no impone (como rangos numéricos), permitiendo que Pydantic capture lo que la garantía a nivel de esquema no puede.
Errores comunes
- Leer
response.parsed_outputantes de comprobarstop_reason. En una respuesta truncada, el valor analizado puede faltar o el propio paso de análisis puede haber fallado. Solución: compruebastop_reason != "max_tokens"(y!= "refusal") antes de tocarparsed_output. - Asumir que
parse()ycreate()necesitan esquemas diferentes. Utilizan exactamente el mismo esquemaoutput_config.format; la única diferencia es lo que el SDK hace con la respuesta después. Solución: define el esquema una vez y pásalo a la llamada que utilices. - No capturar
pydantic.ValidationErrorcuando tienes validadores personalizados en el modelo. Una respuesta válida según el esquema aún puede fallar una restricción a nivel de Pydantic, como unfield_validatorpersonalizado. Solución: envuelve la llamadaparse()en untry/exceptque capture tanto errores de API como errores de validación de Pydantic si tu modelo tiene lógica de validación personalizada. - Tratar un análisis exitoso como prueba de que los datos son correctos. La validación confirma la forma y cualquier restricción a nivel de Pydantic, no que los valores extraídos sean factualmente precisos. Solución: añade un paso de revisión o comprobación posterior para cualquier cosa de alto riesgo.
- Olvidar que
create()todavía devuelve texto que debesjson.loads()tú mismo. A veces los desarrolladores esperan quecreate()analice automáticamente como lo haceparse(). Solución: usaparse()cuando quieras el objeto; usacreate()solo cuando quieras específicamente el texto crudo o un control más manual sobre el manejo de la respuesta.
Alternativas
| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
client.messages.parse() | Tienes un modelo Pydantic y quieres un objeto validado en una sola llamada | Necesitas inspeccionar el texto crudo de la respuesta o transmitir la respuesta |
client.messages.create() + json.loads() manual | Quieres control total sobre el flujo de análisis y manejo de errores | Solo quieres el objeto finalizado con el mínimo código |
Extracción de JSON hecha a mano a partir de texto libre (sin output_config.format) | Código heredado que aún no has migrado | Cualquier integración nueva: no ofrece garantía de fiabilidad sobre las salidas estructuradas |
Preguntas frecuentes
¿Cuál es la diferencia real entre create() y parse()?
- Ambos envían la misma forma de solicitud con el mismo esquema
output_config.format. parse()además valida y deserializa la respuesta enresponse.parsed_output;create()te deja llamar ajson.loads()tú mismo.
¿Necesito un modelo Pydantic para usar parse()?
- Un modelo Pydantic es el patrón común porque
model_json_schema()genera el esquema y te da un valor de retorno tipado. - Un diccionario de esquema crudo también funciona con
parse(), pero pierdes la conveniencia del objeto tipado:parsed_outputse convierte en un diccionario plano en ese caso.
¿Debo comprobar stop_reason antes o después de llamar a parse()?
- Compruébalo en el objeto
responsedevuelto antes de depender deparsed_output, de la misma manera que lo harías concreate(). - Una razón de parada
max_tokensorefusalsignifica que el contenido puede no estar completo o ser utilizable, independientemente del método que hayas llamado.
¿Qué tipos de excepción debo capturar alrededor de una llamada a parse()?
- Los fallos a nivel de API (red, autenticación, límites de tasa) se presentan como
anthropic.APIStatusErrory sus subclases. - Los problemas a nivel de contenido, como un validador Pydantic personalizado que falla, se presentan como
pydantic.ValidationError; captura ambos si tu modelo tiene validación personalizada.
¿Está response.parsed_output garantizado que no sea None?
- Solo cuando la respuesta se completó con éxito y pasó la validación; siempre comprueba
stop_reasony maneja las excepciones antes de asumir que está poblado.
¿Todavía puedo acceder al texto crudo junto con parsed_output?
- Sí, el objeto
responsedevuelto porparse()todavía lleva el campocontentnormal con los bloques de texto crudo, además deparsed_output.
¿parse() reintenta automáticamente en caso de truncamiento?
- No,
parse()no reintenta automáticamente con unmax_tokensmayor. Necesitas detectar el truncamiento a través destop_reasone implementar tu propia lógica de reintento.
¿Pueden los validadores Pydantic rechazar una respuesta que es válida según el esquema?
- Sí, un
field_validatoromodel_validatoren tu modelo Pydantic se ejecuta después de que la comprobación del JSON Schema pasa, por lo que aún puede rechazar valores que el esquema mismo permitió.
¿Es más lento usar parse() en lugar de create()?
- La llamada a la API es idéntica;
parse()solo añade un paso de validación/deserialización del lado del cliente después de que la respuesta llega, lo cual es insignificante en comparación con la latencia de la red.
¿Cómo se ve parsed_output si paso un esquema de diccionario crudo en lugar de un modelo Pydantic?
- Es un
dictde Python plano que coincide con la forma del esquema, en lugar de una instancia de modelo tipada. - Usa un modelo Pydantic cuando quieras acceso a atributos y tipado estático en lugar de indexación de diccionarios.
¿Puedo reutilizar el mismo modelo Pydantic tanto para parse() como para json.loads() manual?
data = json.loads(raw_text)
invoice = Invoice.model_validate(data)- Sí,
Model.model_validate(data)realiza el mismo paso de validación queparse()hace internamente, útil si estás en la ruta manual decreate()pero aún quieres un objeto tipado.
Relacionado
- Conceptos básicos de salidas estructuradas - la primera introducción a client.messages.parse.
- Definición de un esquema JSON para output_config.format - cómo se construye el esquema que esta página analiza.
- Manejo de salidas JSON mal formadas o truncadas - una mirada más profunda a las comprobaciones de stop_reason utilizadas en toda esta página.
- Referencia de tipos de campo y restricciones de salida estructurada - qué construcciones de esquema son seguras de confiar antes de llegar al análisis.
Versiones de la pila: Escrito contra la línea de modelos 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
anthropicde Python (última versión 0.x). Los nombres de modelos, versiones de SDK y precios cambian rápidamente; verifica los detalles actuales en platform.claude.com/docs antes de confiar en ellos.