Manejo de JSON malformado para uso de herramientas y fallos de validación de esquemas
El uso de herramientas es el contrato entre Claude y tu código: el modelo produce argumentos estructurados, tu código ejecuta una función contra ellos. La mayoría de las veces ese contrato se cumple. Ocasionalmente no es así, argumentos que no coinciden con el esquema declarado, falta un campo requerido, una discrepancia de tipo, y si tu código asume que el contrato siempre se cumple, esa única llamada a herramienta incorrecta puede bloquear un bucle de agente o corromper su estado varias vueltas después.
Resumen
El uso de herramientas de Claude devuelve bloques de contenido tool_use donde input ya ha sido analizado en un objeto Python por el SDK, coincidiendo con la forma JSON que produjo el modelo.
El fallo no suele ser sintaxis JSON inválida, el análisis del SDK se encarga de eso, es una discrepancia entre lo que produjo el modelo y lo que realmente requiere el esquema de tu herramienta: falta un campo requerido, un tipo incorrecto, un valor de enumeración que no está entre las opciones permitidas.
Si no se maneja, este tipo de discrepancia provoca un error profundo dentro de tu código de ejecución de herramientas con un rastreo de pila confuso, o peor aún, no provoca ningún error y tu herramienta recibe silenciosamente un valor para el que no fue construida.
La solución es una capa de validación entre "el modelo produjo la entrada de la herramienta" y "tu código de herramienta se ejecuta", una que captura la discrepancia, la registra con suficiente detalle para diagnosticarla, y repara la entrada o falla la llamada a la herramienta limpiamente con un mensaje de error estructurado que el modelo pueda ver y al que pueda reaccionar.
Este artículo construye esa capa utilizando pydantic para la validación de esquemas, ya que se compone bien con el estilo JSON Schema que ya requieren las definiciones de herramientas.
Receta
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
from pydantic import BaseModel, ValidationError
class SearchArgs(BaseModel):
query: str
max_results: int = 10
def validate_tool_input(raw_input: dict) -> SearchArgs | None:
try:
return SearchArgs.model_validate(raw_input)
except ValidationError as e:
print("invalid tool input:", e)
return NoneCuándo usar esto:
- Cualquier herramienta cuyos argumentos provengan directamente de un bloque
tool_usede Claude, antes de pasarlos a tu función real. - Bucles de agente multivuelta, donde una llamada a herramienta incorrecta hoy puede corromper silenciosamente el estado utilizado varias vueltas después.
- Herramientas con esquemas no triviales: enumeraciones, objetos anidados, campos opcionales con valores predeterminados, cualquier cosa más allá de un único argumento de cadena.
- Cualquier sistema donde un fallo de herramienta deba ser reportado de vuelta al modelo de una manera en que pueda actuar, en lugar de bloquear todo el turno.
Ejemplo de trabajo
import json
import logging
from typing import Literal
import anthropic
from pydantic import BaseModel, ValidationError, Field
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("tool_use_validation")
client = anthropic.Anthropic()
class CreateTicketArgs(BaseModel):
title: str
priority: Literal["low", "medium", "high"]
assignee: str | None = Field(default=None)
TOOLS = [
{
"name": "create_ticket",
"description": "Create a support ticket.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "medium", "high"]},
"assignee": {"type": "string"},
},
"required": ["title", "priority"],
},
}
]
def create_ticket(args: CreateTicketArgs) -> str:
return f"Created ticket '{args.title}' (priority={args.priority})"
def handle_tool_use(block: anthropic.types.ToolUseBlock) -> dict:
"""Validate a tool_use block's input before executing the tool.
Returns a tool_result content block, success or structured error,
ready to send back to the model."""
logger.info("tool_use.raw", extra={"tool": block.name, "raw_input": json.dumps(block.input)})
if block.name != "create_ticket":
return {
"type": "tool_result",
"tool_use_id": block.id,
"content": f"Unknown tool: {block.name}",
"is_error": True,
}
try:
args = CreateTicketArgs.model_validate(block.input)
except ValidationError as e:
logger.warning("tool_use.invalid", extra={"tool": block.name, "errors": e.errors()})
error_summary = "; ".join(
f"{err['loc']}: {err['msg']}" for err in e.errors()
)
return {
"type": "tool_result",
"tool_use_id": block.id,
"content": f"Invalid arguments for create_ticket: {error_summary}",
"is_error": True,
}
result = create_ticket(args)
logger.info("tool_use.success", extra={"tool": block.name})
return {
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
}
if __name__ == "__main__":
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=512,
tools=TOOLS,
messages=[{"role": "user", "content": "Create a high priority ticket titled 'Payments down'."}],
)
for block in response.content:
if block.type == "tool_use":
result = handle_tool_use(block)
print(result)Lo que esto demuestra:
- Registro de la entrada
tool_usesin procesar antes de la validación, de modo que una llamada rechazada aún deje evidencia para la depuración. - Uso de
pydanticpara validarblock.inputcontra la misma forma declarada eninput_schema, capturando campos faltantes, tipos incorrectos y valores de enumeración inválidos en un solo lugar. - Devolución de un
tool_resultestructurado conis_error: Trueen caso de fallo, para que el modelo vea exactamente qué salió mal y pueda reintentar con argumentos corregidos en el mismo turno. - Mantener la ejecución de la herramienta (
create_ticket) completamente separada de la validación, para que la función en sí misma nunca tenga que defenderse contra entradas malformadas.
Inmersión profunda
Cómo funciona
- El SDK de
anthropicanaliza el JSON que el modelo produce para los argumentos de la herramienta en undictde Python automáticamente, por lo que rara vez verá unJSONDecodeErrorsin procesar de un bloquetool-useen sí. - Lo que sí verá es una discrepancia de esquema: el diccionario analizado no satisface las restricciones que su herramienta realmente necesita, falta una clave requerida, una cadena donde esperaba un entero, un valor de enumeración fuera del conjunto permitido.
- Devolver los errores de validación al modelo como un
tool_resultconis_error: Truecierra el bucle, el modelo puede ver exactamente qué salió mal y a menudo se autocorrige en su próximo turno, sin ninguna lógica de reintento a nivel de código. - Ignorar silenciosamente un fallo de validación (capturando la excepción y sin hacer nada) es la opción más peligrosa, porque la conversación del agente continúa como si la llamada a la herramienta hubiera tenido éxito, y el fallo real aparece más tarde como un error que no está relacionado.
Formas de fallo que realmente verá
| Fallo | Ejemplo | Detección |
|---|---|---|
| Campo requerido faltante | El modelo omite priority | pydantic lanza ValidationError con un tipo de error missing |
| Tipo incorrecto | El modelo envía "priority": 1 en lugar de una cadena | ValidationError con un type_error |
| Valor de enumeración inválido | El modelo envía "priority": "urgent" (no está en el conjunto permitido) | ValidationError con un literal_error |
| Campos adicionales/inesperados | El modelo envía un campo no declarado | Ignorado por defecto en pydantic; use model_config = {"extra": "forbid"} para rechazar en su lugar |
| Discrepancia de estructura anidada | Se esperaba una lista, se envió un solo objeto | ValidationError apuntando a la ruta loc anidada |
Reparar vs. Rechazar
- Reparar cuando la discrepancia es pequeña y no ambigua, por ejemplo, el modelo envió
"priority": "High"en lugar de"high", una reparación de normalización de mayúsculas/minúsculas es segura y ahorra un viaje de ida y vuelta. - Rechazar cuando la discrepancia cambia el significado, por ejemplo, falta un campo requerido, no hay un valor predeterminado seguro para inventar y adivinar corre el riesgo de ejecutar la herramienta con datos incorrectos.
- Nunca repare omitiendo silenciosamente un fallo de validación y procediendo con datos parciales o predeterminados para algo que cambia el efecto real de la herramienta (un cambio de asignatario, un monto de pago). Rechace esos explícitamente en su lugar.
Notas de Python
# Un pase de reparación para el caso seguro y no ambiguo: coincidencia de enumeración insensible a mayúsculas/minúsculas.
# Intente reparaciones solo donde la corrección sea inequívoca; cualquier otra cosa debe ser rechazada.
def repair_enum_case(raw_input: dict, field: str, allowed: list[str]) -> dict:
value = raw_input.get(field)
if isinstance(value, str):
lowered = value.lower()
if lowered in allowed:
raw_input[field] = lowered
return raw_inputTrampas
-
Asumir que
block.inputsiempre coincide con tu esquema. Nada garantiza estructuralmente que la llamada a la herramienta del modelo satisfaga tuinput_schema, es una fuerte convención a nivel de prompt, no un contrato aplicado. Solución: siempre valide antes de ejecutar, incluso para herramientas que "siempre" reciben argumentos simples en la práctica. -
Capturar el error de validación y no hacer nada. Un error omitido silenciosamente parece un turno exitoso para el resto del bucle del agente, y el fallo real aparece más tarde, desconectado de su causa. Solución: siempre repare explícitamente y regístrelo, o devuelva un resultado de herramienta estructurado
is_errorque el modelo pueda ver. -
Registrar solo que la validación falló, no qué falló específicamente. "Entrada de herramienta inválida" sin los errores a nivel de campo hace que los fallos repetidos sean difíciles de diagnosticar en muchas llamadas. Solución: registre
e.errors()(o detalles a nivel de campo equivalentes) en cada fallo de validación, no solo un booleano. -
Reparar discrepancias ambiguas en lugar de rechazarlas. Asignar silenciosamente un valor predeterminado a un campo requerido faltante puede ejecutar una herramienta con datos significativamente incorrectos. Solución: repare solo discrepancias inequívocas y de bajo riesgo (normalización de mayúsculas/minúsculas, espacios en blanco); rechace todo lo demás.
-
No devolver
is_error: Trueen un resultado de herramienta fallido. Devolver un resultado de herramienta al modelo sin marcarlo como un error puede hacer que el modelo trate un fallo como un éxito y continúe como si la herramienta se hubiera ejecutado correctamente. Solución: siempre establezcais_error: Trueen el bloquetool_resultcuando la llamada falló. -
Tratar cada nombre de herramienta desconocido como un fallo de validación. Un
block.nameno reconocido es un problema diferente, un error de enrutamiento o una definición de herramienta obsoleta, no una discrepancia de esquema. Solución: maneje los nombres de herramientas desconocidos como una rama distinta con su propio registro, separada de la validación de esquemas.
Alternativas
| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
Validación pydantic (este artículo) | Quieres esquemas tipados y componibles que reflejen tus definiciones de input_schema | Necesitas cero dependencias y solo tienes uno o dos campos de cadena triviales |
Comprobaciones manuales de claves/tipos de dict | Una sola herramienta con argumentos muy simples y estables | Los esquemas crecen más allá de un par de campos; las comprobaciones manuales se vuelven rápidamente ilegibles |
Biblioteca jsonschema directamente contra input_schema | Quieres validar contra el esquema exacto que ya envías a la API, sin duplicación | Quieres tipos nativos de Python y autocompletado IDE en datos validados, pydantic te da eso, jsonschema puro no |
| Modo JSON estricto / salidas estructuradas a nivel de API | Tu flujo de trabajo no requiere un envoltorio de bloque de contenido tool_use y una respuesta estructurada más simple sería suficiente | Realmente necesitas el bucle tool_use (agentes multiferramienta, resultados de herramientas que alimentan la conversación) |
Preguntas frecuentes
¿El SDK de anthropic alguna vez falla al analizar el JSON de uso de herramientas?
Raramente a nivel de sintaxis, el SDK se encarga del análisis de JSON bien formado por ti. Los fallos que realmente encontrarás son discrepancias de esquema: JSON válido que no satisface la forma requerida de tu herramienta.
¿Por qué usar pydantic en lugar de simplemente comprobar las claves del dict manualmente?
- Los esquemas de
pydanticreflejan elinput_schemaque ya declaras para la herramienta, reduciendo la duplicación. - Los mensajes de error a nivel de campo (
e.errors()) son estructurados y consistentes, lo que facilita mucho el registro y la depuración en comparación con las comprobaciones ad hoc conif. - La coerción y validación de tipos se manejan en una sola llamada en lugar de comprobaciones manuales dispersas.
¿Siempre debo devolver un tool_result con is_error, o puedo simplemente lanzar una excepción?
Devuelve un tool_result estructurado con is_error: True cuando quieras que el modelo vea el fallo y potencialmente se autocorriga dentro de la misma conversación. Lanza una excepción solo cuando el fallo deba detener todo el turno, no continuar el bucle del agente.
¿Cuál es la diferencia entre reparar y rechazar una llamada a herramienta mal formada?
Reparar significa corregir automáticamente una discrepancia inequívoca y de bajo riesgo (como la normalización de mayúsculas/minúsculas) y continuar. Rechazar significa devolver un error estructurado en lugar de adivinar, se usa siempre que la corrección no sea obviamente segura, como un campo requerido faltante.
¿Puede una llamada a herramienta mal formada corromper el estado incluso si mi código no falla?
Sí. Si un fallo de validación se captura e ignora silenciosamente, la conversación continúa como si la herramienta hubiera tenido éxito, pero cualquier estado que dependiera del efecto real de la herramienta (un registro creado, un valor actualizado) nunca ocurrió realmente, y esa brecha puede aparecer como un error confuso mucho más tarde.
¿Cómo manejo una llamada a herramienta para un nombre de herramienta que no existe?
Trátalo como una rama de fallo distinta de la validación de esquemas, regístralo por separado y devuelve un tool_result con is_error: True que describa el nombre de herramienta desconocido para que el modelo pueda ver explícitamente el fallo de enrutamiento.
¿Es seguro simplemente agregar `extra: forbid` para rechazar campos inesperados?
Es un valor predeterminado razonable para la mayoría de las herramientas, ya que los campos inesperados generalmente indican deriva del modelo o un esquema obsoleto. Solo ten en cuenta que rechazará llamadas si alguna vez relajas el esquema de una herramienta sin actualizar el modelo pydantic correspondiente.
¿Qué debe contener realmente el mensaje de error en un tool_result rechazado?
Suficiente para que el modelo se autocorriga: el campo o campos específicos que fallaron y por qué, no solo "entrada inválida". Pasar e.errors() de pydantic de forma legible generalmente le da al modelo lo que necesita para reintentar correctamente.
¿La lógica de validación debe vivir dentro de la función de la herramienta o separada de ella?
Separada. Mantener la validación como su propia capa significa que la función de la herramienta puede asumir que su entrada ya es correcta, lo que mantiene la función en sí simple y hace que la lógica de validación sea reutilizable y probada de forma independiente.
¿Este patrón cambia para herramientas que llaman a APIs externas en lugar de funciones locales?
La capa de validación en sí no cambia, todavía valida antes de ejecutar. Lo que cambia es lo que significa "rechazar" downstream, una llamada a API externa también puede necesitar su propio manejo de errores para fallos que la validación de esquemas no puede capturar, como si el propio servicio externo rechazara un valor técnicamente válido.
Relacionado
- Understanding Why Claude Agents Fail in Production - dónde encajan los fallos de contrato en la taxonomía general de fallos.
- Troubleshooting & Reliability Basics - registro de bloques
tool-useantes de agregar validación encima. - Retry and Exponential Backoff Strategies for Transient Claude API Failures - patrones de resiliencia para fallos a nivel de transporte que se encuentran junto con fallos de contrato.
- Root-Cause-Analysis Template for Agent Failures - documentación de un incidente de
tool-useuna vez resuelto. - Troubleshooting & Reliability Best Practices - dónde encaja la validación de
tool-useen la lista de verificación general.
Versiones de pila: Escrito contra la línea de modelos Claude actual hasta ~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 modelos, precios y versiones de SDK cambian rápidamente; verifique los detalles actuales en platform.claude.com/docs antes de confiar en ellos.