---
title: Streaming d'outils à granularité fine
url: https://platform.claude.com/docs/fr/agents-and-tools/tool-use/fine-grained-tool-streaming
description: Diffusez les entrées d'outils sans mise en mémoire tampon JSON côté serveur pour les applications sensibles à la latence.
---

<Note>
  Pour savoir comment la « zero data retention » (rétention zéro des données), ou ZDR, s'applique à cette fonctionnalité, consultez [API et rétention des données](https://platform.claude.com/docs/fr/manage-claude/api-and-data-retention).
</Note>

Le « fine-grained tool streaming » (streaming d'outils à granularité fine) transmet l'entrée d'un outil à votre client au fur et à mesure que Claude la génère, sans mise en mémoire tampon côté serveur ni validation JSON. Le fait de sauter l'étape de mise en mémoire tampon réduit le délai avant le premier fragment d'un paramètre volumineux, tel qu'un document ou un bloc de code, et les fragments arrivent via les mêmes événements de [Streaming de messages](https://platform.claude.com/docs/fr/build-with-claude/streaming) que l'utilisation d'outils standard.

<Warning>
  Comme l'API ne met pas en mémoire tampon ni ne valide l'entrée d'un outil avant de la diffuser, vous pourriez recevoir du JSON partiel ou invalide. Une réponse qui se termine par le [motif d'arrêt](https://platform.claude.com/docs/fr/build-with-claude/handling-stop-reasons) `max_tokens` peut également couper un paramètre en plein milieu. Accumulez les fragments, protégez l'analyse, et consultez [Gestion du JSON invalide dans les réponses d'outils](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/fine-grained-tool-streaming#handling-invalid-json-in-tool-responses) pour savoir comment renvoyer une entrée non analysable à Claude.
</Warning>

## Comment utiliser le streaming d'outils à granularité fine

Tous les modèles prennent en charge le streaming d'outils à granularité fine sur l'API Claude, [Amazon Bedrock](https://platform.claude.com/docs/fr/build-with-claude/claude-in-amazon-bedrock), [Claude Platform sur AWS](https://platform.claude.com/docs/fr/build-with-claude/claude-platform-on-aws), [Google Cloud](https://platform.claude.com/docs/fr/build-with-claude/claude-on-vertex-ai) et [Microsoft Foundry](https://platform.claude.com/docs/fr/build-with-claude/claude-in-microsoft-foundry). Pour l'utiliser, définissez `eager_input_streaming` sur `true` pour tout outil défini par l'utilisateur sur lequel vous souhaitez activer le streaming à granularité fine, et activez le streaming sur votre requête.

Le champ `eager_input_streaming` est facultatif. Le définir sur `true` active le streaming à granularité fine pour cet outil, et l'omettre vous donne le streaming standard avec mise en mémoire tampon, dans lequel l'API met en mémoire tampon et valide chaque valeur de paramètre avant de la renvoyer en streaming. L'exception concerne une requête qui envoie encore l'en-tête bêta hérité `fine-grained-tool-streaming-2025-05-14`, qui active le streaming à granularité fine pour les outils qui laissent le champ non défini. Le champ par outil remplace cet en-tête, et une valeur explicite `false` conserve le streaming avec mise en mémoire tampon pour un outil même lorsqu'une requête l'envoie encore. L'en-tête hérité ne peut pas être combiné avec une entrée d'ensemble d'outils [computer use](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/computer-use-tool) ou [browser use](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/browser-use-tool) : l'API rejette une requête qui envoie les deux, donc supprimez l'en-tête et définissez `eager_input_streaming` sur les outils définis par l'utilisateur qui en ont besoin. Consultez la [Référence des outils](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/tool-reference) pour la définition du champ.

L'exemple suivant active le streaming à granularité fine pour un outil `make_file` et demande à Claude un long poème, afin que l'entrée de l'outil soit suffisamment volumineuse pour la voir arriver en streaming :

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

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

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

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

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

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

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

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

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

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

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

  // L'exemple C# assemble lui-même l'entrée : index du bloc de contenu -> JSON accumulé.
  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?',
          ],
      ],
  );

  // L'exemple PHP assemble lui-même l'entrée : index => chaîne JSON accumulée.
  $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>

