---
title: Feingranulares Tool-Streaming
url: https://platform.claude.com/docs/de/agents-and-tools/tool-use/fine-grained-tool-streaming
description: Streame Tool-Eingaben ohne serverseitige JSON-Pufferung für latenzempfindliche Anwendungen.
---

<Note>
  Um zu erfahren, wie „zero data retention“ (Null-Datenspeicherung), oder ZDR, auf diese Funktion angewendet wird, siehe [API und Datenspeicherung](https://platform.claude.com/docs/de/manage-claude/api-and-data-retention).
</Note>

„Fine-grained tool streaming“ (feingranulares Tool-Streaming) liefert die Eingabe eines Tools an deinen Client, während Claude sie generiert, ohne serverseitige Pufferung oder JSON-Validierung. Das Überspringen des Pufferungsschritts verkürzt die Zeit bis zum ersten Fragment eines großen Parameters, etwa eines Dokuments oder eines Codeblocks, und die Fragmente kommen über dieselben [Streaming-Nachrichten](https://platform.claude.com/docs/de/build-with-claude/streaming)-Events an wie bei der standardmäßigen Tool-Nutzung.

<Warning>
  Da die API die Eingabe eines Tools vor dem Streaming weder puffert noch validiert, erhältst du möglicherweise unvollständiges oder ungültiges JSON. Eine Antwort, die mit dem [Stop-Reason](https://platform.claude.com/docs/de/build-with-claude/handling-stop-reasons) `max_tokens` endet, kann einen Parameter außerdem mittendrin abschneiden. Sammle die Fragmente, sichere das Parsen ab und sieh dir [Umgang mit ungültigem JSON in Tool-Antworten](https://platform.claude.com/docs/de/agents-and-tools/tool-use/fine-grained-tool-streaming#handling-invalid-json-in-tool-responses) an, um zu erfahren, wie du nicht parsbare Eingaben an Claude zurückgibst.
</Warning>

## So verwendest du feingranulares Tool-Streaming

Alle Modelle unterstützen feingranulares Tool-Streaming auf der Claude API, [Amazon Bedrock](https://platform.claude.com/docs/de/build-with-claude/claude-in-amazon-bedrock), [Claude Platform on AWS](https://platform.claude.com/docs/de/build-with-claude/claude-platform-on-aws), [Google Cloud](https://platform.claude.com/docs/de/build-with-claude/claude-on-vertex-ai) und [Microsoft Foundry](https://platform.claude.com/docs/de/build-with-claude/claude-in-microsoft-foundry). Um es zu verwenden, setze `eager_input_streaming` auf `true` bei jedem benutzerdefinierten Tool, für das du feingranulares Streaming aktivieren möchtest, und aktiviere Streaming für deine Anfrage.

Das Feld `eager_input_streaming` ist optional. Wenn du es auf `true` setzt, wird feingranulares Streaming für dieses Tool aktiviert; lässt du es weg, erhältst du standardmäßiges gepuffertes Streaming, bei dem die API jeden Parameterwert puffert und validiert, bevor sie ihn zurückstreamt. Die Ausnahme ist eine Anfrage, die noch den veralteten Beta-Header `fine-grained-tool-streaming-2025-05-14` sendet, der feingranulares Streaming für Tools aktiviert, bei denen das Feld nicht gesetzt ist. Das Feld pro Tool ersetzt diesen Header, und ein explizites `false` behält gepuffertes Streaming für ein Tool bei, selbst wenn eine Anfrage den Header noch sendet. Der veraltete Header kann nicht mit einem Toolset-Eintrag für [Computer Use](https://platform.claude.com/docs/de/agents-and-tools/tool-use/computer-use-tool) oder [Browser Use](https://platform.claude.com/docs/de/agents-and-tools/tool-use/browser-use-tool) kombiniert werden: Die API lehnt eine Anfrage ab, die beides sendet. Entferne daher den Header und setze `eager_input_streaming` bei den benutzerdefinierten Tools, die es benötigen. Siehe [Tool-Referenz](https://platform.claude.com/docs/de/agents-and-tools/tool-use/tool-reference) für die Felddefinition.

Das folgende Beispiel aktiviert feingranulares Streaming für ein `make_file`-Tool und bittet Claude um ein langes Gedicht, sodass die Tool-Eingabe groß genug ist, um sie beim Hereinstreamen zu beobachten:

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

  // Das C#-Beispiel setzt die Eingabe selbst zusammen: Content-Block-Index -> akkumuliertes 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?',
          ],
      ],
  );

  // Das PHP-Beispiel setzt die Eingabe selbst zusammen: index => akkumulierter JSON-String
  $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>

Jeder Tab aktiviert feingranulares Streaming für das `make_file`-Tool. Die SDK-Tabs geben jedes Eingabefragment in dem Moment aus, in dem es ankommt, und geben dann die vollständige gesammelte Eingabe aus, sobald der Stream endet. Der cURL-Tab zeigt den rohen Event-Stream, und der CLI-Tab verwendet `jq`, um nur die Fragmente auszugeben. Da sich die ausgegebenen Fragmente zur vollständigen Tool-Eingabe zusammenfügen, füllt das Gedicht dein Terminal, während Claude es schreibt:

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

Ohne `eager_input_streaming` puffert und validiert die API jeden Parameterwert, bevor sie ihn zurückstreamt, sodass für einen großen Parameter nichts ausgegeben wird, bis Claude ihn vollständig generiert hat. Mit dieser Option beginnen Fragmente anzukommen, sobald Claude mit dem Parameter beginnt, und sie sind typischerweise länger und enthalten weniger Umbrüche mitten im Wort.

## Sammeln von Tool-Eingabe-Deltas

Der Sammelvertrag ist derselbe wie beim standardmäßigen Tool-Use-Streaming, daher gilt dieser Abschnitt mit und ohne `eager_input_streaming`. Siehe [Input JSON delta](https://platform.claude.com/docs/de/build-with-claude/streaming#input-json-delta) in Streaming-Nachrichten für das Event-Format. Feingranulares Tool-Streaming ändert, was du über das Ergebnis annehmen kannst: Der Server streamt Fragmente, ohne sie zu validieren, sodass der gesammelte String möglicherweise kein gültiges JSON ist.

Wenn ein `tool_use`-Content-Block gestreamt wird, enthält das anfängliche `content_block_start`-Event `input: {}` (ein leeres Objekt). Dies ist ein Platzhalter. Die eigentliche Eingabe kommt als Reihe von `input_json_delta`-Events an, die jeweils ein `partial_json`-String-Fragment enthalten. Um die vollständige Eingabe zusammenzusetzen, verkette diese Fragmente und parse das Ergebnis, wenn der Block geschlossen wird.

Wenn dein SDK einen Akkumulator-Helfer bereitstellt (wie es die Python-, TypeScript-, Go-, Java- und Ruby-Tabs im vorherigen Beispiel tun), übernimmt dieser das für dich. Verwende das manuelle Muster, wenn dein SDK keinen Helfer hat oder wenn du die volle Kontrolle darüber haben möchtest, wie die Eingabe zusammengesetzt wird.

Der Sammelvertrag:

1. Bei `content_block_start` mit `type: "tool_use"` initialisiere einen leeren String: `input_json = ""`
2. Für jedes `content_block_delta` mit `type: "input_json_delta"` hänge an: `input_json += event.delta.partial_json`
3. Bei `content_block_stop` parse den gesammelten String

Sichere das Parsen ab, wie es die folgenden SDK-Beispiele tun. Eine Antwort kann auch bei `max_tokens` mitten in einem Parameter stoppen. Prüfe den [Stop-Reason](https://platform.claude.com/docs/de/build-with-claude/handling-stop-reasons) und entscheide, ob du die Anfrage mit einem höheren `max_tokens` wiederholst oder die unvollständige Eingabe reparierst.

Die Typabweichung zwischen dem anfänglichen `input: {}` (Objekt) und `partial_json` (String) ist beabsichtigt. Das leere Objekt markiert den Platz im Content-Array. Die Delta-Strings bauen den eigentlichen Wert auf.

<CodeGroup exclude="shell">
  <CodeGroupItem language="cURL">
    Das Sammeln der Eingabefragmente jedes `tool_use`-Blocks erfordert mehr Logik als ein einzelner Shell-Befehl. Verwende dafür das [Claude SDK](https://platform.claude.com/docs/de/cli-sdks-libraries/overview). Um die rohen Fragmente beim Streaming zu sehen, führe die Anfrage aus [So verwendest du feingranulares Tool-Streaming](https://platform.claude.com/docs/de/agents-and-tools/tool-use/fine-grained-tool-streaming#how-to-use-fine-grained-tool-streaming) aus.
  </CodeGroupItem>

  <CodeGroupItem language="CLI">
    Das Sammeln der Eingabefragmente jedes `tool_use`-Blocks erfordert mehr Logik als ein einzelner Shell-Befehl. Verwende dafür das [Claude SDK](https://platform.claude.com/docs/de/cli-sdks-libraries/overview). Um die rohen Fragmente beim Streaming zu sehen, führe die Anfrage aus [So verwendest du feingranulares Tool-Streaming](https://platform.claude.com/docs/de/agents-and-tools/tool-use/fine-grained-tool-streaming#how-to-use-fine-grained-tool-streaming) aus.
  </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:
                      # Der zusammengesetzte String ist nicht garantiert gültiges JSON.
                      # Siehe „Umgang mit ungültigem JSON in Tool-Antworten“ auf dieser Seite.
                      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 {
            // Der zusammengesetzte String ist nicht garantiert gültiges JSON.
            // Siehe „Handling invalid JSON in tool responses“ auf dieser Seite.
            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?" }],
  };

  // Block-Index -> gesammelte JSON-Fragmente
  // Dieses Beispiel sammelt die Deltas manuell, um den Roh-Stream zu zeigen;
  // der `MessageContentAggregator` des SDK kann Tool-Eingaben auch automatisch sammeln.
  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)
          {
              // Der gesammelte String ist nicht garantiert gültiges JSON.
              // Siehe „Umgang mit ungültigem JSON in Tool-Antworten“ auf dieser Seite.
              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 {
  				// Der zusammengesetzte String ist nicht garantiert gültiges JSON.
  				// Siehe „Handling invalid JSON in tool responses“ auf dieser Seite.
  				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();

  // Content-Block-Index -> akkumuliertes Tool-Input-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) {
                          // Der akkumulierte String ist nicht garantiert gültiges JSON.
                          // Siehe „Umgang mit ungültigem JSON in Tool-Antworten“ auf dieser Seite.
                          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();

  // Das PHP SDK bietet keinen Stream-Akkumulator für Tool-Eingaben;
  // das hier gezeigte manuelle Muster ist der unterstützte Ansatz.
  $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) {
                      // Der akkumulierte String ist nicht garantiert gültiges JSON.
                      // Siehe "Umgang mit ungültigem JSON in Tool-Antworten" auf dieser Seite.
                      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
          # Der zusammengesetzte String ist nicht garantiert gültiges JSON.
          # Siehe „Umgang mit ungültigem JSON in Tool-Antworten“ auf dieser Seite.
          puts "Invalid tool input: #{accumulated}"
        end
      end
    end
  end
  ```
</CodeGroup>

<Tip>
  Auf Fragmente zu reagieren und sie zusammenzusetzen sind getrennte Anliegen. Das erste Beispiel reagiert auf jedes Fragment, sobald es ankommt, und überlässt das Zusammensetzen in den Tabs, die einen Akkumulator-Helfer verwenden, dennoch dem SDK. Verwende das manuelle Muster, wenn du keinen Akkumulator-Helfer verwendest oder wenn du volle Kontrolle über das Zusammensetzen haben möchtest.
</Tip>

## Umgang mit ungültigem JSON in Tool-Antworten

Beim feingranularen Tool-Streaming kann die gesammelte Eingabe für einen Tool-Aufruf ungültiges oder unvollständiges JSON sein. Wenn das der Fall ist, kannst du das Tool nicht ausführen, also melde den Fehler stattdessen an Claude zurück. Der `content` eines Tool-Ergebnisses muss kein JSON sein, aber wenn du den rohen String in ein JSON-Objekt unter einem einzelnen Schlüssel verpackst, ist für Claude eindeutig, dass du ungültiges JSON erhalten hast, und die ursprüngliche Eingabe bleibt für das Debugging erhalten:

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

Gib den Wrapper, zu einem String serialisiert, als `content` eines [Tool-Ergebnis](https://platform.claude.com/docs/de/agents-and-tools/tool-use/handle-tool-calls#handling-errors-with-is-error)-Content-Blocks zurück, bei dem `is_error` auf `true` gesetzt ist:

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

<Note>
  Erstelle den Wrapper mit deiner JSON-Bibliothek statt durch das Verketten von Strings, damit Anführungszeichen und andere Sonderzeichen in der ungültigen Eingabe korrekt escaped werden.
</Note>

## Nächste Schritte

<CardGroup cols={2}>
  <Card title="Kontextfenster" icon="stack" href="https://platform.claude.com/docs/de/build-with-claude/context-windows">
    Verstehe, wie das Kontextfenster funktioniert, wie erweitertes Nachdenken und Tool-Nutzung darauf angerechnet werden und wie du den Kontext verwaltest, wenn Gespräche wachsen.
  </Card>

  <Card title="Streaming-Nachrichten" icon="lightning" href="https://platform.claude.com/docs/de/build-with-claude/streaming">
    Streame Messages-API-Antworten inkrementell mit Server-Sent Events, einschließlich Text-, Tool-Use- und Extended-Thinking-Deltas.
  </Card>

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

  <Card title="Tool-Referenz" icon="book" href="https://platform.claude.com/docs/de/agents-and-tools/tool-use/tool-reference">
    Verzeichnis der von Anthropic bereitgestellten Tools und Referenz für optionale Eigenschaften von Tool-Definitionen.
  </Card>
</CardGroup>
