---
title: ツールランナー（SDK）
url: https://platform.claude.com/docs/ja/agents-and-tools/tool-use/tool-runner
description: SDKのツールランナーを使用して、エージェントループ、エラーのラップ、型安全性を自動的に処理します。
---

ツールランナーは、エージェントループ、エラーのラップ、型安全性を処理するため、自分で対応する必要がありません。人間による承認（human-in-the-loop）、カスタムロギング、条件付き実行が必要な場合は、代わりに[手動ループ](https://platform.claude.com/docs/ja/agents-and-tools/tool-use/handle-tool-calls)を使用してください。

ツール呼び出し、ツール結果、会話管理を手動で処理する代わりに、ツールランナーは自動的に以下を行います。

* Claudeがツールを呼び出したときにツールを実行する
* リクエスト/レスポンスのサイクルを処理する
* 会話の状態を管理する
* 型安全性とバリデーションを提供する

<Note>
  ツールランナーはベータ版であり、[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スキーマを導出します。
  </Tab>

  <Tab title="TypeScript">
    Zodバリデーションによる型安全なツール定義には`betaZodTool()`を、JSON Schemaベースの定義には`betaTool()`を使用します。

    TypeScriptでは、ツールを定義するための2つのアプローチが提供されています。

    **Zodを使用する（推奨）** - Zodバリデーションによる型安全なツール定義には`betaZodTool()`を使用します（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を使用する** - Zodなしで型安全なツール定義を行うには`betaTool()`を使用します。

    <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スキーマを持つ`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スキーマを生成します。

    ```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:`構造体タグが入力スキーマを生成します。たとえば、`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">
    各ツールを、入力クラスとClaudeがツールを呼び出したときに実行される関数を組み合わせた`BetaRunnableTool`として定義します。ツールの説明には入力クラスに`@JsonClassDescription`を、パラメーターの説明には各publicフィールドに`@JsonPropertyDescription`をアノテーションとして付けます。SDKはクラスからJSONスキーマ、ツール名（スネークケースに変換したクラス名）、入力のパースを導出し、ツールに`strict: true`（[厳密なツール使用](https://platform.claude.com/docs/ja/agents-and-tools/tool-use/strict-tool-use)）を設定します。

    関数はパースされた入力をそのクラスのインスタンスとして受け取り、`BetaToolResultBlockParam.Content`を返します。テキストを返すには、`BetaToolResultBlockParam.Content.ofString()`でラップします。関数はラムダであるため、次の例の`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();

        // ラムダ内で 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スキーマを生成します。

    ```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">
    各ツールを、ツールのJSONスキーマ定義とそれを実行するクロージャを組み合わせた`BetaRunnableTool`として定義します。

    ```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`クラスを使用して、型付きの入力スキーマでツールを定義します。

    ```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スキーマ形式に自動的に変換します。
  </Tab>
</Tabs>

## ツールランナーのイテレーション

ツールランナーは、Claudeからのメッセージをyieldするイテラブルです。各イテレーションで、ランナーはClaudeがツール使用をリクエストしたかどうかを確認します。リクエストした場合、ツールを実行して結果を自動的にClaudeに送り返し、その後Claudeからの次のメッセージをyieldしてループを継続します。

`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. ランナーはレスポンスメッセージをループ本体にyieldします。

3. ループ本体が実行されます。メッセージを読み取り、必要に応じてランナーの状態を変更できます。

4. ループ本体が戻ると、ランナーはメッセージ履歴が変更されたかどうかを確認します。

   * **メッセージ履歴を変更しなかった場合：** メッセージにツール呼び出しが含まれている場合、ランナーはアシスタントメッセージとツール結果を追加して続行します。ツール呼び出しがない場合、ループは終了します。
   * **メッセージ履歴を変更した場合：** ランナーは自動追加をスキップし、あなたの状態をそのまま使用します。[メッセージ履歴の引き継ぎ](https://platform.claude.com/docs/ja/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`)を渡してください。7つの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() は状態を変更済みとしてフラグを立てるため、ランナーはこの
            # イテレーションでの自動追加をスキップします。アシスタントメッセージと
            # 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.setMessagesParams((params) => ({ ...params, max_tokens: doubled }));
      }
      // それ以外の場合は状態をそのままにし、ランナーが自動追加して続行できるようにします。
    }
    ```
  </Tab>

  <Tab title="C#">
    `SetParams()`または`PushMessages()`を呼び出すと状態が変更済みとしてフラグ付けされ、ランナーはそのターンの自動追加をスキップします。C#ランナーはそのターンでマッチしたツールを引き続き実行し、自動構築された結果を破棄するため、ループ本体内で自分でも実行するツールは、考慮しない限り2回実行されます。引き継ぐ場合は、アシスタントメッセージとツール結果を自分でプッシュしてください。そうしないと会話が前に進みません。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)
        {
            // ツール呼び出しなし: ループが正常に終了するよう state はそのままにします。
            continue;
        }

        // ツールを自分で実行し、結果ブロックを構築します。
        var toolResult = new BetaToolResultBlockParam(toolUseBlock.ID)
        {
            Content = await getWeatherTool.ExecuteAsync(toolUseBlock, default),
        };

        // PushMessages() は state を変更済みとマークし、ランナーは自動追加をスキップします。
        // アシスタントのターンとツール結果を自分で提供し、その後フォローアップを追加します。
        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ランナーはパラメーターをpublicな`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.Params.MaxTokens = 2048
    }
    ```
  </Tab>

  <Tab title="Java">
    `runner.params()`を使用して現在のパラメーターを読み取り、`runner.setNextParams()`を使用して次のイテレーション用にそれらを置き換えます。ループ内で`setNextParams()`を呼び出すと、ランナーは自動追加をスキップします。直前にyieldされたメッセージは破棄され、次のイテレーションは新しいパラメーターをそのまま送信します。

    次の例では、トークン制限に達したターンを`max_tokens`を2倍にしてリトライします。`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.setNextParams(runner.params().toBuilder().maxTokens(doubled).build());
        }
        // 通常のターンでは変更なし: ランナーが自動追加して続行します。
    }
    ```
  </Tab>

  <Tab title="PHP">
    `setMessagesParams()`と`pushMessages()`を使用してランナーの状態を変更し、`getParams()`を使用して読み取ります。ループ内でいずれかのセッターを呼び出すと、ランナーは自動追加をスキップするため、会話は代わりに変更された状態から継続します。

    次の例では、レスポンスが途中で切れた場合に`max_tokens`を2倍にしてリトライします。

    ```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() を呼び出すと、ランナーは自動追加を
            // スキップします。切り詰められたメッセージは破棄され、次の
            // イテレーションでより大きなバジェットで再試行されます。
            // キーはキャメルケースで、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?"}]
    )

    # ランナーを1ステップ進めます。アシスタントメッセージとツール結果は
    # 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/ja/build-with-claude/context-editing#client-side-compaction-sdk)をサポートしています。これはトークン使用量がしきい値を超えたときに要約を生成し、会話が「context window」（コンテキストウィンドウ）の制限を超えて継続できるようにするものです。両SDKとも、このクライアントサイドのオプションを非推奨とし、[サーバーサイドのコンパクション](https://platform.claude.com/docs/ja/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/ja/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">
    Claudeに送信される前にツールエラーをインターセプトすることは、現在Go SDKではサポートされていません。ランナーはハンドラーから返されたエラーを内部的に`is_error: true`を持つツール結果に変換します。エラー内容をカスタマイズするには、ハンドラー内でエラーをキャッチし、エラーを返す代わりに結果を返してください。
  </Tab>

  <Tab title="Java">
    Claudeに送信される前にツールエラーをインターセプトすることは、現在Java SDKではサポートされていません。ランナーは、ツールの関数からスローされた例外をキャッチし、`is_error: true`を持つツール結果に自動的に変換します。エラーの内容を制御するには、関数内で例外をキャッチし、独自の内容を返してください。
  </Tab>

  <Tab title="PHP">
    PHPツールランナーは現在、追加される前のツール結果を公開していません。ツールの`run`クロージャからスローされた例外はキャッチされ、自動的に`is_error: true`を持つツール結果としてClaudeに送信されます。エラー内容を検査または置き換えるには、[ツール結果の変更](https://platform.claude.com/docs/ja/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 が返る時点で、ランナーはこのターンのツールを実行し、
      # その結果を最後の（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に送り返される前にツール結果を変更できます。これは、ツール結果で「prompt caching」（プロンプトキャッシング）を有効にするために`cache_control`などのメタデータを追加したり、ツール出力を変換したりするのに便利です。詳細は[プロンプトキャッシング](https://platform.claude.com/docs/ja/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 の呼び出しは不要です。ランナーがアシスタントの
        // メッセージと（変更済みの）キャッシュされたツールレスポンスの両方を自動で追加します。
      }

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

    <Note>
      `runner.generateToolResponse()`

      を呼び出してツール結果を取得し、その場で変更します。ランナーが変更後の結果を追加します。
    </Note>
  </Tab>

  <Tab title="C#">
    追加される前にツール結果を変更すること（たとえば`cache_control`を追加すること）は、現在C# SDKではサポートされていません。ランナーは`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 ランナーからは設定できません。
    				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`を設定するには、返す`BetaToolResultBlockParam.Content`を`ofString()`ではなく`ofBlocks()`で構築し、内側のテキストブロックに`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->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 配列から最新のツール結果にアクセスします
      # ランナーはツール結果を自動的に追加しますが、変更することもできます
      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>
  ツール結果への`cache_control`の追加は、後続のAPI呼び出しのためにキャッシュしたい大量のデータ（ドキュメント検索結果など）をツールが返す場合に特に便利です。キャッシング戦略の詳細については、[プロンプトキャッシング](https://platform.claude.com/docs/ja/build-with-claude/prompt-caching)を参照してください。
</Tip>

## ストリーミング

ストリーミングを有効にすると、各ターンのレスポンスを段階的に処理できます。各イテレーションは、イベントをイテレーションできるストリームオブジェクトをyieldします。

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

    # ストリーミング時、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
    });

    // ストリーミング時、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呼び出しごとに1つの内側のストリームがあります。

    ```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)`をイテレーションします。外側の各イテレーションは、1回のAPI呼び出しのイベントのストリームをyieldします。

    ```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/ja/agents-and-tools/tool-use/strict-tool-use">
    文法制約付きサンプリングにより、ClaudeのツールインプットにJSON Schemaへの準拠を強制します。
  </Card>

  <Card title="ツール呼び出しの処理" icon="arrows-left-right" href="https://platform.claude.com/docs/ja/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/ja/agents-and-tools/tool-use/parallel-tool-use">
    並列ツール呼び出しの有効化、フォーマット、無効化について、メッセージ履歴のガイダンスとトラブルシューティングとともに説明します。
  </Card>

  <Card title="ツールの定義" icon="hammer" href="https://platform.claude.com/docs/ja/agents-and-tools/tool-use/define-tools">
    ツールスキーマを指定し、効果的な説明を記述し、Claudeがツールを呼び出すタイミングを制御します。
  </Card>
</CardGroup>