Chaque onglet active le streaming à granularité fine pour l'outil `make_file`. Les onglets SDK affichent chaque fragment d'entrée dès son arrivée, puis affichent l'entrée complète accumulée une fois le flux terminé. L'onglet cURL montre le flux d'événements brut, et l'onglet CLI utilise `jq` pour n'afficher que les fragments. Comme les fragments affichés se rejoignent pour former l'entrée complète de l'outil, le poème remplit votre terminal au fur et à mesure que Claude l'écrit :

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

Sans `eager_input_streaming`, l'API met en mémoire tampon et valide chaque valeur de paramètre avant de la renvoyer en streaming, de sorte que rien ne s'affiche pour un paramètre volumineux tant que Claude n'a pas fini de le générer. Avec ce champ, les fragments commencent à arriver dès que Claude entame le paramètre, et ils sont généralement plus longs, avec moins de coupures au milieu des mots.

## Accumulation des deltas d'entrée d'outil

Le contrat d'accumulation est le même que pour le streaming d'utilisation d'outils standard, donc cette section s'applique avec et sans `eager_input_streaming`. Consultez [Delta JSON d'entrée](https://platform.claude.com/docs/fr/build-with-claude/streaming#input-json-delta) dans Streaming de messages pour le format des événements. Le streaming d'outils à granularité fine modifie ce que vous pouvez supposer du résultat : le serveur diffuse les fragments sans les valider, de sorte que la chaîne accumulée pourrait ne pas être du JSON valide.

Lorsqu'un bloc de contenu `tool_use` est diffusé en streaming, l'événement initial `content_block_start` contient `input: {}` (un objet vide). Il s'agit d'un espace réservé. L'entrée réelle arrive sous la forme d'une série d'événements `input_json_delta`, chacun transportant un fragment de chaîne `partial_json`. Pour assembler l'entrée complète, concaténez ces fragments et analysez le résultat lorsque le bloc se ferme.

Lorsque votre SDK fournit un utilitaire d'accumulation (comme le font les onglets Python, TypeScript, Go, Java et Ruby de l'exemple précédent), il s'en charge pour vous. Utilisez le modèle manuel lorsque votre SDK ne dispose d'aucun utilitaire ou lorsque vous souhaitez un contrôle total sur la manière dont l'entrée est assemblée.

Le contrat d'accumulation :

1. Sur `content_block_start` avec `type: "tool_use"`, initialisez une chaîne vide : `input_json = ""`
2. Pour chaque `content_block_delta` avec `type: "input_json_delta"`, ajoutez : `input_json += event.delta.partial_json`
3. Sur `content_block_stop`, analysez la chaîne accumulée

