---
title: Tool runner (SDK)
url: https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/tool-runner
description: Use o tool runner do SDK para lidar automaticamente com o loop agêntico, o encapsulamento de erros e a segurança de tipos.
---

O "tool runner" (executor de ferramentas) lida com o loop agêntico, o encapsulamento de erros e a segurança de tipos para que você não precise fazer isso. Quando você precisar de aprovação humana no loop (human-in-the-loop), logging personalizado ou execução condicional, use o [loop manual](https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/handle-tool-calls) em vez disso.

Em vez de lidar manualmente com chamadas de ferramentas, resultados de ferramentas e gerenciamento de conversa, o tool runner automaticamente:

* Executa ferramentas quando Claude as chama
* Lida com o ciclo de requisição/resposta
* Gerencia o estado da conversa
* Fornece segurança de tipos e validação

<Note>
  O tool runner está em beta e disponível no [SDK Python](https://github.com/anthropics/anthropic-sdk-python/blob/main/tools.md), [SDK TypeScript](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/helpers.md#tool-helpers), [SDK C#](https://github.com/anthropics/anthropic-sdk-csharp/blob/main/examples/ToolRunnerExample/Program.cs), [SDK Go](https://github.com/anthropics/anthropic-sdk-go/blob/main/tools.md), [SDK Java](https://github.com/anthropics/anthropic-sdk-java/blob/main/anthropic-java-example/src/main/java/com/anthropic/example/BetaToolRunnerRunnableToolExample.java), [SDK PHP](https://github.com/anthropics/anthropic-sdk-php/blob/main/examples/beta/beta_tool_runner.php) e [SDK Ruby](https://github.com/anthropics/anthropic-sdk-ruby/blob/main/helpers.md#3-auto-looping-tool-runner-beta).
</Note>

## Uso básico

Defina ferramentas usando os helpers do SDK e, em seguida, use o tool runner para executá-las.

Dependendo da assinatura de ferramenta do SDK, uma ferramenta retorna seu resultado como uma string ou como blocos de conteúdo (blocos de texto, imagem ou documento), de modo que uma ferramenta pode retornar resultados multimodais. Uma string retornada se torna um único bloco de conteúdo de texto. Para retornar dados estruturados, como um objeto JSON ou um número, codifique-os primeiro como uma string.

<Tabs exclude="shell">
  <Tab title="Python">
    Use o decorador `@beta_tool` para definir ferramentas com type hints e docstrings.

    <Note>
      Se você estiver usando o cliente assíncrono, substitua `@beta_tool` por `@beta_async_tool` e defina a função com `async def`.
    </Note>

    ```python
    import json
    from anthropic import Anthropic, beta_tool

    client = Anthropic()


    @beta_tool
    def get_weather(location: str, unit: str = "fahrenheit") -> str:
        """Get the current weather in a given location.

        Args:
            location: The city and state, e.g. San Francisco, CA
            unit: Temperature unit, either 'celsius' or 'fahrenheit'
        """
        return json.dumps({"temperature": "20°C", "condition": "Sunny"})


    @beta_tool
    def calculate_sum(a: int, b: int) -> str:
        """Add two numbers together.

        Args:
            a: First number
            b: Second number
        """
        return str(a + b)


    runner = client.beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=1024,
        tools=[get_weather, calculate_sum],
        messages=[
            {
                "role": "user",
                "content": "What's the weather like in Paris? Also, what's 15 + 27?",
            }
        ],
    )
    for message in runner:
        print(message)
    ```

    O decorador `@beta_tool` inspeciona os argumentos da função e a docstring para derivar o JSON schema para você.
  </Tab>

  <Tab title="TypeScript">
    Use `betaZodTool()` para definições de ferramentas com segurança de tipos e validação Zod, ou `betaTool()` para definições baseadas em JSON Schema.

    O TypeScript oferece duas abordagens para definir ferramentas:

    **Usando Zod (recomendado)** - Use `betaZodTool()` para definições de ferramentas com segurança de tipos e validação Zod (requer Zod 3.25.0 ou superior):

    ```typescript
    import Anthropic from "@anthropic-ai/sdk";
    import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
    import { z } from "zod";

    const client = new Anthropic();

    const getWeatherTool = betaZodTool({
      name: "get_weather",
      description: "Get the current weather in a given location",
      inputSchema: z.object({
        location: z.string().describe("The city and state, e.g. San Francisco, CA"),
        unit: z.enum(["celsius", "fahrenheit"]).default("fahrenheit").describe("Temperature unit")
      }),
      run: async (input) => {
        return JSON.stringify({ temperature: "20°C", condition: "Sunny" });
      }
    });

    const finalMessage = await client.beta.messages.toolRunner({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [getWeatherTool],
      messages: [{ role: "user", content: "What's the weather like in Paris?" }]
    });

    for (const block of finalMessage.content) {
      if (block.type === "text") {
        console.log(block.text);
      }
    }
    ```

    **Usando JSON Schema** - Use `betaTool()` para definições de ferramentas com segurança de tipos sem Zod:

    <Note>
      A entrada gerada por Claude não é validada em tempo de execução. Realize a validação dentro da função `run`, se necessário.
    </Note>

    ```typescript
    import Anthropic from "@anthropic-ai/sdk";
    import { betaTool } from "@anthropic-ai/sdk/helpers/beta/json-schema";

    const client = new Anthropic();

    const calculateSumTool = betaTool({
      name: "calculate_sum",
      description: "Add two numbers together",
      inputSchema: {
        type: "object",
        properties: {
          a: { type: "number", description: "First number" },
          b: { type: "number", description: "Second number" }
        },
        required: ["a", "b"]
      },
      run: async (input) => {
        return String(input.a + input.b);
      }
    });

    const finalMessage = await client.beta.messages.toolRunner({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [calculateSumTool],
      messages: [{ role: "user", content: "What's 15 + 27?" }]
    });

    for (const block of finalMessage.content) {
      if (block.type === "text") {
        console.log(block.text);
      }
    }
    ```
  </Tab>

  <Tab title="C#">
    Defina cada ferramenta como um `BetaRunnableTool`, fornecendo uma `Definition` com um JSON schema e um delegate `Run` que é executado quando Claude chama a ferramenta.

    ```csharp
    using System.Text.Json;
    using Anthropic;
    using Anthropic.Helpers.Beta;
    using Anthropic.Models.Beta.Messages;
    using MessageCreateParams = Anthropic.Models.Beta.Messages.MessageCreateParams;
    using InputSchema = Anthropic.Models.Beta.Messages.InputSchema;
    using Role = Anthropic.Models.Beta.Messages.Role;
    using Model = Anthropic.Models.Messages.Model;

    var client = new AnthropicClient();

    var getWeatherTool = new BetaRunnableTool
    {
        Name = "get_weather",
        Definition = new BetaTool
        {
            Name = "get_weather",
            Description = "Get the current weather in a given location.",
            InputSchema = new InputSchema
            {
                Properties = new Dictionary<string, JsonElement>
                {
                    ["location"] = JsonSerializer.SerializeToElement(
                        new { type = "string", description = "The city and state, e.g. San Francisco, CA" }
                    ),
                },
                Required = ["location"],
            },
        },
        Run = (toolUse, _) =>
        {
            var location = toolUse.Input["location"].GetString();
            return Task.FromResult<BetaToolResultBlockParamContent>(
                $"Weather in {location}: 20°C, sunny"
            );
        },
    };

    var calculateSumTool = new BetaRunnableTool
    {
        Name = "calculate_sum",
        Definition = new BetaTool
        {
            Name = "calculate_sum",
            Description = "Add two numbers together.",
            InputSchema = new InputSchema
            {
                Properties = new Dictionary<string, JsonElement>
                {
                    ["a"] = JsonSerializer.SerializeToElement(new { type = "number" }),
                    ["b"] = JsonSerializer.SerializeToElement(new { type = "number" }),
                },
                Required = ["a", "b"],
            },
        },
        Run = (toolUse, _) =>
        {
            var a = toolUse.Input["a"].GetDouble();
            var b = toolUse.Input["b"].GetDouble();
            return Task.FromResult<BetaToolResultBlockParamContent>($"{a + b}");
        },
    };

    var runner = client.Beta.Messages.ToolRunner(
        new MessageCreateParams
        {
            Model = Model.ClaudeOpus5_5,
            MaxTokens = 1024,
            Messages =
            [
                new()
                {
                    Role = Role.User,
                    Content = "What's the weather like in Paris? Also, what's 15 + 27?",
                },
            ],
        },
        [getWeatherTool, calculateSumTool]
    );

    await foreach (var message in runner)
    {
        Console.WriteLine(message);
    }
    ```
  </Tab>

  <Tab title="Go">
    Defina uma ferramenta com `toolrunner.NewBetaToolFromJSONSchema`. O tipo de entrada do handler é uma struct com tags `jsonschema:`. O SDK usa reflexão sobre ela para gerar o JSON schema.

    ```go
    package main

    import (
    	"context"
    	"fmt"
    	"log"

    	"github.com/anthropics/anthropic-sdk-go"
    	"github.com/anthropics/anthropic-sdk-go/toolrunner"
    )

    type GetWeatherInput struct {
    	Location string `json:"location" jsonschema:"required,description=The city and state, e.g. San Francisco, CA"`
    	Unit     string `json:"unit,omitempty" jsonschema:"enum=celsius,enum=fahrenheit,description=Temperature unit"`
    }

    type CalculateSumInput struct {
    	A int `json:"a" jsonschema:"required,description=First number"`
    	B int `json:"b" jsonschema:"required,description=Second number"`
    }

    func main() {
    	client := anthropic.NewClient()
    	ctx := context.Background()

    	getWeather, err := toolrunner.NewBetaToolFromJSONSchema(
    		"get_weather",
    		"Get the current weather in a given location.",
    		func(ctx context.Context, input GetWeatherInput) (anthropic.BetaToolResultBlockParamContentUnion, error) {
    			return anthropic.BetaToolResultBlockParamContentUnion{
    				OfText: &anthropic.BetaTextBlockParam{Text: "20°C, Sunny"},
    			}, nil
    		},
    	)
    	if err != nil {
    		log.Fatal(err)
    	}

    	calculateSum, err := toolrunner.NewBetaToolFromJSONSchema(
    		"calculate_sum",
    		"Add two numbers together.",
    		func(ctx context.Context, input CalculateSumInput) (anthropic.BetaToolResultBlockParamContentUnion, error) {
    			return anthropic.BetaToolResultBlockParamContentUnion{
    				OfText: &anthropic.BetaTextBlockParam{Text: fmt.Sprintf("%d", input.A+input.B)},
    			}, nil
    		},
    	)
    	if err != nil {
    		log.Fatal(err)
    	}

    	runner := client.Beta.Messages.NewToolRunner(
    		[]anthropic.BetaTool{getWeather, calculateSum},
    		anthropic.BetaToolRunnerParams{
    			BetaMessageNewParams: anthropic.BetaMessageNewParams{
    				Model:     anthropic.ModelClaudeOpus5_5,
    				MaxTokens: 1024,
    				Messages: []anthropic.BetaMessageParam{
    					anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(
    						"What's the weather like in Paris? Also, what's 15 + 27?",
    					)),
    				},
    			},
    		},
    	)

    	for message, err := range runner.All(ctx) {
    		if err != nil {
    			log.Fatal(err)
    		}
    		fmt.Println(message)
    	}
    }
    ```

    As tags de struct `jsonschema:` geram o schema de entrada. Por exemplo, `CalculateSumInput` se torna:

    ```json
    {
      "name": "calculate_sum",
      "description": "Add two numbers together.",
      "input_schema": {
        "type": "object",
        "properties": {
          "a": { "type": "integer", "description": "First number" },
          "b": { "type": "integer", "description": "Second number" }
        },
        "required": ["a", "b"]
      }
    }
    ```
  </Tab>

  <Tab title="Java">
    Defina cada ferramenta como um `BetaRunnableTool` que associa uma classe de entrada a uma função que é executada quando Claude chama a ferramenta. Anote a classe de entrada com `@JsonClassDescription` para a descrição da ferramenta, e cada campo público com `@JsonPropertyDescription` para as descrições dos parâmetros. O SDK deriva o JSON schema, o nome da ferramenta (nome da classe em snake\_case) e o parsing da entrada a partir da classe, e marca a ferramenta com `strict: true` ([uso estrito de ferramentas](https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/strict-tool-use)).

    A função recebe a entrada já processada como uma instância dessa classe e retorna um `BetaToolResultBlockParam.Content`. Para retornar texto, encapsule-o com `BetaToolResultBlockParam.Content.ofString()`. Como a função é uma lambda, ela pode usar objetos da sua aplicação, como o `WeatherService` no exemplo a seguir.

    ```java
    import com.anthropic.client.AnthropicClient;
    import com.anthropic.client.okhttp.AnthropicOkHttpClient;
    import com.anthropic.helpers.BetaRunnableTool;
    import com.anthropic.helpers.BetaToolRunner;
    import com.anthropic.models.beta.messages.BetaMessage;
    import com.anthropic.models.beta.messages.BetaToolResultBlockParam;
    import com.anthropic.models.beta.messages.MessageCreateParams;
    import com.anthropic.models.messages.Model;
    import com.fasterxml.jackson.annotation.JsonClassDescription;
    import com.fasterxml.jackson.annotation.JsonPropertyDescription;

    @JsonClassDescription("Get the current weather in a given location")
    static class GetWeather {
        @JsonPropertyDescription("The city and state, e.g. San Francisco, CA")
        public String location;

        @JsonPropertyDescription("Temperature unit, either 'celsius' or 'fahrenheit'")
        public String unit;
    }

    @JsonClassDescription("Add two numbers together")
    static class CalculateSum {
        @JsonPropertyDescription("First number")
        public double a;

        @JsonPropertyDescription("Second number")
        public double b;
    }

    // Representa uma classe que sua aplicação já tem,
    // como um cliente de banco de dados ou um wrapper de API.
    static class WeatherService {
        String currentWeather(String location, String unit) {
            return "{\"temperature\": \"20°C\", \"condition\": \"Sunny\"}";
        }
    }

    void main() {
        AnthropicClient client = AnthropicOkHttpClient.fromEnv();
        WeatherService weatherService = new WeatherService();

        // A lambda pode usar weatherService.
        BetaRunnableTool getWeather = BetaRunnableTool.of(
                GetWeather.class,
                input -> BetaToolResultBlockParam.Content.ofString(
                        weatherService.currentWeather(input.location, input.unit)));

        BetaRunnableTool calculateSum = BetaRunnableTool.of(
                CalculateSum.class,
                input -> BetaToolResultBlockParam.Content.ofString(
                        String.valueOf(input.a + input.b)));

        BetaToolRunner runner = client.beta()
                .messages()
                .toolRunner(MessageCreateParams.builder()
                        .model(Model.CLAUDE_OPUS_5_5)
                        .maxTokens(1024)
                        .addBeta("structured-outputs-2025-11-13")
                        .addUserMessage("What's the weather like in Paris? Also, what's 15 + 27?")
                        .addTool(getWeather)
                        .addTool(calculateSum)
                        .build());

        for (BetaMessage message : runner) {
            IO.println(message);
        }
    }
    ```

    O nome da classe `CalculateSum` se torna o nome da ferramenta `calculate_sum`, e o SDK gera um JSON schema a partir dos campos anotados:

    ```json
    {
      "name": "calculate_sum",
      "description": "Add two numbers together",
      "input_schema": {
        "type": "object",
        "properties": {
          "a": { "description": "First number", "type": "number" },
          "b": { "description": "Second number", "type": "number" }
        },
        "required": ["a", "b"],
        "additionalProperties": false
      },
      "strict": true
    }
    ```
  </Tab>

  <Tab title="PHP">
    Defina cada ferramenta como um `BetaRunnableTool` que combina a definição de JSON schema da ferramenta com uma closure que a executa.

    ```php
    <?php

    use Anthropic\Client;
    use Anthropic\Beta\Messages\BetaTextBlock;
    use Anthropic\Beta\Messages\BetaToolUseBlock;
    use Anthropic\Lib\Tools\BetaRunnableTool;
    use Anthropic\Messages\Model;

    $client = new Client();

    $getWeather = new BetaRunnableTool(
        definition: [
            'name' => 'get_weather',
            'description' => 'Get the current weather in a given location.',
            'input_schema' => [
                'type' => 'object',
                'properties' => [
                    'location' => [
                        'type' => 'string',
                        'description' => 'The city and state, e.g. San Francisco, CA',
                    ],
                    'unit' => [
                        'type' => 'string',
                        'enum' => ['celsius', 'fahrenheit'],
                    ],
                ],
                'required' => ['location'],
            ],
        ],
        run: fn (array $input): string => json_encode([
            'temperature' => '20°C',
            'condition' => 'Sunny',
        ]),
    );

    $calculateSum = new BetaRunnableTool(
        definition: [
            'name' => 'calculate_sum',
            'description' => 'Add two numbers together.',
            'input_schema' => [
                'type' => 'object',
                'properties' => [
                    'a' => ['type' => 'number', 'description' => 'First number'],
                    'b' => ['type' => 'number', 'description' => 'Second number'],
                ],
                'required' => ['a', 'b'],
            ],
        ],
        run: fn (array $input): string => (string) ($input['a'] + $input['b']),
    );

    $runner = $client->beta->messages->toolRunner(
        maxTokens: 1024,
        messages: [
            ['role' => 'user', 'content' => "What's the weather like in Paris? Also, what's 15 + 27?"],
        ],
        model: Model::CLAUDE_OPUS_5_5,
        tools: [$getWeather, $calculateSum],
    );

    foreach ($runner as $message) {
        foreach ($message->content as $block) {
            switch (true) {
                case $block instanceof BetaTextBlock:
                    echo $block->text, "\n";
                    break;
                case $block instanceof BetaToolUseBlock:
                    echo "[Tool call: {$block->name}]\n";
                    break;
            }
        }
    }
    ```
  </Tab>

  <Tab title="Ruby">
    Use a classe `Anthropic::BaseTool` para definir ferramentas com schemas de entrada tipados.

    ```ruby
    require "anthropic"

    # Inicializa o cliente
    client = Anthropic::Client.new

    # Define o schema de entrada
    class GetWeatherInput < Anthropic::BaseModel
      required :location, String, doc: "The city and state, e.g. San Francisco, CA"
      optional :unit, Anthropic::InputSchema::EnumOf["celsius", "fahrenheit"],
               doc: "Temperature unit"
    end

    # Define a ferramenta
    class GetWeather < Anthropic::BaseTool
      doc "Get the current weather in a given location"
      input_schema GetWeatherInput

      def call(input)
        # Em uma implementação completa, você chamaria uma API de clima aqui
        JSON.generate({temperature: "20°C", condition: "Sunny"})
      end
    end

    class CalculateSumInput < Anthropic::BaseModel
      required :a, Integer, doc: "First number"
      required :b, Integer, doc: "Second number"
    end

    class CalculateSum < Anthropic::BaseTool
      doc "Add two numbers together"
      input_schema CalculateSumInput

      def call(input)
        (input.a + input.b).to_s
      end
    end

    # Usa o tool runner
    runner = client.beta.messages.tool_runner(
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [GetWeather.new, CalculateSum.new],
      messages: [
        {role: "user", content: "What's the weather like in Paris? Also, what's 15 + 27?"}
      ]
    )

    runner.each_message do |message|
      message.content.each do |block|
        puts block.text if block.type == :text
      end
    end
    ```

    A classe `Anthropic::BaseTool` usa o método `doc` para a descrição da ferramenta e `input_schema` para definir os parâmetros esperados. O SDK converte isso automaticamente para o formato de JSON schema apropriado.
  </Tab>
</Tabs>

## Iterando sobre o tool runner

O tool runner é um iterável que produz mensagens de Claude. Em cada iteração, o runner verifica se Claude solicitou um uso de ferramentas. Se sim, ele executa a ferramenta e envia o resultado de volta para Claude automaticamente, e então produz a próxima mensagem de Claude para continuar seu loop.

Você pode encerrar o loop em qualquer iteração com uma instrução `break`. O runner continua em loop até que Claude retorne uma mensagem sem uso de ferramentas, ou até atingir `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`), se você o definir.

