---
title: Tool-Runner (SDK)
url: https://platform.claude.com/docs/de/agents-and-tools/tool-use/tool-runner
description: Verwende den Tool-Runner des SDK, um die agentische Schleife, das Error-Wrapping und die Typsicherheit automatisch zu handhaben.
---

Der „tool runner“ (Tool-Runner) übernimmt die agentische Schleife, das Error-Wrapping und die Typsicherheit, damit du dich nicht darum kümmern musst. Wenn du eine Human-in-the-Loop-Freigabe, benutzerdefiniertes Logging oder bedingte Ausführung benötigst, verwende stattdessen die [manuelle Schleife](https://platform.claude.com/docs/de/agents-and-tools/tool-use/handle-tool-calls).

Anstatt Tool-Aufrufe, Tool-Ergebnisse und die Konversationsverwaltung manuell zu handhaben, erledigt der Tool-Runner automatisch Folgendes:

* Führt Tools aus, wenn Claude sie aufruft
* Handhabt den Request/Response-Zyklus
* Verwaltet den Konversationszustand
* Bietet Typsicherheit und Validierung

<Note>
  Der Tool-Runner befindet sich in der Beta-Phase und ist im [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) und [Ruby SDK](https://github.com/anthropics/anthropic-sdk-ruby/blob/main/helpers.md#3-auto-looping-tool-runner-beta) verfügbar.
</Note>

## Grundlegende Verwendung

Definiere Tools mit den SDK-Helfern und verwende dann den Tool-Runner, um sie auszuführen.

Je nach Tool-Signatur des SDK gibt ein Tool sein Ergebnis als String oder als Content-Blöcke (Text-, Bild- oder Dokument-Blöcke) zurück, sodass ein Tool multimodale Ergebnisse zurückgeben kann. Ein zurückgegebener String wird zu einem einzelnen Text-Content-Block. Um strukturierte Daten wie ein JSON-Objekt oder eine Zahl zurückzugeben, kodiere sie zuerst als String.

<Tabs exclude="shell">
  <Tab title="Python">
    Verwende den `@beta_tool`-Decorator, um Tools mit Type Hints und Docstrings zu definieren.

    <Note>
      Wenn du den asynchronen Client verwendest, ersetze `@beta_tool` durch `@beta_async_tool` und definiere die Funktion mit `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)
    ```

    Der `@beta_tool`-Decorator untersucht die Funktionsargumente und den Docstring, um das JSON-Schema für dich abzuleiten.
  </Tab>

  <Tab title="TypeScript">
    Verwende `betaZodTool()` für typsichere Tool-Definitionen mit Zod-Validierung oder `betaTool()` für JSON-Schema-basierte Definitionen.

    TypeScript bietet zwei Ansätze zur Definition von Tools:

    **Mit Zod (empfohlen)** – Verwende `betaZodTool()` für typsichere Tool-Definitionen mit Zod-Validierung (erfordert Zod 3.25.0 oder höher):

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

    **Mit JSON Schema** – Verwende `betaTool()` für typsichere Tool-Definitionen ohne Zod:

    <Note>
      Die von Claude generierte Eingabe wird zur Laufzeit nicht validiert. Führe die Validierung bei Bedarf innerhalb der `run`-Funktion durch.
    </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#">
    Definiere jedes Tool als `BetaRunnableTool` und gib dabei eine `Definition` mit einem JSON-Schema sowie einen `Run`-Delegate an, der ausgeführt wird, wenn Claude das Tool aufruft.

    ```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">
    Definiere ein Tool mit `toolrunner.NewBetaToolFromJSONSchema`. Der Eingabetyp des Handlers ist ein Struct mit `jsonschema:`-Tags. Das SDK nutzt Reflection darauf, um das JSON-Schema zu generieren.

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

    Die `jsonschema:`-Struct-Tags generieren das Eingabeschema. Zum Beispiel wird `CalculateSumInput` zu:

    ```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">
    Definiere jedes Tool als `BetaRunnableTool`, das eine Eingabeklasse mit einer Funktion verknüpft, die ausgeführt wird, wenn Claude das Tool aufruft. Annotiere die Eingabeklasse mit `@JsonClassDescription` für die Tool-Beschreibung und jedes öffentliche Feld mit `@JsonPropertyDescription` für die Parameterbeschreibungen. Das SDK leitet das JSON-Schema, den Tool-Namen (Klassenname in snake\_case) und das Parsen der Eingabe aus der Klasse ab und kennzeichnet das Tool mit `strict: true` ([strikte Tool-Nutzung](https://platform.claude.com/docs/de/agents-and-tools/tool-use/strict-tool-use)).

    Die Funktion erhält die geparste Eingabe als Instanz dieser Klasse und gibt einen `BetaToolResultBlockParam.Content` zurück. Um Text zurückzugeben, umschließe ihn mit `BetaToolResultBlockParam.Content.ofString()`. Da die Funktion ein Lambda ist, kann sie Objekte aus deiner Anwendung verwenden, wie etwa den `WeatherService` im folgenden Beispiel.

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

    // Steht stellvertretend für eine Klasse, die deine Anwendung bereits hat,
    // etwa einen Datenbank-Client oder einen API-Wrapper.
    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();

        // Das Lambda kann weatherService verwenden.
        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);
        }
    }
    ```

    Der Klassenname `CalculateSum` wird zum Tool-Namen `calculate_sum`, und das SDK generiert ein JSON-Schema aus den annotierten Feldern:

    ```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">
    Definiere jedes Tool als `BetaRunnableTool`, das die JSON-Schema-Definition des Tools mit einer Closure verbindet, die es ausführt.

    ```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">
    Verwende die Klasse `Anthropic::BaseTool`, um Tools mit typisierten Eingabeschemas zu definieren.

    ```ruby
    require "anthropic"

    # Client initialisieren
    client = Anthropic::Client.new

    # Eingabeschema definieren
    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

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

      def call(input)
        # In einer vollständigen Implementierung würdest du hier eine Wetter-API aufrufen
        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

    # Tool-Runner verwenden
    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
    ```

    Die Klasse `Anthropic::BaseTool` verwendet die Methode `doc` für die Tool-Beschreibung und `input_schema`, um die erwarteten Parameter zu definieren. Das SDK konvertiert dies automatisch in das passende JSON-Schema-Format.
  </Tab>
</Tabs>

## Über den Tool-Runner iterieren

Der Tool-Runner ist ein Iterable, das Nachrichten von Claude liefert. Bei jeder Iteration prüft der Runner, ob Claude eine Tool-Nutzung angefordert hat. Falls ja, führt er das Tool aus, sendet das Ergebnis automatisch an Claude zurück und liefert dann die nächste Nachricht von Claude, um deine Schleife fortzusetzen.

Du kannst die Schleife bei jeder Iteration mit einer `break`-Anweisung beenden. Der Runner läuft so lange, bis Claude eine Nachricht ohne Tool-Nutzung zurückgibt oder bis er `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`) erreicht, sofern du diesen Wert gesetzt hast.

Wenn du keine Zwischennachrichten benötigst, kannst du die finale Nachricht direkt abrufen:

<Tabs exclude="shell">
  <Tab title="Python">
    Verwende `runner.until_done()`, um die finale Nachricht zu erhalten.

    ```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">
    Verwende `await` auf dem Runner, um die finale Nachricht zu erhalten.

    ```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#">
    Verwende `runner.RunUntilDoneAsync()`, um die finale Nachricht zu erhalten.

    ```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">
    Verwende `runner.RunToCompletion(ctx)`, um die finale Nachricht zu erhalten.

    ```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">
    Das Java SDK hat keine `until_done()`-Abkürzung. Iteriere bis zum Ende und behalte die letzte Nachricht.

    ```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">
    Verwende `runUntilDone()`, um die finale Nachricht zu erhalten.

    ```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">
    Verwende `runner.run_until_finished`, um alle Nachrichten zu erhalten.

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

## Erweiterte Verwendung

Innerhalb der Schleife kannst du jede Antwortnachricht lesen und den Zustand des Runners vor dem nächsten API-Aufruf ändern. Jede Iteration folgt diesem Lebenszyklus:

1. Der Runner sendet eine Anfrage mit seinem aktuellen Zustand an die Messages API.

2. Der Runner liefert die Antwortnachricht an deinen Schleifenkörper.

3. Dein Schleifenkörper wird ausgeführt. Du kannst die Nachricht lesen und optional den Zustand des Runners ändern.

4. Wenn dein Schleifenkörper zurückkehrt, prüft der Runner, ob du seinen Nachrichtenverlauf geändert hast.

   * **Wenn du den Nachrichtenverlauf nicht geändert hast:** Enthält die Nachricht Tool-Aufrufe, hängt der Runner die Assistant-Nachricht und die Tool-Ergebnisse an und fährt fort. Gibt es keine Tool-Aufrufe, wird die Schleife beendet.
   * **Wenn du den Nachrichtenverlauf geändert hast:** Der Runner überspringt sein automatisches Anhängen und verwendet deinen Zustand unverändert. Siehe [Den Nachrichtenverlauf übernehmen](https://platform.claude.com/docs/de/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
```

### Den Nachrichtenverlauf übernehmen

Standardmäßig verwaltet der Runner den Konversationszustand für dich: Nach jedem Zug mit Tool-Aufruf hängt er die Assistant-Nachricht und etwaige Tool-Ergebnisse an seinen eigenen Nachrichtenverlauf an. Du übernimmst den Nachrichtenverlauf, wenn du einen Zug wiederholen (die Antwort verwerfen und erneut senden), eine Folgenachricht einfügen oder das Tool-Ergebnis selbst erstellen möchtest.

Du übernimmst, indem du die Nachrichten des Runners innerhalb des Schleifenkörpers änderst. Die genaue Methode hängt vom SDK ab. Siehe die folgenden sprachspezifischen Tabs.

Wenn du für eine Iteration übernimmst, hängt der Runner die Assistant-Nachricht und die Tool-Ergebnisse dieses Zugs nicht an. Du bist dann dafür verantwortlich, das Gespräch gültig zu halten: Hänge die Assistant-Nachricht und ein Tool-Ergebnis selbst an (wenn der Zug zählen soll), ändere den Zustand nur unter bestimmten Bedingungen, damit die Schleife weiterhin beendet werden kann, wenn keine Tool-Aufrufe vorliegen, und übergib `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`), um die Schleife zu begrenzen. Alle sieben SDKs unterstützen `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`).

