Este tutorial constrói um agente de gerenciamento de calendário em cinco anéis concêntricos. Cada anel é um programa completo e executável que adiciona exatamente um conceito ao anel anterior. Ao final, você terá escrito o loop agêntico à mão e depois o substituído pela abstração Tool Runner do SDK.
A ferramenta de exemplo é create_calendar_event. Seu schema usa objetos aninhados, arrays e campos opcionais, então você verá como Claude lida com formatos de entrada realistas em vez de uma única string plana.
O menor programa possível que usa ferramentas: uma ferramenta, uma mensagem do usuário, uma chamada de ferramenta, um resultado. O código é fortemente comentado para que você possa mapear cada linha ao ciclo de vida do uso de ferramentas.
A requisição envia um array tools junto com a mensagem do usuário. Quando Claude determina que uma chamada de ferramenta é necessária, a resposta retorna com stop_reason: "tool_use" e um bloco de conteúdo tool_use contendo o nome da ferramenta, um id único e o input estruturado. Seu código executa a ferramenta e, em seguida, envia o resultado de volta em um bloco tool_result cujo tool_use_id corresponde ao id da chamada.
# Anel 1: Ferramenta única, turno único.
import json
import anthropic
# Crie um cliente. Ele lê ANTHROPIC_API_KEY do ambiente.
client = anthropic.Anthropic()
# Defina uma ferramenta. O input_schema é um objeto JSON Schema que descreve
# os argumentos que o Claude deve passar ao chamar esta ferramenta. Este schema
# inclui objetos aninhados (recurrence), arrays (attendees) e campos
# opcionais, mais próximo de ferramentas reais do que um argumento string simples.
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
}
]
# Envie a solicitação do usuário junto com a definição da ferramenta. O Claude
# decide se chama a ferramenta com base na solicitação e na descrição dela.
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=[
{
"role": "user",
"content": "Schedule a 30-minute sync with [email protected] and [email protected] on Monday, March 30, 2026 at 10am.",
}
],
)
# Quando o Claude chama uma ferramenta, a resposta tem stop_reason "tool_use"
# e o array content contém um bloco tool_use junto com qualquer texto.
print(f"stop_reason: {response.stop_reason}")
# Encontre o bloco tool_use. Uma resposta pode conter blocos de texto antes do
# bloco tool_use, então percorra o array content em vez de assumir a posição.
tool_use = next(block for block in response.content if block.type == "tool_use")
print(f"Tool: {tool_use.name}")
print(f"Input: {tool_use.input}")
# Execute a ferramenta. Em um sistema real, isso chamaria sua API de calendário.
# Aqui o resultado é fixo no código para manter o exemplo autocontido.
result = {"event_id": "evt_123", "status": "created"}
# Envie o resultado de volta. O bloco tool_result vai em uma mensagem do usuário
# e seu tool_use_id deve corresponder ao id do bloco tool_use acima. A resposta
# anterior do assistente é incluída para que o Claude tenha o histórico completo.
followup = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=[
{
"role": "user",
"content": "Schedule a 30-minute sync with [email protected] and [email protected] on Monday, March 30, 2026 at 10am.",
},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": json.dumps(result),
}
],
},
],
)
# Com o resultado da ferramenta em mãos, o Claude produz uma resposta final em
# linguagem natural e stop_reason se torna "end_turn".
print(f"stop_reason: {followup.stop_reason}")
final_text = next(block for block in followup.content if block.type == "text")
print(final_text.text)O que esperar
stop_reason: tool_use
Tool: create_calendar_event
Input: {'title': 'Sync', 'start': '2026-03-30T10:00:00', 'end': '2026-03-30T10:30:00', 'attendees': ['[email protected]', '[email protected]']}
stop_reason: end_turn
I've scheduled your 30-minute sync with Alice and Bob for Monday, March 30 at 10am.O primeiro stop_reason é tool_use porque Claude está aguardando o resultado do calendário. Depois que você envia o resultado, o segundo stop_reason é end_turn e o conteúdo é linguagem natural para o usuário.
O Anel 1 presumiu que Claude chamaria a ferramenta exatamente uma vez. Tarefas reais frequentemente precisam de várias chamadas: Claude pode criar um evento, ler a confirmação e depois criar outro. A solução é um loop while que continua executando ferramentas e retornando resultados até que stop_reason não seja mais "tool_use".
A outra mudança é o histórico da conversa. Em vez de reconstruir o array messages do zero a cada requisição, mantenha uma lista contínua e adicione a ela. Cada turno vê o contexto anterior completo.
# Anel 2: O loop agêntico.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
}
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
return {"error": f"Unknown tool: {name}"}
# Mantenha o histórico completo da conversa em uma lista para que cada turno veja o contexto anterior.
messages = [
{
"role": "user",
"content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: [email protected], [email protected], [email protected].",
}
]
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=messages,
)
# Itere até que o Claude pare de solicitar ferramentas. Cada iteração executa a ferramenta
# solicitada, anexa o resultado ao histórico e pede ao Claude que continue.
while response.stop_reason == "tool_use":
tool_use = next(block for block in response.content if block.type == "tool_use")
result = run_tool(tool_use.name, tool_use.input)
messages.append({"role": "assistant", "content": response.content})
messages.append(
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": json.dumps(result),
}
],
}
)
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)O que esperar
I've set up your weekly team standup for the next 4 Mondays at 9am with Alice, Bob, and Carol invited.O loop pode ser executado uma ou várias vezes dependendo de como Claude divide a tarefa. Seu código não precisa mais saber disso com antecedência.
Agentes raramente têm apenas uma capacidade. Adicione uma segunda ferramenta, list_calendar_events, para que Claude possa verificar a agenda existente antes de criar algo novo.
Quando Claude tem múltiplas chamadas de ferramenta independentes para fazer, ele pode retornar vários blocos tool_use em uma única resposta. Seu loop precisa processar todos eles e enviar de volta todos os resultados juntos em uma única mensagem do usuário. Itere sobre cada bloco tool_use em response.content, não apenas o primeiro.
# Anel 3: Múltiplas ferramentas, chamadas paralelas.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
},
{
"name": "list_calendar_events",
"description": "List all calendar events on a given date.",
"input_schema": {
"type": "object",
"properties": {
"date": {"type": "string", "format": "date"},
},
"required": ["date"],
},
},
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
if name == "list_calendar_events":
return {"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}
return {"error": f"Unknown tool: {name}"}
messages = [
{
"role": "user",
"content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",
}
]
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
while response.stop_reason == "tool_use":
# Uma única resposta pode conter múltiplos blocos tool_use. Processe todos
# eles e retorne todos os resultados juntos em uma única mensagem do usuário.
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = run_tool(block.name, block.input)
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result),
}
)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)O que esperar
I checked your calendar for next Monday and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.Para mais informações sobre execução concorrente e garantias de ordenação, consulte Uso de ferramentas em paralelo.
Ferramentas falham. Uma API de calendário pode rejeitar um evento com participantes demais, ou uma data pode estar malformada. Quando uma ferramenta gera um erro, envie a mensagem de erro de volta com is_error: true em vez de travar. Claude lê o erro e pode tentar novamente com a entrada corrigida, pedir esclarecimentos ao usuário ou explicar a limitação.
# Anel 4: Tratamento de erros.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "create_calendar_event",
"description": "Create a calendar event with attendees and optional recurrence.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"},
},
"recurrence": {
"type": "object",
"properties": {
"frequency": {"enum": ["daily", "weekly", "monthly"]},
"count": {"type": "integer", "minimum": 1},
},
},
},
"required": ["title", "start", "end"],
},
},
{
"name": "list_calendar_events",
"description": "List all calendar events on a given date.",
"input_schema": {
"type": "object",
"properties": {
"date": {"type": "string", "format": "date"},
},
"required": ["date"],
},
},
]
def run_tool(name, tool_input):
if name == "create_calendar_event":
if "attendees" in tool_input and len(tool_input["attendees"]) > 10:
raise ValueError("Too many attendees (max 10)")
return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
if name == "list_calendar_events":
return {"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}
raise ValueError(f"Unknown tool: {name}")
messages = [
{
"role": "user",
"content": "Schedule an all-hands with everyone: " + ", ".join(f"user{i}@example.com" for i in range(15)),
}
]
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
while response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
try:
result = run_tool(block.name, block.input)
tool_results.append(
{"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result)}
)
except Exception as exc:
# Sinaliza falha para que o Claude possa tentar novamente ou pedir esclarecimentos.
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": str(exc),
"is_error": True,
}
)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)O que esperar
I tried to schedule the all-hands but the calendar only allows 10 attendees per event. I can split this into two sessions, or you can let me know which 10 people to prioritize.A flag is_error é a única diferença em relação a um resultado bem-sucedido. Claude vê a flag e o texto do erro, e responde de acordo. Consulte Lidar com chamadas de ferramentas para a referência completa de tratamento de erros.
Os Anéis 2 a 4 escreveram o mesmo loop à mão: chamar a API, verificar stop_reason, executar ferramentas, adicionar resultados, repetir. O Tool Runner faz isso por você. Defina cada ferramenta como uma função, passe a lista para tool_runner e recupere a mensagem final quando o loop for concluído. O encapsulamento de erros, a formatação de resultados e o gerenciamento da conversa são tratados internamente.
Cada SDK fornece um helper que transforma uma função comum em uma ferramenta executável e deriva o schema de entrada a partir de sua assinatura; as abas abaixo mostram a forma idiomática para cada linguagem.
# Anel 5: A abstração do Tool Runner SDK.
import json
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def create_calendar_event(
title: str,
start: str,
end: str,
attendees: list[str] | None = None,
recurrence: dict | None = None,
) -> str:
"""Create a calendar event with attendees and optional recurrence.
Args:
title: Event title.
start: Start time in ISO 8601 format.
end: End time in ISO 8601 format.
attendees: Email addresses to invite.
recurrence: Dict with 'frequency' (daily, weekly, monthly) and 'count'.
"""
if attendees and len(attendees) > 10:
raise ValueError("Too many attendees (max 10)")
return json.dumps({"event_id": "evt_123", "status": "created", "title": title})
@beta_tool
def list_calendar_events(date: str) -> str:
"""List all calendar events on a given date.
Args:
date: Date in YYYY-MM-DD format.
"""
return json.dumps({"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]})
final_message = client.beta.messages.tool_runner(
model="claude-opus-5",
max_tokens=1024,
tools=[create_calendar_event, list_calendar_events],
messages=[
{
"role": "user",
"content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",
}
],
).until_done()
for block in final_message.content:
if block.type == "text":
print(block.text)O que esperar
I checked your calendar for next Monday and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.A saída é idêntica à do Anel 3. A diferença está no código: aproximadamente metade das linhas, nenhum loop manual, e o schema fica ao lado da implementação.
Você começou com uma única chamada de ferramenta codificada manualmente e terminou com um agente com formato de produção que lida com múltiplas ferramentas, chamadas paralelas e erros, e depois condensou tudo isso no Tool Runner. Ao longo do caminho, você viu cada parte do protocolo de uso de ferramentas: blocos tool_use, blocos tool_result, correspondência de tool_use_id, verificação de stop_reason e sinalização com is_error.
Especificação de schema e melhores práticas.
A referência completa da abstração do SDK.
Corrija erros comuns de uso de ferramentas.
Was this page helpful?