---
title: Tool runner (SDK)
url: https://platform.claude.com/docs/it/agents-and-tools/tool-use/tool-runner
description: Usa il tool runner dell'SDK per gestire automaticamente il ciclo agentico, il wrapping degli errori e la sicurezza dei tipi.
---

Il tool runner gestisce il ciclo agentico, il wrapping degli errori e la sicurezza dei tipi al posto tuo. Quando hai bisogno di approvazione human-in-the-loop, logging personalizzato o esecuzione condizionale, usa invece il [ciclo manuale](https://platform.claude.com/docs/it/agents-and-tools/tool-use/handle-tool-calls).

Invece di gestire manualmente le chiamate agli strumenti, i risultati degli strumenti e la gestione della conversazione, il tool runner automaticamente:

* Esegue gli strumenti quando Claude li chiama
* Gestisce il ciclo richiesta/risposta
* Gestisce lo stato della conversazione
* Fornisce sicurezza dei tipi e validazione

<Note>
  Il tool runner è in beta ed è disponibile nell'[SDK Python](https://github.com/anthropics/anthropic-sdk-python/blob/main/tools.md), nell'[SDK TypeScript](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/helpers.md#tool-helpers), nell'[SDK C#](https://github.com/anthropics/anthropic-sdk-csharp/blob/main/examples/ToolRunnerExample/Program.cs), nell'[SDK Go](https://github.com/anthropics/anthropic-sdk-go/blob/main/tools.md), nell'[SDK Java](https://github.com/anthropics/anthropic-sdk-java/blob/main/anthropic-java-example/src/main/java/com/anthropic/example/BetaToolRunnerRunnableToolExample.java), nell'[SDK PHP](https://github.com/anthropics/anthropic-sdk-php/blob/main/examples/beta/beta_tool_runner.php) e nell'[SDK Ruby](https://github.com/anthropics/anthropic-sdk-ruby/blob/main/helpers.md#3-auto-looping-tool-runner-beta).
</Note>

## Utilizzo di base

Definisci gli strumenti usando gli helper dell'SDK, quindi usa il tool runner per eseguirli.

A seconda della firma dello strumento nell'SDK, uno strumento restituisce il suo risultato come stringa o come blocchi di contenuto (blocchi di testo, immagine o documento), quindi uno strumento può restituire risultati multimodali. Una stringa restituita diventa un singolo blocco di contenuto di testo. Per restituire dati strutturati, come un oggetto JSON o un numero, codificali prima come stringa.

<Tabs exclude="shell">
  <Tab title="Python">
    Usa il decoratore `@beta_tool` per definire strumenti con type hint e docstring.

    <Note>
      Se stai usando il client asincrono, sostituisci `@beta_tool` con `@beta_async_tool` e definisci la funzione con `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)
    ```

    Il decoratore `@beta_tool` ispeziona gli argomenti della funzione e la docstring per derivare lo schema JSON al posto tuo.
  </Tab>

  <Tab title="TypeScript">
    Usa `betaZodTool()` per definizioni di strumenti type-safe con validazione Zod, oppure `betaTool()` per definizioni basate su JSON Schema.

    TypeScript offre due approcci per definire gli strumenti:

    **Usando Zod (consigliato)** - Usa `betaZodTool()` per definizioni di strumenti type-safe con validazione Zod (richiede Zod 3.25.0 o superiore):

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

    const client = new Anthropic();

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

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

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

    **Usando JSON Schema** - Usa `betaTool()` per definizioni di strumenti type-safe senza Zod:

    <Note>
      L'input generato da Claude non viene validato a runtime. Esegui la validazione all'interno della funzione `run` se necessario.
    </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#">
    Definisci ogni strumento come un `BetaRunnableTool`, fornendo una `Definition` con uno schema JSON e un delegate `Run` che viene eseguito quando Claude chiama lo strumento.

    ```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">
    Definisci uno strumento con `toolrunner.NewBetaToolFromJSONSchema`. Il tipo di input dell'handler è una struct con tag `jsonschema:`. L'SDK usa la reflection su di essa per generare lo schema 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)
    	}
    }
    ```

    I tag di struct `jsonschema:` generano lo schema di input. Ad esempio, `CalculateSumInput` diventa:

    ```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">
    Definisci ogni strumento come un `BetaRunnableTool` che associa una classe di input a una funzione che viene eseguita quando Claude chiama lo strumento. Annota la classe di input con `@JsonClassDescription` per la descrizione dello strumento e ogni campo pubblico con `@JsonPropertyDescription` per le descrizioni dei parametri. L'SDK ricava dalla classe lo schema JSON, il nome dello strumento (il nome della classe in snake case) e il parsing dell'input, e contrassegna lo strumento con `strict: true` ([uso rigoroso degli strumenti](https://platform.claude.com/docs/it/agents-and-tools/tool-use/strict-tool-use)).

    La funzione riceve l'input analizzato come istanza di quella classe e restituisce un `BetaToolResultBlockParam.Content`. Per restituire del testo, racchiudilo con `BetaToolResultBlockParam.Content.ofString()`. Poiché la funzione è una lambda, può usare oggetti della tua applicazione, come il `WeatherService` nell'esempio seguente.

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

    // Rappresenta una classe già presente nella tua applicazione,
    // come un client di database o un wrapper di 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();

        // La lambda può usare 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);
        }
    }
    ```

    Il nome della classe `CalculateSum` diventa il nome dello strumento `calculate_sum`, e l'SDK genera uno schema JSON dai campi annotati:

    ```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">
    Definisci ogni strumento come un `BetaRunnableTool` che abbina la definizione dello schema JSON dello strumento a una closure che lo esegue.

    ```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">
    Usa la classe `Anthropic::BaseTool` per definire strumenti con schemi di input tipizzati.

    ```ruby
    require "anthropic"

    # Inizializza il client
    client = Anthropic::Client.new

    # Definisci lo schema di input
    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

    # Definisci lo strumento
    class GetWeather < Anthropic::BaseTool
      doc "Get the current weather in a given location"
      input_schema GetWeatherInput

      def call(input)
        # In un'implementazione completa, qui chiameresti un'API meteo
        JSON.generate({temperature: "20°C", condition: "Sunny"})
      end
    end

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

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

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

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

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

    La classe `Anthropic::BaseTool` usa il metodo `doc` per la descrizione dello strumento e `input_schema` per definire i parametri attesi. L'SDK converte automaticamente tutto questo nel formato di schema JSON appropriato.
  </Tab>
</Tabs>

## Iterare sul tool runner

Il tool runner è un iterabile che produce messaggi da Claude. A ogni iterazione, il runner verifica se Claude ha richiesto un uso degli strumenti. In tal caso, esegue lo strumento e invia automaticamente il risultato a Claude, quindi produce il messaggio successivo di Claude per continuare il tuo ciclo.

Puoi terminare il ciclo a qualsiasi iterazione con un'istruzione `break`. Il runner continua a iterare finché Claude non restituisce un messaggio senza uso degli strumenti, oppure finché non raggiunge `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`), se lo hai impostato.

Se non hai bisogno dei messaggi intermedi, puoi ottenere direttamente il messaggio finale:

<Tabs exclude="shell">
  <Tab title="Python">
    Usa `runner.until_done()` per ottenere il messaggio finale.

    ```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">
    Usa `await` sul runner per ottenere il messaggio finale.

    ```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#">
    Usa `runner.RunUntilDoneAsync()` per ottenere il messaggio finale.

    ```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">
    Usa `runner.RunToCompletion(ctx)` per ottenere il messaggio finale.

    ```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">
    Il Java SDK non ha una scorciatoia `until_done()`. Itera fino all'esaurimento e conserva l'ultimo messaggio.

    ```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">
    Usa `runUntilDone()` per ottenere il messaggio finale.

    ```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">
    Usa `runner.run_until_finished` per ottenere tutti i messaggi.

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

## Utilizzo avanzato

All'interno del ciclo, puoi leggere ogni messaggio di risposta e modificare lo stato del runner prima della successiva chiamata API. Ogni iterazione segue questo ciclo di vita:

1. Il runner invia una richiesta alla Messages API con il suo stato corrente.

2. Il runner produce il messaggio di risposta al corpo del tuo ciclo.

3. Il corpo del tuo ciclo viene eseguito. Puoi leggere il messaggio e facoltativamente modificare lo stato del runner.

4. Quando il corpo del tuo ciclo termina, il runner verifica se hai modificato la sua cronologia dei messaggi.

   * **Se non hai modificato la cronologia dei messaggi:** Se il messaggio contiene chiamate agli strumenti, il runner aggiunge il messaggio dell'assistente e i risultati degli strumenti, quindi continua. Se non ci sono chiamate agli strumenti, il ciclo termina.
   * **Se hai modificato la cronologia dei messaggi:** Il runner salta la sua aggiunta automatica e usa il tuo stato senza modifiche. Consulta [Prendere il controllo della cronologia dei messaggi](https://platform.claude.com/docs/it/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
```

### Prendere il controllo della cronologia dei messaggi

Per impostazione predefinita, il runner gestisce lo stato della conversazione al posto tuo: dopo ogni turno con chiamata agli strumenti, aggiunge il messaggio dell'assistente e gli eventuali risultati degli strumenti alla propria cronologia dei messaggi. Prendi il controllo della cronologia dei messaggi quando vuoi ritentare un turno (scartare la risposta e reinviare), iniettare un messaggio di follow-up o costruire tu stesso il risultato dello strumento.

Prendi il controllo modificando i messaggi del runner dall'interno del corpo del ciclo. Il metodo esatto dipende dall'SDK. Consulta le schede per linguaggio che seguono.

Quando assumi il controllo per un'iterazione, il runner non aggiunge il messaggio dell'assistente né i risultati degli strumenti di quel turno. Diventi responsabile di mantenere valida la conversazione: aggiungi tu stesso il messaggio dell'assistente e un risultato dello strumento (se vuoi che il turno venga conteggiato), modifica lo stato in modo condizionale affinché il ciclo possa comunque terminare quando non ci sono chiamate agli strumenti, e passa `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`) per limitare il ciclo. Tutti e sette gli SDK supportano `max_iterations` (csharp, java, php: `maxIterations`; go: `MaxIterations`).

<Tabs exclude="shell">
  <Tab title="Python">
    Usa `generate_tool_call_response()` per ispezionare o calcolare il risultato dello strumento. Chiamare `append_messages()` all'interno del ciclo indica al runner che stai gestendo tu stesso la cronologia, quindi includi il messaggio dell'assistente e il risultato dello strumento in ciò che aggiungi.

    ```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() segnala lo stato come modificato, quindi il runner salta
            # l'append automatico per questa iterazione. Aggiungi tu stesso il messaggio
            # dell'assistente e il tool result, più eventuali follow-up.
            runner.append_messages(
                message,
                tool_response,
                {"role": "user", "content": "Please be concise."},
            )
        # Se non c'è una chiamata a strumento, lascia lo stato invariato così il ciclo termina.
    ```

    Per cambiare parametri della richiesta come `max_tokens` senza prendere il controllo della cronologia dei messaggi, usa `set_messages_params()`. Il runner continua ad aggiungere automaticamente il messaggio dell'assistente e il risultato dello strumento.

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

  <Tab title="TypeScript">
    Usa `runner.params` per leggere i parametri correnti della richiesta e `setMessagesParams()` per sostituirli. Chiamare `setMessagesParams()` o `pushMessages()` all'interno del ciclo indica al runner che stai gestendo tu stesso lo stato: il messaggio dell'assistente e il risultato dello strumento di questa iterazione vengono scartati, e la richiesta successiva parte con il tuo stato.

    L'esempio seguente ritenta una risposta troncata con un budget `max_tokens` più ampio.

    ```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}.`);
        // Aumenta il budget. setMessagesParams() segna lo stato come modificato, quindi il
        // runner NON aggiunge il messaggio troncato. L'iterazione successiva riprova
        // lo stesso turno con il budget maggiore.
        runner.setMessagesParams((params) => ({ ...params, max_tokens: doubled }));
      }
      // Altrimenti lascia lo stato invariato così il runner aggiunge automaticamente e continua.
    }
    ```
  </Tab>

  <Tab title="C#">
    Chiamare `SetParams()` o `PushMessages()` contrassegna lo stato come modificato, il che fa sì che il runner salti la sua aggiunta automatica per quel turno. Il runner C# esegue comunque gli strumenti corrispondenti per quel turno e scarta i loro risultati costruiti automaticamente, quindi uno strumento che esegui anche tu stesso all'interno del corpo del ciclo viene eseguito due volte, a meno che tu non ne tenga conto. Quando prendi il controllo, aggiungi tu stesso il messaggio dell'assistente e un risultato dello strumento. Altrimenti la conversazione non farà progressi. Il runner C# termina sempre quando una risposta non contiene chiamate agli strumenti, quindi condiziona qualsiasi mutazione dello stato alla presenza di un blocco `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)
        {
            // Nessuna chiamata a strumenti: lascia lo stato invariato così il ciclo termina normalmente.
            continue;
        }

        // Esegui tu stesso lo strumento e costruisci il blocco del risultato.
        var toolResult = new BetaToolResultBlockParam(toolUseBlock.ID)
        {
            Content = await getWeatherTool.ExecuteAsync(toolUseBlock, default),
        };

        // PushMessages() segna lo stato come modificato; il runner salta l'auto-append.
        // Fornisci tu stesso il turno dell'assistente e il risultato dello strumento, poi aggiungi un follow-up.
        runner.PushMessages(
            new()
            {
                Role = Role.Assistant,
                Content = new BetaMessageParamContent(
                    JsonSerializer.SerializeToElement(
                        message.Content.Select(block => block.Json).ToArray()
                    )
                ),
            },
            new()
            {
                Role = Role.User,
                Content = new List<BetaContentBlockParam> { toolResult },
            },
            new() { Role = Role.User, Content = "Please be concise in your response." }
        );
    }
    ```
  </Tab>

  <Tab title="Go">
    Il runner Go espone i parametri come campo pubblico `Params`. Modificare `runner.Params` tra le chiamate a `NextMessage(ctx)` si applica alla successiva richiesta API. A differenza degli altri SDK, il runner Go aggiunge sempre incondizionatamente il messaggio dell'assistente e i risultati degli strumenti. Modificare `Params` non sopprime questo passaggio.

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

    	// Il runner Go aggiunge sempre il messaggio dell'assistente e i risultati degli strumenti.
    	// Le modifiche ai parametri qui si applicano all'iterazione successiva.
    	runner.Params.MaxTokens = 2048
    }
    ```
  </Tab>

  <Tab title="Java">
    Usa `runner.params()` per leggere i parametri correnti e `runner.setNextParams()` per sostituirli per l'iterazione successiva. Quando chiami `setNextParams()` all'interno del ciclo, il runner salta la sua aggiunta automatica. Il messaggio appena prodotto viene scartato e l'iterazione successiva invia i tuoi nuovi parametri senza modifiche.

    L'esempio seguente ritenta un turno che ha raggiunto il limite di token raddoppiando `max_tokens`. Mutare solo nel ramo `max_tokens` mantiene il ciclo convergente: i turni che si completano normalmente proseguono, e il runner aggiunge automaticamente e termina quando non ci sono più chiamate agli strumenti.

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

            // Chiamare setNextParams() contrassegna questo turno come gestito dall'utente: il runner
            // NON aggiunge automaticamente il messaggio troncato, quindi l'iterazione successiva
            // reinvia lo stesso prefisso di conversazione con il budget maggiore.
            runner.setNextParams(runner.params().toBuilder().maxTokens(doubled).build());
        }
        // Nessuna mutazione in un turno normale: il runner aggiunge automaticamente e continua.
    }
    ```
  </Tab>

  <Tab title="PHP">
    Usa `setMessagesParams()` e `pushMessages()` per modificare lo stato del runner, e `getParams()` per leggerlo. Chiamare uno dei due setter all'interno del ciclo indica al runner di saltare la sua aggiunta automatica, quindi la conversazione continua invece dal tuo stato modificato.

    L'esempio seguente raddoppia `max_tokens` e ritenta quando una risposta viene troncata.

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

            // Chiamare setMessagesParams() nel ciclo indica al runner di saltare
            // l'append automatico. Il messaggio troncato viene scartato; l'iterazione
            // successiva riprova con il budget maggiore.
            // Le chiavi sono in camelCase, come i parametri nominati di toolRunner().
            $runner->setMessagesParams(['maxTokens' => $doubled]);
        }
    }
    ```
  </Tab>

  <Tab title="Ruby">
    Usa `next_message` per un controllo passo dopo passo. Quando `next_message` restituisce, il messaggio dell'assistente e il risultato dello strumento per quel turno sono già stati aggiunti. Usa `feed_messages` per iniettare messaggi di follow-up tra i turni, e `runner.params.update(...)` per cambiare i parametri della richiesta sul posto.

    Prendi il controllo della cronologia dei messaggi quando, dall'interno di un blocco `each_message` o `each_streaming`, riassegni `runner.params[:messages]` o chiami `feed_messages`. Il pattern seguente chiama `feed_messages` tra le chiamate a `next_message`, il che non prende il controllo.

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

    # Avanza il runner di un passo. Il messaggio dell'assistente e il tool result vengono aggiunti
    # a runner.params[:messages] prima che next_message ritorni.
    message = runner.next_message
    puts message.content

    # Inietta un follow-up prima di continuare. feed_messages accetta uno splat, non un array.
    runner.feed_messages({role: "user", content: "Also check Boston."})

    # Modifica i parametri sul posto. Riassegnare runner.params[:messages] sostituisce
    # la cronologia dei messaggi solo se avviene dentro un blocco each_message o each_streaming.
    runner.params.update(max_tokens: 2048)

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

