---
title: Детализированная потоковая передача инструментов
url: https://platform.claude.com/docs/ru/agents-and-tools/tool-use/fine-grained-tool-streaming
description: Передавайте входные данные инструментов потоком без буферизации JSON на стороне сервера для приложений, чувствительных к задержкам.
---

<Note>
  Чтобы узнать, как «zero data retention» (нулевое хранение данных), или ZDR, применяется к этой функции, см. раздел [API и хранение данных](https://platform.claude.com/docs/ru/manage-claude/api-and-data-retention).
</Note>

«Fine-grained tool streaming» (детализированная потоковая передача инструментов) доставляет входные данные инструмента вашему клиенту по мере того, как Claude их генерирует, без буферизации на стороне сервера и без валидации JSON. Пропуск этапа буферизации сокращает время до получения первого фрагмента большого параметра, например документа или блока кода, а фрагменты поступают через те же события [потоковой передачи сообщений](https://platform.claude.com/docs/ru/build-with-claude/streaming), что и при стандартном использовании инструментов.

<Warning>
  Поскольку API не буферизует и не валидирует входные данные инструмента перед их потоковой передачей, вы можете получить частичный или невалидный JSON. Ответ, завершающийся с [причиной остановки](https://platform.claude.com/docs/ru/build-with-claude/handling-stop-reasons) `max_tokens`, также может обрезать параметр на середине. Накапливайте фрагменты, защищайте разбор и смотрите раздел [Обработка невалидного JSON в ответах инструментов](https://platform.claude.com/docs/ru/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/ru/build-with-claude/claude-in-amazon-bedrock), [Claude Platform on AWS](https://platform.claude.com/docs/ru/build-with-claude/claude-platform-on-aws), [Google Cloud](https://platform.claude.com/docs/ru/build-with-claude/claude-on-vertex-ai) и [Microsoft Foundry](https://platform.claude.com/docs/ru/build-with-claude/claude-in-microsoft-foundry). Чтобы использовать её, установите `eager_input_streaming` в значение `true` для любого пользовательского инструмента, для которого вы хотите включить детализированную потоковую передачу, и включите потоковую передачу в своём запросе.

Поле `eager_input_streaming` необязательно. Значение `true` включает детализированную потоковую передачу для этого инструмента, а если поле не указано, используется стандартная буферизованная потоковая передача, при которой API буферизует и проверяет каждое значение параметра перед его потоковой передачей. Исключение составляет запрос, который всё ещё отправляет устаревший бета-заголовок `fine-grained-tool-streaming-2025-05-14`: он включает детализированную потоковую передачу для инструментов, у которых это поле не задано. Поле на уровне инструмента заменяет этот заголовок, а явное значение `false` сохраняет буферизованную потоковую передачу для инструмента, даже если запрос всё ещё отправляет заголовок. Устаревший заголовок нельзя сочетать с записью набора инструментов [computer use](https://platform.claude.com/docs/ru/agents-and-tools/tool-use/computer-use-tool) (использование компьютера) или [browser use](https://platform.claude.com/docs/ru/agents-and-tools/tool-use/browser-use-tool) (использование браузера): API отклоняет запрос, содержащий и то и другое, поэтому удалите заголовок и установите `eager_input_streaming` для тех пользовательских инструментов, которым это нужно. Определение поля см. в [справочнике по инструментам](https://platform.claude.com/docs/ru/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# сам собирает входные данные: индекс блока содержимого -> накопленный 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/ru/build-with-claude/streaming#input-json-delta) на странице «Потоковая передача сообщений». Детализированная потоковая передача инструментов меняет то, что вы можете предполагать о результате: сервер передаёт фрагменты без проверки, поэтому накопленная строка может не быть допустимым JSON.

Когда передаётся блок содержимого `tool_use`, начальное событие `content_block_start` содержит `input: {}` (пустой объект). Это заполнитель. Фактические входные данные поступают в виде серии событий `input_json_delta`, каждое из которых несёт строковый фрагмент `partial_json`. Чтобы собрать полные входные данные, объедините эти фрагменты и разберите результат, когда блок закроется.

Если ваш SDK предоставляет «accumulator helper» (вспомогательное средство накопления), как во вкладках Python, TypeScript, Go, Java и Ruby в предыдущем примере, оно делает это за вас. Используйте ручной подход, если в вашем SDK нет такого вспомогательного средства или если вы хотите полностью контролировать сборку входных данных.

Контракт накопления:

1. При `content_block_start` с `type: "tool_use"` инициализируйте пустую строку: `input_json = ""`
2. Для каждого `content_block_delta` с `type: "input_json_delta"` добавляйте: `input_json += event.delta.partial_json`
3. При `content_block_stop` разберите накопленную строку

Защищайте разбор от ошибок, как это делается в следующих примерах SDK. Ответ также может остановиться на `max_tokens` посреди параметра. Проверьте [причину остановки](https://platform.claude.com/docs/ru/build-with-claude/handling-stop-reasons) и решите, повторить ли запрос с большим значением `max_tokens` или восстановить частичные входные данные.

Несоответствие типов между начальным `input: {}` (объект) и `partial_json` (строка) предусмотрено намеренно. Пустой объект обозначает позицию в массиве содержимого. Строки дельт формируют реальное значение.

<CodeGroup exclude="shell">
  <CodeGroupItem language="cURL">
    Накопление фрагментов входных данных каждого блока `tool_use` требует больше логики, чем одна команда оболочки. Используйте для этого [Claude SDK](https://platform.claude.com/docs/ru/cli-sdks-libraries/overview). Чтобы увидеть необработанные фрагменты по мере их потоковой передачи, выполните запрос из раздела [Как использовать детализированную потоковую передачу инструментов](https://platform.claude.com/docs/ru/agents-and-tools/tool-use/fine-grained-tool-streaming#how-to-use-fine-grained-tool-streaming).
  </CodeGroupItem>

  <CodeGroupItem language="CLI">
    Накопление фрагментов входных данных каждого блока `tool_use` требует больше логики, чем одна команда оболочки. Используйте для этого [Claude SDK](https://platform.claude.com/docs/ru/cli-sdks-libraries/overview). Чтобы увидеть необработанные фрагменты по мере их потоковой передачи, выполните запрос из раздела [Как использовать детализированную потоковую передачу инструментов](https://platform.claude.com/docs/ru/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» на этой странице.
                      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.
            // См. раздел «Обработка недопустимого JSON в ответах инструментов» на этой странице.
            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
  // В этом примере дельты накапливаются вручную, чтобы показать необработанный поток;
  // MessageContentAggregator из SDK также может накапливать входные данные инструмента автоматически.
  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.
  				// См. раздел «Обработка недопустимого JSON в ответах инструментов» на этой странице.
  				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.
                          // См. раздел «Обработка некорректного 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.
                      // См. раздел «Handling invalid JSON in tool responses» на этой странице.
                      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>"
}
```

Верните обёртку, сериализованную в строку, в качестве `content` блока содержимого [результата инструмента](https://platform.claude.com/docs/ru/agents-and-tools/tool-use/handle-tool-calls#handling-errors-with-is-error) с `is_error`, установленным в `true`:

```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/ru/build-with-claude/context-windows">
    Узнайте, как работает контекстное окно, как расширенные размышления и использование инструментов учитываются в нём и как управлять контекстом по мере роста диалогов.
  </Card>

  <Card title="Потоковая передача сообщений" icon="lightning" href="https://platform.claude.com/docs/ru/build-with-claude/streaming">
    Получайте ответы Messages API потоком по частям с помощью server-sent events, включая дельты текста, использования инструментов и расширенных размышлений.
  </Card>

  <Card title="Обработка вызовов инструментов" icon="arrows-left-right" href="https://platform.claude.com/docs/ru/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/ru/agents-and-tools/tool-use/tool-reference">
    Каталог инструментов, предоставляемых Anthropic, и справочник по необязательным свойствам определения инструментов.
  </Card>
</CardGroup>
