Claude Platform Docs
MessagesFerramentas

Tutorial: Construa um agente que usa ferramentas

Um passo a passo guiado desde uma única chamada de ferramenta até um loop agêntico pronto para produção.

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 "agentic loop" (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 o Claude lida com formatos de entrada realistas em vez de uma única string simples.

Anel 1: Uma ferramenta, um turno

O menor programa possível com uso de ferramentas: uma ferramenta, uma mensagem do usuário, uma chamada de ferramenta, um resultado. O código é amplamente 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 o 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 então 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-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 alice@example.com and bob@example.com 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-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 alice@example.com and bob@example.com 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

Output
stop_reason: tool_use
Tool: create_calendar_event
Input: {'title': 'Sync', 'start': '2026-03-30T10:00:00', 'end': '2026-03-30T10:30:00', 'attendees': ['alice@example.com', 'bob@example.com']}
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 o 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.

Anel 2: O loop agêntico

O Anel 1 assumiu que o Claude chamaria a ferramenta exatamente uma vez. Tarefas reais frequentemente precisam de várias chamadas: o Claude pode criar um evento, ler a confirmação e então criar outro. A solução é um loop while que continua executando ferramentas e devolvendo 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 em cada requisição, mantenha uma lista contínua e acrescente 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, starting Monday, March 30, 2026. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.",
    }
]

response = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto", "disable_parallel_tool_use": True},
    messages=messages,
)

# Repita até que Claude pare de pedir ferramentas. Cada iteração executa a ferramenta
# solicitada, adiciona o resultado ao histórico e pede que Claude 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-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

Output
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 o Claude divide a tarefa. Seu código não precisa mais saber com antecedência.

Anel 3: Múltiplas ferramentas, chamadas paralelas

Agentes raramente têm apenas uma capacidade. Adicione uma segunda ferramenta, list_calendar_events, para que o Claude possa verificar a agenda existente antes de criar algo novo.

Quando o Claude tem várias chamadas de ferramentas independentes a 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: várias 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 on Monday, March 30, 2026, then schedule a one-hour planning session that day that avoids any conflicts.",
    }
]

response = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    tools=tools,
    messages=messages,
)

while response.stop_reason == "tool_use":
    # Uma única resposta pode conter vários 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-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

Output
I checked your calendar for Monday, March 30 and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.

Para saber mais sobre execução concorrente e garantias de ordenação, consulte Uso paralelo de ferramentas.

Anel 4: Tratamento de erros

Ferramentas falham. Uma API de calendário pode rejeitar um evento com muitos participantes, 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. O 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 a one-hour all-hands on Monday, March 30, 2026 at 10am with everyone: " + ", ".join(f"user{i}@example.com" for i in range(15)),
    }
]

response = client.messages.create(
    model="claude-opus-5-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 a 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-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

Output
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. O Claude vê a flag e o texto do erro, e responde de acordo. Consulte Tratar chamadas de ferramentas para a referência completa de tratamento de erros.

Anel 5: A abstração Tool Runner do SDK

Os Anéis 2 a 4 escreveram o mesmo loop manualmente: 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 client.beta.messages.tool_runner() e obtenha a mensagem final quando o loop terminar. 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 SDK Tool Runner.

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-5",
    max_tokens=1024,
    tools=[create_calendar_event, list_calendar_events],
    messages=[
        {
            "role": "user",
            "content": "Check what I have on Monday, March 30, 2026, then schedule a one-hour planning session that day that avoids any conflicts.",
        }
    ],
).until_done()

for block in final_message.content:
    if block.type == "text":
        print(block.text)

O que esperar

Output
I checked your calendar for Monday, March 30 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.

O que você construiu

Você começou com uma única chamada de ferramenta fixa no código e terminou com um agente com formato de produção que lida com múltiplas ferramentas, chamadas paralelas e erros, e então 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 de is_error.

Próximos passos

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?