Se você não precisa das mensagens intermediárias, pode obter a mensagem final diretamente:

<Tabs exclude="shell">
  <Tab title="Python">
    Use `runner.until_done()` para obter a mensagem final.

    ```python
    client = anthropic.Anthropic()
    # ...
    runner = client.beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=1024,
        tools=[get_weather, calculate_sum],
        messages=[
            {
                "role": "user",
                "content": "What's the weather like in Paris? Also, what's 15 + 27?",
            }
        ],
    )
    final_message = runner.until_done()
    for block in final_message.content:
        if block.type == "text":
            print(block.text)
    ```
  </Tab>

  <Tab title="TypeScript">
    Use `await` no runner para obter a mensagem final.

    ```typescript
    const client = new Anthropic();
    // ...
    const runner = client.beta.messages.toolRunner({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [getWeatherTool],
      messages: [{ role: "user", content: "What's the weather like in Paris?" }]
    });

    const finalMessage = await runner;
    for (const block of finalMessage.content) {
      if (block.type === "text") {
        console.log(block.text);
      }
    }
    ```
  </Tab>

  <Tab title="C#">
    Use `runner.RunUntilDoneAsync()` para obter a mensagem final.

    ```csharp
    var client = new AnthropicClient();
    // ...
    var runner = client.Beta.Messages.ToolRunner(
        new MessageCreateParams
        {
            Model = Model.ClaudeOpus5_5,
            MaxTokens = 1024,
            Messages =
            [
                new()
                {
                    Role = Role.User,
                    Content = "What's the weather like in Paris?",
                },
            ],
        },
        [getWeatherTool]
    );

    var finalMessage = await runner.RunUntilDoneAsync();
    foreach (var block in finalMessage.Content)
    {
        if (block.TryPickText(out var textBlock))
        {
            Console.WriteLine(textBlock.Text);
        }
    }
    ```
  </Tab>

  <Tab title="Go">
    Use `runner.RunToCompletion(ctx)` para obter a mensagem final.

    ```go
    client := anthropic.NewClient()
    ctx := context.Background()
    // ...
    runner := client.Beta.Messages.NewToolRunner(
    	[]anthropic.BetaTool{getWeather},
    	anthropic.BetaToolRunnerParams{
    		BetaMessageNewParams: anthropic.BetaMessageNewParams{
    			Model:     anthropic.ModelClaudeOpus5_5,
    			MaxTokens: 1024,
    			Messages: []anthropic.BetaMessageParam{
    				anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(
    					"What's the weather like in Paris?",
    				)),
    			},
    		},
    	},
    )

    finalMessage, err := runner.RunToCompletion(ctx)
    if err != nil {
    	log.Fatal(err)
    }
    for _, block := range finalMessage.Content {
    	if textBlock, ok := block.AsAny().(anthropic.BetaTextBlock); ok {
    		fmt.Println(textBlock.Text)
    	}
    }
    ```
  </Tab>

  <Tab title="Java">
    O SDK Java não tem um atalho `until_done()`. Itere até o fim e mantenha a última mensagem.

    ```java
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();

    BetaToolRunner runner = client.beta()
            .messages()
            .toolRunner(MessageCreateParams.builder()
                    .model(Model.CLAUDE_OPUS_5_5)
                    .maxTokens(1024)
                    .addBeta("structured-outputs-2025-11-13")
                    .addUserMessage("What's the weather like in Paris? Also, what's 15 + 27?")
                    .addTool(getWeather)
                    .addTool(calculateSum)
                    .build());

    BetaMessage finalMessage = null;
    for (BetaMessage message : runner) {
        finalMessage = message;
    }
    for (BetaContentBlock block : finalMessage.content()) {
        block.text().ifPresent(textBlock -> IO.println(textBlock.text()));
    }
    ```
  </Tab>

  <Tab title="PHP">
    Use `runUntilDone()` para obter a mensagem final.

    ```php
    $client = new Client();
    // ...
    $runner = $client->beta->messages->toolRunner(
        maxTokens: 1024,
        messages: [
            ['role' => 'user', 'content' => "What's the weather like in Paris? Also, what's 15 + 27?"],
        ],
        model: Model::CLAUDE_OPUS_5_5,
        tools: [$getWeather, $calculateSum],
    );

    $finalMessage = $runner->runUntilDone();
    foreach ($finalMessage->content as $block) {
        if ($block->type === 'text') {
            echo $block->text, "\n";
        }
    }
    ```
  </Tab>

  <Tab title="Ruby">
    Use `runner.run_until_finished` para obter todas as mensagens.

    ```ruby
    client = Anthropic::Client.new
    # ...
    runner = client.beta.messages.tool_runner(
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [GetWeather.new, CalculateSum.new],
      messages: [
        {role: "user", content: "What's the weather like in Paris? Also, what's 15 + 27?"}
      ]
    )

    all_messages = runner.run_until_finished
    all_messages.each { |msg| puts msg.content }
    ```
  </Tab>