<Tabs exclude="shell">
  <Tab title="Python">
    Verwende `generate_tool_call_response()`, um das Tool-Ergebnis zu untersuchen oder zu berechnen. Der Aufruf von `append_messages()` innerhalb der Schleife signalisiert dem Runner, dass du den Verlauf selbst verwaltest. Schließe daher die Assistant-Nachricht und das Tool-Ergebnis in das ein, was du anhängst.

    ```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() markiert den Zustand als geändert, daher überspringt der Runner
            # sein automatisches Anhängen für diese Iteration. Hänge die Assistant-Nachricht und
            # das Tool-Ergebnis selbst an, plus etwaige Folgenachrichten.
            runner.append_messages(
                message,
                tool_response,
                {"role": "user", "content": "Please be concise."},
            )
        # Wenn kein Tool-Aufruf vorliegt, lass den Zustand unverändert, damit die Schleife endet.
    ```

    Um Anfrageparameter wie `max_tokens` zu ändern, ohne den Nachrichtenverlauf zu übernehmen, verwende `set_messages_params()`. Der Runner hängt die Assistant-Nachricht und das Tool-Ergebnis weiterhin automatisch an.

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

  <Tab title="TypeScript">
    Verwende `runner.params`, um die aktuellen Anfrageparameter zu lesen, und `setMessagesParams()`, um sie zu ersetzen. Der Aufruf von `setMessagesParams()` oder `pushMessages()` innerhalb der Schleife signalisiert dem Runner, dass du den Zustand selbst verwaltest: Die Assistant-Nachricht und das Tool-Ergebnis dieser Iteration werden verworfen, und die nächste Anfrage wird mit deinem Zustand gesendet.

    Das folgende Beispiel wiederholt eine abgeschnittene Antwort mit einem größeren `max_tokens`-Budget.

    ```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}.`);
        // Erhöhe das Budget. setMessagesParams() markiert den State als geändert, daher
        // hängt der Runner die abgeschnittene Nachricht NICHT an. Die nächste Iteration
        // wiederholt denselben Turn mit dem größeren Budget.
        runner.setMessagesParams((params) => ({ ...params, max_tokens: doubled }));
      }
      // Andernfalls lasse den State unberührt, damit der Runner auto-anhängt und fortfährt.
    }
    ```
  </Tab>

  <Tab title="C#">
    Der Aufruf von `SetParams()` oder `PushMessages()` markiert den Zustand als geändert, wodurch der Runner sein automatisches Anhängen für diesen Zug überspringt. Der C#-Runner führt die zugeordneten Tools für diesen Zug dennoch aus und verwirft deren automatisch erstellte Ergebnisse. Ein Tool, das du zusätzlich selbst im Schleifenkörper ausführst, läuft daher zweimal, sofern du dies nicht berücksichtigst. Wenn du übernimmst, füge die Assistant-Nachricht und ein Tool-Ergebnis selbst hinzu. Andernfalls macht die Konversation keinen Fortschritt. Der C#-Runner endet immer, wenn eine Antwort keine Tool-Aufrufe enthält. Knüpfe daher jede Zustandsänderung an das Vorhandensein eines `tool_use`-Blocks.

    ```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)
        {
            // Kein Tool-Aufruf: lass den State unverändert, damit die Schleife normal endet.
            continue;
        }

        // Führe das Tool selbst aus und erstelle den Result-Block.
        var toolResult = new BetaToolResultBlockParam(toolUseBlock.ID)
        {
            Content = await getWeatherTool.ExecuteAsync(toolUseBlock, default),
        };

        // PushMessages() markiert State als geändert; der Runner überspringt Auto-Append.
        // Liefere Assistant-Turn und Tool-Result selbst, füge dann ein Follow-up hinzu.
        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">
    Der Go-Runner stellt Parameter als öffentliches `Params`-Feld bereit. Änderungen an `runner.Params` zwischen Aufrufen von `NextMessage(ctx)` gelten für die nächste API-Anfrage. Anders als andere SDKs hängt der Go-Runner die Assistant-Nachricht und die Tool-Ergebnisse immer bedingungslos an. Das Ändern von `Params` unterdrückt diesen Schritt nicht.

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

    	// Der Go-Runner hängt immer die Assistant-Nachricht und Tool-Ergebnisse an.
    	// Parameteränderungen hier gelten für die nächste Iteration.
    	runner.Params.MaxTokens = 2048
    }
    ```
  </Tab>

  <Tab title="Java">
    Verwende `runner.params()`, um die aktuellen Parameter zu lesen, und `runner.setNextParams()`, um sie für die nächste Iteration zu ersetzen. Wenn du `setNextParams()` innerhalb der Schleife aufrufst, überspringt der Runner sein automatisches Anhängen. Die gerade gelieferte Nachricht wird verworfen, und die nächste Iteration sendet deine neuen Parameter unverändert.

    Das folgende Beispiel wiederholt einen Zug, der das Token-Limit erreicht hat, indem `max_tokens` verdoppelt wird. Nur im `max_tokens`-Zweig zu ändern, sorgt dafür, dass die Schleife konvergiert: Züge, die normal abgeschlossen werden, laufen durch, und der Runner hängt automatisch an und endet, wenn es keine weiteren Tool-Aufrufe gibt.

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

            // Der Aufruf von setNextParams() markiert diesen Turn als benutzerverwaltet: Der Runner
            // hängt die abgeschnittene Nachricht NICHT automatisch an, sodass die nächste Iteration
            // dasselbe Konversationspräfix mit dem größeren Budget erneut sendet.
            runner.setNextParams(runner.params().toBuilder().maxTokens(doubled).build());
        }
        // Keine Mutation bei einem normalen Turn: Der Runner hängt automatisch an und fährt fort.
    }
    ```
  </Tab>

  <Tab title="PHP">
    Verwende `setMessagesParams()` und `pushMessages()`, um den Zustand des Runners zu ändern, und `getParams()`, um ihn zu lesen. Der Aufruf eines der beiden Setter innerhalb der Schleife weist den Runner an, sein automatisches Anhängen zu überspringen, sodass die Konversation stattdessen von deinem geänderten Zustand aus fortgesetzt wird.

    Das folgende Beispiel verdoppelt `max_tokens` und wiederholt den Zug, wenn eine Antwort abgeschnitten wird.

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

            // Der Aufruf von setMessagesParams() innerhalb der Schleife weist den Runner an,
            // sein automatisches Anhängen zu überspringen. Die abgeschnittene Nachricht wird
            // verworfen; die nächste Iteration versucht es erneut mit dem größeren Budget.
            // Schlüssel sind camelCase, passend zu den benannten Parametern von toolRunner().
            $runner->setMessagesParams(['maxTokens' => $doubled]);
        }
    }
    ```
  </Tab>

  <Tab title="Ruby">
    Verwende `next_message` für schrittweise Kontrolle. Wenn `next_message` zurückkehrt, sind die Assistant-Nachricht und das Tool-Ergebnis für diesen Zug bereits angehängt. Verwende `feed_messages`, um Folgenachrichten zwischen Zügen einzufügen, und `runner.params.update(...)`, um Anfrageparameter direkt zu ändern.

    Du übernimmst den Nachrichtenverlauf, wenn du innerhalb eines `each_message`- oder `each_streaming`-Blocks `runner.params[:messages]` neu zuweist oder `feed_messages` aufrufst. Das folgende Muster ruft `feed_messages` zwischen `next_message`-Aufrufen auf, was keine Übernahme darstellt.

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

    # Führe den Runner einmal aus. Die Assistant-Nachricht und das Tool-Ergebnis werden
    # an runner.params[:messages] angehängt, bevor next_message zurückkehrt.
    message = runner.next_message
    puts message.content

    # Füge vor dem Fortfahren eine Folgenachricht ein. feed_messages nimmt ein Splat, kein Array.
    runner.feed_messages({role: "user", content: "Also check Boston."})

    # Ändere Parameter direkt. Das Neuzuweisen von runner.params[:messages] übernimmt den
    # Nachrichtenverlauf nur, wenn es innerhalb eines each_message- oder each_streaming-Blocks geschieht.
    runner.params.update(max_tokens: 2048)

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

### Automatische Kontextverwaltung

Für lang laufende agentische Aufgaben unterstützen die Tool-Runner von TypeScript und Ruby die automatische [„compaction“ (Komprimierung)](https://platform.claude.com/docs/de/build-with-claude/context-editing#client-side-compaction-sdk), die Zusammenfassungen generiert, wenn die Token-Nutzung einen Schwellenwert überschreitet, sodass das Gespräch über die Grenzen des Kontextfensters hinaus fortgesetzt werden kann. Beide SDKs haben diese clientseitige Option zugunsten der [serverseitigen Komprimierung](https://platform.claude.com/docs/de/build-with-claude/compaction-threshold) abgekündigt, die mit dem Tool-Runner jedes SDK über den Anfrageparameter `context_management` funktioniert. Das Python SDK (v1.0 und höher) sowie die Tool-Runner von Go, Java, C# und PHP enthalten keine clientseitige Komprimierung. Der Tool-Runner verfügt über einen Helfer `compact_before_next_turn()` (typescript, java, php: `compactBeforeNextTurn()`; csharp, go: `CompactBeforeNextTurn()`) für die Komprimierung bei Bedarf. Siehe [In einer Schleife komprimieren](https://platform.claude.com/docs/de/build-with-claude/compaction-on-demand#compact-in-a-loop). Verwende bei einem Runner entweder diesen Helfer oder eine `context_management`-Komprimierungsbearbeitung, nicht beides.

### Tool-Ausführung debuggen

Wenn ein Tool eine Exception wirft, fängt der Tool-Runner sie ab und gibt den Fehler als Tool-Ergebnis mit `is_error: true` an Claude zurück. Das Tool-Ergebnis enthält die Nachricht der Exception (in Python ihren Typ und ihre Nachricht), nicht den vollständigen Stack-Trace.

Was das SDK protokolliert, ist sprachspezifisch. Das Python SDK protokolliert die vollständige Exception einschließlich Stack-Trace über das Standardmodul `logging`, wann immer ein Tool eine unbehandelte Exception auslöst. Die SDKs für Python, TypeScript und Java lesen die Umgebungsvariable `ANTHROPIC_LOG`, um das Logging des SDK zu aktivieren, das Details zu Anfragen und Antworten umfasst:

```bash
# Auf Info-Level protokollieren
export ANTHROPIC_LOG=info

