Objetivo: ao final, o consultor define uma tool com schema, executa o loop agêntico corretamente, conhece o tool runner do SDK como caminho padrão, e sabe distinguir ferramenta que roda no cliente de ferramenta que roda no servidor da Anthropic.
response.content inteiro ao histórico (perde o bloco tool_use e a próxima chamada é rejeitada) e separar os tool_result em mensagens diferentes (não dá erro — ensina o modelo a parar de pedir em paralelo).tools = [
{
"name": "consultar_pedido",
"description": (
"Consulta a situação de um pedido de compra no MRD. "
"Use quando a pergunta envolver um número de pedido específico — "
"situação, bloqueio, fornecedor ou itens."
),
"input_schema": {
"type": "object",
"properties": {
"numero_pedido": {
"type": "string",
"description": "Número do pedido de compra, 10 dígitos. Ex.: 4500012345",
},
"ambiente": {
"type": "string",
"enum": ["DEV", "QAS", "PRD"],
"description": "Ambiente do MRD a consultar. Padrão: QAS.",
},
},
"required": ["numero_pedido"],
},
}
]
Três decisões nessa definição valem nota:
description diz quando usar, não só o que faz. É o mesmo princípio da description de skill da aula 2.4, e o efeito é o mesmo: descrição que só descreve subaciona.enum no ambiente. Sem ele, nada impede o modelo de mandar "producao" e o seu código quebrar — ou pior, acertar por acaso.ambiente não é obrigatório. Campo obrigatório demais faz o modelo inventar valor para preencher.messages = [{"role": "user", "content": "O pedido 4500012345 está bloqueado? Se estiver, por quê?"}]
while True:
response = client.messages.create(
model="claude-opus-5",
max_tokens=16000,
tools=tools,
messages=messages,
)
if response.stop_reason != "tool_use":
break
# 1) devolve a resposta INTEIRA ao histórico — inclusive os blocos tool_use
messages.append({"role": "assistant", "content": response.content})
# 2) executa TODAS as ferramentas pedidas
resultados = []
for bloco in response.content:
if bloco.type == "tool_use":
try:
saida = executar(bloco.name, bloco.input)
resultados.append({
"type": "tool_result",
"tool_use_id": bloco.id,
"content": saida,
})
except Exception as e:
resultados.append({
"type": "tool_result",
"tool_use_id": bloco.id,
"content": f"Erro ao consultar: {e}",
"is_error": True,
})
# 3) TODOS os resultados numa ÚNICA mensagem de usuário
messages.append({"role": "user", "content": resultados})
texto = next(b.text for b in response.content if b.type == "text")
| Regra | O que acontece se você violar |
|---|---|
Devolver response.content inteiro |
A próxima chamada é rejeitada: falta o tool_use correspondente ao tool_result |
Um tool_result por tool_use, com o tool_use_id certo |
A API rejeita a mensagem |
Todos os tool_result numa única mensagem de usuário |
Não dá erro — o modelo aprende a não pedir mais em paralelo. Degradação silenciosa |
Ferramenta que falhou volta com is_error: true |
Omitir o resultado quebra o pareamento; devolver o erro deixa o Claude se recuperar |
A terceira linha é a que mais aparece em código de produção com desempenho pior do que deveria — e é invisível, porque nada falha.
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def consultar_pedido(numero_pedido: str, ambiente: str = "QAS") -> str:
"""Consulta a situação de um pedido de compra no MRD.
Use quando a pergunta envolver um número de pedido específico.
Args:
numero_pedido: Número do pedido, 10 dígitos.
ambiente: DEV, QAS ou PRD. Padrão QAS.
"""
return consulta_real(numero_pedido, ambiente)
runner = client.beta.messages.tool_runner(
model="claude-opus-5",
max_tokens=16000,
tools=[consultar_pedido],
messages=[{"role": "user", "content": "O pedido 4500012345 está bloqueado?"}],
)
for message in runner:
print(message)
preview — pode mudar· O tool runner é beta (client.beta.messages). O schema sai da assinatura e do docstring da função — o docstring é adescription.
Quando ainda vale o laço manual: transporte próprio, formato de requisição que o SDK não monta, ou fluxo de controle que os ganchos por turno do runner não cobrem. Aprovação humana antes de executar não é motivo — dá para barrar dentro da própria função da tool, devolvendo "o usuário recusou" como resultado.
| Tipo | Quem executa | Exemplos |
|---|---|---|
| Tool sua | Seu código | Consulta ao MRD, leitura de arquivo, chamada a API interna |
| Tool de servidor | Anthropic | web_search_20260209, web_fetch_20260209, code_execution_20260521 |
| Tool definida pela Anthropic, executada por você | Seu código | bash, editor de texto — schema fixo, execução sua |
tools = [
{"type": "web_search_20260209", "name": "web_search"},
{"type": "code_execution_20260521", "name": "code_execution"},
]
Ferramenta de servidor não tem loop: você declara, e o resultado volta como bloco de conteúdo na mesma resposta. Dois pontos práticos:
stop_reason: "pause_turn" — basta reenviar a conversa para continuar, sem acrescentar mensagem nenhuma.E há uma decisão de dado embutida. web_search manda a consulta para fora. code_execution roda o código num contêiner da Anthropic. Nos dois casos, o que você mandar sai do seu ambiente — é a mesma análise que o módulo 3.1 fez para servidor MCP e que o 3.9 formaliza.
Regra Wayon Tool que escreve em sistema de cliente — cria, altera, libera, transporta — não é executada direto pelo agente: ela devolve uma proposta que um humano aprova, ou exige confirmação explícita no código antes de executar. Tool de leitura pode rodar automática. E ferramenta de servidor da Anthropic com dado de cliente segue a classificação do módulo 3.9 — o que sai do ambiente é decisão de política, não de conveniência.
📖 Tool use overview · Skills de verdade — módulo 2.4
response.content inteiro?tool_resultNão reconstrói: o pareamento é por tool_use_id, e sem o bloco original não há par.
tool_use que corresponde ao tool_resultCorreto. Todo tool_result precisa do tool_use correspondente no histórico.
Não chega a repetir: a requisição é recusada antes.
tool_use correspondenteOs dois tool_use existem no histórico; a requisição passa.
Ele não é ignorado — a conversa segue normalmente, e é justamente isso que esconde o problema.
Correto. É o motivo de a regra ser "todos os tool_result numa única mensagem".
tool_result com o mesmo tool_use_id, a mensagem de erro e is_error: trueCorreto. Devolver o erro mantém o pareamento e deixa o Claude tentar outro caminho.
A omissão quebra o pareamento e a requisição é rejeitada.
tool_result com conteúdo vazio, para não induzir o modelo ao erroConteúdo vazio faz o Claude tratar como resposta válida; a falha precisa ser explícita.
Ele executa a sua função, e a sua função pode recusar; além disso o runner expõe ganchos por turno.
Correto. Aprovação humana não é motivo para descer ao laço manual.
O número de tools não muda nada; a intervenção cabe na função em qualquer caso.
web_search numa aplicação que processa chamados do Meridiano? ---Ela roda na infraestrutura da Anthropic, não no seu processo.
Ferramenta de servidor não tem loop — o resultado volta na mesma resposta.
Correto. É a mesma análise feita para servidor MCP no módulo 3.1 e formalizada no 3.9.