</Tabs>

## Uso avançado

Dentro do loop, você pode ler cada mensagem de resposta e modificar o estado do runner antes da próxima chamada à API. Cada iteração segue este ciclo de vida:

1. O runner envia uma requisição para a Messages API com seu estado atual.

2. O runner produz a mensagem de resposta para o corpo do seu loop.

3. O corpo do seu loop é executado. Você pode ler a mensagem e, opcionalmente, modificar o estado do runner.

4. Quando o corpo do seu loop retorna, o runner verifica se você modificou o histórico de mensagens dele.

   * **Se você não modificou o histórico de mensagens:** Se a mensagem contém chamadas de ferramentas, o runner anexa a mensagem do assistente e os resultados das ferramentas, e então continua. Se não houver chamadas de ferramentas, o loop termina.
   * **Se você modificou o histórico de mensagens:** O runner pula sua anexação automática e usa seu estado sem alterações. Consulte [Assumindo o controle do histórico de mensagens](https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/tool-runner#taking-over-message-history).

```mermaid
sequenceDiagram
  accTitle: The tool runner loop
  accDescr: In each iteration, the tool runner sends a request with the current state to the Messages API. It receives the response message and yields it to your code. Your loop body runs, then the tool runner resumes. If the message history is unchanged and there are tool calls, it appends the assistant message and the tool results and continues. If there are none, it exits the loop. If the message history changed, it uses your state unchanged.
  participant U as Your code
  participant TR as ToolRunner
  participant API as Messages API

  loop For each iteration
    TR->>API: Send request with current state
    API-->>TR: Response message
    TR-->>U: Yield message
    note over U: Your loop body runs
    U->>TR: Resume
    alt Message history unchanged
      TR->>TR: If tool calls, append assistant<br/>message + tool results and continue.<br/>If none, exit the loop
    else Message history changed
      TR->>TR: Use your state unchanged
    end
  end
```

### Assumindo o controle do histórico de mensagens

Por padrão, o runner gerencia o estado da conversa para você: após cada turno com chamada de ferramenta, ele anexa a mensagem do assistente e quaisquer resultados de ferramentas ao seu próprio histórico de mensagens. Você assume o controle do histórico de mensagens quando deseja repetir um turno (descartar a resposta e reenviar), injetar uma mensagem de acompanhamento ou construir o resultado da ferramenta você mesmo.

Você assume o controle modificando as mensagens do runner de dentro do corpo do loop. O método exato depende do SDK. Consulte as abas por linguagem a seguir.

Quando você assume o controle em uma iteração, o runner não anexa a mensagem do assistente nem os resultados das ferramentas daquele turno. Você passa a ser responsável por manter a conversa válida: anexe você mesmo a mensagem do assistente e um resultado de ferramenta (se quiser que o turno conte), modifique o estado condicionalmente para que o loop ainda possa terminar quando não houver chamadas de ferramentas, e passe `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`) para limitar o loop. Todos os sete SDKs suportam `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`).

