---
title: 工具執行器（SDK）
url: https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/tool-runner
description: 使用 SDK 的工具執行器自動處理代理迴圈、錯誤包裝與型別安全。
---

「Tool runner」（工具執行器）會替您處理「agentic loop」（代理迴圈）、錯誤包裝與型別安全，讓您不必親自處理。當您需要人工介入審核、自訂日誌記錄或條件式執行時，請改用[手動迴圈](https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/handle-tool-calls)。

工具執行器不需要您手動處理工具呼叫、工具結果與對話管理，而是會自動：

* 在 Claude 呼叫工具時執行工具
* 處理請求／回應循環
* 管理對話狀態
* 提供型別安全與驗證

<Note>
  工具執行器目前為 beta 版，可在 [Python SDK](https://github.com/anthropics/anthropic-sdk-python/blob/main/tools.md)、[TypeScript SDK](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/helpers.md#tool-helpers)、[C# SDK](https://github.com/anthropics/anthropic-sdk-csharp/blob/main/examples/ToolRunnerExample/Program.cs)、[Go SDK](https://github.com/anthropics/anthropic-sdk-go/blob/main/tools.md)、[Java SDK](https://github.com/anthropics/anthropic-sdk-java/blob/main/anthropic-java-example/src/main/java/com/anthropic/example/BetaToolRunnerRunnableToolExample.java)、[PHP SDK](https://github.com/anthropics/anthropic-sdk-php/blob/main/examples/beta/beta_tool_runner.php) 與 [Ruby SDK](https://github.com/anthropics/anthropic-sdk-ruby/blob/main/helpers.md#3-auto-looping-tool-runner-beta) 中使用。
</Note>

## 基本用法

使用 SDK 輔助工具定義工具，然後使用工具執行器來執行它們。

依據 SDK 的工具簽章不同，工具會以字串或內容區塊（文字、圖片或文件區塊）的形式回傳結果，因此工具可以回傳多模態結果。回傳的字串會成為單一文字內容區塊。若要回傳結構化資料（例如 JSON 物件或數字），請先將其編碼為字串。

<Tabs exclude="shell">
  <Tab title="Python">
    使用 `@beta_tool` 裝飾器，透過型別提示與 docstring 來定義工具。

    <Note>
      如果您使用的是非同步用戶端，請將 `@beta_tool` 替換為 `@beta_async_tool`，並以 `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)
    ```

    `@beta_tool` 裝飾器會檢查函式引數與 docstring，為您推導出 JSON schema。
  </Tab>

  <Tab title="TypeScript">
    使用 `betaZodTool()` 建立具備 Zod 驗證的型別安全工具定義，或使用 `betaTool()` 建立基於 JSON Schema 的定義。

    TypeScript 提供兩種定義工具的方式：

    **使用 Zod（建議）** - 使用 `betaZodTool()` 建立具備 Zod 驗證的型別安全工具定義（需要 Zod 3.25.0 或更高版本）：

    ```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);
      }
    }
    ```

    **使用 JSON Schema** - 使用 `betaTool()` 建立不依賴 Zod 的型別安全工具定義：

    <Note>
      Claude 產生的輸入不會在執行階段進行驗證。如有需要，請在 `run` 函式內執行驗證。
    </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#">
    將每個工具定義為 `BetaRunnableTool`，提供帶有 JSON schema 的 `Definition`，以及在 Claude 呼叫工具時執行的 `Run` 委派。

    ```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">
    使用 `toolrunner.NewBetaToolFromJSONSchema` 定義工具。處理函式的輸入型別是帶有 `jsonschema:` 標籤的結構體。SDK 會對其進行反射以產生 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)
    	}
    }
    ```

    `jsonschema:` 結構體標籤會產生輸入 schema。例如，`CalculateSumInput` 會變成：

    ```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">
    將每個工具定義為 `BetaRunnableTool`，將輸入類別與在 Claude 呼叫該工具時執行的函式配對。以 `@JsonClassDescription` 標註輸入類別作為工具描述，並以 `@JsonPropertyDescription` 標註每個公開欄位作為參數描述。SDK 會從該類別推導出 JSON schema、工具名稱（snake-case 形式的類別名稱）與輸入解析，並以 `strict: true` 標記該工具（[嚴格工具使用](https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/strict-tool-use)）。

    該函式會以該類別的實例接收解析後的輸入，並傳回 `BetaToolResultBlockParam.Content`。若要傳回文字，請以 `BetaToolResultBlockParam.Content.ofString()` 包裝。由於該函式是 lambda，它可以使用您應用程式中的物件，例如以下範例中的 `WeatherService`。

    ```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;
    }

    // 代表您的應用程式中既有的類別，
    // 例如資料庫用戶端或 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();

        // 此 lambda 可以使用 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);
        }
    }
    ```

    類別名稱 `CalculateSum` 會成為工具名稱 `calculate_sum`，而 SDK 會從已標註的欄位產生 JSON schema：

    ```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">
    將每個工具定義為 `BetaRunnableTool`，它將工具的 JSON schema 定義與執行該工具的閉包配對在一起。

    ```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">
    使用 `Anthropic::BaseTool` 類別，以具型別的輸入 schema 定義工具。

    ```ruby
    require "anthropic"

    # 初始化用戶端
    client = Anthropic::Client.new

    # 定義輸入結構描述
    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

    # 定義工具
    class GetWeather < Anthropic::BaseTool
      doc "Get the current weather in a given location"
      input_schema GetWeatherInput

      def call(input)
        # 在完整實作中，您會在此呼叫天氣 API
        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

    # 使用工具執行器
    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
    ```

    `Anthropic::BaseTool` 類別使用 `doc` 方法作為工具描述，並使用 `input_schema` 定義預期的參數。SDK 會自動將其轉換為適當的 JSON schema 格式。
  </Tab>
</Tabs>

## 迭代工具執行器

工具執行器是一個可迭代物件，會產出來自 Claude 的訊息。在每次迭代中，執行器會檢查 Claude 是否請求了工具使用。若是，它會執行該工具並自動將結果傳回給 Claude，然後產出 Claude 的下一則訊息以繼續您的迴圈。

您可以在任何一次迭代中使用 `break` 陳述式結束迴圈。執行器會持續迴圈，直到 Claude 傳回不含工具使用的訊息，或者在您有設定的情況下，直到達到 `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`) 為止。

如果您不需要中間訊息，可以直接取得最終訊息：

<Tabs exclude="shell">
  <Tab title="Python">
    使用 `runner.until_done()` 取得最終訊息。

    ```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">
    對執行器使用 `await` 以取得最終訊息。

    ```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#">
    使用 `runner.RunUntilDoneAsync()` 取得最終訊息。

    ```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">
    使用 `runner.RunToCompletion(ctx)` 取得最終訊息。

    ```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">
    Java SDK 沒有 `until_done()` 捷徑。請迭代至結束並保留最後一則訊息。

    ```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">
    使用 `runUntilDone()` 取得最終訊息。

    ```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">
    使用 `runner.run_until_finished` 取得所有訊息。

    ```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>