# Auf Debug-Level protokollieren für ausführlichere Ausgabe
export ANTHROPIC_LOG=debug
```

Die SDKs für Go, Ruby, C# und PHP lesen `ANTHROPIC_LOG` nicht. Außerhalb von Python protokolliert kein SDK ein fehlgeschlagenes Tool: Um zu sehen, warum ein Tool fehlgeschlagen ist, fange die Exception innerhalb der Tool-Funktion ab und protokolliere sie, bevor du zurückkehrst oder sie erneut wirfst.

### Tool-Fehler abfangen

Standardmäßig werden Tool-Fehler an Claude zurückgegeben, das dann angemessen reagieren kann. Möglicherweise möchtest du jedoch Fehler erkennen und anders behandeln, zum Beispiel um die Ausführung frühzeitig zu stoppen oder eine benutzerdefinierte Fehlerbehandlung zu implementieren.

<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 ist ein Dict: {"role": "user", "content": [...]}
            # Prüfe, ob ein Tool-Ergebnis einen Fehler enthält
            for block in tool_response["content"]:
                if block.get("is_error"):
                    # Option 1: Löse eine Exception aus, um die Schleife zu beenden
                    raise RuntimeError(f"Tool failed: {json.dumps(block['content'])}")

                    # Option 2: Protokolliere und fahre fort (lass Claude es behandeln)
                    # logger.error(f"Tool error: {json.dumps(block['content'])}")

        # Verarbeite die Nachricht normal
        print(message.content)
    ```

    <Note>
      Rufe 

      `runner.generate_tool_call_response()`

       in der Schleife auf, um die Tool-Ergebnisse abzurufen und sie auf Fehler zu prüfen, bevor der Runner sie an Claude sendet.
    </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") {
        // Prüfe, ob ein Tool-Ergebnis einen Fehler enthält
        for (const block of toolResultMessage.content) {
          if (block.type === "tool_result" && block.is_error) {
            // Option 1: Wirf einen Fehler, um die Schleife zu beenden
            throw new Error(`Tool failed: ${JSON.stringify(block.content)}`);

            // Option 2: Protokollieren und fortfahren (Claude kümmert sich darum)
            // console.error(`Tool error: ${JSON.stringify(block.content)}`);
          }
        }
      }

      // Verarbeite die Nachricht wie gewohnt
      console.log(message.content);
    }
    ```

    <Note>
      Rufe 

      `runner.generateToolResponse()`

       in der Schleife auf, um die Tool-Ergebnisse abzurufen und sie auf Fehler zu prüfen, bevor der Runner sie an Claude sendet.
    </Note>
  </Tab>

  <Tab title="C#">
    Der C#-Tool-Runner stellt keinen Hook bereit, um das Tool-Ergebnis zu untersuchen, bevor es an Claude gesendet wird. Um den Fehlerinhalt zu steuern, wirf `BetaToolError` innerhalb des Tool-Körpers. Der Runner konvertiert dies in ein `tool_result` mit `is_error: true` und dem von dir angegebenen Inhalt.

    ```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)
            {
                // Logge hier, wenn du den Fehler prüfen musst, bevor Claude ihn sieht.
                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">
    Das Abfangen von Tool-Fehlern, bevor sie an Claude gesendet werden, wird im Go SDK derzeit nicht unterstützt. Der Runner konvertiert einen von deinem Handler zurückgegebenen Fehler intern in ein Tool-Ergebnis mit `is_error: true`. Um den Fehlerinhalt anzupassen, fange den Fehler innerhalb deines Handlers ab und gib ein Ergebnis zurück, anstatt den Fehler zurückzugeben.
  </Tab>

  <Tab title="Java">
    Das Abfangen von Tool-Fehlern, bevor sie an Claude gesendet werden, wird im Java SDK derzeit nicht unterstützt. Der Runner fängt jede von der Funktion eines Tools ausgelöste Exception ab und wandelt sie automatisch in ein Tool-Ergebnis mit `is_error: true` um. Um den Fehlerinhalt zu steuern, fange die Exception innerhalb der Funktion ab und gib deinen eigenen Inhalt zurück.
  </Tab>

  <Tab title="PHP">
    Der PHP-Tool-Runner stellt Tool-Ergebnisse derzeit nicht bereit, bevor sie angehängt werden. Exceptions, die von der `run`-Closure eines Tools geworfen werden, werden abgefangen und automatisch als Tool-Ergebnisse mit `is_error: true` an Claude gesendet. Um Fehlerinhalte zu untersuchen oder zu ersetzen, verwende das manuelle `pushMessages()`-Muster, das unter [Tool-Ergebnisse ändern](https://platform.claude.com/docs/de/agents-and-tools/tool-use/tool-runner#modifying-tool-results) gezeigt wird.
  </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

      # Wenn next_message zurückkehrt, hat der Runner die Tools dieses Turns ausgeführt und
      # ihre Ergebnisse als letzte Nachricht (Rolle user) angehängt. Prüfe sie hier,
      # bevor die nächste Anfrage sie an Claude sendet.
      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]
            # Option 1: Löse eine Exception aus, um die Schleife zu stoppen
            raise "Tool failed: #{block[:content]}"

            # Option 2: Protokolliere und fahre fort (lass Claude es behandeln)
            # logger.error("Tool error: #{block[:content]}")
          end
        end
      end

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

    <Note>
      Der Ruby-Tool-Runner hat keinen Hook, der die Tool-Ergebnisse zurückgibt. Sobald 

      `runner.next_message`

       zurückkehrt, sind sie der letzte Eintrag in 

      `runner.params[:messages]`

      . Prüfe sie daher dort, bevor die nächste Anfrage sie an Claude sendet.
    </Note>
  </Tab>
</Tabs>

### Tool-Ergebnisse ändern

Du kannst Tool-Ergebnisse ändern, bevor sie an Claude zurückgesendet werden. Dies ist nützlich, um Metadaten wie `cache_control` hinzuzufügen, um [Prompt-Caching](https://platform.claude.com/docs/de/build-with-claude/prompt-caching) für Tool-Ergebnisse zu aktivieren, oder um die Tool-Ausgabe zu transformieren.

<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 ist ein dict: {"role": "user", "content": [...]}
            # Ändere das Tool-Ergebnis, um Cache-Control hinzuzufügen
            for block in tool_response["content"]:
                if block["type"] == "tool_result":
                    # Füge cache_control hinzu, um dieses Tool-Ergebnis zu cachen
                    block["cache_control"] = {"type": "ephemeral"}

            # Hänge die geänderte Antwort an (verhindert das automatische Anhängen des Originals)
            runner.append_messages(message, tool_response)

        print(message.content)
    ```

    <Note>
      Rufe 

      `runner.generate_tool_call_response()`

       auf, um das Tool-Ergebnis abzurufen, ändere es und übergib es an 

      `runner.append_messages()`

      , damit der Runner nicht das Original anhängt.
    </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") {
        // Ändere das Tool-Ergebnis, um Cache-Control hinzuzufügen
        for (const block of toolResultMessage.content) {
          if (block.type === "tool_result") {
            // Füge cache_control hinzu, um dieses Tool-Ergebnis zu cachen
            block.cache_control = { type: "ephemeral" };
          }
        }
        // Kein pushMessages-Aufruf nötig: Der Runner hängt automatisch sowohl die Assistant-
        // Nachricht als auch die (nun veränderte) gecachte Tool-Antwort an.
      }

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

    <Note>
      Rufe 

      `runner.generateToolResponse()`

       auf, um das Tool-Ergebnis abzurufen, und ändere es dann direkt. Der Runner hängt das geänderte Ergebnis für dich an.
    </Note>
  </Tab>

  <Tab title="C#">
    Das Ändern von Tool-Ergebnissen, bevor sie angehängt werden (zum Beispiel um `cache_control` hinzuzufügen), wird im C# SDK derzeit nicht unterstützt. Der Runner erstellt den `tool_result`-Block intern und bietet keinen Hook, um ihn zu verändern.
  </Tab>

  <Tab title="Go">
    Der Go-Runner stellt keinen Hook bereit, um den äußeren `tool_result`-Block zu ändern. Du kannst jedoch `cache_control` auf den inneren Content-Blöcken setzen, die dein Handler zurückgibt.

    ```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),
    				// Setze cache_control auf dem inneren Content-Block. Das cache_control
    				// des äußeren tool_result-Blocks lässt sich derzeit nicht
    				// über den Go-Runner setzen.
    				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">
    Um `cache_control` für ein Tool-Ergebnis zu setzen, erstelle den zurückgegebenen `BetaToolResultBlockParam.Content` mit `ofBlocks()` statt mit `ofString()` und setze `cacheControl` für den inneren Textblock. Der Runner unterstützt derzeit nicht das Setzen von `cache_control` für den äußeren `tool_result`-Block.

    ```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">
    Der PHP-Tool-Runner hat keinen Callback, um den automatisch generierten `tool_result`-Block zu verändern. Um Felder wie `cache_control` hinzuzufügen, erstelle das Tool-Ergebnis selbst und füge es per Push hinzu. Der Aufruf von `pushMessages()` überspringt das automatische Anhängen des Runners für diesen Zug.

    ```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),
                    // Füge cache_control hinzu, um dieses Tool-Ergebnis zu cachen
                    'cache_control' => ['type' => 'ephemeral'],
                ];
            }
        }

        if ($toolResults !== []) {
            // pushMessages() markiert den State als verändert, daher überspringt der Runner sein
            // automatisches Anhängen. Füge die Assistant-Nachricht und die Tool-Ergebnisse hinzu.
            $runner->pushMessages(
                ['role' => 'assistant', 'content' => $message->content],
                ['role' => 'user', 'content' => $toolResults],
            );
        }
        // Kein Tool-Aufruf: State unverändert lassen, damit die Schleife endet.
    }
    ```
  </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

      # Greife auf die neuesten Tool-Ergebnisse im messages-Array zu
      # Der Runner fügt Tool-Ergebnisse automatisch hinzu, du kannst sie aber ändern
      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
            # Ändere das Tool-Ergebnis, um Cache-Control hinzuzufügen
            block[:cache_control] = {type: "ephemeral"}
          end
        end
      end

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

    <Note>
      Der Ruby-Tool-Runner hat keinen Hook, der die Tool-Ergebnisse zurückgibt. Sobald 

      `runner.next_message`

       zurückkehrt, sind sie der letzte Eintrag in 

      `runner.params[:messages]`

      . Ändere sie daher dort, bevor die nächste Anfrage sie an Claude sendet.
    </Note>
  </Tab>
</Tabs>

<Tip>
  Das Hinzufügen von `cache_control` zu Tool-Ergebnissen ist besonders nützlich, wenn Tools große Datenmengen zurückgeben (wie Ergebnisse einer Dokumentensuche), die du für nachfolgende API-Aufrufe cachen möchtest. Siehe [Prompt-Caching](https://platform.claude.com/docs/de/build-with-claude/prompt-caching) für weitere Details zu Caching-Strategien.
</Tip>

## Streaming

Aktiviere „streaming“ (Streaming), um die Antwort jedes Zugs inkrementell zu verarbeiten. Jede Iteration liefert ein Stream-Objekt, über dessen Events du iterieren kannst.

<Tabs exclude="shell">
  <Tab title="Python">
    Setze `stream=True` und verwende `get_final_message()`, um die akkumulierte Nachricht zu erhalten.

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

    # Beim Streaming gibt der Runner BetaMessageStream zurück
    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">
    Setze `stream: true` und verwende `finalMessage()`, um die akkumulierte Nachricht zu erhalten.

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

    // Beim Streaming gibt der Runner BetaMessageStream zurück
    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#">
    Rufe `runner.Streaming()` auf, um eine verschachtelte asynchrone Sequenz zu erhalten: einen inneren Stream für jeden API-Aufruf.

    ```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">
    Verwende `NewToolRunnerStreaming` und iteriere über `runner.AllStreaming(ctx)`. Jede äußere Iteration liefert einen Stream von Events für einen API-Aufruf.

    ```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">
    Rufe `runner.streaming()` auf, um einen Stream für jeden Zug zu erhalten. Jede `StreamResponse` muss nach der Verwendung geschlossen werden.

    ```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">
    Streaming ist mit dem PHP-Tool-Runner derzeit nicht verfügbar.
  </Tab>

  <Tab title="Ruby">
    Verwende `each_streaming`, um über Streaming-Events zu iterieren.

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

