---
title: 細粒度工具串流
url: https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/fine-grained-tool-streaming
description: 針對對延遲敏感的應用程式，在不經伺服器端 JSON 緩衝的情況下串流工具輸入。
---

<Note>
  若要了解「zero data retention」（零資料保留），即 ZDR 如何適用於此功能，請參閱 [API 與資料保留](https://platform.claude.com/docs/zh-TW/manage-claude/api-and-data-retention)。
</Note>

「Fine-grained tool streaming」（細粒度工具串流）會在 Claude 生成工具輸入的同時將其傳送至您的用戶端，而不經過伺服器端緩衝或 JSON 驗證。省略緩衝步驟可縮短大型參數（例如一份文件或一段程式碼）第一個片段到達的時間，而這些片段會透過與標準工具使用相同的[串流訊息](https://platform.claude.com/docs/zh-TW/build-with-claude/streaming)事件送達。

<Warning>
  由於 API 在串流工具輸入之前不會對其進行緩衝或驗證，您可能會收到不完整或無效的 JSON。以[停止原因](https://platform.claude.com/docs/zh-TW/build-with-claude/handling-stop-reasons) `max_tokens` 結束的回應也可能在參數中途將其截斷。請累積這些片段、對解析加以防護，並參閱[處理工具回應中的無效 JSON](https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/fine-grained-tool-streaming#handling-invalid-json-in-tool-responses)，了解如何將無法解析的輸入回傳給 Claude。
</Warning>

## 如何使用細粒度工具串流

所有模型皆在 Claude API、[Amazon Bedrock](https://platform.claude.com/docs/zh-TW/build-with-claude/claude-in-amazon-bedrock)、[Claude Platform on AWS](https://platform.claude.com/docs/zh-TW/build-with-claude/claude-platform-on-aws)、[Google Cloud](https://platform.claude.com/docs/zh-TW/build-with-claude/claude-on-vertex-ai) 以及 [Microsoft Foundry](https://platform.claude.com/docs/zh-TW/build-with-claude/claude-in-microsoft-foundry) 上支援細粒度工具串流。若要使用它，請在任何您想啟用細粒度串流的使用者定義工具上將 `eager_input_streaming` 設為 `true`，並在您的請求中啟用串流（streaming）。

`eager_input_streaming` 欄位是選用的。將其設為 `true` 會為該工具開啟細粒度串流；省略它則會得到標準的緩衝串流，此時 API 會在將每個參數值串流回傳之前先對其進行緩衝與驗證。例外情況是仍然傳送舊版 `fine-grained-tool-streaming-2025-05-14` beta 標頭的請求，該標頭會為未設定此欄位的工具開啟細粒度串流。這個逐工具設定的欄位取代了該標頭，而明確設為 `false` 則會讓工具維持緩衝串流，即使請求仍然傳送該標頭亦然。舊版標頭不能與[電腦使用](https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/computer-use-tool)或[瀏覽器使用](https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/browser-use-tool)工具集項目合併使用：API 會拒絕同時傳送兩者的請求，因此請移除該標頭，並在需要的使用者定義工具上設定 `eager_input_streaming`。欄位定義請參閱[工具參考](https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/tool-reference)。

以下範例為 `make_file` 工具開啟細粒度串流，並要求 Claude 寫一首長詩，使工具輸入大到足以觀察其串流過程：

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

  // 此 C# 範例自行組合輸入：content block 索引 -> 累積的 JSON
  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?',
          ],
      ],
  );

  // PHP 範例會自行組合輸入：index => 累積的 JSON 字串
  $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>

每個分頁都為 `make_file` 工具開啟了細粒度串流。SDK 分頁會在每個輸入片段到達的當下將其印出，然後在串流結束後印出完整累積的輸入。cURL 分頁顯示原始事件串流，而 CLI 分頁使用 `jq` 僅印出片段。由於印出的片段會拼接成完整的工具輸入，這首詩會隨著 Claude 的書寫逐漸填滿您的終端機：

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

若沒有 `eager_input_streaming`，API 會在將每個參數值串流回傳之前先對其進行緩衝與驗證，因此對於大型參數，在 Claude 完成生成之前不會印出任何內容。啟用後，片段會在 Claude 開始生成該參數時立即開始到達，而且它們通常較長、較少在單字中間斷開。

## 累積工具輸入增量

累積的約定與標準工具使用串流相同，因此本節無論是否使用 `eager_input_streaming` 皆適用。事件格式請參閱串流訊息中的[輸入 JSON 增量](https://platform.claude.com/docs/zh-TW/build-with-claude/streaming#input-json-delta)。細粒度工具串流改變的是您對結果所能做的假設：伺服器在不驗證的情況下串流片段，因此累積的字串可能不是有效的 JSON。

當 `tool_use` 內容區塊進行串流時，初始的 `content_block_start` 事件包含 `input: {}`（一個空物件）。這是一個佔位符。實際的輸入會以一系列 `input_json_delta` 事件送達，每個事件攜帶一個 `partial_json` 字串片段。若要組合完整的輸入，請串接這些片段，並在區塊關閉時解析結果。

如果您的 SDK 提供累積器輔助工具（如前一個範例中的 Python、TypeScript、Go、Java 和 Ruby 分頁），它會為您處理這些工作。當您的 SDK 沒有輔助工具，或您想完全掌控輸入的組合方式時，請使用手動模式。

累積的約定：

1. 在 `type: "tool_use"` 的 `content_block_start` 時，初始化一個空字串：`input_json = ""`
2. 對於每個 `type: "input_json_delta"` 的 `content_block_delta`，進行附加：`input_json += event.delta.partial_json`
3. 在 `content_block_stop` 時，解析累積的字串

請如以下 SDK 範例一樣對解析加以防護。回應也可能在參數中途因 `max_tokens` 而停止。請檢查[停止原因](https://platform.claude.com/docs/zh-TW/build-with-claude/handling-stop-reasons)，並決定是要以更高的 `max_tokens` 重試請求，還是修復不完整的輸入。

初始的 `input: {}`（物件）與 `partial_json`（字串）之間的型別不一致是刻意設計的。空物件標記了內容陣列中的位置，而增量字串則建構出真正的值。

<CodeGroup exclude="shell">
  <CodeGroupItem language="cURL">
    累積每個 `tool_use` 區塊的輸入片段所需的邏輯超出單一 shell 指令所能處理的範圍。請使用 [Claude SDK](https://platform.claude.com/docs/zh-TW/cli-sdks-libraries/overview) 來完成此操作。若要查看串流中的原始片段，請執行[如何使用細粒度工具串流](https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/fine-grained-tool-streaming#how-to-use-fine-grained-tool-streaming)中的請求。
  </CodeGroupItem>

  <CodeGroupItem language="CLI">
    累積每個 `tool_use` 區塊的輸入片段所需的邏輯超出單一 shell 指令所能處理的範圍。請使用 [Claude SDK](https://platform.claude.com/docs/zh-TW/cli-sdks-libraries/overview) 來完成此操作。若要查看串流中的原始片段，請執行[如何使用細粒度工具串流](https://platform.claude.com/docs/zh-TW/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:
                      # 累積的字串不保證是有效的 JSON。
                      # 請參閱本頁的「Handling invalid JSON in tool responses」（處理工具回應中的無效 JSON）。
                      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 {
            // 累積的字串不保證是有效的 JSON。
            // 請參閱本頁的「Handling invalid JSON in tool responses」。
            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?" }],
  };

  // 區塊索引 -> 累積的 JSON 片段
  // 此範例手動累積 delta，以呈現原始串流；
  // SDK 的 MessageContentAggregator 也能自動累積工具輸入。
  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)
          {
              // 累積的字串不保證是有效的 JSON。
              // 請參閱本頁的「處理工具回應中的無效 JSON」。
              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 {
  				// 累積的字串不保證是有效的 JSON。
  				// 請參閱本頁的「Handling invalid JSON in tool responses」。
  				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();

  // 內容區塊索引 -> 累積的工具輸入 JSON
  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) {
                          // 累積的字串不保證是有效的 JSON。
                          // 請參閱本頁的「Handling invalid JSON in tool responses」（處理工具回應中的無效 JSON）。
                          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();

  // PHP SDK 並未提供用於工具輸入的串流累加器；
  // 此處示範的手動模式即為支援的做法。
  $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) {
                      // 累加後的字串不保證是有效的 JSON。
                      // 請參閱本頁的「處理工具回應中的無效 JSON」。
                      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
          # 累積的字串不保證是有效的 JSON。
          # 請參閱本頁的「Handling invalid JSON in tool responses」。
          puts "Invalid tool input: #{accumulated}"
        end
      end
    end
  end
  ```
</CodeGroup>

<Tip>
  對片段做出反應與組合片段是兩件不同的事。第一個範例在每個片段到達時對其做出反應，而在使用累積器輔助工具的分頁中仍將組合工作交給 SDK。當您未使用累積器輔助工具，或想完全掌控組合過程時，請使用手動模式。
</Tip>

## 處理工具回應中的無效 JSON

使用細粒度工具串流時，工具呼叫所累積的輸入可能是無效或不完整的 JSON。發生這種情況時，您無法執行該工具，因此請改為將失敗回報給 Claude。工具結果的 `content` 不一定要是 JSON，但將原始字串包裝在單一鍵的 JSON 物件中，可以讓 Claude 明確知道您收到了無效的 JSON，並保留原始輸入以供除錯：

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

將此包裝物件序列化為字串後，作為 `is_error` 設為 `true` 的[工具結果](https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/handle-tool-calls#handling-errors-with-is-error)內容區塊的 `content` 回傳：

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

<Note>
  請使用您的 JSON 函式庫來建構包裝物件，而非透過串接字串，如此無效輸入中的引號與其他特殊字元才能被正確跳脫。
</Note>

## 後續步驟

<CardGroup cols={2}>
  <Card title="上下文視窗" icon="stack" href="https://platform.claude.com/docs/zh-TW/build-with-claude/context-windows">
    了解上下文視窗的運作方式、擴展思考與工具使用如何計入其中，以及如何隨著對話增長管理上下文。
  </Card>

  <Card title="串流訊息" icon="lightning" href="https://platform.claude.com/docs/zh-TW/build-with-claude/streaming">
    透過伺服器傳送事件以增量方式串流 Messages API 回應，包括文字、工具使用與擴展思考增量。
  </Card>

  <Card title="處理工具呼叫" icon="arrows-left-right" href="https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/handle-tool-calls">
    解析 tool\_use 區塊、格式化 tool\_result 回應，並使用 is\_error 處理錯誤。
  </Card>

  <Card title="工具參考" icon="book" href="https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/tool-reference">
    Anthropic 提供之工具的目錄，以及選用工具定義屬性的參考。
  </Card>
</CardGroup>