<Tabs exclude="shell">
  <Tab title="Python">
    Use `generate_tool_call_response()` para inspecionar ou calcular o resultado da ferramenta. Chamar `append_messages()` dentro do loop informa ao runner que você está gerenciando o histórico por conta própria, portanto inclua a mensagem do assistente e o resultado da ferramenta no que você anexar.

    ```python
    runner = client.beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=1024,
        max_iterations=10,
        tools=[get_weather],
        messages=[{"role": "user", "content": "What's the weather in San Francisco?"}],
    )

    for message in runner:
        tool_response = runner.generate_tool_call_response()
        if tool_response is not None:
            # append_messages() marca o estado como modificado, então o runner pula o
            # append automático nesta iteração. Faça o append da mensagem do assistente
            # e do tool result você mesmo, além de qualquer follow-up.
            runner.append_messages(
                message,
                tool_response,
                {"role": "user", "content": "Please be concise."},
            )
        # Quando não há chamada de ferramenta, deixe o estado intacto para o loop sair.
    ```

    Para alterar parâmetros de requisição como `max_tokens` sem assumir o controle do histórico de mensagens, use `set_messages_params()`. O runner ainda anexa a mensagem do assistente e o resultado da ferramenta automaticamente.

    ```python
    for message in runner:
        runner.set_messages_params(lambda params: {**params, "max_tokens": 2048})
    ```
  </Tab>

  <Tab title="TypeScript">
    Use `runner.params` para ler os parâmetros de requisição atuais e `setMessagesParams()` para substituí-los. Chamar `setMessagesParams()` ou `pushMessages()` dentro do loop informa ao runner que você está gerenciando o estado por conta própria: a mensagem do assistente e o resultado da ferramenta desta iteração são descartados, e a próxima requisição é enviada com o seu estado.

    O exemplo a seguir repete uma resposta truncada com um orçamento maior de `max_tokens`.

    ```typescript
    const runner = client.beta.messages.toolRunner({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      max_iterations: 10,
      tools: [getWeatherTool],
      messages: [
        {
          role: "user",
          content: "Give me a detailed weather report for every major US city."
        }
      ]
    });

    const MAX_TOKEN_CEILING = 8192;

    for await (const message of runner) {
      if (message.stop_reason === "max_tokens") {
        const current = runner.params.max_tokens;
        if (current >= MAX_TOKEN_CEILING) {
          console.warn(`Hit ceiling (${MAX_TOKEN_CEILING}); stopping.`);
          break;
        }
        const doubled = Math.min(current * 2, MAX_TOKEN_CEILING);
        console.log(`Response truncated at ${current} tokens; retrying with ${doubled}.`);
        // Aumente o orçamento. setMessagesParams() marca o estado como modificado, então
        // o runner NÃO anexa a mensagem truncada. A próxima iteração tenta novamente
        // o mesmo turno com o orçamento maior.
        runner.setMessagesParams((params) => ({ ...params, max_tokens: doubled }));
      }
      // Caso contrário, deixe o estado intacto para o runner anexar automaticamente e continuar.
    }
    ```
  </Tab>

  <Tab title="C#">
    Chamar `SetParams()` ou `PushMessages()` marca o estado como modificado, o que faz com que o runner pule sua anexação automática naquele turno. O runner C# ainda executa as ferramentas correspondentes daquele turno e descarta seus resultados construídos automaticamente, portanto uma ferramenta que você também executa por conta própria dentro do corpo do loop é executada duas vezes, a menos que você leve isso em conta. Quando você assume o controle, faça o push da mensagem do assistente e de um resultado de ferramenta você mesmo. Caso contrário, a conversa não avançará. O runner C# sempre termina quando uma resposta não tem chamadas de ferramentas, portanto condicione qualquer mutação de estado à presença de um bloco `tool_use`.

    ```csharp
    var runner = client.Beta.Messages.ToolRunner(
        new MessageCreateParams
        {
            Model = Model.ClaudeOpus5_5,
            MaxTokens = 1024,
            Messages = [new() { Role = Role.User, Content = "What's the weather in San Francisco?" }],
        },
        [getWeatherTool],
        maxIterations: 10
    );

    await foreach (var message in runner)
    {
        var toolUseBlock = message
            .Content.Select(block => block.TryPickToolUse(out var toolUse) ? toolUse : null)
            .FirstOrDefault(toolUse => toolUse is not null);

        if (toolUseBlock is null)
        {
            // Sem chamada de ferramenta: deixe o estado intacto para o loop sair normalmente.
            continue;
        }

        // Execute a ferramenta você mesmo e construa o bloco de resultado.
        var toolResult = new BetaToolResultBlockParam(toolUseBlock.ID)
        {
            Content = await getWeatherTool.ExecuteAsync(toolUseBlock, default),
        };

        // PushMessages() marca o estado como modificado; o runner pula o auto-append.
        // Forneça o turno do assistente e o resultado da ferramenta, depois um follow-up.
        runner.PushMessages(
            new()
            {
                Role = Role.Assistant,
                Content = new BetaMessageParamContent(
                    JsonSerializer.SerializeToElement(
                        message.Content.Select(block => block.Json).ToArray()
                    )
                ),
            },
            new()
            {
                Role = Role.User,
                Content = new List<BetaContentBlockParam> { toolResult },
            },
            new() { Role = Role.User, Content = "Please be concise in your response." }
        );
    }
    ```
  </Tab>

  <Tab title="Go">
    O runner Go expõe os parâmetros como um campo público `Params`. Modificar `runner.Params` entre chamadas a `NextMessage(ctx)` se aplica à próxima requisição à API. Diferentemente de outros SDKs, o runner Go sempre anexa a mensagem do assistente e os resultados das ferramentas incondicionalmente. Modificar `Params` não suprime essa etapa.

    ```go
    runner := client.Beta.Messages.NewToolRunner(
    	[]anthropic.BetaTool{getWeather},
    	anthropic.BetaToolRunnerParams{
    		BetaMessageNewParams: anthropic.BetaMessageNewParams{
    			Model:     anthropic.ModelClaudeOpus5_5,
    			MaxTokens: 1024,
    			Messages: []anthropic.BetaMessageParam{
    				anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(
    					"What's the weather in San Francisco?",
    				)),
    			},
    		},
    		MaxIterations: 10,
    	},
    )

    for {
    	message, err := runner.NextMessage(ctx)
    	if err != nil {
    		log.Fatal(err)
    	}
    	if message == nil {
    		break // conversation complete
    	}

    	// O runner em Go sempre anexa a mensagem do assistente e os resultados das ferramentas.
    	// Alterações de parâmetros aqui se aplicam à próxima iteração.
    	runner.Params.MaxTokens = 2048
    }
    ```
  </Tab>

  <Tab title="Java">
    Use `runner.params()` para ler os parâmetros atuais e `runner.setNextParams()` para substituí-los na próxima iteração. Quando você chama `setNextParams()` dentro do loop, o runner pula sua anexação automática. A mensagem recém-produzida é descartada, e a próxima iteração envia seus novos parâmetros sem alterações.

    O exemplo a seguir repete um turno que atingiu o limite de tokens dobrando `max_tokens`. Mutar apenas no ramo de `max_tokens` mantém o loop convergindo: turnos que terminam normalmente seguem adiante, e o runner anexa automaticamente e termina quando não há mais chamadas de ferramentas.

    ```java
    BetaToolRunner runner = client.beta()
            .messages()
            .toolRunner(ToolRunnerCreateParams.builder()
                    .initialMessageParams(MessageCreateParams.builder()
                            .model(Model.CLAUDE_OPUS_5_5)
                            .maxTokens(1024)
                            .addBeta("structured-outputs-2025-11-13")
                            .addUserMessage("Give me a detailed weather report for every major US city.")
                            .addTool(getWeather)
                            .build())
                    .maxIterations(10L)
                    .build());

    long ceiling = 8192;

    for (BetaMessage message : runner) {
        if (BetaStopReason.MAX_TOKENS.equals(message.stopReason().orElse(null))) {
            long current = runner.params().maxTokens();
            if (current >= ceiling) {
                IO.println("Hit ceiling (" + ceiling + "), accepting truncated response.");
                break;
            }
            long doubled = Math.min(current * 2, ceiling);
            IO.println("Response truncated at " + current + " tokens, retrying with " + doubled + ".");

            // Chamar setNextParams() marca este turno como gerenciado pelo usuário: o runner
            // NÃO anexa automaticamente a mensagem truncada, então a próxima iteração
            // reenvia o mesmo prefixo de conversa com o orçamento maior.
            runner.setNextParams(runner.params().toBuilder().maxTokens(doubled).build());
        }
        // Sem mutação em um turno normal: o runner anexa automaticamente e continua.
    }
    ```
  </Tab>

  <Tab title="PHP">
    Use `setMessagesParams()` e `pushMessages()` para modificar o estado do runner, e `getParams()` para lê-lo. Chamar qualquer um dos setters dentro do loop informa ao runner para pular sua anexação automática, de modo que a conversa continua a partir do seu estado modificado.

    O exemplo a seguir dobra `max_tokens` e repete quando uma resposta é cortada.

    ```php
    use Anthropic\Beta\Messages\BetaStopReason;

    $runner = $client->beta->messages->toolRunner(
        maxTokens: 1024,
        messages: [
            ['role' => 'user', 'content' => 'Give a detailed weather report for every major US city.'],
        ],
        model: Model::CLAUDE_OPUS_5_5,
        tools: [$getWeather],
        maxIterations: 10,
    );

    $maxTokenCeiling = 8192;

    foreach ($runner as $message) {
        if ($message->stopReason === BetaStopReason::MAX_TOKENS->value) {
            $current = $runner->getParams()['maxTokens'];

            if ($current >= $maxTokenCeiling) {
                echo "Hit ceiling ({$maxTokenCeiling}), accepting truncated response.\n";
                break;
            }

            $doubled = min($current * 2, $maxTokenCeiling);
            echo "Response truncated at {$current} tokens, retrying with {$doubled}.\n";

            // Chamar setMessagesParams() dentro do loop instrui o runner a pular
            // seu append automático. A mensagem truncada é descartada; a próxima
            // iteração tenta novamente com o orçamento maior.
            // As chaves estão em camelCase, correspondendo aos parâmetros nomeados de toolRunner().
            $runner->setMessagesParams(['maxTokens' => $doubled]);
        }
    }
    ```
  </Tab>

  <Tab title="Ruby">
    Use `next_message` para controle passo a passo. Quando `next_message` retorna, a mensagem do assistente e o resultado da ferramenta daquele turno já foram anexados. Use `feed_messages` para injetar mensagens de acompanhamento entre turnos, e `runner.params.update(...)` para alterar parâmetros de requisição no local.

    Você assume o controle do histórico de mensagens quando, de dentro de um bloco `each_message` ou `each_streaming`, você reatribui `runner.params[:messages]` ou chama `feed_messages`. O padrão a seguir chama `feed_messages` entre chamadas a `next_message`, o que não assume o controle.

    ```ruby
    runner = client.beta.messages.tool_runner(
      model: "claude-opus-5-5",
      max_tokens: 1024,
      max_iterations: 10,
      tools: [GetWeather.new],
      messages: [{role: "user", content: "What's the weather in San Francisco?"}]
    )

    # Avança o runner uma vez. A mensagem do assistente e o resultado da ferramenta são anexados
    # a runner.params[:messages] antes que next_message retorne.
    message = runner.next_message
    puts message.content

    # Injeta um follow-up antes de continuar. feed_messages recebe um splat, não um array.
    runner.feed_messages({role: "user", content: "Also check Boston."})

    # Altera parâmetros no local. Reatribuir runner.params[:messages] assume o controle do
    # histórico de mensagens apenas quando ocorre dentro de um bloco each_message ou each_streaming.
    runner.params.update(max_tokens: 2048)

    runner.run_until_finished
    ```
  </Tab>