## 進階用法

在迴圈內，您可以讀取每則回應訊息，並在下一次 API 呼叫之前修改執行器的狀態。每次迭代都遵循以下生命週期：

1. 執行器以其目前狀態向 Messages API 傳送請求。

2. 執行器將回應訊息產出給您的迴圈主體。

3. 您的迴圈主體執行。您可以讀取訊息，並選擇性地修改執行器的狀態。

4. 當您的迴圈主體返回時，執行器會檢查您是否修改了其訊息歷史。

   * **如果您未修改訊息歷史：** 若訊息包含工具呼叫，執行器會附加助理訊息與工具結果，然後繼續。若沒有工具呼叫，迴圈便會結束。
   * **如果您修改了訊息歷史：** 執行器會略過其自動附加，並原封不動地使用您的狀態。請參閱[接管訊息歷史](https://platform.claude.com/docs/zh-TW/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
```

### 接管訊息歷史

預設情況下，執行器會為您管理對話狀態：在每個工具呼叫回合之後，它會將助理訊息與任何工具結果附加到自己的訊息歷史中。當您想要重試某個回合（捨棄回應並重新傳送）、插入後續訊息，或自行建構工具結果時，您便需要接管訊息歷史。

您可以在迴圈主體內修改執行器的訊息來進行接管。確切的方法取決於 SDK。請參閱下方各語言的分頁。

當您在某次迭代中接管時，執行器不會附加該回合的助理訊息或工具結果。您需要負責維持對話的有效性：自行附加助理訊息與工具結果（如果您希望該回合被計入）、有條件地修改狀態，讓迴圈在沒有工具呼叫時仍能結束，並傳入 `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`) 來限制迴圈。全部七個 SDK 都支援 `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`)。

<Tabs exclude="shell">
  <Tab title="Python">
    使用 `generate_tool_call_response()` 檢查或計算工具結果。在迴圈內呼叫 `append_messages()` 會告知執行器您正在自行管理歷史，因此請在您附加的內容中包含助理訊息與工具結果。

    ```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() 會將狀態標記為已修改，因此 runner 會跳過
            # 本次迭代的自動附加。請自行附加 assistant 訊息與
            # tool result，以及任何後續內容。
            runner.append_messages(
                message,
                tool_response,
                {"role": "user", "content": "Please be concise."},
            )
        # 若沒有工具呼叫，則保持狀態不變以便迴圈結束。
    ```

    若要變更 `max_tokens` 等請求參數而不接管訊息歷史，請使用 `set_messages_params()`。執行器仍會自動附加助理訊息與工具結果。

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

  <Tab title="TypeScript">
    使用 `runner.params` 讀取目前的請求參數，並使用 `setMessagesParams()` 取代它們。在迴圈內呼叫 `setMessagesParams()` 或 `pushMessages()` 會告知執行器您正在自行管理狀態：本次迭代的助理訊息與工具結果會被捨棄，而下一個請求會以您的狀態送出。

    以下範例以更大的 `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}.`);
        // 提高預算。setMessagesParams() 會將狀態標記為已修改，因此
        // runner「不會」附加被截斷的訊息。下一次迭代會以更大的預算
        // 重試同一回合。
        runner.setMessagesParams((params) => ({ ...params, max_tokens: doubled }));
      }
      // 否則保持狀態不變，讓 runner 自動附加並繼續執行。
    }
    ```
  </Tab>

  <Tab title="C#">
    呼叫 `SetParams()` 或 `PushMessages()` 會將狀態標記為已修改，這會使執行器略過該回合的自動附加。C# 執行器仍會執行該回合所匹配的工具，並捨棄其自動建構的結果，因此若您也在迴圈主體內自行執行某個工具，除非您加以處理，否則該工具會執行兩次。當您接管時，請自行推入助理訊息與工具結果，否則對話將無法向前推進。C# 執行器在回應不含工具呼叫時一律會結束，因此請將任何狀態變更的條件設為存在 `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)
        {
            // 無工具呼叫：保持狀態不變，讓迴圈正常結束。
            continue;
        }

        // 自行執行工具並建構結果區塊。
        var toolResult = new BetaToolResultBlockParam(toolUseBlock.ID)
        {
            Content = await getWeatherTool.ExecuteAsync(toolUseBlock, default),
        };

        // PushMessages() 會將狀態標記為已修改；執行器會略過其自動附加。
        // 自行提供助理回合與工具結果，然後新增後續訊息。
        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">
    Go 執行器將參數公開為公開的 `Params` 欄位。在 `NextMessage(ctx)` 呼叫之間修改 `runner.Params` 會套用至下一個 API 請求。與其他 SDK 不同，Go 執行器一律無條件地附加助理訊息與工具結果。修改 `Params` 不會抑制該步驟。

    ```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
    	}

    	// Go runner 一律會附加助理訊息與工具結果。
    	// 此處的參數變更將套用至下一次迭代。
    	runner.Params.MaxTokens = 2048
    }
    ```
  </Tab>

  <Tab title="Java">
    使用 `runner.params()` 讀取目前的參數，並使用 `runner.setNextParams()` 為下一次迭代取代它們。當您在迴圈內呼叫 `setNextParams()` 時，執行器會略過其自動附加。剛產出的訊息會被捨棄，而下一次迭代會原封不動地傳送您的新參數。

    以下範例透過將 `max_tokens` 加倍來重試達到 token 上限的回合。僅在 `max_tokens` 分支上進行變更可讓迴圈保持收斂：正常完成的回合會直接通過，而執行器會自動附加，並在沒有更多工具呼叫時結束。

    ```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 + ".");

            // 呼叫 setNextParams() 會將此回合標記為使用者管理：runner
            // 「不會」自動附加被截斷的訊息，因此下一次迭代
            // 會以更大的預算重新傳送相同的對話前綴。
            runner.setNextParams(runner.params().toBuilder().maxTokens(doubled).build());
        }
        // 一般回合不做變更：runner 會自動附加並繼續執行。
    }
    ```
  </Tab>

  <Tab title="PHP">
    使用 `setMessagesParams()` 與 `pushMessages()` 修改執行器的狀態，並使用 `getParams()` 讀取狀態。在迴圈內呼叫任一 setter 會告知執行器略過其自動附加，因此對話會改從您修改後的狀態繼續。

    以下範例在回應被截斷時將 `max_tokens` 加倍並重試。

    ```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";

            // 在迴圈內呼叫 setMessagesParams() 會告知 runner 跳過
            // 其自動附加動作。被截斷的訊息會被捨棄；下一次
            // 迭代會以更大的預算重試。
            // 鍵名採用 camelCase，與 toolRunner() 的具名參數一致。
            $runner->setMessagesParams(['maxTokens' => $doubled]);
        }
    }
    ```
  </Tab>

  <Tab title="Ruby">
    使用 `next_message` 進行逐步控制。當 `next_message` 返回時，該回合的助理訊息與工具結果已經附加完成。使用 `feed_messages` 在回合之間插入後續訊息，並使用 `runner.params.update(...)` 就地變更請求參數。

    當您在 `each_message` 或 `each_streaming` 區塊內重新指派 `runner.params[:messages]` 或呼叫 `feed_messages` 時，即為接管訊息歷史。以下模式在 `next_message` 呼叫之間呼叫 `feed_messages`，這不會構成接管。

    ```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?"}]
    )

    # 讓 runner 執行一步。助理訊息與工具結果會在 next_message 回傳前
    # 被附加到 runner.params[:messages]。
    message = runner.next_message
    puts message.content

    # 在繼續之前注入後續訊息。feed_messages 接受 splat 參數，而非陣列。
    runner.feed_messages({role: "user", content: "Also check Boston."})

    # 就地修改參數。重新指派 runner.params[:messages] 只有在 each_message
    # 或 each_streaming 區塊內執行時，才會接管訊息歷史。
    runner.params.update(max_tokens: 2048)

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

### 自動上下文管理

對於長時間執行的代理任務，TypeScript 與 Ruby 工具執行器支援自動「[compaction](https://platform.claude.com/docs/zh-TW/build-with-claude/context-editing#client-side-compaction-sdk)」（壓縮），會在 token 使用量超過閾值時產生摘要，讓對話能夠超越「context window」（上下文視窗）的限制繼續進行。這兩個 SDK 都已棄用此用戶端選項，改為推薦[伺服器端壓縮](https://platform.claude.com/docs/zh-TW/build-with-claude/compaction-threshold)，它可透過 `context_management` 請求參數搭配每個 SDK 的工具執行器使用。Python SDK（v1.0 及更新版本）以及 Go、Java、C# 與 PHP 工具執行器不包含用戶端壓縮。工具執行器提供 `compact_before_next_turn()` (typescript, java, php: `compactBeforeNextTurn()`; csharp, go: `CompactBeforeNextTurn()`) 輔助方法以進行隨需壓縮。請參閱[在迴圈中壓縮](https://platform.claude.com/docs/zh-TW/build-with-claude/compaction-on-demand#compact-in-a-loop)。在執行器上請擇一使用它或 `context_management` 壓縮編輯，不要兩者並用。

### 偵錯工具執行

當工具拋出例外時，工具執行器會捕捉它，並以 `is_error: true` 的工具結果形式將錯誤回傳給 Claude。工具結果攜帶的是例外的訊息（在 Python 中為其型別與訊息），而非完整的堆疊追蹤。

SDK 記錄的內容因語言而異。每當工具引發未處理的例外時，Python SDK 會透過標準 `logging` 模組記錄完整的例外，包括其堆疊追蹤。Python、TypeScript 與 Java SDK 會讀取 `ANTHROPIC_LOG` 環境變數以開啟 SDK 的日誌記錄，其中包含請求與回應的詳細資訊：

```bash
# 以 info 層級記錄日誌
export ANTHROPIC_LOG=info