### Gestione automatica del contesto

Per attività agentiche di lunga durata, i tool runner TypeScript e Ruby supportano la [compaction](https://platform.claude.com/docs/it/build-with-claude/context-editing#client-side-compaction-sdk) (compattazione) automatica, che genera riepiloghi quando l'utilizzo dei token supera una soglia, così la conversazione può proseguire oltre i limiti della "context window" (finestra di contesto). Entrambi gli SDK hanno deprecato questa opzione lato client a favore della [compattazione lato server](https://platform.claude.com/docs/it/build-with-claude/compaction-threshold), che funziona con il tool runner di ogni SDK tramite il parametro di richiesta `context_management`. L'SDK Python (v1.0 e successive) e i tool runner Go, Java, C# e PHP non includono la compattazione lato client. Il tool runner dispone di un helper `compact_before_next_turn()` (typescript, java, php: `compactBeforeNextTurn()`; csharp, go: `CompactBeforeNextTurn()`) per la compattazione su richiesta. Consulta [Compatta in un ciclo](https://platform.claude.com/docs/it/build-with-claude/compaction-on-demand#compact-in-a-loop). Usa questo helper oppure una modifica di compattazione `context_management` su un runner, non entrambi.

### Debug dell'esecuzione degli strumenti

Quando uno strumento lancia un'eccezione, il tool runner la intercetta e restituisce l'errore a Claude come risultato dello strumento con `is_error: true`. Il risultato dello strumento contiene il messaggio dell'eccezione (in Python, il suo tipo e messaggio), non lo stack trace completo.

Ciò che l'SDK registra nei log dipende dal linguaggio. Il Python SDK registra l'eccezione completa, incluso il suo stack trace, tramite il modulo standard `logging` ogni volta che uno strumento solleva un'eccezione non gestita. Gli SDK Python, TypeScript e Java leggono la variabile d'ambiente `ANTHROPIC_LOG` per attivare il logging dell'SDK, che include i dettagli di richiesta e risposta:

```bash
# Log a livello info
export ANTHROPIC_LOG=info

# Log a livello debug per un output più dettagliato
export ANTHROPIC_LOG=debug
```

Gli SDK Go, Ruby, C# e PHP non leggono `ANTHROPIC_LOG`. Al di fuori di Python, nessun SDK registra nei log uno strumento fallito: per vedere perché uno strumento è fallito, intercetta e registra l'eccezione all'interno della funzione dello strumento prima di restituirla o rilanciarla.

### Intercettare gli errori degli strumenti

Per impostazione predefinita, gli errori degli strumenti vengono passati a Claude, che può quindi rispondere in modo appropriato. Tuttavia, potresti voler rilevare gli errori e gestirli diversamente, ad esempio per interrompere l'esecuzione in anticipo o implementare una gestione degli errori personalizzata.

<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 è un dict: {"role": "user", "content": [...]}
            # Verifica se un risultato di uno strumento contiene un errore
            for block in tool_response["content"]:
                if block.get("is_error"):
                    # Opzione 1: solleva un'eccezione per interrompere il ciclo
                    raise RuntimeError(f"Tool failed: {json.dumps(block['content'])}")

                    # Opzione 2: registra nel log e continua (lascia che sia Claude a gestirlo)
                    # logger.error(f"Tool error: {json.dumps(block['content'])}")

        # Elabora il messaggio normalmente
        print(message.content)
    ```

    <Note>
      Chiama 

      `runner.generate_tool_call_response()`

       nel ciclo per ottenere i risultati degli strumenti e verificare la presenza di errori prima che il runner li invii a 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") {
        // Controlla se un risultato di uno strumento contiene un errore
        for (const block of toolResultMessage.content) {
          if (block.type === "tool_result" && block.is_error) {
            // Opzione 1: lancia un'eccezione per interrompere il ciclo
            throw new Error(`Tool failed: ${JSON.stringify(block.content)}`);

            // Opzione 2: registra e continua (lascia che sia Claude a gestirlo)
            // console.error(`Tool error: ${JSON.stringify(block.content)}`);
          }
        }
      }

      // Elabora il messaggio normalmente
      console.log(message.content);
    }
    ```

    <Note>
      Chiama 

      `runner.generateToolResponse()`

       nel ciclo per ottenere i risultati degli strumenti e verificare la presenza di errori prima che il runner li invii a Claude.
    </Note>
  </Tab>

  <Tab title="C#">
    Il tool runner C# non espone un hook per ispezionare il risultato dello strumento prima che venga inviato a Claude. Per controllare il contenuto dell'errore, lancia `BetaToolError` dall'interno del corpo dello strumento. Il runner lo converte in un `tool_result` con `is_error: true` e il contenuto che fornisci.

    ```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)
            {
                // Registra qui un log se devi esaminare l'errore prima che Claude lo veda.
                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">
    L'intercettazione degli errori degli strumenti prima che vengano inviati a Claude non è attualmente supportata nel Go SDK. Il runner converte internamente un errore restituito dal tuo handler in un risultato dello strumento con `is_error: true`. Per personalizzare il contenuto dell'errore, intercetta l'errore all'interno del tuo handler e restituisci un risultato invece di restituire l'errore.
  </Tab>

  <Tab title="Java">
    L'intercettazione degli errori degli strumenti prima che vengano inviati a Claude non è attualmente supportata nell'SDK Java. Il runner intercetta qualsiasi eccezione generata dalla funzione di uno strumento e la converte automaticamente in un risultato dello strumento con `is_error: true`. Per controllare il contenuto dell'errore, intercetta l'eccezione all'interno della funzione e restituisci il tuo contenuto.
  </Tab>

  <Tab title="PHP">
    Il tool runner PHP attualmente non espone i risultati degli strumenti prima che vengano aggiunti. Le eccezioni lanciate dalla closure `run` di uno strumento vengono intercettate e inviate automaticamente a Claude come risultati dello strumento con `is_error: true`. Per ispezionare o sostituire il contenuto dell'errore, usa il pattern manuale `pushMessages()` mostrato in [Modificare i risultati degli strumenti](https://platform.claude.com/docs/it/agents-and-tools/tool-use/tool-runner#modifying-tool-results).
  </Tab>

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

    loop do
      message = runner.next_message
      break unless message

      # Quando next_message restituisce il controllo, il runner ha già eseguito gli strumenti di questo turno e
      # ne ha aggiunto i risultati come ultimo messaggio (ruolo user). Esaminali qui,
      # prima che la richiesta successiva li invii a 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]
            # Opzione 1: solleva un'eccezione per interrompere il ciclo
            raise "Tool failed: #{block[:content]}"

            # Opzione 2: registra nel log e continua (lascia che sia Claude a gestirlo)
            # logger.error("Tool error: #{block[:content]}")
          end
        end
      end

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

    <Note>
      Il tool runner Ruby non ha un hook che restituisca i risultati degli strumenti. Dopo il ritorno di 

      `runner.next_message`

      , i risultati sono l'ultima voce in 

      `runner.params[:messages]`

      , quindi verificali lì prima che la richiesta successiva li invii a Claude.
    </Note>
  </Tab>
</Tabs>

### Modificare i risultati degli strumenti

Puoi modificare i risultati degli strumenti prima che vengano inviati a Claude. Questo è utile per aggiungere metadati come `cache_control` per abilitare la [cache dei prompt](https://platform.claude.com/docs/it/build-with-claude/prompt-caching) sui risultati degli strumenti, o per trasformare l'output dello strumento.

<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 è un dict: {"role": "user", "content": [...]}
            # Modifica il risultato dello strumento per aggiungere il controllo della cache
            for block in tool_response["content"]:
                if block["type"] == "tool_result":
                    # Aggiungi cache_control per memorizzare nella cache questo risultato dello strumento
                    block["cache_control"] = {"type": "ephemeral"}

            # Accoda la risposta modificata (evita l'accodamento automatico dell'originale)
            runner.append_messages(message, tool_response)

        print(message.content)
    ```

    <Note>
      Chiama 

      `runner.generate_tool_call_response()`

       per ottenere il risultato dello strumento, modificalo e passalo a 

      `runner.append_messages()`

       così che il runner non aggiunga l'originale.
    </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") {
        // Modifica il risultato dello strumento per aggiungere il controllo della cache
        for (const block of toolResultMessage.content) {
          if (block.type === "tool_result") {
            // Aggiungi cache_control per memorizzare nella cache questo risultato dello strumento
            block.cache_control = { type: "ephemeral" };
          }
        }
        // Nessuna chiamata a pushMessages necessaria: il runner aggiunge in automatico sia il messaggio
        // dell'assistente sia la risposta dello strumento in cache (ora modificata).
      }

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

    <Note>
      Chiama 

      `runner.generateToolResponse()`

       per ottenere il risultato dello strumento, poi modificalo sul posto. Il runner aggiunge il risultato modificato al posto tuo.
    </Note>
  </Tab>

  <Tab title="C#">
    La modifica dei risultati degli strumenti prima che vengano aggiunti (ad esempio, per aggiungere `cache_control`) non è attualmente supportata nel C# SDK. Il runner costruisce internamente il blocco `tool_result` e non fornisce alcun hook per alterarlo.
  </Tab>

  <Tab title="Go">
    Il runner Go non espone un hook per modificare il blocco `tool_result` esterno. Puoi, tuttavia, impostare `cache_control` sui blocchi di contenuto interni restituiti dal tuo handler.

    ```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),
    				// Imposta cache_control sul blocco di contenuto interno. Il cache_control
    				// del blocco tool_result esterno non è attualmente
    				// impostabile tramite il runner 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">
    Per impostare `cache_control` su un risultato dello strumento, costruisci il `BetaToolResultBlockParam.Content` restituito con `ofBlocks()` invece di `ofString()` e imposta `cacheControl` sul blocco di testo interno. Il runner attualmente non supporta l'impostazione di `cache_control` sul blocco `tool_result` esterno.

    ```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">
    Il tool runner PHP non ha alcun callback per mutare il blocco `tool_result` generato automaticamente. Per aggiungere campi come `cache_control`, costruisci tu stesso il risultato dello strumento e aggiungilo. Chiamare `pushMessages()` salta l'aggiunta automatica del runner per quel turno.

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

    foreach ($runner as $message) {
        $toolResults = [];
        foreach ($message->content as $block) {
            if ($block instanceof \Anthropic\Beta\Messages\BetaToolUseBlock) {
                $toolResults[] = [
                    'type' => 'tool_result',
                    'tool_use_id' => $block->id,
                    'content' => $searchDocuments->run($block->input),
                    // Aggiungi cache_control per memorizzare nella cache questo risultato dello strumento
                    'cache_control' => ['type' => 'ephemeral'],
                ];
            }
        }

        if ($toolResults !== []) {
            // pushMessages() contrassegna lo stato come modificato, quindi il runner salta la sua
            // aggiunta automatica. Inserisci il messaggio dell'assistente e i risultati degli strumenti.
            $runner->pushMessages(
                ['role' => 'assistant', 'content' => $message->content],
                ['role' => 'user', 'content' => $toolResults],
            );
        }
        // Nessuna chiamata a strumenti: lascia lo stato invariato così il ciclo termina.
    }
    ```
  </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

      # Accedi ai risultati degli strumenti più recenti dall'array messages
      # Il runner aggiunge automaticamente i risultati degli strumenti, ma puoi modificarli
      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
            # Modifica il risultato dello strumento per aggiungere il controllo della cache
            block[:cache_control] = {type: "ephemeral"}
          end
        end
      end

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

    <Note>
      Il tool runner Ruby non ha un hook che restituisca i risultati degli strumenti. Dopo il ritorno di 

      `runner.next_message`

      , i risultati sono l'ultima voce in 

      `runner.params[:messages]`

      , quindi modificali lì prima che la richiesta successiva li invii a Claude.
    </Note>
  </Tab>
</Tabs>

<Tip>
  Aggiungere `cache_control` ai risultati degli strumenti è particolarmente utile quando gli strumenti restituiscono grandi quantità di dati (come risultati di ricerca di documenti) che vuoi mettere in cache per le chiamate API successive. Consulta [Cache dei prompt](https://platform.claude.com/docs/it/build-with-claude/prompt-caching) per maggiori dettagli sulle strategie di caching.
</Tip>

## Streaming

Abilita lo streaming per elaborare la risposta di ogni turno in modo incrementale. Ogni iterazione produce un oggetto stream su cui puoi iterare per ottenere gli eventi.

<Tabs exclude="shell">
  <Tab title="Python">
    Imposta `stream=True` e usa `get_final_message()` per ottenere il messaggio accumulato.

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

    # Durante lo streaming, il runner restituisce 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">
    Imposta `stream: true` e usa `finalMessage()` per ottenere il messaggio accumulato.

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

    // Durante lo streaming, il runner restituisce 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#">
    Chiama `runner.Streaming()` per ottenere una sequenza asincrona annidata: uno stream interno per ogni chiamata API.

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

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

  <Tab title="Go">
    Usa `NewToolRunnerStreaming` e itera su `runner.AllStreaming(ctx)`. Ogni iterazione esterna produce uno stream di eventi per una chiamata API.

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

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

  <Tab title="Java">
    Chiama `runner.streaming()` per ottenere uno stream per ogni turno. Ogni `StreamResponse` deve essere chiuso dopo l'uso.

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

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

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

  <Tab title="PHP">
    Lo streaming non è attualmente disponibile con il tool runner PHP.
  </Tab>

  <Tab title="Ruby">
    Usa `each_streaming` per iterare sugli eventi di 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>

## Prossimi passi

<CardGroup cols={2}>
  <Card title="Uso rigoroso degli strumenti" icon="check" href="https://platform.claude.com/docs/it/agents-and-tools/tool-use/strict-tool-use">
    Imponi la conformità a JSON Schema sugli input degli strumenti di Claude con il campionamento vincolato da grammatica.
  </Card>

  <Card title="Gestire le chiamate agli strumenti" icon="arrows-left-right" href="https://platform.claude.com/docs/it/agents-and-tools/tool-use/handle-tool-calls">
    Analizza i blocchi `tool_use`, formatta le risposte `tool_result` e gestisci gli errori con `is_error`.
  </Card>

  <Card title="Uso parallelo degli strumenti" icon="grid" href="https://platform.claude.com/docs/it/agents-and-tools/tool-use/parallel-tool-use">
    Abilita, formatta e disabilita le chiamate parallele agli strumenti, con indicazioni sulla cronologia dei messaggi e risoluzione dei problemi.
  </Card>

  <Card title="Definire gli strumenti" icon="hammer" href="https://platform.claude.com/docs/it/agents-and-tools/tool-use/define-tools">
    Specifica gli schemi degli strumenti, scrivi descrizioni efficaci e controlla quando Claude chiama i tuoi strumenti.
  </Card>
</CardGroup>
