Tratando JSON Malformado de Uso de Ferramentas e Falhas de Validação de Esquema
O uso de ferramentas é o contrato entre Claude e seu código: o modelo produz argumentos estruturados, seu código executa uma função com base neles. Na maioria das vezes, esse contrato é cumprido. Ocasionalmente, não é: argumentos que não correspondem ao esquema declarado, um campo obrigatório ausente, uma incompatibilidade de tipo e, se seu código assumir que o contrato sempre se mantém, essa única chamada de ferramenta ruim pode travar um loop de agente ou corromper seu estado várias voltas depois.
Resumo
O uso de ferramentas do Claude retorna blocos de conteúdo tool_use onde input já foi analisado em um objeto Python pelo SDK, correspondendo à forma JSON que o modelo produziu.
A falha geralmente não é sintaxe JSON inválida, o parser do SDK cuida disso, é uma incompatibilidade entre o que o modelo produziu e o que o esquema da sua ferramenta realmente exige: um campo obrigatório ausente, um tipo incorreto, um valor de enumeração que não está entre as opções permitidas.
Se não for tratado, esse tipo de incompatibilidade lança um erro profundo no seu código de execução de ferramenta com um rastreamento de pilha confuso, ou pior, não lança nenhum erro e sua ferramenta recebe silenciosamente um valor para o qual não foi construída.
A correção é uma camada de validação entre "o modelo produziu entrada de ferramenta" e "seu código de ferramenta é executado", uma que captura a incompatibilidade, a registra com detalhes suficientes para diagnóstico e repara a entrada ou falha a chamada da ferramenta de forma limpa com uma mensagem de erro estruturada que o modelo pode ver e reagir.
Este artigo constrói essa camada usando pydantic para validação de esquema, pois ele se compõe bem com o estilo JSON Schema que as definições de ferramentas já exigem.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
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 NoneQuando usar isso:
- Qualquer ferramenta cujos argumentos vêm diretamente de um bloco
tool_usedo Claude, antes de passá-los para sua função real. - Loops de agente multi-turno, onde uma chamada de ferramenta ruim hoje pode corromper silenciosamente o estado usado várias voltas depois.
- Ferramentas com esquemas não triviais: enums, objetos aninhados, campos opcionais com padrões, qualquer coisa além de um único argumento de string.
- Qualquer sistema onde uma falha de ferramenta precise ser relatada de volta ao modelo de uma forma que ele possa agir, em vez de travar todo o turno.
Exemplo de Trabalho
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)O que isso demonstra:
- Registro da entrada
tool_usebruta antes da validação, para que uma chamada rejeitada ainda deixe evidências para depuração. - Uso de
pydanticpara validarblock.inputcontra a mesma forma declarada eminput_schema, capturando campos ausentes, tipos incorretos e valores de enumeração inválidos em um só lugar. - Retorno de um
tool_resultestruturado comis_error: Trueem caso de falha, para que o modelo veja exatamente o que estava errado e possa tentar novamente com argumentos corrigidos na mesma rodada. - Manter a execução da ferramenta (
create_ticket) completamente separada da validação, para que a função em si nunca precise se defender contra entradas malformadas.
Mergulho Profundo
Como Funciona
- O SDK
anthropicanalisa o JSON que o modelo produz para argumentos de ferramenta em umdictPython automaticamente, então você raramente vê umJSONDecodeErrorbruto de um blocotool-useem si. - O que você vê é uma incompatibilidade de esquema: o dicionário analisado não satisfaz as restrições que sua ferramenta realmente precisa, uma chave obrigatória ausente, uma string onde você esperava um inteiro, um valor de enumeração fora do conjunto permitido.
- Alimentar erros de validação de volta ao modelo como um
tool_resultcomis_error: Truefecha o ciclo, o modelo pode ver exatamente o que estava errado e muitas vezes se autocorrege em sua próxima rodada, sem nenhuma lógica de nova tentativa em nível de código. - Engolir silenciosamente uma falha de validação (capturando a exceção e não fazendo nada) é a opção mais perigosa, porque o loop de conversação do agente continua como se a chamada da ferramenta tivesse sido bem-sucedida, e a falha real surge mais tarde como um bug de aparência não relacionada.
Formas de Falha que Você Realmente Verá
| Falha | Exemplo | Detecção |
|---|---|---|
| Campo obrigatório ausente | Modelo omite priority | pydantic lança ValidationError com tipo de erro missing |
| Tipo incorreto | Modelo envia "priority": 1 em vez de uma string | ValidationError com type_error |
| Valor de enumeração inválido | Modelo envia "priority": "urgent" (não no conjunto permitido) | ValidationError com literal_error |
| Campos extras/inesperados | Modelo envia um campo não declarado | Ignorado por padrão em pydantic; use model_config = {"extra": "forbid"} para rejeitar em vez disso |
| Incompatibilidade de estrutura aninhada | Esperava-se uma lista, enviou-se um único objeto | ValidationError apontando para o caminho loc aninhado |
Reparo vs. Rejeição
- Reparar quando a incompatibilidade é pequena e inequívoca, por exemplo, o modelo enviou
"priority": "High"em vez de"high", um reparo de normalização de maiúsculas/minúsculas é seguro e economiza uma viagem de ida e volta. - Rejeitar quando a incompatibilidade muda o significado, por exemplo, um campo obrigatório ausente, não há um padrão seguro para inventar e adivinhar corre o risco de executar a ferramenta com dados incorretos.
- Nunca repare descartando silenciosamente um erro de validação e prosseguindo com dados parciais ou padrão para algo que altera o efeito do mundo real da ferramenta (uma alteração de atribuição, um valor de pagamento). Rejeite esses explicitamente em vez disso.
Notas Python
# Uma passagem de reparo para o caso seguro e inequívoco: correspondência de enumeração insensível a maiúsculas/minúsculas.
# Tente reparos apenas onde a correção for inequívoca; qualquer outra coisa deve ser rejeitada.
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_inputArmadilhas
-
Assumir que
block.inputsempre corresponde ao seu esquema. Nada garante estruturalmente que a chamada de ferramenta do modelo satisfaça seuinput_schema, é uma forte convenção de nível de prompt, não um contrato imposto. Correção: sempre valide antes de executar, mesmo para ferramentas que "sempre" recebem argumentos simples na prática. -
Capturar o erro de validação e não fazer nada. Um erro silenciosamente engolido parece um turno bem-sucedido para o resto do loop do agente, e a falha real surge mais tarde, desconectada de sua causa. Correção: sempre repare explicitamente e registre, ou retorne um resultado de ferramenta
is_errorestruturado que o modelo possa ver. -
Registrar apenas que a validação falhou, não o que especificamente falhou. "Entrada de ferramenta inválida" sem os erros de nível de campo torna falhas repetidas difíceis de diagnosticar em muitas chamadas. Correção: registre
e.errors()(ou detalhes de nível de campo equivalentes) em cada falha de validação, não apenas um booleano. -
Reparar incompatibilidades ambíguas em vez de rejeitá-las. Definir silenciosamente um campo obrigatório ausente para um valor adivinhado pode executar uma ferramenta com dados significativamente incorretos. Correção: repare apenas incompatibilidades inequívocas e de baixo risco (normalização de maiúsculas/minúsculas, espaços em branco); rejeite todo o resto.
-
Não retornar
is_error: Trueem um resultado de ferramenta com falha. Enviar um resultado de ferramenta de volta ao modelo sem marcá-lo como um erro pode fazer com que o modelo trate uma falha como um sucesso e continue como se a ferramenta tivesse sido executada corretamente. Correção: sempre definais_error: Trueno blocotool_resultquando a chamada falhou. -
Tratar cada nome de ferramenta desconhecido como uma falha de validação. Um
block.namenão reconhecido é um problema diferente, um bug de roteamento ou uma definição de ferramenta desatualizada, não uma incompatibilidade de esquema. Correção: trate nomes de ferramentas desconhecidos como um ramo de falha distinto com seu próprio registro, separado da validação de esquema.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
Validação pydantic (este artigo) | Você deseja esquemas tipados e compostos que espelhem suas definições input_schema | Você precisa de zero dependências e tem apenas um ou dois campos de string triviais |
Verificações manuais de chaves/tipos de dict | Uma única ferramenta com argumentos muito simples e estáveis | Esquemas crescem além de alguns campos; verificações manuais ficam rapidamente ilegíveis |
Biblioteca jsonschema diretamente contra input_schema | Você deseja validar contra o esquema exato que já envia para a API, sem duplicação | Você deseja tipos nativos do Python e autocompletar IDE em dados validados, pydantic oferece isso, jsonschema bruto não |
| Modo JSON estrito / saídas estruturadas no nível da API | Seu fluxo de trabalho não requer um wrapper de bloco de conteúdo tool_use e uma resposta estruturada mais simples seria suficiente | Você realmente precisa do loop tool_use (agentes multi-ferramenta, resultados de ferramenta alimentando de volta na conversa) |
FAQs
O SDK da anthropic falha em analisar o JSON de uso de ferramentas?
Raramente no nível da sintaxe, o SDK cuida da análise de JSON bem formado para você. As falhas que você realmente encontrará são incompatibilidades de esquema: JSON válido que não satisfaz a forma exigida por sua ferramenta.
Por que usar pydantic em vez de apenas verificar chaves de dict manualmente?
- Os esquemas
pydanticespelham oinput_schemaque você já declara para a ferramenta, reduzindo a duplicação. - Mensagens de erro de nível de campo (
e.errors()) são estruturadas e consistentes, o que torna o registro e a depuração muito mais fáceis do que verificaçõesifad hoc. - A coerção e validação de tipos são tratadas em uma única chamada em vez de verificações manuais dispersas.
Devo sempre retornar um tool_result com is_error, ou posso apenas levantar uma exceção?
Retorne um tool_result estruturado com is_error: True quando você quiser que o modelo veja a falha e potencialmente se autocorreja dentro da mesma conversa. Levante uma exceção apenas quando a falha dever interromper todo o turno, não continuar o loop do agente.
Qual é a diferença entre reparar e rejeitar uma chamada de ferramenta malformada?
Reparar significa corrigir uma incompatibilidade inequívoca e de baixo risco automaticamente (como normalização de maiúsculas/minúsculas) e prosseguir. Rejeitar significa retornar um erro estruturado em vez de adivinhar, usado sempre que a correção não for obviamente segura, como um campo obrigatório ausente.
Uma chamada de ferramenta malformada pode corromper o estado mesmo que meu código não trave?
Sim. Se uma falha de validação for capturada e ignorada silenciosamente, a conversa continua como se a ferramenta tivesse sido bem-sucedida, mas qualquer estado que dependesse do efeito real da ferramenta (um registro criado, um valor atualizado) nunca aconteceu, e essa lacuna pode surgir como um bug confuso muito mais tarde.
Como lidar com uma chamada de ferramenta para um nome de ferramenta que não existe?
Trate-a como um ramo de falha distinto da validação de esquema, registre-a separadamente e retorne um tool_result com is_error: True descrevendo o nome da ferramenta desconhecido para que o modelo possa ver explicitamente a falha de roteamento.
É seguro apenas adicionar `extra: forbid` para rejeitar campos inesperados?
É um padrão razoável para a maioria das ferramentas, pois campos inesperados geralmente indicam desvio do modelo ou um esquema desatualizado. Apenas esteja ciente de que isso rejeitará chamadas se você um dia flexibilizar o esquema de uma ferramenta sem atualizar o modelo pydantic correspondente.
O que a mensagem de erro em um tool_result rejeitado deve conter?
O suficiente para o modelo se autocorreger: o(s) campo(s) específico(s) que falharam e por quê, não apenas "entrada inválida". Passar e.errors() do pydantic de forma legível geralmente dá ao modelo o que ele precisa para tentar novamente corretamente.
A lógica de validação deve ficar dentro da função de ferramenta ou separada dela?
Separada. Manter a validação como sua própria camada significa que a função de ferramenta pode assumir que sua entrada já está correta, o que mantém a função em si simples e torna a lógica de validação reutilizável e testável independentemente.
Este padrão muda para ferramentas que chamam APIs externas versus funções locais?
A camada de validação em si não muda, você ainda valida antes de executar. O que muda é o que "rejeitar" deve significar downstream, uma chamada de API externa também pode precisar de seu próprio tratamento de erros para falhas que a validação de esquema não pode capturar, como o próprio serviço externo rejeitando um valor tecnicamente válido.
Relacionados
- Entendendo Por Que os Agentes Claude Falham em Produção - onde as falhas de contrato se encaixam na taxonomia mais ampla de falhas.
- Noções Básicas de Solução de Problemas e Confiabilidade - registro de blocos
tool-useantes de adicionar validação por cima. - Estratégias de Retentativa e Backoff Exponencial para Falhas Transitórias da API Claude - padrões de resiliência para falhas de nível de transporte que ficam ao lado de falhas de contrato.
- Modelo de Análise de Causa Raiz para Falhas de Agente - documentando um incidente de
tool-useapós sua resolução. - Melhores Práticas de Solução de Problemas e Confiabilidade - onde a validação de
tool-usese encaixa na lista de verificação mais ampla.
Versões de Stack: Escrito contra a linha de modelos Claude atual em ~junho de 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (o padrão), e Claude Haiku 4.5 - e o SDK oficial
anthropicPython (última versão 0.x). Nomes de modelos, preços e versões de SDK mudam rapidamente - verifique os detalhes atuais em platform.claude.com/docs antes de confiar neles.