---
title: Diagnostics du cache
url: https://platform.claude.com/docs/fr/build-with-claude/cache-diagnostics
description: Diagnostiquez les échecs inattendus du cache de prompts en comparant des requêtes consécutives et en identifiant exactement où le préfixe du prompt a divergé.
featureMetadata:
  status: ga
  zdr:
    eligibility: eligible
    note: Excludes [Covered Models](https://platform.claude.com/docs/en/manage-claude/api-and-data-retention#model-specific-data-retention-requirements).
  supportedPlatforms:
    Claude API: ga
---

La [mise en cache des prompts](https://platform.claude.com/docs/fr/build-with-claude/prompt-caching) (« prompt caching ») réduit considérablement la latence et les coûts, mais uniquement lorsque le début de votre prompt est identique octet pour octet à une requête récente. Un outil réordonné, un horodatage interpolé dans votre invite système (« system prompt ») ou une modification d'un message antérieur peuvent invalider silencieusement le cache. Sans les diagnostics du cache, le seul signal est la chute de `usage.cache_read_input_tokens` à zéro, sans aucune indication de ce qui a changé.

Les diagnostics du cache comblent cette lacune. Transmettez l'`id` de votre réponse précédente, et l'API compare les deux requêtes et vous indique où elles ont divergé (le modèle, l'invite système, les outils ou l'historique des messages) afin que vous puissiez corriger la cause première au lieu de deviner.

## Fonctionnement des diagnostics du cache

Pour chaque requête qui inclut l'objet `diagnostics`, l'API stocke une « fingerprint » (empreinte) légère indexée par l'`id` de la réponse. Elle ne stocke rien pour les requêtes qui omettent cet objet. Lors de votre requête suivante, incluez l'`id` de la réponse précédente en tant que `diagnostics.previous_message_id`. L'API reconstruit l'empreinte de la nouvelle requête, la compare à celle qui a été stockée et joint à la réponse un objet `diagnostics` décrivant le premier point de divergence.

La comparaison porte sur la structure de la requête, indépendamment du fait que le cache ait effectivement été atteint ou non. Consultez [Lire les diagnostics conjointement avec l'utilisation](https://platform.claude.com/docs/fr/build-with-claude/cache-diagnostics#reading-diagnostics-alongside-usage) pour savoir comment combiner le résultat `diagnostics` avec `usage.cache_read_input_tokens`.

Les empreintes ne contiennent que des hachages et des estimations du nombre de tokens (jamais le contenu brut du prompt), sont conservées pendant une durée limitée, sont restreintes à votre organisation et à votre espace de travail, et ne sont utilisées à aucune autre fin.

## Utilisation de base

Incluez l'objet `diagnostics` à chaque tour. C'est cet objet qui constitue l'activation : l'API ne stocke une empreinte que pour les requêtes qui l'incluent. Au premier tour, transmettez `"previous_message_id": null` pour activer la fonctionnalité sans message antérieur auquel comparer. Aux tours suivants, transmettez l'`id` de la réponse précédente. L'en-tête bêta `cache-diagnosis-2026-04-07` n'est plus requis, et les requêtes qui l'envoient encore fonctionnent comme auparavant.

<CodeGroup>
  ```bash cURL
  # Tour 1 : établir le cache et activer les diagnostics.
  response=$(curl -sS --fail-with-body https://api.anthropic.com/v1/messages \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "cache_control": {"type": "ephemeral"},
      "system": "You are an AI assistant analyzing a large document. <document>...</document>",
      "messages": [{"role": "user", "content": "Summarize section 1."}],
      "diagnostics": {"previous_message_id": null}
    }')
  jq '{id, diagnostics}' <<< "$response"
  message_id=$(jq -r '.id' <<< "$response")

  # Tour 2 : référencer le tour précédent pour que l'API puisse comparer les préfixes.
  curl -sS --fail-with-body https://api.anthropic.com/v1/messages \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "cache_control": {"type": "ephemeral"},
      "system": "You are an AI assistant analyzing a large document. <document>...</document>",
      "messages": [
        {"role": "user", "content": "Summarize section 1."},
        {"role": "assistant", "content": "Section 1 covers..."},
        {"role": "user", "content": "Now summarize section 2."}
      ],
      "diagnostics": {"previous_message_id": "'"$message_id"'"}
    }' | jq '{id, diagnostics}'  # diagnostics: null means no divergence was found
  ```

  ```bash CLI
  # Tour 1
  turn1=$(ant beta:messages create \
    --transform '{id,usage,diagnostics}' <<'YAML'
  model: claude-opus-5-5
  max_tokens: 1024
  cache_control:
    type: ephemeral
  system: "You are an AI assistant analyzing a large document. <document>...</document>"
  messages:
    - role: user
      content: Summarize section 1.
  diagnostics:
    previous_message_id: null
  YAML
  )
  printf '%s\n' "$turn1"

  # Tour 2 : passez l'id du tour 1 comme previous_message_id.
  message_id=$(jq -r '.id' <<<"$turn1")
  ant beta:messages create \
    --transform '{id,usage,diagnostics}' <<YAML
  model: claude-opus-5-5
  max_tokens: 1024
  cache_control:
    type: ephemeral
  system: "You are an AI assistant analyzing a large document. <document>...</document>"
  messages:
    - role: user
      content: Summarize section 1.
    - role: assistant
      content: Section 1 covers...
    - role: user
      content: Now summarize section 2.
  diagnostics:
    previous_message_id: $message_id
  YAML
  ```

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

  SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>"

  # Tour 1 : activation avec previous_message_id=None.
  r1 = client.beta.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      cache_control={"type": "ephemeral"},
      system=SYSTEM,
      messages=[{"role": "user", "content": "Summarize section 1."}],
      diagnostics={"previous_message_id": None},
  )

  # Tour 2 : référencer l'identifiant de la réponse précédente.
  r2 = client.beta.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      cache_control={"type": "ephemeral"},
      system=SYSTEM,
      messages=[
          {"role": "user", "content": "Summarize section 1."},
          {"role": "assistant", "content": r1.content},
          {"role": "user", "content": "Now summarize section 2."},
      ],
      diagnostics={"previous_message_id": r1.id},
  )

  diagnostics = r2.diagnostics
  if diagnostics is None:
      print("No divergence detected.")
  elif diagnostics.cache_miss_reason is None:
      print("Comparison still pending.")
  else:
      print(f"cache_miss_reason: {diagnostics.cache_miss_reason.type}")
  ```

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

  const SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>";

  // Tour 1 : activez l'option avec previous_message_id: null.
  const r1 = await client.beta.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    cache_control: { type: "ephemeral" },
    system: SYSTEM,
    messages: [{ role: "user", content: "Summarize section 1." }],
    diagnostics: { previous_message_id: null }
  });

  // Tour 2 : référencez l'id de la réponse précédente.
  const r2 = await client.beta.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    cache_control: { type: "ephemeral" },
    system: SYSTEM,
    messages: [
      { role: "user", content: "Summarize section 1." },
      { role: "assistant", content: r1.content },
      { role: "user", content: "Now summarize section 2." }
    ],
    diagnostics: { previous_message_id: r1.id }
  });

  if (r2.diagnostics === null) {
    console.log("No divergence detected.");
  } else if (r2.diagnostics.cache_miss_reason === null) {
    console.log("Comparison still pending.");
  } else {
    console.log(`cache_miss_reason: ${r2.diagnostics.cache_miss_reason.type}`);
  }
  ```

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

  var system = "You are an AI assistant analyzing a large document. <document>...</document>";

  var r1 = await client.Beta.Messages.Create(
      new()
      {
          Model = Messages::Model.ClaudeOpus5_5,
          MaxTokens = 1024,
          CacheControl = new(),
          System = system,
          Messages =
          [
              new() { Role = Role.User, Content = "Summarize section 1." },
          ],
          Diagnostics = new() { PreviousMessageID = null },
      }
  );

  var r2 = await client.Beta.Messages.Create(
      new()
      {
          Model = Messages::Model.ClaudeOpus5_5,
          MaxTokens = 1024,
          CacheControl = new(),
          System = system,
          Messages =
          [
              new() { Role = Role.User, Content = "Summarize section 1." },
              new()
              {
                  Role = Role.Assistant,
                  Content = r1.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
              },
              new() { Role = Role.User, Content = "Now summarize section 2." },
          ],
          Diagnostics = new() { PreviousMessageID = r1.ID },
      }
  );

  Console.WriteLine(r2.Diagnostics switch
  {
      null => "No divergence detected.",
      { CacheMissReason: null } => "Comparison still pending.",
      { CacheMissReason.Type: var type } => $"cache_miss_reason: {type.GetString()}",
  });
  ```

  ```go Go
  client := anthropic.NewClient()
  ctx := context.Background()

  system := []anthropic.BetaTextBlockParam{
  	{Text: "You are an AI assistant analyzing a large document. <document>...</document>"},
  }

  r1, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
  	Model:        anthropic.ModelClaudeOpus5_5,
  	MaxTokens:    1024,
  	CacheControl: anthropic.BetaCacheControlEphemeralParam{},
  	System:       system,
  	Messages: []anthropic.BetaMessageParam{
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Summarize section 1.")),
  	},
  	Diagnostics: anthropic.BetaDiagnosticsParam{
  		PreviousMessageID: param.Null[string](),
  	},
  })
  if err != nil {
  	panic(err)
  }

  r2, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
  	Model:        anthropic.ModelClaudeOpus5_5,
  	MaxTokens:    1024,
  	CacheControl: anthropic.BetaCacheControlEphemeralParam{},
  	System:       system,
  	Messages: []anthropic.BetaMessageParam{
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Summarize section 1.")),
  		r1.ToParam(),
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Now summarize section 2.")),
  	},
  	Diagnostics: anthropic.BetaDiagnosticsParam{
  		PreviousMessageID: anthropic.String(r1.ID),
  	},
  })
  if err != nil {
  	panic(err)
  }

  switch {
  case !r2.JSON.Diagnostics.Valid():
  	fmt.Println("No divergence detected.")
  case !r2.Diagnostics.JSON.CacheMissReason.Valid():
  	fmt.Println("Comparison still pending.")
  default:
  	fmt.Printf("cache_miss_reason: %s\n", r2.Diagnostics.CacheMissReason.Type)
  }
  ```

  ```java Java
  var client = AnthropicOkHttpClient.fromEnv();

  var system = "You are an AI assistant analyzing a large document. <document>...</document>";

  var r1 = client.beta().messages().create(
      MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          .cacheControl(BetaCacheControlEphemeral.builder().build())
          .system(system)
          .addUserMessage("Summarize section 1.")
          // Passez null au premier tour pour activer l'option sans message précédent à comparer.
          .diagnostics(BetaDiagnosticsParam.builder().previousMessageId((String) null).build())
          .build()
  );

  var r2 = client.beta().messages().create(
      MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          .cacheControl(BetaCacheControlEphemeral.builder().build())
          .system(system)
          .addUserMessage("Summarize section 1.")
          .addMessage(r1)
          .addUserMessage("Now summarize section 2.")
          .diagnostics(BetaDiagnosticsParam.builder().previousMessageId(r1.id()).build())
          .build()
  );

  if (r2.diagnostics().isEmpty()) {
      IO.println("No divergence detected.");
  } else if (r2.diagnostics().get().cacheMissReason().isEmpty()) {
      IO.println("Comparison still pending.");
  } else {
      var reason = r2.diagnostics().get().cacheMissReason().get();
      // CacheMissReason n'expose pas d'accesseur typé .type() ; lisez-le depuis le JSON brut.
      @SuppressWarnings("unchecked")
      var json = (Map<String, JsonValue>) reason._json().orElseThrow().asObject().orElseThrow();
      IO.println("cache_miss_reason: " + json.get("type").asStringOrThrow());
  }
  ```

  ```php PHP
  $client = new Client();

  $system = 'You are an AI assistant analyzing a large document. <document>...</document>';

  $r1 = $client->beta->messages->create(
      model: Model::CLAUDE_OPUS_5_5,
      maxTokens: 1024,
      cacheControl: new BetaCacheControlEphemeral,
      system: $system,
      messages: [
          ['role' => 'user', 'content' => 'Summarize section 1.'],
      ],
      diagnostics: (new BetaDiagnosticsParam)->withPreviousMessageID(null),
  );

  $r2 = $client->beta->messages->create(
      model: Model::CLAUDE_OPUS_5_5,
      maxTokens: 1024,
      cacheControl: new BetaCacheControlEphemeral,
      system: $system,
      messages: [
          ['role' => 'user', 'content' => 'Summarize section 1.'],
          ['role' => 'assistant', 'content' => $r1->content],
          ['role' => 'user', 'content' => 'Now summarize section 2.'],
      ],
      diagnostics: (new BetaDiagnosticsParam)->withPreviousMessageID($r1->id),
  );

  echo match (true) {
      $r2->diagnostics === null => "No divergence detected.\n",
      $r2->diagnostics->cacheMissReason === null => "Comparison still pending.\n",
      default => "cache_miss_reason: {$r2->diagnostics->cacheMissReason->type}\n",
  };
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>"

  r1 = client.beta.messages.create(
    model: :"claude-opus-5-5",
    max_tokens: 1024,
    cache_control: {type: "ephemeral"},
    system_: SYSTEM,
    messages: [
      {role: "user", content: "Summarize section 1."}
    ],
    diagnostics: {previous_message_id: nil}
  )

  r2 = client.beta.messages.create(
    model: :"claude-opus-5-5",
    max_tokens: 1024,
    cache_control: {type: "ephemeral"},
    system_: SYSTEM,
    messages: [
      {role: "user", content: "Summarize section 1."},
      {role: "assistant", content: r1.content},
      {role: "user", content: "Now summarize section 2."}
    ],
    diagnostics: {previous_message_id: r1.id}
  )

  case r2.diagnostics
  in nil
    puts "No divergence detected."
  in {cache_miss_reason: nil}
    puts "Comparison still pending."
  in {cache_miss_reason: {type:}}
    puts "cache_miss_reason: #{type}"
  end
  ```
</CodeGroup>

## Streaming

Dans les réponses en streaming, `diagnostics` apparaît dans l'événement `message_start`.

<CodeGroup>
  ```bash cURL
  # Tour 2 : réponse en streaming. Le champ diagnostics arrive dans l'événement message_start ;
  # une valeur null signifie qu'aucune divergence n'a été détectée.
  curl -sS --fail-with-body https://api.anthropic.com/v1/messages \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "stream": true,
      "cache_control": {"type": "ephemeral"},
      "system": "You are an AI assistant analyzing a large document. <document>...</document>",
      "messages": [
        {"role": "user", "content": "Summarize section 1."},
        {"role": "assistant", "content": "Section 1 covers..."},
        {"role": "user", "content": "Now summarize section 2."}
      ],
      "diagnostics": {"previous_message_id": "'"$message_id"'"}
    }' | jq -R 'select(startswith("data: ")) | ltrimstr("data: ") | fromjson | select(.type == "message_start") | .message.diagnostics'
  ```

  ```bash CLI
  # Tour 2 : streaming. Avec --stream, la CLI émet chaque événement SSE sous forme d'un objet JSON.
  # diagnostics arrive dans l'événement message_start ; extrayez-le avec jq.
  ant beta:messages create \
    --stream --format jsonl <<YAML |
  model: claude-opus-5-5
  max_tokens: 1024
  cache_control:
    type: ephemeral
  system: "You are an AI assistant analyzing a large document. <document>...</document>"
  messages:
    - role: user
      content: Summarize section 1.
    - role: assistant
      content: Section 1 covers...
    - role: user
      content: Now summarize section 2.
  diagnostics:
    previous_message_id: $message_id
  YAML
    jq -c 'select(.type == "message_start") | .message | {id,usage,diagnostics}'
  ```

  ```python Python
  # Tour 2 : streaming, en référençant l'id de la réponse précédente.
  with client.beta.messages.stream(
      model="claude-opus-5-5",
      max_tokens=1024,
      cache_control={"type": "ephemeral"},
      system=SYSTEM,
      messages=[
          {"role": "user", "content": "Summarize section 1."},
          {"role": "assistant", "content": r1.content},
          {"role": "user", "content": "Now summarize section 2."},
      ],
      diagnostics={"previous_message_id": r1.id},
  ) as stream:
      for text in stream.text_stream:
          print(text, end="", flush=True)
      print()
      r2 = stream.get_final_message()

  diagnostics = r2.diagnostics
  if diagnostics is None:
      print("No divergence detected.")
  elif diagnostics.cache_miss_reason is None:
      print("Comparison still pending.")
  else:
      print(f"cache_miss_reason: {diagnostics.cache_miss_reason.type}")
  ```

  ```typescript TypeScript
  const stream = client.beta.messages.stream({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    cache_control: { type: "ephemeral" },
    system: SYSTEM,
    messages: [
      { role: "user", content: "Summarize section 1." },
      { role: "assistant", content: r1.content },
      { role: "user", content: "Now summarize section 2." }
    ],
    diagnostics: { previous_message_id: r1.id }
  });

  for await (const event of stream) {
    if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
      process.stdout.write(event.delta.text);
    }
  }
  process.stdout.write("\n");

  // diagnostics arrive avec message_start et est conservé jusqu'au message final.
  const r2 = await stream.finalMessage();

  if (r2.diagnostics === null) {
    console.log("No divergence detected.");
  } else if (r2.diagnostics.cache_miss_reason === null) {
    console.log("Comparison still pending.");
  } else {
    console.log(`cache_miss_reason: ${r2.diagnostics.cache_miss_reason.type}`);
  }
  ```

  ```csharp C#
  // Tour 2 : streaming, en référençant l'id de la réponse précédente.
  BetaDiagnostics? diagnostics = null;

  var stream = client.Beta.Messages.CreateStreaming(
      new()
      {
          Model = Messages::Model.ClaudeOpus5_5,
          MaxTokens = 1024,
          CacheControl = new(),
          System = system,
          Messages =
          [
              new() { Role = Role.User, Content = "Summarize section 1." },
              new()
              {
                  Role = Role.Assistant,
                  Content = r1.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
              },
              new() { Role = Role.User, Content = "Now summarize section 2." },
          ],
          Diagnostics = new() { PreviousMessageID = r1.ID },
      }
  );

  await foreach (var streamEvent in stream)
  {
      if (streamEvent.TryPickStart(out var start))
      {
          // diagnostics arrive avec l'événement message_start.
          diagnostics = start.Message.Diagnostics;
      }
      else if (streamEvent.TryPickContentBlockDelta(out var delta) && delta.Delta.TryPickText(out var textDelta))
      {
          Console.Write(textDelta.Text);
      }
  }
  Console.WriteLine();

  Console.WriteLine(diagnostics switch
  {
      null => "No divergence detected.",
      { CacheMissReason: null } => "Comparison still pending.",
      { CacheMissReason.Type: var type } => $"cache_miss_reason: {type.GetString()}",
  });
  ```

  ```go Go
  // Tour 2 : streaming, en référençant l'id de la réponse précédente.
  stream := client.Beta.Messages.NewStreaming(ctx, anthropic.BetaMessageNewParams{
  	Model:        anthropic.ModelClaudeOpus5_5,
  	MaxTokens:    1024,
  	CacheControl: anthropic.BetaCacheControlEphemeralParam{},
  	System:       system,
  	Messages: []anthropic.BetaMessageParam{
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Summarize section 1.")),
  		r1.ToParam(),
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Now summarize section 2.")),
  	},
  	Diagnostics: anthropic.BetaDiagnosticsParam{
  		PreviousMessageID: anthropic.String(r1.ID),
  	},
  })
  defer stream.Close()

  // diagnostics arrive sur message_start ; Accumulate le reporte dans r2.
  var r2 anthropic.BetaMessage
  for stream.Next() {
  	if err := r2.Accumulate(stream.Current()); err != nil {
  		panic(err)
  	}
  }
  if err := stream.Err(); err != nil {
  	panic(err)
  }

  switch {
  case !r2.JSON.Diagnostics.Valid():
  	fmt.Println("No divergence detected.")
  case !r2.Diagnostics.JSON.CacheMissReason.Valid():
  	fmt.Println("Comparison still pending.")
  default:
  	fmt.Printf("cache_miss_reason: %s\n", r2.Diagnostics.CacheMissReason.Type)
  }
  ```

  ```java Java
  // Tour 2 : streaming, en référençant l'id de la réponse précédente.
  var params = MessageCreateParams.builder()
      .model(Model.CLAUDE_OPUS_5_5)
      .maxTokens(1024)
      .cacheControl(BetaCacheControlEphemeral.builder().build())
      .system(system)
      .addUserMessage("Summarize section 1.")
      .addMessage(r1)
      .addUserMessage("Now summarize section 2.")
      .diagnostics(BetaDiagnosticsParam.builder().previousMessageId(r1.id()).build())
      .build();

  var accumulator = BetaMessageAccumulator.create();
  try (var streamResponse = client.beta().messages().createStreaming(params)) {
      streamResponse.stream()
          .peek(accumulator::accumulate)
          .flatMap(event -> event.contentBlockDelta().stream())
          .flatMap(deltaEvent -> deltaEvent.delta().text().stream())
          .forEach(textDelta -> IO.print(textDelta.text()));
      IO.println("");
  }

  // diagnostics arrive sur message_start et est conservé jusqu'au message accumulé.
  var diagnostics = accumulator.message().diagnostics();
  if (diagnostics.isEmpty()) {
      IO.println("No divergence detected.");
  } else if (diagnostics.get().cacheMissReason().isEmpty()) {
      IO.println("Comparison still pending.");
  } else {
      var reason = diagnostics.get().cacheMissReason().get();
      // CacheMissReason n'expose pas d'accesseur typé .type() ; lisez-le depuis le JSON brut.
      @SuppressWarnings("unchecked")
      var json = (Map<String, JsonValue>) reason._json().orElseThrow().asObject().orElseThrow();
      IO.println("cache_miss_reason: " + json.get("type").asStringOrThrow());
  }
  ```

  ```php PHP
  // Tour 2 : streaming, en référençant l'id de la réponse précédente.
  $stream = $client->beta->messages->createStream(
      model: Model::CLAUDE_OPUS_5_5,
      maxTokens: 1024,
      cacheControl: new BetaCacheControlEphemeral,
      system: $system,
      messages: [
          ['role' => 'user', 'content' => 'Summarize section 1.'],
          ['role' => 'assistant', 'content' => $r1->content],
          ['role' => 'user', 'content' => 'Now summarize section 2.'],
      ],
      diagnostics: (new BetaDiagnosticsParam)->withPreviousMessageID($r1->id),
  );

  $diagnostics = null;
  foreach ($stream as $event) {
      switch (true) {
          case $event instanceof \Anthropic\Beta\Messages\BetaRawMessageStartEvent:
              // diagnostics arrive dans le BetaMessage intégré à l'événement message_start.
              $diagnostics = $event->message->diagnostics;
              break;
          case $event instanceof \Anthropic\Beta\Messages\BetaRawContentBlockDeltaEvent:
              if ($event->delta instanceof \Anthropic\Beta\Messages\BetaTextDelta) {
                  echo $event->delta->text;
              }
              break;
      }
  }
  echo PHP_EOL;

  echo match (true) {
      $diagnostics === null => "No divergence detected.\n",
      $diagnostics->cacheMissReason === null => "Comparison still pending.\n",
      default => "cache_miss_reason: {$diagnostics->cacheMissReason->type}\n",
  };
  ```

  ```ruby Ruby
  # Tour 2 : streaming, en référençant l'id de la réponse précédente.
  stream = client.beta.messages.stream(
    model: :"claude-opus-5-5",
    max_tokens: 1024,
    cache_control: {type: "ephemeral"},
    system_: SYSTEM,
    messages: [
      {role: "user", content: "Summarize section 1."},
      {role: "assistant", content: r1.content},
      {role: "user", content: "Now summarize section 2."}
    ],
    diagnostics: {previous_message_id: r1.id}
  )

  stream.each do |event|
    print(event.text) if event.is_a?(Anthropic::Streaming::TextEvent)
  end
  puts

  # diagnostics arrive sur message_start et est conservé dans le message accumulé.
  r2 = stream.accumulated_message

  case r2.diagnostics
  in nil
    puts "No divergence detected."
  in {cache_miss_reason: nil}
    puts "Comparison still pending."
  in {cache_miss_reason: {type:}}
    puts "cache_miss_reason: #{type}"
  end
  ```
</CodeGroup>

L'événement `message_start` contient le champ `diagnostics` complet ; consultez [Format de la réponse](https://platform.claude.com/docs/fr/build-with-claude/cache-diagnostics#response-format) pour connaître les valeurs possibles.

## Propager les diagnostics dans une boucle de conversation

Dans une conversation à plusieurs tours, reportez le dernier `id` de réponse en tant que `previous_message_id` à chaque tour. La première itération transmet `null` pour activer la fonctionnalité ; chaque itération suivante transmet l'`id` de la réponse précédente.

<Tabs>
  <Tab title="cURL">
    <Info>
      Ce flux de travail se prête mal à une commande shell ponctuelle. Consultez les onglets SDK pour le modèle de boucle ; la requête HTTP de chaque tour est identique à celle de la section [Utilisation de base](https://platform.claude.com/docs/fr/build-with-claude/cache-diagnostics#basic-usage).
    </Info>
  </Tab>

  <Tab title="CLI">
    <Info>
      Ce flux de travail se prête mal à une commande shell ponctuelle. Consultez les onglets SDK pour le modèle de boucle ; l'invocation CLI de chaque tour est identique à celle de la section [Utilisation de base](https://platform.claude.com/docs/fr/build-with-claude/cache-diagnostics#basic-usage).
    </Info>
  </Tab>

  <Tab title="Python">
    ```python
    client = anthropic.Anthropic()

    SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>"

    messages = []
    prev_id = None

    for i, user_message in enumerate(
        ["Summarize section 1.", "Now section 2.", "Now section 3."]
    ):
        messages.append({"role": "user", "content": user_message})

        r = client.beta.messages.create(
            model="claude-opus-5-5",
            max_tokens=1024,
            cache_control={"type": "ephemeral"},
            system=SYSTEM,
            messages=messages,
            diagnostics={"previous_message_id": prev_id},
        )

        if r.diagnostics is not None and r.diagnostics.cache_miss_reason is not None:
            print(f"Turn {i + 1} cache_miss_reason: {r.diagnostics.cache_miss_reason.type}")

        messages.append({"role": "assistant", "content": r.content})
        prev_id = r.id
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript
    const client = new Anthropic();

    const SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>";

    const prompts = ["Summarize section 1.", "Now section 2.", "Now section 3."];

    const messages: BetaMessageParam[] = [];
    let prevId: string | null = null;

    for (const [i, prompt] of prompts.entries()) {
      messages.push({ role: "user", content: prompt });

      const r: BetaMessage = await client.beta.messages.create({
        model: "claude-opus-5-5",
        max_tokens: 1024,
        cache_control: { type: "ephemeral" },
        system: SYSTEM,
        messages,
        diagnostics: { previous_message_id: prevId }
      });

      if (r.diagnostics?.cache_miss_reason) {
        console.log(`Turn ${i + 1} cache_miss_reason: ${r.diagnostics.cache_miss_reason.type}`);
      }

      messages.push({ role: "assistant", content: r.content });
      prevId = r.id;
    }
    ```
  </Tab>

  <Tab title="C#">
    ```csharp
    AnthropicClient client = new();

    var system = "You are an AI assistant analyzing a large document. <document>...</document>";

    List<BetaMessageParam> messages = [];
    string? prevId = null;
    string[] prompts = ["Summarize section 1.", "Now section 2.", "Now section 3."];

    for (int i = 0; i < prompts.Length; i++)
    {
        messages.Add(new() { Role = Role.User, Content = prompts[i] });

        var r = await client.Beta.Messages.Create(
            new()
            {
                Model = Messages::Model.ClaudeOpus5_5,
                MaxTokens = 1024,
                CacheControl = new(),
                System = system,
                Messages = messages,
                Diagnostics = new() { PreviousMessageID = prevId },
            }
        );

        if (r.Diagnostics?.CacheMissReason is { Type: var type })
        {
            Console.WriteLine($"Turn {i + 1} cache_miss_reason: {type.GetString()}");
        }

        messages.Add(
            new()
            {
                Role = Role.Assistant,
                Content = r.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
            }
        );
        prevId = r.ID;
    }
    ```
  </Tab>

  <Tab title="Go">
    ```go
    client := anthropic.NewClient()
    ctx := context.Background()

    system := []anthropic.BetaTextBlockParam{
    	{Text: "You are an AI assistant analyzing a large document. <document>...</document>"},
    }

    prompts := []string{"Summarize section 1.", "Now section 2.", "Now section 3."}

    var messages []anthropic.BetaMessageParam
    prevID := param.Null[string]()

    for turn, prompt := range prompts {
    	messages = append(messages, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(prompt)))

    	r, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
    		Model:        anthropic.ModelClaudeOpus5_5,
    		MaxTokens:    1024,
    		CacheControl: anthropic.BetaCacheControlEphemeralParam{},
    		System:       system,
    		Messages:     messages,
    		Diagnostics: anthropic.BetaDiagnosticsParam{
    			PreviousMessageID: prevID,
    		},
    	})
    	if err != nil {
    		panic(err)
    	}

    	if r.JSON.Diagnostics.Valid() && r.Diagnostics.JSON.CacheMissReason.Valid() {
    		fmt.Printf("Turn %d cache_miss_reason: %s\n", turn+1, r.Diagnostics.CacheMissReason.Type)
    	}

    	messages = append(messages, r.ToParam())
    	prevID = anthropic.String(r.ID)
    }
    ```
  </Tab>

  <Tab title="Java">
    ```java
    var client = AnthropicOkHttpClient.fromEnv();

    var system = "You are an AI assistant analyzing a large document. <document>...</document>";
    var prompts = List.of("Summarize section 1.", "Now section 2.", "Now section 3.");

    var messages = new ArrayList<BetaMessageParam>();
    String prevId = null;

    for (var turn = 0; turn < prompts.size(); turn++) {
        messages.add(
            BetaMessageParam.builder()
                .role(BetaMessageParam.Role.USER)
                .content(prompts.get(turn))
                .build()
        );

        var r = client.beta().messages().create(
            MessageCreateParams.builder()
                .model(Model.CLAUDE_OPUS_5_5)
                .maxTokens(1024)
                .cacheControl(BetaCacheControlEphemeral.builder().build())
                .system(system)
                .messages(messages)
                .diagnostics(BetaDiagnosticsParam.builder().previousMessageId(prevId).build())
                .build()
        );

        if (r.diagnostics().isPresent() && r.diagnostics().get().cacheMissReason().isPresent()) {
            var reason = r.diagnostics().get().cacheMissReason().get();
            // CacheMissReason n'expose pas d'accesseur typé .type() ; lisez-le depuis le JSON brut.
            @SuppressWarnings("unchecked")
            var json = (Map<String, JsonValue>) reason._json().orElseThrow().asObject().orElseThrow();
            IO.println("Turn " + (turn + 1) + " cache_miss_reason: " + json.get("type").asStringOrThrow());
        }

        messages.add(r.toParam());
        prevId = r.id();
    }
    ```
  </Tab>

  <Tab title="PHP">
    ```php
    $client = new Client();

    $system = 'You are an AI assistant analyzing a large document. <document>...</document>';

    $messages = [];
    $prevId = null;

    foreach (['Summarize section 1.', 'Now section 2.', 'Now section 3.'] as $i => $userMsg) {
        $turn = $i + 1;
        $messages[] = ['role' => 'user', 'content' => $userMsg];

        $r = $client->beta->messages->create(
            model: Model::CLAUDE_OPUS_5_5,
            maxTokens: 1024,
            cacheControl: new BetaCacheControlEphemeral,
            system: $system,
            messages: $messages,
            diagnostics: (new BetaDiagnosticsParam)->withPreviousMessageID($prevId),
        );

        if ($r->diagnostics?->cacheMissReason !== null) {
            echo "Turn {$turn} cache_miss_reason: {$r->diagnostics->cacheMissReason->type}\n";
        }

        $messages[] = ['role' => 'assistant', 'content' => $r->content];
        $prevId = $r->id;
    }
    ```
  </Tab>

  <Tab title="Ruby">
    ```ruby
    client = Anthropic::Client.new

    SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>"

    messages = []
    prev_id = nil

    ["Summarize section 1.", "Now section 2.", "Now section 3."].each_with_index do |user_msg, i|
      messages << {role: "user", content: user_msg}

      r = client.beta.messages.create(
        model: :"claude-opus-5-5",
        max_tokens: 1024,
        cache_control: {type: "ephemeral"},
        system_: SYSTEM,
        messages: messages,
        diagnostics: {previous_message_id: prev_id}
      )

      if (reason = r.diagnostics&.cache_miss_reason)
        puts "Turn #{i + 1} cache_miss_reason: #{reason.type}"
      end

      messages << {role: "assistant", content: r.content}
      prev_id = r.id
    end
    ```
  </Tab>
</Tabs>

## Format de la réponse

Le champ `diagnostics` du `Message` de réponse peut prendre trois valeurs :

| Valeur                         | Signification                                                                                                                                                                                                           |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `null`                         | La requête n'incluait pas l'objet `diagnostics`, `previous_message_id` valait `null` (premier tour, rien à comparer), ou une comparaison a été effectuée et n'a trouvé aucune divergence.                               |
| `{"cache_miss_reason": null}`  | La comparaison était encore en cours lorsque la réponse a été sérialisée. Cela peut se produire lorsque la réponse démarre très rapidement. Considérez ce résultat comme non concluant et vérifiez au tour suivant.     |
| `{"cache_miss_reason": {...}}` | Un `cache_miss_reason` est joint. Pour les types `*_changed`, il identifie le premier point de divergence ; `previous_message_not_found` et `unavailable` correspondent aux cas où aucune comparaison n'a été produite. |

Lorsque `cache_miss_reason` est non nul, il se présente ainsi :

```json
{
  "id": "msg_01Xyz...",
  "type": "message",
  "role": "assistant",
  "content": [{ "type": "text", "text": "..." }],
  "usage": {
    "input_tokens": 42,
    "cache_read_input_tokens": 0,
    "cache_creation_input_tokens": 41850,
    "output_tokens": 210
  },
  "diagnostics": {
    "cache_miss_reason": {
      "type": "system_changed",
      "cache_missed_input_tokens": 41850
    }
  }
}
```

## Types de raisons d'échec du cache

`cache_miss_reason` est une union discriminée sur `type`. La réponse ne signale que la divergence la plus précoce ; corrigez-la donc en premier, car des divergences ultérieures peuvent se cacher derrière elle.

| Type                         | Signification                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | Ce qu'il faut modifier                                                                                                                                                                                                                                                                                                                                                                                         |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model_changed`              | Le `model` diffère de celui de la requête précédente (par exemple, un routeur, un test A/B ou un mécanisme de repli a sélectionné un modèle différent). Le cache est propre à chaque modèle.                                                                                                                                                                                                                                                                                                             | Conservez le même modèle tout au long d'une conversation mise en cache.                                                                                                                                                                                                                                                                                                                                        |
| `system_changed`             | Le paramètre `system` diffère. Généralement, un horodatage, un identifiant de requête ou une autre valeur propre à chaque requête a été interpolé dans l'invite système.                                                                                                                                                                                                                                                                                                                                 | Faites de l'invite système une constante stable à l'octet près et déplacez les données dynamiques dans le premier message `user` situé après votre point de rupture du cache.                                                                                                                                                                                                                                  |
| `tools_changed`              | Le tableau `tools` diffère : des outils ont été ajoutés, supprimés ou réordonnés entre les tours, ou le JSON `input_schema` d'un outil a été sérialisé de manière non déterministe.                                                                                                                                                                                                                                                                                                                      | Envoyez la même liste d'outils à chaque tour, dans un ordre fixe, avec des schémas sérialisés de manière déterministe (par exemple, en triant les clés).                                                                                                                                                                                                                                                       |
| `messages_changed`           | Le modèle, l'invite système et les outils correspondent tous, mais une entrée antérieure de `messages` a été modifiée, réordonnée ou supprimée au lieu d'être simplement complétée par ajout. Généralement, l'historique de la conversation a été tronqué ou modifié, ou les tours de l'assistant et les blocs `tool_result` ont été resérialisés différemment lors du renvoi.                                                                                                                           | Traitez l'historique comme étant en ajout seul ; renvoyez le `content` de l'assistant et les résultats d'outils tels quels.                                                                                                                                                                                                                                                                                    |
| `previous_message_not_found` | Aucune empreinte stockée n'existe pour le `previous_message_id` fourni. Cela ne prouve pas que votre requête a changé. En général, la requête précédente n'incluait pas l'objet `diagnostics`, elle provenait d'un autre espace de travail, ou trop de temps s'est écoulé depuis son envoi.                                                                                                                                                                                                              | Incluez l'objet `diagnostics` à chaque tour et maintenez des tours consécutifs rapprochés dans le temps.                                                                                                                                                                                                                                                                                                       |
| `unavailable`                | Les informations de diagnostic n'étaient pas disponibles pour cette requête. Cela inclut le cas où `model`, `system` et `tools` correspondent mais où un autre paramètre de requête affectant le prompt (`tool_choice`, `thinking`, `context_management`, `output_config`, `output_format` ou l'ensemble des en-têtes `anthropic-beta` actifs) diffère, ainsi que les conversations très longues où la divergence se situe au-delà de l'horizon de comparaison. Votre requête a été traitée normalement. | Maintenez constants les paramètres de requête affectant le prompt pendant toute la durée de vie d'une conversation mise en cache. Si le problème persiste, appliquez les vérifications manuelles décrites dans la section [Résolution des problèmes courants](https://platform.claude.com/docs/fr/build-with-claude/prompt-caching#troubleshooting-common-issues) de la page sur la mise en cache des prompts. |

<Note>
  Les quatre types `*_changed` comportent également un entier `cache_missed_input_tokens` : une estimation du nombre de tokens d'entrée situés après le point de divergence, qui vous donne une idée de la quantité de préfixe pouvant être mis en cache qui a été perdue. Cette valeur est dérivée des longueurs en octets avant la tokenisation ; considérez-la donc comme un indicateur d'ordre de grandeur plutôt que comme un chiffre de facturation. Elle peut différer de `usage.input_tokens` (et parfois le dépasser).
</Note>

## Lire les diagnostics conjointement avec l'utilisation

`diagnostics` répond à la question « ma requête a-t-elle changé ? » tandis que `usage.cache_read_input_tokens` répond à la question « le cache a-t-il été atteint ? ». Les combiner vous indique où chercher.

Cette matrice s'applique aux tours où vous avez transmis un véritable `previous_message_id`. Au premier tour (`previous_message_id: null`), `diagnostics` vaut toujours `null` et `cache_read_input_tokens` est normalement nul, car le cache est en cours d'écriture et non de lecture ; aucun dépannage n'est nécessaire. La matrice ne s'applique pas non plus lorsque `cache_miss_reason` vaut `null` (la comparaison est encore en attente ; vérifiez au tour suivant) ni lorsque son `type` est `previous_message_not_found` ou `unavailable` (aucune comparaison n'a été produite).

| Résultat des diagnostics                      | Tokens lus depuis le cache | Interprétation                                                                                                                                                                                                                                                            |
| --------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `null`                                        | élevé                      | Fonctionnement conforme aux attentes. Votre préfixe est stable et le cache a été atteint.                                                                                                                                                                                 |
| `null`                                        | faible ou nul              | Vos requêtes correspondent, mais l'entrée du cache n'était plus disponible. Envisagez de réduire les intervalles entre les tours ou d'utiliser le [TTL de cache d'une heure](https://platform.claude.com/docs/fr/build-with-claude/prompt-caching#1-hour-cache-duration). |
| `cache_miss_reason` est d'un type `*_changed` | faible ou nul              | Votre bogue. La requête a changé ; corrigez la cause indiquée par `type`.                                                                                                                                                                                                 |
| `cache_miss_reason` est d'un type `*_changed` | élevé                      | Rare. Un changement est survenu tard dans le prompt, mais un point de rupture `cache_control` antérieur a tout de même été atteint. Cela vaut la peine d'être corrigé, mais l'impact est faible.                                                                          |

## Limitations

* **Claude API uniquement :** non disponible sur Amazon Bedrock ni sur Google Cloud.
* **Conservation limitée :** les empreintes utilisées pour la recherche par `previous_message_id` expirent après une courte période. Effectuez les comparaisons de diagnostic entre des requêtes rapprochées dans le temps.
* **Même espace de travail :** la requête précédente doit avoir été exécutée dans la même organisation et le même espace de travail. Pour le vérifier, comparez l'[en-tête de réponse](https://platform.claude.com/docs/fr/api/overview#response-headers) `anthropic-workspace-id` des deux réponses.
* **Horizon de comparaison :** pour les conversations très longues où le seul changement se situe profondément dans la liste des messages, la réponse peut être `unavailable` plutôt qu'un emplacement précis.
* **Au mieux :** les diagnostics ne bloquent ni ne font jamais échouer votre requête. Si les informations de diagnostic ne sont pas disponibles, la réponse renvoie `unavailable`, ou `cache_miss_reason: null` lorsque la comparaison était encore en cours.

## Conservation des données

Les diagnostics du cache sont éligibles au ZDR (sous conditions). Anthropic ne stocke pas le texte brut de vos prompts ni les sorties de Claude pour cette fonctionnalité.

L'API ne stocke une empreinte que pour les requêtes qui incluent l'objet `diagnostics`. L'empreinte se compose uniquement de hachages cryptographiques et d'estimations du nombre de tokens, indexés par l'`id` de la réponse et limités à votre organisation et à votre espace de travail. Les empreintes expirent après une courte période et ne sont utilisées à aucune autre fin.

Pour connaître l'éligibilité au ZDR de l'ensemble des fonctionnalités, consultez [API et conservation des données](https://platform.claude.com/docs/fr/manage-claude/api-and-data-retention).

## Voir aussi

* [Mise en cache des prompts](https://platform.claude.com/docs/fr/build-with-claude/prompt-caching)
* [Comptage des tokens](https://platform.claude.com/docs/fr/build-with-claude/token-counting)
