---
title: Streaming de ferramentas de granularidade fina
url: https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/fine-grained-tool-streaming
description: Faça streaming de entradas de ferramentas sem buffer de JSON no lado do servidor para aplicações sensíveis à latência.
---

<Note>
  Para saber como a "zero data retention" (retenção zero de dados), ou ZDR, se aplica a este recurso, consulte [API e retenção de dados](https://platform.claude.com/docs/pt-BR/manage-claude/api-and-data-retention).
</Note>

O "fine-grained tool streaming" (streaming de ferramentas de granularidade fina) entrega a entrada de uma ferramenta ao seu cliente à medida que Claude a gera, sem buffer no lado do servidor nem validação de JSON. Pular a etapa de buffer reduz o tempo até o primeiro fragmento de um parâmetro grande, como um documento ou um bloco de código, e os fragmentos chegam por meio dos mesmos eventos de [Streaming de mensagens](https://platform.claude.com/docs/pt-BR/build-with-claude/streaming) que o uso de ferramentas padrão.

<Warning>
  Como a API não armazena em buffer nem valida a entrada de uma ferramenta antes de fazer o streaming dela, você pode receber JSON parcial ou inválido. Uma resposta que termina com o [motivo de parada](https://platform.claude.com/docs/pt-BR/build-with-claude/handling-stop-reasons) `max_tokens` também pode cortar um parâmetro no meio. Acumule os fragmentos, proteja a análise (parse) e consulte [Tratando JSON inválido em respostas de ferramentas](https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/fine-grained-tool-streaming#handling-invalid-json-in-tool-responses) para saber como devolver entradas não analisáveis ao Claude.
</Warning>

## Como usar o streaming de ferramentas de granularidade fina

Todos os modelos oferecem suporte ao streaming de ferramentas de granularidade fina na Claude API, no [Amazon Bedrock](https://platform.claude.com/docs/pt-BR/build-with-claude/claude-in-amazon-bedrock), na [Claude Platform on AWS](https://platform.claude.com/docs/pt-BR/build-with-claude/claude-platform-on-aws), no [Google Cloud](https://platform.claude.com/docs/pt-BR/build-with-claude/claude-on-vertex-ai) e no [Microsoft Foundry](https://platform.claude.com/docs/pt-BR/build-with-claude/claude-in-microsoft-foundry). Para usá-lo, defina `eager_input_streaming` como `true` em qualquer ferramenta definida pelo usuário na qual você queira habilitar o streaming de granularidade fina e habilite o streaming na sua requisição.

O campo `eager_input_streaming` é opcional. Defini-lo como `true` ativa o streaming de granularidade fina para essa ferramenta, e omiti-lo oferece o streaming padrão com buffer, no qual a API armazena em buffer e valida cada valor de parâmetro antes de fazer o streaming de volta. A exceção é uma requisição que ainda envia o cabeçalho beta legado `fine-grained-tool-streaming-2025-05-14`, que ativa o streaming de granularidade fina para ferramentas que deixam o campo sem definição. O campo por ferramenta substitui esse cabeçalho, e um `false` explícito mantém o streaming com buffer para uma ferramenta mesmo quando uma requisição ainda o envia. O cabeçalho legado não pode ser combinado com uma entrada de conjunto de ferramentas de [uso de computador](https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/computer-use-tool) ou de [uso do navegador](https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/browser-use-tool): a API rejeita uma requisição que envia ambos, então remova o cabeçalho e defina `eager_input_streaming` nas ferramentas definidas pelo usuário que precisam dele. Consulte a [Referência de ferramentas](https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/tool-reference) para a definição do campo.

O exemplo a seguir ativa o streaming de granularidade fina para uma ferramenta `make_file` e pede a Claude um poema longo, para que a entrada da ferramenta seja grande o suficiente para você acompanhar o 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 --format jsonl <<'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
    jq -rj 'select(.delta.type == "input_json_delta") | .delta.partial_json'
  ```

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

  // O exemplo em C# monta a entrada por conta própria: índice do bloco de conteúdo -> 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?',
          ],
      ],
  );

  // O exemplo em PHP monta a entrada por conta própria: índice => string 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>

Todas as abas ativam o streaming de granularidade fina para a ferramenta `make_file`. As abas de SDK imprimem cada fragmento de entrada no momento em que ele chega e, em seguida, imprimem a entrada completa acumulada quando o stream termina. A aba cURL mostra o stream de eventos bruto, e a aba CLI usa `jq` para imprimir apenas os fragmentos. Como os fragmentos impressos se juntam para formar a entrada completa da ferramenta, o poema preenche seu terminal à medida que Claude o escreve:

```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", ...]}
```

Sem `eager_input_streaming`, a API armazena em buffer e valida cada valor de parâmetro antes de fazer o streaming de volta, então nada é impresso para um parâmetro grande até que Claude termine de gerá-lo. Com ele, os fragmentos começam a chegar assim que Claude inicia o parâmetro, e eles costumam ser mais longos, com menos quebras no meio de palavras.

## Acumulando deltas de entrada de ferramentas

O contrato de acumulação é o mesmo do streaming padrão de uso de ferramentas, então esta seção se aplica com e sem `eager_input_streaming`. Consulte [Delta de JSON de entrada](https://platform.claude.com/docs/pt-BR/build-with-claude/streaming#input-json-delta) em Streaming de mensagens para o formato do evento. O streaming de ferramentas de granularidade fina muda o que você pode presumir sobre o resultado: o servidor faz o streaming dos fragmentos sem validá-los, então a string acumulada pode não ser um JSON válido.

Quando um bloco de conteúdo `tool_use` é transmitido via streaming, o evento inicial `content_block_start` contém `input: {}` (um objeto vazio). Isso é um espaço reservado. A entrada real chega como uma série de eventos `input_json_delta`, cada um carregando um fragmento de string `partial_json`. Para montar a entrada completa, concatene esses fragmentos e analise o resultado quando o bloco for fechado.

Quando seu SDK fornece um helper acumulador (como fazem as abas Python, TypeScript, Go, Java e Ruby no exemplo anterior), ele cuida disso para você. Use o padrão manual quando seu SDK não tiver um helper ou quando você quiser controle total sobre como a entrada é montada.

O contrato de acumulação:

1. Em `content_block_start` com `type: "tool_use"`, inicialize uma string vazia: `input_json = ""`
2. Para cada `content_block_delta` com `type: "input_json_delta"`, concatene: `input_json += event.delta.partial_json`
3. Em `content_block_stop`, analise a string acumulada

Proteja a análise, como fazem os exemplos de SDK a seguir. Uma resposta também pode parar em `max_tokens` no meio de um parâmetro. Verifique o [motivo de parada](https://platform.claude.com/docs/pt-BR/build-with-claude/handling-stop-reasons) e decida se deve repetir a requisição com um `max_tokens` maior ou reparar a entrada parcial.

A incompatibilidade de tipos entre o `input: {}` inicial (objeto) e `partial_json` (string) é intencional. O objeto vazio marca a posição no array de conteúdo. As strings delta constroem o valor real.

<CodeGroup exclude="shell">
  <CodeGroupItem language="cURL">
    Acumular os fragmentos de entrada de cada bloco `tool_use` exige mais lógica do que um único comando de shell. Use o [Claude SDK](https://platform.claude.com/docs/pt-BR/cli-sdks-libraries/overview) para isso. Para ver os fragmentos brutos à medida que são transmitidos, execute a requisição em [Como usar o streaming de ferramentas de granularidade fina](https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/fine-grained-tool-streaming#how-to-use-fine-grained-tool-streaming).
  </CodeGroupItem>

  <CodeGroupItem language="CLI">
    Acumular os fragmentos de entrada de cada bloco `tool_use` exige mais lógica do que um único comando de shell. Use o [Claude SDK](https://platform.claude.com/docs/pt-BR/cli-sdks-libraries/overview) para isso. Para ver os fragmentos brutos à medida que são transmitidos, execute a requisição em [Como usar o streaming de ferramentas de granularidade fina](https://platform.claude.com/docs/pt-BR/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:
                      # Não há garantia de que a string acumulada seja um JSON válido.
                      # Consulte "Handling invalid JSON in tool responses" nesta 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 {
            // Não há garantia de que a string acumulada seja um JSON válido.
            // Consulte "Handling invalid JSON in tool responses" nesta 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 do bloco -> fragmentos JSON acumulados
  // Este exemplo acumula os deltas manualmente para mostrar o stream bruto;
  // o MessageContentAggregator do SDK também pode acumular a entrada da ferramenta automaticamente.
  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)
          {
              // Não há garantia de que a string acumulada seja um JSON válido.
              // Veja "Tratamento de JSON inválido em respostas de ferramentas" nesta 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 {
  				// Não há garantia de que a string acumulada seja um JSON válido.
  				// Consulte "Handling invalid JSON in tool responses" nesta 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 do bloco de conteúdo -> JSON acumulado da entrada da ferramenta
  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) {
                          // Não há garantia de que a string acumulada seja um JSON válido.
                          // Consulte "Tratamento de JSON inválido nas respostas de ferramentas" nesta 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();

  // O SDK PHP não fornece um acumulador de stream para a entrada da ferramenta;
  // o padrão manual mostrado aqui é a abordagem suportada.
  $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) {
                      // Não há garantia de que a string acumulada seja um JSON válido.
                      // Veja "Handling invalid JSON in tool responses" nesta 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
          # Não há garantia de que a string acumulada seja um JSON válido.
          # Consulte "Handling invalid JSON in tool responses" nesta página.
          puts "Invalid tool input: #{accumulated}"
        end
      end
    end
  end
  ```
</CodeGroup>

<Tip>
  Reagir aos fragmentos e montá-los são tarefas separadas. O primeiro exemplo reage a cada fragmento à medida que ele chega e ainda delega a montagem ao SDK nas abas que usam um helper acumulador. Use o padrão manual quando você não estiver usando um helper acumulador ou quando quiser controle total sobre a montagem.
</Tip>

## Tratando JSON inválido em respostas de ferramentas

Com o streaming de ferramentas de granularidade fina, a entrada acumulada de uma chamada de ferramenta pode ser JSON inválido ou incompleto. Quando for, você não pode executar a ferramenta, então reporte a falha de volta ao Claude. O `content` de um resultado de ferramenta não precisa ser JSON, mas envolver a string bruta em um objeto JSON sob uma única chave deixa inequívoco para o Claude que você recebeu JSON inválido e preserva a entrada original para depuração:

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

Retorne o wrapper, serializado como string, como o `content` de um bloco de conteúdo de [resultado de ferramenta](https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/handle-tool-calls#handling-errors-with-is-error) com `is_error` definido como `true`:

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

<Note>
  Construa o wrapper com sua biblioteca JSON em vez de concatenar strings, para que aspas e outros caracteres especiais na entrada inválida sejam escapados corretamente.
</Note>

## Próximos passos

<CardGroup cols={2}>
  <Card title="Janelas de contexto" icon="stack" href="https://platform.claude.com/docs/pt-BR/build-with-claude/context-windows">
    Entenda como a janela de contexto funciona, como o pensamento estendido e o uso de ferramentas contam para ela e como gerenciar o contexto à medida que as conversas crescem.
  </Card>

  <Card title="Streaming de mensagens" icon="lightning" href="https://platform.claude.com/docs/pt-BR/build-with-claude/streaming">
    Faça streaming das respostas da Messages API de forma incremental com server-sent events, incluindo deltas de texto, uso de ferramentas e pensamento estendido.
  </Card>

  <Card title="Tratar chamadas de ferramentas" icon="arrows-left-right" href="https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/handle-tool-calls">
    Analise blocos tool\_use, formate respostas tool\_result e trate erros com is\_error.
  </Card>

  <Card title="Referência de ferramentas" icon="book" href="https://platform.claude.com/docs/pt-BR/agents-and-tools/tool-use/tool-reference">
    Diretório de ferramentas fornecidas pela Anthropic e referência para propriedades opcionais de definição de ferramentas.
  </Card>
</CardGroup>