</Tabs>

### Gerenciamento automático de contexto

Para tarefas agênticas de longa duração, os tool runners de TypeScript e Ruby suportam [compactação](https://platform.claude.com/docs/pt-BR/build-with-claude/context-editing#client-side-compaction-sdk) automática, que gera resumos quando o uso de tokens excede um limite, para que a conversa possa continuar além dos limites da "context window" (janela de contexto). Ambos os SDKs descontinuaram essa opção do lado do cliente em favor da [compactação do lado do servidor](https://platform.claude.com/docs/pt-BR/build-with-claude/compaction-threshold), que funciona com o tool runner de todos os SDKs por meio do parâmetro de requisição `context_management`. O SDK Python (v1.0 e posteriores) e os tool runners de Go, Java, C# e PHP não incluem compactação do lado do cliente. O tool runner tem um helper `compact_before_next_turn()` (typescript, java, php: `compactBeforeNextTurn()`; csharp, go: `CompactBeforeNextTurn()`) para compactação sob demanda. Consulte [Compactar em um loop](https://platform.claude.com/docs/pt-BR/build-with-claude/compaction-on-demand#compact-in-a-loop). Use-o ou uma edição de compactação `context_management` em um runner, não ambos.

### Depurando a execução de ferramentas

Quando uma ferramenta lança uma exceção, o tool runner a captura e retorna o erro para Claude como um resultado de ferramenta com `is_error: true`. O resultado da ferramenta carrega a mensagem da exceção (em Python, seu tipo e mensagem), não o stack trace completo.

O que o SDK registra em log é específico de cada linguagem. O SDK Python registra a exceção completa, incluindo seu stack trace, por meio do módulo padrão `logging` sempre que uma ferramenta lança uma exceção não tratada. Os SDKs Python, TypeScript e Java leem a variável de ambiente `ANTHROPIC_LOG` para ativar o logging do SDK, que inclui detalhes de requisição e resposta:

```bash
# Registrar no nível info
export ANTHROPIC_LOG=info

# Registrar no nível debug para uma saída mais detalhada
export ANTHROPIC_LOG=debug
```

Os SDKs Go, Ruby, C# e PHP não leem `ANTHROPIC_LOG`. Fora do Python, nenhum SDK registra em log uma ferramenta que falhou: para ver por que uma ferramenta falhou, capture e registre a exceção dentro da função da ferramenta antes de retorná-la ou relançá-la.

### Interceptando erros de ferramentas

Por padrão, os erros de ferramentas são repassados para Claude, que pode então responder adequadamente. No entanto, você pode querer detectar erros e tratá-los de forma diferente, por exemplo, para interromper a execução antecipadamente ou implementar tratamento de erros personalizado.

<Tabs exclude="shell">
  <Tab title="Python">
    ```python
    client = anthropic.Anthropic()
    # ...
    runner = client.beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=1024,
        tools=[my_tool],
        messages=[{"role": "user", "content": "Run my_tool with the query 'hello'."}],
    )

    for message in runner:
        tool_response = runner.generate_tool_call_response()

        if tool_response is not None:
            # tool_response é um dict: {"role": "user", "content": [...]}
            # Verifica se algum resultado de ferramenta tem erro
            for block in tool_response["content"]:
                if block.get("is_error"):
                    # Opção 1: lançar uma exceção para interromper o loop
                    raise RuntimeError(f"Tool failed: {json.dumps(block['content'])}")

                    # Opção 2: registrar no log e continuar (deixar o Claude lidar com isso)
                    # logger.error(f"Tool error: {json.dumps(block['content'])}")

        # Processa a mensagem normalmente
        print(message.content)
    ```

    <Note>
      Chame 

      `runner.generate_tool_call_response()`

       no loop para obter os resultados das ferramentas e verificá-los quanto a erros antes que o runner os envie para Claude.
    </Note>
  </Tab>

  <Tab title="TypeScript">
    ```typescript
    const client = new Anthropic();
    // ...
    const runner = client.beta.messages.toolRunner({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [myTool],
      messages: [{ role: "user", content: "Run my_tool with the query 'hello'." }]
    });

    for await (const message of runner) {
      const toolResultMessage = await runner.generateToolResponse();

      if (toolResultMessage && typeof toolResultMessage.content !== "string") {
        // Verifica se algum resultado de ferramenta tem erro
        for (const block of toolResultMessage.content) {
          if (block.type === "tool_result" && block.is_error) {
            // Opção 1: lançar uma exceção para interromper o loop
            throw new Error(`Tool failed: ${JSON.stringify(block.content)}`);

            // Opção 2: registrar no log e continuar (deixar o Claude lidar com isso)
            // console.error(`Tool error: ${JSON.stringify(block.content)}`);
          }
        }
      }

      // Processa a mensagem normalmente
      console.log(message.content);
    }
    ```

    <Note>
      Chame 

      `runner.generateToolResponse()`

       no loop para obter os resultados das ferramentas e verificá-los quanto a erros antes que o runner os envie para Claude.
    </Note>
  </Tab>

  <Tab title="C#">
    O tool runner C# não expõe um hook para inspecionar o resultado da ferramenta antes que ele seja enviado para Claude. Para controlar o conteúdo do erro, lance `BetaToolError` de dentro do corpo da ferramenta. O runner o converte em um `tool_result` com `is_error: true` e o conteúdo que você fornecer.

    ```csharp
    var client = new AnthropicClient();

    var getWeatherTool = new BetaRunnableTool
    {
        Name = "get_weather",
        Definition = new BetaTool
        {
            Name = "get_weather",
            Description = "Get the current weather in a given location.",
            InputSchema = new InputSchema
            {
                Properties = new Dictionary<string, JsonElement>
                {
                    ["location"] = JsonSerializer.SerializeToElement(new { type = "string" }),
                },
                Required = ["location"],
            },
        },
        Run = async (toolUse, cancellationToken) =>
        {
            try
            {
                return await CallExternalWeatherService(
                    toolUse.Input["location"].GetString()!,
                    cancellationToken
                );
            }
            catch (HttpRequestException ex)
            {
                // Registre um log aqui se precisar inspecionar a falha antes que o Claude a veja.
                throw new BetaToolError($"Weather service unavailable: {ex.Message}");
            }
        },
    };

    var runner = client.Beta.Messages.ToolRunner(
        new MessageCreateParams
        {
            Model = Model.ClaudeOpus5_5,
            MaxTokens = 1024,
            Messages =
            [
                new() { Role = Role.User, Content = "What's the weather in San Francisco?" },
            ],
        },
        [getWeatherTool]
    );

    Console.WriteLine(await runner.RunUntilDoneAsync());
    ```
  </Tab>

  <Tab title="Go">
    Interceptar erros de ferramentas antes que sejam enviados para Claude não é suportado atualmente no SDK Go. O runner converte internamente um erro retornado pelo seu handler em um resultado de ferramenta com `is_error: true`. Para personalizar o conteúdo do erro, capture o erro dentro do seu handler e retorne um resultado em vez de retornar o erro.
  </Tab>

  <Tab title="Java">
    Interceptar erros de ferramentas antes que sejam enviados para Claude não é suportado atualmente no SDK Java. O runner captura qualquer exceção lançada pela função de uma ferramenta e a converte automaticamente em um resultado de ferramenta com `is_error: true`. Para controlar o conteúdo do erro, capture a exceção dentro da função e retorne seu próprio conteúdo.
  </Tab>

  <Tab title="PHP">
    O tool runner PHP atualmente não expõe os resultados de ferramentas antes de serem anexados. Exceções lançadas pela closure `run` de uma ferramenta são capturadas e enviadas para Claude como resultados de ferramentas com `is_error: true` automaticamente. Para inspecionar ou substituir o conteúdo do erro, use o padrão manual `pushMessages()` mostrado em [Modificando resultados de ferramentas](https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/tool-runner#modifying-tool-results).
  </Tab>

  <Tab title="Ruby">
    ```ruby
    client = Anthropic::Client.new
    # ...
    runner = client.beta.messages.tool_runner(
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [MyTool.new],
      messages: [{role: "user", content: "Run my_tool with the query 'hello'."}]
    )

    loop do
      message = runner.next_message
      break unless message

      # Quando next_message retorna, o runner já executou as ferramentas deste turno e
      # anexou os resultados como a última mensagem (papel user). Inspecione-os aqui,
      # antes que a próxima requisição os envie ao Claude.
      tool_results = runner.params[:messages].last

      if tool_results && tool_results[:role] == :user && tool_results[:content].is_a?(Array)
        tool_results[:content].each do |block|
          if block[:type] == :tool_result && block[:is_error]
            # Opção 1: lançar uma exceção para interromper o loop
            raise "Tool failed: #{block[:content]}"

            # Opção 2: registrar no log e continuar (deixar o Claude lidar com isso)
            # logger.error("Tool error: #{block[:content]}")
          end
        end
      end

      puts message.content
      break if message.stop_reason != :tool_use
    end
    ```

    <Note>
      O tool runner Ruby não tem um hook que retorne os resultados das ferramentas. Assim que 

      `runner.next_message`

       retorna, eles são a última entrada em 

      `runner.params[:messages]`

      , então verifique-os ali antes que a próxima requisição os envie para Claude.
    </Note>
  </Tab>
</Tabs>

### Modificando resultados de ferramentas

Você pode modificar os resultados de ferramentas antes que sejam enviados de volta para Claude. Isso é útil para adicionar metadados como `cache_control` para habilitar o "prompt caching" (cache de prompt) em resultados de ferramentas, ou para transformar a saída da ferramenta. Consulte [cache de prompt](https://platform.claude.com/docs/pt-BR/build-with-claude/prompt-caching).

<Tabs exclude="shell">
  <Tab title="Python">
    ```python
    client = anthropic.Anthropic()
    # ...
    runner = client.beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=1024,
        tools=[search_documents],
        messages=[
            {
                "role": "user",
                "content": "Search for information about the climate of San Francisco",
            }
        ],
    )

    for message in runner:
        tool_response = runner.generate_tool_call_response()

        if tool_response is not None:
            # tool_response é um dict: {"role": "user", "content": [...]}
            # Modifica o resultado da ferramenta para adicionar controle de cache
            for block in tool_response["content"]:
                if block["type"] == "tool_result":
                    # Adiciona cache_control para armazenar em cache este resultado da ferramenta
                    block["cache_control"] = {"type": "ephemeral"}

            # Anexa a resposta modificada (isso impede o anexo automático da original)
            runner.append_messages(message, tool_response)

        print(message.content)
    ```

    <Note>
      Chame 

      `runner.generate_tool_call_response()`

       para obter o resultado da ferramenta, modifique-o e passe-o para 

      `runner.append_messages()`

       para que o runner não anexe o original.
    </Note>
  </Tab>

  <Tab title="TypeScript">
    ```typescript
    const client = new Anthropic();
    // ...
    const runner = client.beta.messages.toolRunner({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [searchDocuments],
      messages: [
        { role: "user", content: "Search for information about the climate of San Francisco" }
      ]
    });

    for await (const message of runner) {
      const toolResultMessage = await runner.generateToolResponse();

      if (toolResultMessage && typeof toolResultMessage.content !== "string") {
        // Modifique o resultado da ferramenta para adicionar controle de cache
        for (const block of toolResultMessage.content) {
          if (block.type === "tool_result") {
            // Adicione cache_control para armazenar em cache este resultado da ferramenta
            block.cache_control = { type: "ephemeral" };
          }
        }
        // Não é preciso chamar pushMessages: o runner anexa automaticamente tanto a mensagem
        // do assistente quanto a resposta da ferramenta em cache (agora modificada).
      }

      console.log(message.content);
    }
    ```

    <Note>
      Chame 

      `runner.generateToolResponse()`

       para obter o resultado da ferramenta e, em seguida, modifique-o no local. O runner anexa o resultado modificado para você.
    </Note>
  </Tab>

  <Tab title="C#">
    Modificar resultados de ferramentas antes de serem anexados (por exemplo, para adicionar `cache_control`) não é suportado atualmente no SDK C#. O runner constrói o bloco `tool_result` internamente e não fornece nenhum hook para alterá-lo.
  </Tab>

  <Tab title="Go">
    O runner Go não expõe um hook para modificar o bloco `tool_result` externo. Você pode, no entanto, definir `cache_control` nos blocos de conteúdo internos que seu handler retorna.

    ```go
    client := anthropic.NewClient()
    ctx := context.Background()

    searchDocuments, err := toolrunner.NewBetaToolFromJSONSchema(
    	"search_documents",
    	"Search documents for relevant information.",
    	func(ctx context.Context, input SearchDocumentsInput) (anthropic.BetaToolResultBlockParamContentUnion, error) {
    		return anthropic.BetaToolResultBlockParamContentUnion{
    			OfText: &anthropic.BetaTextBlockParam{
    				Text: fmt.Sprintf("Found 3 documents matching: %s", input.Query),
    				// Defina cache_control no bloco de conteúdo interno. O cache_control
    				// do bloco tool_result externo atualmente não pode
    				// ser definido pelo runner de Go.
    				CacheControl: anthropic.NewBetaCacheControlEphemeralParam(),
    			},
    		}, nil
    	},
    )
    if err != nil {
    	log.Fatal(err)
    }

    runner := client.Beta.Messages.NewToolRunner(
    	[]anthropic.BetaTool{searchDocuments},
    	anthropic.BetaToolRunnerParams{
    		BetaMessageNewParams: anthropic.BetaMessageNewParams{
    			Model:     anthropic.ModelClaudeOpus5_5,
    			MaxTokens: 1024,
    			Messages: []anthropic.BetaMessageParam{
    				anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(
    					"Search for information about the climate of San Francisco",
    				)),
    			},
    		},
    	},
    )

    finalMessage, err := runner.RunToCompletion(ctx)
    if err != nil {
    	log.Fatal(err)
    }
    fmt.Println(finalMessage)
    ```
  </Tab>

  <Tab title="Java">
    Para definir `cache_control` em um resultado de ferramenta, construa o `BetaToolResultBlockParam.Content` retornado com `ofBlocks()` em vez de `ofString()` e defina `cacheControl` no bloco de texto interno. O runner atualmente não suporta definir `cache_control` no bloco `tool_result` externo.

    ```java
    @JsonClassDescription("Look up reference documentation for a topic")
    static class SearchDocuments {
        @JsonPropertyDescription("The search query")
        public String query;
    }

    BetaRunnableTool searchDocuments = BetaRunnableTool.of(SearchDocuments.class, input -> {
        String largeResult = "..."; // a long document worth caching
        return BetaToolResultBlockParam.Content.ofBlocks(List.of(
                BetaToolResultBlockParam.Content.Block.ofText(
                        BetaTextBlockParam.builder()
                                .text(largeResult)
                                .cacheControl(BetaCacheControlEphemeral.builder().build())
                                .build())));
    });
    ```
  </Tab>

  <Tab title="PHP">
    O tool runner PHP não tem callback para mutar o bloco `tool_result` gerado automaticamente. Para adicionar campos como `cache_control`, construa o resultado da ferramenta você mesmo e faça o push dele. Chamar `pushMessages()` pula a anexação automática do runner naquele turno.

    ```php
    $client = new Client();
    // ...
    $runner = $client->beta->messages->toolRunner(
        maxTokens: 1024,
        messages: [
            ['role' => 'user', 'content' => 'Search for information about the climate of San Francisco.'],
        ],
        model: Model::CLAUDE_OPUS_5_5,
        tools: [$searchDocuments],
    );

    foreach ($runner as $message) {
        $toolResults = [];
        foreach ($message->content as $block) {
            if ($block instanceof \Anthropic\Beta\Messages\BetaToolUseBlock) {
                $toolResults[] = [
                    'type' => 'tool_result',
                    'tool_use_id' => $block->id,
                    'content' => $searchDocuments->run($block->input),
                    // Adicione cache_control para armazenar em cache este resultado de ferramenta
                    'cache_control' => ['type' => 'ephemeral'],
                ];
            }
        }

        if ($toolResults !== []) {
            // pushMessages() marca o estado como modificado, então o runner ignora sua
            // adição automática. Adicione a mensagem do assistente e os resultados das ferramentas.
            $runner->pushMessages(
                ['role' => 'assistant', 'content' => $message->content],
                ['role' => 'user', 'content' => $toolResults],
            );
        }
        // Sem chamada de ferramenta: deixe o estado inalterado para que o loop termine.
    }
    ```
  </Tab>

  <Tab title="Ruby">
    ```ruby
    client = Anthropic::Client.new
    # ...
    runner = client.beta.messages.tool_runner(
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [SearchDocuments.new],
      messages: [{role: "user", content: "Search for information about the climate of San Francisco"}]
    )

    loop do
      message = runner.next_message
      break unless message

      # Acesse os resultados de ferramentas mais recentes no array de mensagens
      # O runner adiciona os resultados de ferramentas automaticamente, mas você pode modificá-los
      tool_results_message = runner.params[:messages].last

      if tool_results_message && tool_results_message[:role] == :user && tool_results_message[:content].is_a?(Array)
        tool_results_message[:content].each do |block|
          if block[:type] == :tool_result
            # Modifique o resultado da ferramenta para adicionar controle de cache
            block[:cache_control] = {type: "ephemeral"}
          end
        end
      end

      puts message.content
      break if message.stop_reason != :tool_use
    end
    ```

    <Note>
      O tool runner Ruby não tem um hook que retorne os resultados das ferramentas. Assim que 

      `runner.next_message`

       retorna, eles são a última entrada em 

      `runner.params[:messages]`

      , então modifique-os ali antes que a próxima requisição os envie para Claude.
    </Note>
  </Tab>
</Tabs>

<Tip>
  Adicionar `cache_control` a resultados de ferramentas é particularmente útil quando as ferramentas retornam grandes quantidades de dados (como resultados de busca em documentos) que você deseja armazenar em cache para chamadas subsequentes à API. Consulte [Cache de prompt](https://platform.claude.com/docs/pt-BR/build-with-claude/prompt-caching) para mais detalhes sobre estratégias de cache.
</Tip>

## Streaming

Habilite o streaming para processar a resposta de cada turno de forma incremental. Cada iteração produz um objeto de stream que você pode iterar para obter eventos.

<Tabs exclude="shell">
  <Tab title="Python">
    Defina `stream=True` e use `get_final_message()` para obter a mensagem acumulada.

    ```python
    client = anthropic.Anthropic()
    # ...
    runner = client.beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=1024,
        tools=[calculate_sum],
        messages=[{"role": "user", "content": "What is 15 + 27?"}],
        stream=True,
    )

    # Ao usar streaming, o runner retorna BetaMessageStream
    for message_stream in runner:
        for event in message_stream:
            print("event:", event)
        print("message:", message_stream.get_final_message())

    print(runner.until_done())
    ```
  </Tab>

  <Tab title="TypeScript">
    Defina `stream: true` e use `finalMessage()` para obter a mensagem acumulada.

    ```typescript
    const client = new Anthropic();
    // ...
    const runner = client.beta.messages.toolRunner({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      messages: [{ role: "user", content: "What is the weather in San Francisco?" }],
      tools: [getWeatherTool],
      stream: true
    });

    // Ao usar streaming, o runner retorna BetaMessageStream
    for await (const messageStream of runner) {
      for await (const event of messageStream) {
        console.log("event:", event);
      }
      console.log("message:", await messageStream.finalMessage());
    }

    console.log(await runner);
    ```
  </Tab>

  <Tab title="C#">
    Chame `runner.Streaming()` para obter uma sequência assíncrona aninhada: um stream interno para cada chamada à API.

    ```csharp
    var client = new AnthropicClient();
    // ...
    var runner = client.Beta.Messages.ToolRunner(
        new MessageCreateParams
        {
            Model = Model.ClaudeOpus5_5,
            MaxTokens = 1024,
            Messages =
            [
                new() { Role = Role.User, Content = "What is 15 + 27?" },
            ],
        },
        [calculateSumTool]
    );

    await foreach (var stream in runner.Streaming())
    {
        await foreach (var streamEvent in stream)
        {
            if (
                streamEvent.TryPickContentBlockDelta(out var deltaEvent)
                && deltaEvent.Delta.TryPickText(out var textDelta)
            )
            {
                Console.Write(textDelta.Text);
            }
        }
        Console.WriteLine();
    }
    ```
  </Tab>

  <Tab title="Go">
    Use `NewToolRunnerStreaming` e itere sobre `runner.AllStreaming(ctx)`. Cada iteração externa produz um stream de eventos para uma chamada à API.

    ```go
    client := anthropic.NewClient()
    ctx := context.Background()
    // ...
    runner := client.Beta.Messages.NewToolRunnerStreaming(
    	[]anthropic.BetaTool{calculateSum},
    	anthropic.BetaToolRunnerParams{
    		BetaMessageNewParams: anthropic.BetaMessageNewParams{
    			Model:     anthropic.ModelClaudeOpus5_5,
    			MaxTokens: 1024,
    			Messages: []anthropic.BetaMessageParam{
    				anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("What is 15 + 27?")),
    			},
    		},
    	},
    )

    for events, err := range runner.AllStreaming(ctx) {
    	if err != nil {
    		log.Fatal(err)
    	}
    	for event, err := range events {
    		if err != nil {
    			log.Fatal(err)
    		}
    		switch eventVariant := event.AsAny().(type) {
    		case anthropic.BetaRawContentBlockDeltaEvent:
    			switch deltaVariant := eventVariant.Delta.AsAny().(type) {
    			case anthropic.BetaTextDelta:
    				fmt.Print(deltaVariant.Text)
    			case anthropic.BetaInputJSONDelta:
    				fmt.Print(deltaVariant.PartialJSON)
    			}
    		case anthropic.BetaRawMessageStopEvent:
    			fmt.Println()
    		}
    	}
    }
    ```
  </Tab>

  <Tab title="Java">
    Chame `runner.streaming()` para obter um stream para cada turno. Cada `StreamResponse` deve ser fechado após o uso.

    ```java
    void main() {
        AnthropicClient client = AnthropicOkHttpClient.fromEnv();

        BetaToolRunner runner = client.beta()
                .messages()
                .toolRunner(MessageCreateParams.builder()
                        .model(Model.CLAUDE_OPUS_5_5)
                        .maxTokens(1024)
                        .addBeta("structured-outputs-2025-11-13")
                        .addUserMessage("What is 15 + 27?")
                        .addTool(calculateSum)
                        .build());

        for (StreamResponse<BetaRawMessageStreamEvent> stream : runner.streaming()) {
            try (stream) {
                stream.stream().forEach(event -> IO.println("event: " + event));
            }
        }
    }
    ```
  </Tab>

  <Tab title="PHP">
    O streaming não está disponível atualmente com o tool runner PHP.
  </Tab>

  <Tab title="Ruby">
    Use `each_streaming` para iterar sobre eventos de streaming.

    ```ruby
    client = Anthropic::Client.new
    # ...
    runner = client.beta.messages.tool_runner(
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [CalculateSum.new],
      messages: [{role: "user", content: "What is 15 + 27?"}]
    )

    runner.each_streaming do |stream|
      stream.each do |event|
        case event
        when Anthropic::Streaming::TextEvent
          print event.text
        when Anthropic::Streaming::InputJsonEvent
          print event.partial_json
        end
      end
      puts
    end
    ```
  </Tab>
</Tabs>

## Próximos passos

<CardGroup cols={2}>
  <Card title="Uso estrito de ferramentas" icon="check" href="https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/strict-tool-use">
    Imponha conformidade com JSON Schema nas entradas de ferramentas de Claude com amostragem restrita por gramática.
  </Card>

  <Card title="Lidar com chamadas de ferramentas" icon="arrows-left-right" href="https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/handle-tool-calls">
    Faça o parsing de blocos `tool_use`, formate respostas `tool_result` e trate erros com `is_error`.
  </Card>

  <Card title="Uso paralelo de ferramentas" icon="grid" href="https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/parallel-tool-use">
    Habilite, formate e desabilite chamadas paralelas de ferramentas, com orientações sobre histórico de mensagens e solução de problemas.
  </Card>

  <Card title="Definir ferramentas" icon="hammer" href="https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/define-tools">
    Especifique schemas de ferramentas, escreva descrições eficazes e controle quando Claude chama suas ferramentas.
  </Card>
</CardGroup>