# 以 debug 層級記錄日誌以取得更詳細的輸出
export ANTHROPIC_LOG=debug
```

Go、Ruby、C# 與 PHP SDK 不會讀取 `ANTHROPIC_LOG`。除 Python 之外，沒有任何 SDK 會記錄失敗的工具：若要查看工具失敗的原因，請在工具函式內捕捉並記錄例外，然後再回傳或重新拋出。

### 攔截工具錯誤

預設情況下，工具錯誤會傳回給 Claude，Claude 隨後可以做出適當的回應。然而，您可能想要偵測錯誤並以不同方式處理，例如提前停止執行或實作自訂錯誤處理。

<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 是一個 dict：{"role": "user", "content": [...]}
            # 檢查是否有任何工具結果發生錯誤
            for block in tool_response["content"]:
                if block.get("is_error"):
                    # 選項 1：拋出例外以停止迴圈
                    raise RuntimeError(f"Tool failed: {json.dumps(block['content'])}")

                    # 選項 2：記錄後繼續（交由 Claude 處理）
                    # logger.error(f"Tool error: {json.dumps(block['content'])}")

        # 正常處理訊息
        print(message.content)
    ```

    <Note>
      在迴圈中呼叫 

      `runner.generate_tool_call_response()`

       以取得工具結果，並在執行器將其發送給 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") {
        // 檢查是否有任何工具結果發生錯誤
        for (const block of toolResultMessage.content) {
          if (block.type === "tool_result" && block.is_error) {
            // 選項 1：拋出例外以停止迴圈
            throw new Error(`Tool failed: ${JSON.stringify(block.content)}`);

            // 選項 2：記錄後繼續（交由 Claude 處理）
            // console.error(`Tool error: ${JSON.stringify(block.content)}`);
          }
        }
      }

      // 正常處理訊息
      console.log(message.content);
    }
    ```

    <Note>
      在迴圈中呼叫 

      `runner.generateToolResponse()`

       以取得工具結果，並在執行器將其發送給 Claude 之前檢查是否有錯誤。
    </Note>
  </Tab>

  <Tab title="C#">
    C# 工具執行器未公開在工具結果傳送給 Claude 之前檢查它的掛鉤。若要控制錯誤內容，請在工具主體內拋出 `BetaToolError`。執行器會將其轉換為帶有 `is_error: true` 與您所提供內容的 `tool_result`。

    ```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)
            {
                // 若您需要在 Claude 看到失敗結果之前先檢查它，請在此處記錄日誌。
                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">
    Go SDK 目前不支援在工具錯誤傳送給 Claude 之前攔截它們。執行器會在內部將您的處理函式回傳的錯誤轉換為帶有 `is_error: true` 的工具結果。若要自訂錯誤內容，請在處理函式內捕捉錯誤並回傳結果，而非回傳錯誤。
  </Tab>

  <Tab title="Java">
    Java SDK 目前不支援在工具錯誤傳送給 Claude 之前加以攔截。執行器會捕捉工具函式拋出的任何例外，並自動將其轉換為帶有 `is_error: true` 的工具結果。若要控制錯誤內容，請在函式內捕捉例外並傳回您自己的內容。
  </Tab>

  <Tab title="PHP">
    PHP 工具執行器目前不會在工具結果被附加之前公開它們。從工具的 `run` 閉包拋出的例外會被捕捉，並自動以帶有 `is_error: true` 的工具結果形式傳送給 Claude。若要檢查或取代錯誤內容，請使用[修改工具結果](https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/tool-runner#modifying-tool-results)中所示的手動 `pushMessages()` 模式。
  </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

      # 當 next_message 回傳時，runner 已執行完本輪的工具，並
      # 將其結果附加為最後一則（user 角色）訊息。請在此檢查這些結果，
      # 再由下一個請求將其傳送給 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]
            # 選項 1：引發例外以停止迴圈
            raise "Tool failed: #{block[:content]}"

            # 選項 2：記錄後繼續（交由 Claude 處理）
            # logger.error("Tool error: #{block[:content]}")
          end
        end
      end

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

    <Note>
      Ruby 工具執行器沒有回傳工具結果的掛鉤。一旦 

      `runner.next_message`

       返回，工具結果就是 

      `runner.params[:messages]`

       中的最後一個項目，因此請在下一個請求將其發送給 Claude 之前在該處檢查。
    </Note>
  </Tab>
</Tabs>

### 修改工具結果

您可以在工具結果傳回給 Claude 之前修改它們。這對於新增 `cache_control` 等中繼資料以在工具結果上啟用「prompt caching」（提示快取）([提示快取](https://platform.claude.com/docs/zh-TW/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 是一個 dict：{"role": "user", "content": [...]}
            # 修改工具結果以加入快取控制
            for block in tool_response["content"]:
                if block["type"] == "tool_result":
                    # 加入 cache_control 以快取此工具結果
                    block["cache_control"] = {"type": "ephemeral"}

            # 附加修改後的回應（可避免自動附加原始回應）
            runner.append_messages(message, tool_response)

        print(message.content)
    ```

    <Note>
      呼叫 

      `runner.generate_tool_call_response()`

       取得工具結果、修改它，然後將其傳給 

      `runner.append_messages()`

      ，讓執行器不會附加原始結果。
    </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") {
        // 修改工具結果以加入快取控制
        for (const block of toolResultMessage.content) {
          if (block.type === "tool_result") {
            // 加入 cache_control 以快取此工具結果
            block.cache_control = { type: "ephemeral" };
          }
        }
        // 不需呼叫 pushMessages：runner 會自動附加 assistant
        // 訊息以及（已修改的）快取工具回應。
      }

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

    <Note>
      呼叫 

      `runner.generateToolResponse()`

       取得工具結果，然後就地修改它。執行器會為您附加修改後的結果。
    </Note>
  </Tab>

  <Tab title="C#">
    C# SDK 目前不支援在工具結果被附加之前修改它們（例如新增 `cache_control`）。執行器在內部建構 `tool_result` 區塊，且未提供變更它的掛鉤。
  </Tab>

  <Tab title="Go">
    Go 執行器未公開修改外層 `tool_result` 區塊的掛鉤。不過，您可以在處理函式回傳的內層內容區塊上設定 `cache_control`。

    ```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),
    				// 請在內層內容區塊上設定 cache_control。外層
    				// tool_result 區塊的 cache_control 目前無法
    				// 透過 Go runner 設定。
    				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">
    若要在工具結果上設定 `cache_control`，請使用 `ofBlocks()` 而非 `ofString()` 建構傳回的 `BetaToolResultBlockParam.Content`，並在內層文字區塊上設定 `cacheControl`。執行器目前不支援在外層 `tool_result` 區塊上設定 `cache_control`。

    ```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">
    PHP 工具執行器沒有可變更自動產生之 `tool_result` 區塊的回呼。若要新增 `cache_control` 等欄位，請自行建構工具結果並推入。呼叫 `pushMessages()` 會略過執行器在該回合的自動附加。

    ```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),
                    // 加入 cache_control 以快取此工具結果
                    'cache_control' => ['type' => 'ephemeral'],
                ];
            }
        }

        if ($toolResults !== []) {
            // pushMessages() 會將狀態標記為已變更，因此 runner 會略過其
            // 自動附加。請推入 assistant 訊息與工具結果。
            $runner->pushMessages(
                ['role' => 'assistant', 'content' => $message->content],
                ['role' => 'user', 'content' => $toolResults],
            );
        }
        // 沒有工具呼叫：保持狀態不變，讓迴圈結束。
    }
    ```
  </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

      # 從 messages 陣列中存取最新的工具結果
      # runner 會自動加入工具結果，但您可以修改它們
      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
            # 修改工具結果以加入快取控制
            block[:cache_control] = {type: "ephemeral"}
          end
        end
      end

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

    <Note>
      Ruby 工具執行器沒有回傳工具結果的掛鉤。一旦 

      `runner.next_message`

       返回，工具結果就是 

      `runner.params[:messages]`

       中的最後一個項目，因此請在下一個請求將其發送給 Claude 之前在該處修改。
    </Note>
  </Tab>
</Tabs>

<Tip>
  當工具回傳大量資料（例如文件搜尋結果）而您希望為後續 API 呼叫快取這些資料時，在工具結果中新增 `cache_control` 特別有用。請參閱[提示快取](https://platform.claude.com/docs/zh-TW/build-with-claude/prompt-caching)以了解更多快取策略的詳細資訊。
</Tip>

## 串流

啟用「streaming」（串流）以漸進方式處理每個回合的回應。每次迭代會產出一個串流物件，您可以迭代它以取得事件。

<Tabs exclude="shell">
  <Tab title="Python">
    設定 `stream=True` 並使用 `get_final_message()` 取得累積的訊息。

    ```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,
    )

    # 使用 streaming（串流）時，runner 會回傳 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">
    設定 `stream: true` 並使用 `finalMessage()` 取得累積的訊息。

    ```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
    });

    // 使用 streaming（串流）時，runner 會回傳 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#">
    呼叫 `runner.Streaming()` 取得巢狀的非同步序列：每次 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">
    使用 `NewToolRunnerStreaming` 並迭代 `runner.AllStreaming(ctx)`。每次外層迭代會產出一次 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">
    呼叫 `runner.streaming()` 取得每個回合的串流。每個 `StreamResponse` 在使用後都必須關閉。

    ```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">
    PHP 工具執行器目前不提供串流功能。
  </Tab>

  <Tab title="Ruby">
    使用 `each_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>

## 後續步驟

<CardGroup cols={2}>
  <Card title="嚴格工具使用" icon="check" href="https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/strict-tool-use">
    透過文法約束取樣，強制 Claude 的工具輸入符合 JSON Schema。
  </Card>

  <Card title="處理工具呼叫" icon="arrows-left-right" href="https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/handle-tool-calls">
    解析 `tool_use` 區塊、格式化 `tool_result` 回應，並使用 `is_error` 處理錯誤。
  </Card>

  <Card title="平行工具使用" icon="grid" href="https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/parallel-tool-use">
    啟用、格式化與停用平行工具呼叫，並提供訊息歷史指引與疑難排解。
  </Card>

  <Card title="定義工具" icon="hammer" href="https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/define-tools">
    指定工具 schema、撰寫有效的描述，並控制 Claude 何時呼叫您的工具。
  </Card>
</CardGroup>
