---
title: Streaming de herramientas de grano fino
url: https://platform.claude.com/docs/es/agents-and-tools/tool-use/fine-grained-tool-streaming
description: Transmite las entradas de herramientas sin almacenamiento en búfer de JSON del lado del servidor para aplicaciones sensibles a la latencia.
---

<Note>
  Para saber cómo se aplica la "zero data retention" (retención cero de datos), o ZDR, a esta función, consulta [API y retención de datos](https://platform.claude.com/docs/es/manage-claude/api-and-data-retention).
</Note>

El "fine-grained tool streaming" (streaming de herramientas de grano fino) entrega la entrada de una herramienta a tu cliente a medida que Claude la genera, sin almacenamiento en búfer ni validación de JSON del lado del servidor. Omitir el paso de almacenamiento en búfer reduce el tiempo hasta el primer fragmento de un parámetro grande, como un documento o un bloque de código, y los fragmentos llegan a través de los mismos eventos de [Streaming de mensajes](https://platform.claude.com/docs/es/build-with-claude/streaming) que el uso de herramientas estándar.

<Warning>
  Debido a que la API no almacena en búfer ni valida la entrada de una herramienta antes de transmitirla, podrías recibir JSON parcial o inválido. Una respuesta que termina con el [motivo de detención](https://platform.claude.com/docs/es/build-with-claude/handling-stop-reasons) `max_tokens` también puede cortar un parámetro a la mitad. Acumula los fragmentos, protege el análisis y consulta [Manejo de JSON inválido en respuestas de herramientas](https://platform.claude.com/docs/es/agents-and-tools/tool-use/fine-grained-tool-streaming#handling-invalid-json-in-tool-responses) para saber cómo devolver a Claude una entrada que no se puede analizar.
</Warning>

## Cómo usar el streaming de herramientas de grano fino

Todos los modelos admiten el streaming de herramientas de grano fino en la API de Claude, [Amazon Bedrock](https://platform.claude.com/docs/es/build-with-claude/claude-in-amazon-bedrock), [Claude Platform en AWS](https://platform.claude.com/docs/es/build-with-claude/claude-platform-on-aws), [Google Cloud](https://platform.claude.com/docs/es/build-with-claude/claude-on-vertex-ai) y [Microsoft Foundry](https://platform.claude.com/docs/es/build-with-claude/claude-in-microsoft-foundry). Para usarlo, establece `eager_input_streaming` en `true` en cualquier herramienta definida por el usuario en la que quieras habilitar el streaming de grano fino, y habilita el streaming en tu solicitud.

El campo `eager_input_streaming` es opcional. Establecerlo en `true` activa el streaming de grano fino para esa herramienta, y omitirlo te da el streaming estándar con búfer, en el que la API almacena en búfer y valida cada valor de parámetro antes de transmitirlo de vuelta. La excepción es una solicitud que todavía envía el encabezado beta heredado `fine-grained-tool-streaming-2025-05-14`, que activa el streaming de grano fino para las herramientas que dejan el campo sin establecer. El campo por herramienta reemplaza ese encabezado, y un `false` explícito mantiene el streaming con búfer para una herramienta incluso cuando una solicitud todavía lo envía. El encabezado heredado no se puede combinar con una entrada de conjunto de herramientas de [uso de computadora](https://platform.claude.com/docs/es/agents-and-tools/tool-use/computer-use-tool) o [uso de navegador](https://platform.claude.com/docs/es/agents-and-tools/tool-use/browser-use-tool): la API rechaza una solicitud que envía ambos, así que elimina el encabezado y establece `eager_input_streaming` en las herramientas definidas por el usuario que lo necesiten. Consulta la [Referencia de herramientas](https://platform.claude.com/docs/es/agents-and-tools/tool-use/tool-reference) para ver la definición del campo.

El siguiente ejemplo activa el streaming de grano fino para una herramienta `make_file` y le pide a Claude un poema largo, de modo que la entrada de la herramienta sea lo suficientemente grande como para verla llegar por streaming:

<CodeGroup>
  ```bash cURL
  curl https://api.anthropic.com/v1/messages \
    -H "content-type: application/json" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 65536,
      "tools": [
        {
          "name": "make_file",
          "description": "Write text to a file",
          "eager_input_streaming": true,
          "input_schema": {
            "type": "object",
            "properties": {
              "filename": {
                "type": "string",
                "description": "The filename to write text to"
              },
              "lines_of_text": {
                "type": "array",
                "description": "An array of lines of text to write to the file"
              }
            },
            "required": ["filename", "lines_of_text"]
          }
        }
      ],
      "messages": [
        {
          "role": "user",
          "content": "Can you write a long poem and make a file called poem.txt?"
        }
      ],
      "stream": true
    }'
  ```

  ```bash CLI
  ant messages create --stream <<'YAML'
  model: claude-opus-5-5
  max_tokens: 65536
  tools:
    - name: make_file
      description: Write text to a file
      eager_input_streaming: true
      input_schema:
        type: object
        properties:
          filename:
            type: string
            description: The filename to write text to
          lines_of_text:
            type: array
            description: An array of lines of text to write to the file
        required:
          - filename
          - lines_of_text
  messages:
    - role: user
      content: Can you write a long poem and make a file called poem.txt?
  YAML
  ```

  ```python Python
  client = anthropic.Anthropic()

  with client.messages.stream(
      max_tokens=65536,
      model="claude-opus-5-5",
      tools=[
          {
              "name": "make_file",
              "description": "Write text to a file",
              "eager_input_streaming": True,
              "input_schema": {
                  "type": "object",
                  "properties": {
                      "filename": {
                          "type": "string",
                          "description": "The filename to write text to",
                      },
                      "lines_of_text": {
                          "type": "array",
                          "description": "An array of lines of text to write to the file",
                      },
                  },
                  "required": ["filename", "lines_of_text"],
              },
          }
      ],
      messages=[
          {
              "role": "user",
              "content": "Can you write a long poem and make a file called poem.txt?",
          }
      ],
  ) as stream:
      for event in stream:
          if event.type == "input_json":
              print(event.partial_json, end="", flush=True)
      final_message = stream.get_final_message()

  print()
  for block in final_message.content:
      if block.type == "tool_use":
          print(f"Complete tool input: {block.input}")
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  const stream = client.messages.stream({
    model: "claude-opus-5-5",
    max_tokens: 65536,
    tools: [
      {
        name: "make_file",
        description: "Write text to a file",
        eager_input_streaming: true,
        input_schema: {
          type: "object",
          properties: {
            filename: {
              type: "string",
              description: "The filename to write text to"
            },
            lines_of_text: {
              type: "array",
              description: "An array of lines of text to write to the file"
            }
          },
          required: ["filename", "lines_of_text"]
        }
      }
    ],
    messages: [
      {
        role: "user",
        content: "Can you write a long poem and make a file called poem.txt?"
      }
    ]
  });

  stream.on("inputJson", (partialJson) => {
    process.stdout.write(partialJson);
  });

  const message = await stream.finalMessage();
  console.log();
  for (const block of message.content) {
    if (block.type === "tool_use") {
      console.log("Complete tool input:", block.input);
    }
  }
  ```

  ```csharp C#
  AnthropicClient client = new();

  MessageCreateParams parameters = new()
  {
      Model = Model.ClaudeOpus5_5,
      MaxTokens = 65536,
      Tools =
      [
          new Tool
          {
              Name = "make_file",
              Description = "Write text to a file",
              EagerInputStreaming = true,
              InputSchema = new InputSchema
              {
                  Properties = new Dictionary<string, JsonElement>
                  {
                      ["filename"] = JsonSerializer.SerializeToElement(
                          new { type = "string", description = "The filename to write text to" }
                      ),
                      ["lines_of_text"] = JsonSerializer.SerializeToElement(
                          new { type = "array", description = "An array of lines of text to write to the file" }
                      ),
                  },
                  Required = ["filename", "lines_of_text"],
              },
          },
      ],
      Messages =
      [
          new()
          {
              Role = Role.User,
              Content = "Can you write a long poem and make a file called poem.txt?",
          },
      ],
  };

  // El ejemplo de C# ensambla la entrada por sí mismo: índice del bloque de contenido -> JSON acumulado
  var toolInputs = new Dictionary<long, StringBuilder>();

  await foreach (var streamEvent in client.Messages.CreateStreaming(parameters))
  {
      if (
          streamEvent.TryPickContentBlockStart(out var start)
          && start.ContentBlock.TryPickToolUse(out _)
      )
      {
          toolInputs[start.Index] = new StringBuilder();
      }
      else if (
          streamEvent.TryPickContentBlockDelta(out var delta)
          && delta.Delta.TryPickInputJson(out var inputJson)
      )
      {
          Console.Write(inputJson.PartialJson);
          toolInputs[delta.Index].Append(inputJson.PartialJson);
      }
  }

  Console.WriteLine();
  foreach (var accumulatedInput in toolInputs.Values)
  {
      Console.WriteLine($"Complete tool input: {accumulatedInput}");
  }
  ```

  ```go Go
  client := anthropic.NewClient()

  makeFileTool := anthropic.ToolParam{
  	Name:                "make_file",
  	Description:         anthropic.String("Write text to a file"),
  	EagerInputStreaming: anthropic.Bool(true),
  	InputSchema: anthropic.ToolInputSchemaParam{
  		Properties: map[string]any{
  			"filename": map[string]any{
  				"type":        "string",
  				"description": "The filename to write text to",
  			},
  			"lines_of_text": map[string]any{
  				"type":        "array",
  				"description": "An array of lines of text to write to the file",
  			},
  		},
  		Required: []string{"filename", "lines_of_text"},
  	},
  }

  stream := client.Messages.NewStreaming(context.Background(), anthropic.MessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 65536,
  	Tools:     []anthropic.ToolUnionParam{{OfTool: &makeFileTool}},
  	Messages: []anthropic.MessageParam{
  		anthropic.NewUserMessage(anthropic.NewTextBlock(
  			"Can you write a long poem and make a file called poem.txt?",
  		)),
  	},
  })

  message := anthropic.Message{}
  for stream.Next() {
  	event := stream.Current()
  	if err := message.Accumulate(event); err != nil {
  		panic(err)
  	}
  	if delta, ok := event.AsAny().(anthropic.ContentBlockDeltaEvent); ok {
  		if inputJSON, ok := delta.Delta.AsAny().(anthropic.InputJSONDelta); ok {
  			fmt.Print(inputJSON.PartialJSON)
  		}
  	}
  }
  if err := stream.Err(); err != nil {
  	panic(err)
  }

  fmt.Println()
  for _, block := range message.Content {
  	if toolUse, ok := block.AsAny().(anthropic.ToolUseBlock); ok {
  		fmt.Printf("Complete tool input: %s\n", toolUse.Input)
  	}
  }
  ```

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

  Tool makeFileTool = Tool.builder()
      .name("make_file")
      .description("Write text to a file")
      .eagerInputStreaming(true)
      .inputSchema(Tool.InputSchema.builder()
          .properties(Tool.InputSchema.Properties.builder()
              .putAdditionalProperty("filename", JsonValue.from(Map.of(
                  "type", "string",
                  "description", "The filename to write text to")))
              .putAdditionalProperty("lines_of_text", JsonValue.from(Map.of(
                  "type", "array",
                  "description", "An array of lines of text to write to the file")))
              .build())
          .addRequired("filename")
          .addRequired("lines_of_text")
          .build())
      .build();

  MessageCreateParams params = MessageCreateParams.builder()
      .model(Model.CLAUDE_OPUS_5_5)
      .maxTokens(65536L)
      .addTool(makeFileTool)
      .addUserMessage("Can you write a long poem and make a file called poem.txt?")
      .build();

  MessageAccumulator accumulator = MessageAccumulator.create();

  try (StreamResponse<RawMessageStreamEvent> streamResponse =
          client.messages().createStreaming(params)) {
      streamResponse.stream().forEach(event -> {
          accumulator.accumulate(event);
          if (event.isContentBlockDelta()) {
              var delta = event.asContentBlockDelta().delta();
              if (delta.isInputJson()) {
                  IO.print(delta.asInputJson().partialJson());
              }
          }
      });
  }

  IO.println("");
  accumulator.message().content().forEach(block ->
      block.toolUse().ifPresent(toolUse ->
          IO.println("Complete tool input: " + toolUse._input())));
  ```

  ```php PHP
  use Anthropic\Client;
  use Anthropic\Messages\InputJSONDelta;
  use Anthropic\Messages\Model;
  use Anthropic\Messages\RawContentBlockDeltaEvent;
  use Anthropic\Messages\RawContentBlockStartEvent;
  use Anthropic\Messages\ToolUseBlock;

  $client = new Client();

  $stream = $client->messages->createStream(
      maxTokens: 65536,
      model: Model::CLAUDE_OPUS_5_5,
      tools: [
          [
              'name' => 'make_file',
              'description' => 'Write text to a file',
              'eager_input_streaming' => true,
              'input_schema' => [
                  'type' => 'object',
                  'properties' => [
                      'filename' => [
                          'type' => 'string',
                          'description' => 'The filename to write text to',
                      ],
                      'lines_of_text' => [
                          'type' => 'array',
                          'description' => 'An array of lines of text to write to the file',
                      ],
                  ],
                  'required' => ['filename', 'lines_of_text'],
              ],
          ],
      ],
      messages: [
          [
              'role' => 'user',
              'content' => 'Can you write a long poem and make a file called poem.txt?',
          ],
      ],
  );

  // El ejemplo de PHP ensambla la entrada por sí mismo: index => cadena JSON acumulada
  $toolInputs = [];

  foreach ($stream as $event) {
      switch (true) {
          case $event instanceof RawContentBlockStartEvent:
              if ($event->contentBlock instanceof ToolUseBlock) {
                  $toolInputs[$event->index] = '';
              }
              break;
          case $event instanceof RawContentBlockDeltaEvent:
              if ($event->delta instanceof InputJSONDelta) {
                  echo $event->delta->partialJSON;
                  $toolInputs[$event->index] .= $event->delta->partialJSON;
              }
              break;
      }
  }

  echo "\n";
  foreach ($toolInputs as $toolInput) {
      echo "Complete tool input: {$toolInput}\n";
  }
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  stream = client.messages.stream(
    model: Anthropic::Models::Model::CLAUDE_OPUS_5_5,
    max_tokens: 65_536,
    tools: [
      {
        name: "make_file",
        description: "Write text to a file",
        eager_input_streaming: true,
        input_schema: {
          type: "object",
          properties: {
            filename: {
              type: "string",
              description: "The filename to write text to"
            },
            lines_of_text: {
              type: "array",
              description: "An array of lines of text to write to the file"
            }
          },
          required: ["filename", "lines_of_text"]
        }
      }
    ],
    messages: [
      {
        role: "user",
        content: "Can you write a long poem and make a file called poem.txt?"
      }
    ]
  )

  stream.each do |event|
    print event.partial_json if event.is_a?(Anthropic::Streaming::InputJsonEvent)
  end

  puts
  stream.accumulated_message.content.each do |block|
    puts "Complete tool input: #{block.input}" if block.type == :tool_use
  end
  ```
</CodeGroup>

Cada fragmento de entrada llega en el momento en que Claude lo escribe, y los fragmentos se unen para formar la entrada completa de la herramienta, de modo que el poema llega pieza por pieza:

```text wrap
{"filename": "poem.txt", "lines_of_text": ["The Wanderer's Journey", "", "I.", "", "Beneath the vast and star-strewn sky,", "Where silver moonbeams softly lie,", ...
Complete tool input: {"filename": "poem.txt", "lines_of_text": ["The Wanderer's Journey", ...]}
```

Sin `eager_input_streaming`, la API almacena en búfer y valida cada valor de parámetro antes de transmitirlo de vuelta, por lo que no se imprime nada para un parámetro grande hasta que Claude haya terminado de generarlo. Con él, los fragmentos comienzan a llegar tan pronto como Claude inicia el parámetro, y normalmente son más largos, con menos cortes a mitad de palabra.

## Acumulación de deltas de entrada de herramientas

El contrato de acumulación es el mismo que para el streaming de uso de herramientas estándar, por lo que esta sección se aplica con y sin `eager_input_streaming`. Consulta [Delta de JSON de entrada](https://platform.claude.com/docs/es/build-with-claude/streaming#input-json-delta) en Streaming de mensajes para ver el formato del evento. El streaming de herramientas de grano fino cambia lo que puedes suponer sobre el resultado: el servidor transmite fragmentos sin validarlos, por lo que la cadena acumulada podría no ser JSON válido.

Cuando un bloque de contenido `tool_use` se transmite por streaming, el evento inicial `content_block_start` contiene `input: {}` (un objeto vacío). Esto es un marcador de posición. La entrada real llega como una serie de eventos `input_json_delta`, cada uno con un fragmento de cadena `partial_json`. Para ensamblar la entrada completa, concatena estos fragmentos y analiza el resultado cuando el bloque se cierre.

Cuando tu SDK proporciona un asistente acumulador (como lo hacen las pestañas de Python, TypeScript, Go, Java y Ruby en el ejemplo anterior), este se encarga de esto por ti. Usa el patrón manual cuando tu SDK no tenga un asistente o cuando quieras control total sobre cómo se ensambla la entrada.

El contrato de acumulación:

1. En `content_block_start` con `type: "tool_use"`, inicializa una cadena vacía: `input_json = ""`
2. Para cada `content_block_delta` con `type: "input_json_delta"`, agrega: `input_json += event.delta.partial_json`
3. En `content_block_stop`, analiza la cadena acumulada

Protege el análisis, como lo hacen los siguientes ejemplos de SDK. Una respuesta también puede detenerse en `max_tokens` a mitad de un parámetro. Verifica el [motivo de detención](https://platform.claude.com/docs/es/build-with-claude/handling-stop-reasons) y decide si reintentar la solicitud con un `max_tokens` más alto o reparar la entrada parcial.

La discrepancia de tipos entre el `input: {}` inicial (objeto) y `partial_json` (cadena) es intencional. El objeto vacío marca la posición en el arreglo de contenido. Las cadenas delta construyen el valor real.

<CodeGroup exclude="shell">
  <CodeGroupItem language="cURL">
    Acumular los fragmentos de entrada de cada bloque `tool_use` requiere más lógica que un solo comando de shell. Usa el [SDK de Claude](https://platform.claude.com/docs/es/cli-sdks-libraries/overview) para esto. Para ver los fragmentos sin procesar a medida que se transmiten, ejecuta la solicitud de [Cómo usar el streaming de herramientas de grano fino](https://platform.claude.com/docs/es/agents-and-tools/tool-use/fine-grained-tool-streaming#how-to-use-fine-grained-tool-streaming).
  </CodeGroupItem>

  <CodeGroupItem language="CLI">
    Acumular los fragmentos de entrada de cada bloque `tool_use` requiere más lógica que un solo comando de shell. Usa el [SDK de Claude](https://platform.claude.com/docs/es/cli-sdks-libraries/overview) para esto. Para ver los fragmentos sin procesar a medida que se transmiten, ejecuta la solicitud de [Cómo usar el streaming de herramientas de grano fino](https://platform.claude.com/docs/es/agents-and-tools/tool-use/fine-grained-tool-streaming#how-to-use-fine-grained-tool-streaming).
  </CodeGroupItem>

  ```python Python
  client = anthropic.Anthropic()

  tool_inputs: dict[int, str] = {}  # index -> accumulated JSON string

  with client.messages.stream(
      model="claude-opus-5-5",
      max_tokens=1024,
      tools=[
          {
              "name": "get_weather",
              "description": "Get current weather for a city",
              "eager_input_streaming": True,
              "input_schema": {
                  "type": "object",
                  "properties": {"city": {"type": "string"}},
                  "required": ["city"],
              },
          }
      ],
      messages=[{"role": "user", "content": "Weather in Paris?"}],
  ) as stream:
      for event in stream:
          match event.type:
              case "content_block_start" if event.content_block.type == "tool_use":
                  tool_inputs[event.index] = ""
              case "content_block_delta" if event.delta.type == "input_json_delta":
                  tool_inputs[event.index] += event.delta.partial_json
              case "content_block_stop" if event.index in tool_inputs:
                  raw_input = tool_inputs[event.index]
                  try:
                      parsed = json.loads(raw_input)
                  except json.JSONDecodeError:
                      # No se garantiza que la cadena acumulada sea JSON válido.
                      # Consulta "Handling invalid JSON in tool responses" en esta página.
                      print(f"Invalid tool input: {raw_input}")
                  else:
                      print(f"Tool input: {parsed}")
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  const toolInputs = new Map<number, string>();

  const stream = client.messages.stream({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    tools: [
      {
        name: "get_weather",
        description: "Get current weather for a city",
        eager_input_streaming: true,
        input_schema: {
          type: "object",
          properties: { city: { type: "string" } },
          required: ["city"]
        }
      }
    ],
    messages: [{ role: "user", content: "Weather in Paris?" }]
  });

  for await (const event of stream) {
    switch (event.type) {
      case "content_block_start":
        if (event.content_block.type === "tool_use") {
          toolInputs.set(event.index, "");
        }
        break;
      case "content_block_delta":
        if (event.delta.type === "input_json_delta") {
          toolInputs.set(
            event.index,
            (toolInputs.get(event.index) ?? "") + event.delta.partial_json
          );
        }
        break;
      case "content_block_stop":
        if (toolInputs.has(event.index)) {
          const rawInput = toolInputs.get(event.index)!;
          try {
            console.log("Tool input:", JSON.parse(rawInput));
          } catch {
            // No se garantiza que la cadena acumulada sea JSON válido.
            // Consulta "Handling invalid JSON in tool responses" en esta página.
            console.log("Invalid tool input:", rawInput);
          }
        }
        break;
    }
  }
  ```

  ```csharp C#
  AnthropicClient client = new();

  MessageCreateParams parameters = new()
  {
      Model = Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      Tools =
      [
          new Tool
          {
              Name = "get_weather",
              Description = "Get current weather for a city",
              EagerInputStreaming = true,
              InputSchema = new InputSchema
              {
                  Properties = new Dictionary<string, JsonElement>
                  {
                      ["city"] = JsonSerializer.SerializeToElement(new { type = "string" }),
                  },
                  Required = ["city"],
              },
          },
      ],
      Messages = [new() { Role = Role.User, Content = "Weather in Paris?" }],
  };

  // Índice de bloque -> fragmentos JSON acumulados
  // Este ejemplo acumula los deltas manualmente para mostrar el stream sin procesar;
  // el MessageContentAggregator del SDK también puede acumular la entrada de herramientas automáticamente.
  var toolInputs = new Dictionary<long, StringBuilder>();

  await foreach (var streamEvent in client.Messages.CreateStreaming(parameters))
  {
      if (
          streamEvent.TryPickContentBlockStart(out var start)
          && start.ContentBlock.TryPickToolUse(out _)
      )
      {
          toolInputs[start.Index] = new StringBuilder();
      }
      else if (
          streamEvent.TryPickContentBlockDelta(out var delta)
          && delta.Delta.TryPickInputJson(out var inputJson)
      )
      {
          toolInputs[delta.Index].Append(inputJson.PartialJson);
      }
      else if (
          streamEvent.TryPickContentBlockStop(out var stop)
          && toolInputs.TryGetValue(stop.Index, out var accumulated)
      )
      {
          try
          {
              using var parsed = JsonDocument.Parse(accumulated.ToString());
              Console.WriteLine($"Tool input: {parsed.RootElement}");
          }
          catch (JsonException)
          {
              // No se garantiza que la cadena acumulada sea JSON válido.
              // Consulta "Manejo de JSON no válido en respuestas de herramientas" en esta página.
              Console.WriteLine($"Invalid tool input: {accumulated}");
          }
      }
  }
  ```

  ```go Go
  client := anthropic.NewClient()

  toolInputs := map[int64]string{} // content block index -> accumulated JSON

  stream := client.Messages.NewStreaming(context.Background(), anthropic.MessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Tools: []anthropic.ToolUnionParam{{
  		OfTool: &anthropic.ToolParam{
  			Name:                "get_weather",
  			Description:         anthropic.String("Get current weather for a city"),
  			EagerInputStreaming: anthropic.Bool(true),
  			InputSchema: anthropic.ToolInputSchemaParam{
  				Properties: map[string]any{
  					"city": map[string]any{"type": "string"},
  				},
  				Required: []string{"city"},
  			},
  		},
  	}},
  	Messages: []anthropic.MessageParam{
  		anthropic.NewUserMessage(anthropic.NewTextBlock("Weather in Paris?")),
  	},
  })

  for stream.Next() {
  	switch event := stream.Current().AsAny().(type) {
  	case anthropic.ContentBlockStartEvent:
  		if _, ok := event.ContentBlock.AsAny().(anthropic.ToolUseBlock); ok {
  			toolInputs[event.Index] = ""
  		}
  	case anthropic.ContentBlockDeltaEvent:
  		if delta, ok := event.Delta.AsAny().(anthropic.InputJSONDelta); ok {
  			toolInputs[event.Index] += delta.PartialJSON
  		}
  	case anthropic.ContentBlockStopEvent:
  		if accumulated, ok := toolInputs[event.Index]; ok {
  			var parsed map[string]any
  			if err := json.Unmarshal([]byte(accumulated), &parsed); err != nil {
  				// No se garantiza que la cadena acumulada sea JSON válido.
  				// Consulta "Handling invalid JSON in tool responses" en esta página.
  				fmt.Println("Invalid tool input:", accumulated)
  			} else {
  				fmt.Println("Tool input:", parsed)
  			}
  		}
  	}
  }
  if err := stream.Err(); err != nil {
  	panic(err)
  }
  ```

  ```java Java
  AnthropicClient client = AnthropicOkHttpClient.fromEnv();
  ObjectMapper objectMapper = new ObjectMapper();

  Tool weatherTool = Tool.builder()
          .name("get_weather")
          .description("Get current weather for a city")
          .eagerInputStreaming(true)
          .inputSchema(Tool.InputSchema.builder()
                  .properties(Tool.InputSchema.Properties.builder()
                          .putAdditionalProperty("city", JsonValue.from(Map.of("type", "string")))
                          .build())
                  .addRequired("city")
                  .build())
          .build();

  MessageCreateParams createParams = MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          .addTool(weatherTool)
          .addUserMessage("Weather in Paris?")
          .build();

  // Índice del bloque de contenido -> JSON de entrada de herramienta acumulado
  Map<Long, StringBuilder> toolInputs = new HashMap<>();

  try (StreamResponse<RawMessageStreamEvent> streamResponse = client.messages().createStreaming(createParams)) {
      var eventIterator = streamResponse.stream().iterator();
      while (eventIterator.hasNext()) {
          RawMessageStreamEvent event = eventIterator.next();
          switch (event.type().value()) {
              case CONTENT_BLOCK_START -> {
                  var blockStart = event.asContentBlockStart();
                  if (blockStart.contentBlock().isToolUse()) {
                      toolInputs.put(blockStart.index(), new StringBuilder());
                  }
              }
              case CONTENT_BLOCK_DELTA -> {
                  var blockDelta = event.asContentBlockDelta();
                  if (blockDelta.delta().isInputJson() && toolInputs.containsKey(blockDelta.index())) {
                      toolInputs.get(blockDelta.index()).append(blockDelta.delta().asInputJson().partialJson());
                  }
              }
              case CONTENT_BLOCK_STOP -> {
                  var blockStop = event.asContentBlockStop();
                  if (toolInputs.containsKey(blockStop.index())) {
                      String accumulated = toolInputs.get(blockStop.index()).toString();
                      try {
                          IO.println("Tool input: " + objectMapper.readTree(accumulated));
                      } catch (JsonProcessingException e) {
                          // No se garantiza que la cadena acumulada sea JSON válido.
                          // Consulta "Handling invalid JSON in tool responses" en esta página.
                          IO.println("Invalid tool input: " + accumulated);
                      }
                  }
              }
          }
      }
  }
  ```

  ```php PHP
  use Anthropic\Client;
  use Anthropic\Messages\InputJSONDelta;
  use Anthropic\Messages\Model;
  use Anthropic\Messages\RawContentBlockDeltaEvent;
  use Anthropic\Messages\RawContentBlockStartEvent;
  use Anthropic\Messages\RawContentBlockStopEvent;
  use Anthropic\Messages\ToolUseBlock;

  $client = new Client();

  // El SDK de PHP no proporciona un acumulador de stream para la entrada de herramientas;
  // el patrón manual que se muestra aquí es el enfoque admitido.
  $toolInputs = []; // index => accumulated JSON string

  $stream = $client->messages->createStream(
      maxTokens: 1024,
      model: Model::CLAUDE_OPUS_5_5,
      tools: [
          [
              'name' => 'get_weather',
              'description' => 'Get current weather for a city',
              'eager_input_streaming' => true,
              'input_schema' => [
                  'type' => 'object',
                  'properties' => ['city' => ['type' => 'string']],
                  'required' => ['city'],
              ],
          ],
      ],
      messages: [['role' => 'user', 'content' => 'Weather in Paris?']],
  );

  foreach ($stream as $event) {
      switch (true) {
          case $event instanceof RawContentBlockStartEvent:
              if ($event->contentBlock instanceof ToolUseBlock) {
                  $toolInputs[$event->index] = '';
              }
              break;
          case $event instanceof RawContentBlockDeltaEvent:
              if ($event->delta instanceof InputJSONDelta) {
                  $toolInputs[$event->index] .= $event->delta->partialJSON;
              }
              break;
          case $event instanceof RawContentBlockStopEvent:
              if (isset($toolInputs[$event->index])) {
                  $accumulated = $toolInputs[$event->index];
                  try {
                      $parsed = json_decode($accumulated, associative: true, flags: JSON_THROW_ON_ERROR);
                      echo "Tool input: " . json_encode($parsed) . "\n";
                  } catch (\JsonException $e) {
                      // No se garantiza que la cadena acumulada sea JSON válido.
                      // Consulta "Handling invalid JSON in tool responses" en esta página.
                      echo "Invalid tool input: {$accumulated}\n";
                  }
              }
              break;
      }
  }
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  tool_inputs = {} # index -> accumulated JSON string

  stream = client.messages.stream_raw(
    model: Anthropic::Models::Model::CLAUDE_OPUS_5_5,
    max_tokens: 1024,
    tools: [
      {
        name: "get_weather",
        description: "Get current weather for a city",
        eager_input_streaming: true,
        input_schema: {
          type: "object",
          properties: {city: {type: "string"}},
          required: ["city"]
        }
      }
    ],
    messages: [{role: "user", content: "Weather in Paris?"}]
  )

  stream.each do |event|
    case event
    when Anthropic::Models::RawContentBlockStartEvent
      tool_inputs[event.index] = +"" if event.content_block.type == :tool_use
    when Anthropic::Models::RawContentBlockDeltaEvent
      if event.delta.is_a?(Anthropic::Models::InputJSONDelta)
        tool_inputs[event.index] << event.delta.partial_json
      end
    when Anthropic::Models::RawContentBlockStopEvent
      if tool_inputs.key?(event.index)
        accumulated = tool_inputs[event.index]
        begin
          parsed = JSON.parse(accumulated)
          puts "Tool input: #{parsed}"
        rescue JSON::ParserError
          # No se garantiza que la cadena acumulada sea JSON válido.
          # Consulta "Handling invalid JSON in tool responses" en esta página.
          puts "Invalid tool input: #{accumulated}"
        end
      end
    end
  end
  ```
</CodeGroup>

<Tip>
  Reaccionar a los fragmentos y ensamblarlos son asuntos separados. El primer ejemplo reacciona a cada fragmento a medida que llega y aun así delega el ensamblaje al SDK en las pestañas que usan un asistente acumulador. Usa el patrón manual cuando no estés usando un asistente acumulador o cuando quieras control total sobre el ensamblaje.
</Tip>

## Manejo de JSON inválido en respuestas de herramientas

Con el streaming de herramientas de grano fino, la entrada acumulada para una llamada a herramienta podría ser JSON inválido o incompleto. Cuando lo es, no puedes ejecutar la herramienta, así que en su lugar informa el fallo a Claude. El `content` de un resultado de herramienta no tiene que ser JSON, pero envolver la cadena sin procesar en un objeto JSON bajo una sola clave deja claro para Claude, sin ambigüedad, que recibiste JSON inválido, y conserva la entrada original para la depuración:

```json
{
  "INVALID_JSON": "<the unparseable input you received>"
}
```

Devuelve el envoltorio, serializado como cadena, como el `content` de un bloque de contenido de [resultado de herramienta](https://platform.claude.com/docs/es/agents-and-tools/tool-use/handle-tool-calls#handling-errors-with-is-error) con `is_error` establecido en `true`:

```json
{
  "type": "tool_result",
  "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
  "is_error": true,
  "content": "{\"INVALID_JSON\": \"<the unparseable input you received>\"}"
}
```

<Note>
  Construye el envoltorio con tu biblioteca de JSON en lugar de concatenar cadenas, para que las comillas y otros caracteres especiales en la entrada inválida se escapen correctamente.
</Note>

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Ventanas de contexto" icon="stack" href="https://platform.claude.com/docs/es/build-with-claude/context-windows">
    Comprende cómo funciona la ventana de contexto, cómo el pensamiento extendido y el uso de herramientas cuentan para ella, y cómo gestionar el contexto a medida que crecen las conversaciones.
  </Card>

  <Card title="Streaming de mensajes" icon="lightning" href="https://platform.claude.com/docs/es/build-with-claude/streaming">
    Transmite las respuestas de la API de Messages de forma incremental con eventos enviados por el servidor, incluidos los deltas de texto, uso de herramientas y pensamiento extendido.
  </Card>

  <Card title="Manejar llamadas a herramientas" icon="arrows-left-right" href="https://platform.claude.com/docs/es/agents-and-tools/tool-use/handle-tool-calls">
    Analiza bloques tool\_use, da formato a respuestas tool\_result y maneja errores con is\_error.
  </Card>

  <Card title="Referencia de herramientas" icon="book" href="https://platform.claude.com/docs/es/agents-and-tools/tool-use/tool-reference">
    Directorio de herramientas proporcionadas por Anthropic y referencia de las propiedades opcionales de definición de herramientas.
  </Card>
</CardGroup>