## Nächste Schritte

<CardGroup cols={2}>
  <Card title="Strikte Tool-Nutzung" icon="check" href="https://platform.claude.com/docs/de/agents-and-tools/tool-use/strict-tool-use">
    Erzwinge die Einhaltung des JSON-Schemas bei Claudes Tool-Eingaben mit grammatikbeschränktem Sampling.
  </Card>

  <Card title="Tool-Aufrufe verarbeiten" icon="arrows-left-right" href="https://platform.claude.com/docs/de/agents-and-tools/tool-use/handle-tool-calls">
    Parse `tool_use`-Blöcke, formatiere `tool_result`-Antworten und behandle Fehler mit `is_error`.
  </Card>

  <Card title="Parallele Tool-Nutzung" icon="grid" href="https://platform.claude.com/docs/de/agents-and-tools/tool-use/parallel-tool-use">
    Aktiviere, formatiere und deaktiviere parallele Tool-Aufrufe, mit Hinweisen zum Nachrichtenverlauf und zur Fehlerbehebung.
  </Card>

  <Card title="Tools definieren" icon="hammer" href="https://platform.claude.com/docs/de/agents-and-tools/tool-use/define-tools">
    Lege Tool-Schemas fest, schreibe wirksame Beschreibungen und steuere, wann Claude deine Tools aufruft.
  </Card>
</CardGroup>