Protégez l'analyse, comme le font les exemples SDK suivants. Une réponse peut également s'arrêter à `max_tokens` au milieu d'un paramètre. Vérifiez le [motif d'arrêt](https://platform.claude.com/docs/fr/build-with-claude/handling-stop-reasons) et décidez s'il faut relancer la requête avec une valeur `max_tokens` plus élevée ou réparer l'entrée partielle.

La différence de type entre le `input: {}` initial (objet) et `partial_json` (chaîne) est intentionnelle. L'objet vide marque l'emplacement dans le tableau de contenu. Les chaînes delta construisent la valeur réelle.

<CodeGroup exclude="shell">
  <CodeGroupItem language="cURL">
    L'accumulation des fragments d'entrée de chaque bloc `tool_use` nécessite plus de logique qu'une seule commande shell. Utilisez le [SDK Claude](https://platform.claude.com/docs/fr/cli-sdks-libraries/overview) pour cela. Pour voir les fragments bruts au fur et à mesure de leur streaming, exécutez la requête de [Comment utiliser le streaming d'outils à granularité fine](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/fine-grained-tool-streaming#how-to-use-fine-grained-tool-streaming).
  </CodeGroupItem>

  <CodeGroupItem language="CLI">
    L'accumulation des fragments d'entrée de chaque bloc `tool_use` nécessite plus de logique qu'une seule commande shell. Utilisez le [SDK Claude](https://platform.claude.com/docs/fr/cli-sdks-libraries/overview) pour cela. Pour voir les fragments bruts au fur et à mesure de leur streaming, exécutez la requête de [Comment utiliser le streaming d'outils à granularité fine](https://platform.claude.com/docs/fr/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:
                      # La chaîne accumulée n'est pas garantie d'être un JSON valide.
                      # Consultez « Handling invalid JSON in tool responses » sur cette page.
                      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 {
            // La chaîne accumulée n'est pas garantie d'être un JSON valide.
            // Consultez « Handling invalid JSON in tool responses » sur cette page.
            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?" }],
  };

  // Index de bloc -> fragments JSON accumulés
  // Cet exemple accumule les deltas manuellement pour montrer le flux brut ;
  // le MessageContentAggregator du SDK peut aussi accumuler automatiquement l'entrée de l'outil.
  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)
          {
              // Il n'est pas garanti que la chaîne accumulée soit du JSON valide.
              // Consultez « Gestion du JSON invalide dans les réponses d'outils » sur cette page.
              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 {
  				// La chaîne accumulée n'est pas garantie d'être un JSON valide.
  				// Consultez « Handling invalid JSON in tool responses » sur cette page.
  				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();

  // Index du bloc de contenu -> JSON d'entrée d'outil accumulé
  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) {
                          // Il n'est pas garanti que la chaîne accumulée soit un JSON valide.
                          // Consultez « Handling invalid JSON in tool responses » sur cette page.
                          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();

  // Le SDK PHP ne fournit pas d'accumulateur de flux pour les entrées d'outil ;
  // le modèle manuel présenté ici est l'approche prise en charge.
  $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) {
                      // Il n'est pas garanti que la chaîne accumulée soit du JSON valide.
                      // Consultez « Gestion du JSON invalide dans les réponses d'outil » sur cette page.
                      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
          # La chaîne accumulée n'est pas garantie d'être un JSON valide.
          # Consultez « Handling invalid JSON in tool responses » sur cette page.
          puts "Invalid tool input: #{accumulated}"
        end
      end
    end
  end
  ```
</CodeGroup>

<Tip>
  Réagir aux fragments et les assembler sont deux préoccupations distinctes. Le premier exemple réagit à chaque fragment dès son arrivée et confie tout de même l'assemblage au SDK dans les onglets qui utilisent un utilitaire d'accumulation. Utilisez le modèle manuel lorsque vous n'utilisez pas d'utilitaire d'accumulation ou lorsque vous souhaitez un contrôle total sur l'assemblage.
</Tip>

## Gestion du JSON invalide dans les réponses d'outils

Avec le streaming d'outils à granularité fine, l'entrée accumulée pour un appel d'outil pourrait être du JSON invalide ou incomplet. Dans ce cas, vous ne pouvez pas exécuter l'outil, donc signalez plutôt l'échec à Claude. Le `content` d'un résultat d'outil n'a pas besoin d'être du JSON, mais envelopper la chaîne brute dans un objet JSON sous une clé unique indique sans ambiguïté à Claude que vous avez reçu du JSON invalide, et préserve l'entrée d'origine pour le débogage :

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

Renvoyez l'enveloppe, sérialisée en chaîne, comme `content` d'un bloc de contenu de [résultat d'outil](https://platform.claude.com/docs/fr/agents-and-tools/tool-use/handle-tool-calls#handling-errors-with-is-error) avec `is_error` défini sur `true` :

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

<Note>
  Construisez l'enveloppe avec votre bibliothèque JSON plutôt qu'en concaténant des chaînes, afin que les guillemets et autres caractères spéciaux de l'entrée invalide soient correctement échappés.
</Note>

## Prochaines étapes

<CardGroup cols={2}>
  <Card title="Fenêtres de contexte" icon="stack" href="https://platform.claude.com/docs/fr/build-with-claude/context-windows">
    Comprenez comment fonctionne la fenêtre de contexte, comment la réflexion étendue et l'utilisation d'outils y sont comptabilisées, et comment gérer le contexte à mesure que les conversations s'allongent.
  </Card>

  <Card title="Streaming de messages" icon="lightning" href="https://platform.claude.com/docs/fr/build-with-claude/streaming">
    Diffusez les réponses de l'API Messages de manière incrémentale avec des événements envoyés par le serveur, y compris les deltas de texte, d'utilisation d'outils et de réflexion étendue.
  </Card>

  <Card title="Gérer les appels d'outils" icon="arrows-left-right" href="https://platform.claude.com/docs/fr/agents-and-tools/tool-use/handle-tool-calls">
    Analysez les blocs tool\_use, formatez les réponses tool\_result et gérez les erreurs avec is\_error.
  </Card>

  <Card title="Référence des outils" icon="book" href="https://platform.claude.com/docs/fr/agents-and-tools/tool-use/tool-reference">
    Répertoire des outils fournis par Anthropic et référence des propriétés facultatives de définition d'outils.
  </Card>
</CardGroup>
