---
title: Диагностика кэша
url: https://platform.claude.com/docs/ru/build-with-claude/cache-diagnostics
description: Диагностируйте неожиданные промахи кэша подсказок, сравнивая последовательные запросы и точно определяя, где разошёлся префикс подсказки.
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
---

[Кэширование подсказок](https://platform.claude.com/docs/ru/build-with-claude/prompt-caching) (prompt caching) значительно снижает задержку и стоимость, но только когда начало вашей подсказки побайтово идентично недавнему запросу. Переупорядоченный инструмент, временная метка, подставленная в вашу системную подсказку (system prompt), или правка более раннего сообщения могут незаметно сделать кэш недействительным. Без диагностики кэша единственным сигналом является падение `usage.cache_read_input_tokens` до нуля без какого-либо указания на то, что изменилось.

Диагностика кэша закрывает этот пробел. Передайте `id` вашего предыдущего ответа, и API сравнит два запроса и сообщит вам, где они разошлись (модель, системная подсказка, инструменты или история сообщений), чтобы вы могли устранить первопричину, а не гадать.

## Как работает диагностика кэша

Для каждого запроса, включающего объект `diagnostics`, API сохраняет облегчённый «fingerprint» (отпечаток), привязанный к `id` ответа. Для запросов без этого объекта ничего не сохраняется. В следующем запросе укажите `id` предыдущего ответа в `diagnostics.previous_message_id`. API заново строит отпечаток для нового запроса, сравнивает его с сохранённым и прикрепляет к ответу объект `diagnostics`, описывающий первую точку расхождения.

Сравнение касается структуры запроса и не зависит от того, произошло ли фактическое попадание в кэш. См. раздел [Чтение диагностики вместе с usage](https://platform.claude.com/docs/ru/build-with-claude/cache-diagnostics#reading-diagnostics-alongside-usage), чтобы узнать, как сочетать результат `diagnostics` с `usage.cache_read_input_tokens`.

Отпечатки содержат только хэши и оценки количества токенов (никогда — исходное содержимое подсказки), хранятся ограниченное время, ограничены вашей организацией и рабочим пространством и не используются ни для каких других целей.

## Базовое использование

Включайте объект `diagnostics` в каждый ход. Сам объект является согласием на участие: API сохраняет отпечаток только для запросов, которые его включают. В первом ходе передайте `"previous_message_id": null`, чтобы включить функцию без предыдущего сообщения для сравнения. В последующих ходах передавайте `id` из предыдущего ответа. Бета-заголовок `cache-diagnosis-2026-04-07` больше не требуется, а запросы, которые всё ещё его отправляют, работают как прежде.

<CodeGroup>
  ```bash cURL
  # Ход 1: создаём кэш и включаем диагностику
  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")

  # Ход 2: ссылаемся на предыдущий ход, чтобы API мог сравнить префиксы
  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
  # Ход 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"

  # Ход 2: передайте id из хода 1 как 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>"

  # Ход 1: включаем функцию с 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},
  )

  # Ход 2: ссылаемся на id предыдущего ответа
  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>";

  // Ход 1: включите режим, указав 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 }
  });

  // Ход 2: укажите id предыдущего ответа
  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.")
          // Передайте null на первом ходе, чтобы включить функцию без предыдущего сообщения для сравнения.
          .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 не предоставляет типизированный метод доступа .type(); считывайте значение из исходного JSON.
      @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) `diagnostics` появляется в событии `message_start`.

<CodeGroup>
  ```bash cURL
  # Ход 2: получаем ответ потоком. diagnostics приходит в событии message_start;
  # значение null означает, что расхождений не обнаружено.
  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
  # Ход 2: потоковая передача. С --stream CLI выводит каждое событие SSE как отдельный JSON-объект.
  # diagnostics приходит в событии message_start; извлеките его с помощью 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
  # Ход 2: потоковая передача со ссылкой на id предыдущего ответа
  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 приходит в message_start и переносится в итоговое сообщение
  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#
  // Ход 2: потоковая передача со ссылкой на id предыдущего ответа
  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 приходит в событии 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
  // Ход 2: потоковая передача со ссылкой на id предыдущего ответа
  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 приходит в message_start; Accumulate переносит его в 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
  // Ход 2: потоковая передача со ссылкой на id предыдущего ответа
  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 приходит в message_start и переносится в накопленное сообщение
  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 не предоставляет типизированный метод .type(); читаем его из исходного JSON.
      @SuppressWarnings("unchecked")
      var json = (Map<String, JsonValue>) reason._json().orElseThrow().asObject().orElseThrow();
      IO.println("cache_miss_reason: " + json.get("type").asStringOrThrow());
  }
  ```

  ```php PHP
  // Ход 2: потоковая передача со ссылкой на id предыдущего ответа
  $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 приходит во вложенном BetaMessage события 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
  # Ход 2: потоковая передача со ссылкой на id предыдущего ответа
  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 приходит в message_start и сохраняется в накопленном сообщении
  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>

Событие `message_start` содержит полное поле `diagnostics`; возможные значения см. в разделе [Формат ответа](https://platform.claude.com/docs/ru/build-with-claude/cache-diagnostics#response-format).

## Передача диагностики через цикл диалога

В многоходовом диалоге передавайте `id` последнего ответа дальше как `previous_message_id` на каждом ходе. Первая итерация передаёт `null` для включения функции; каждая последующая итерация передаёт `id` из предыдущего ответа.

<Tabs>
  <Tab title="cURL">
    <Info>
      Этот рабочий процесс плохо переводится в одноразовую команду оболочки. Шаблон цикла см. на вкладках SDK; HTTP-запрос на каждом ходе идентичен разделу [Базовое использование](https://platform.claude.com/docs/ru/build-with-claude/cache-diagnostics#basic-usage).
    </Info>
  </Tab>

  <Tab title="CLI">
    <Info>
      Этот рабочий процесс плохо переводится в одноразовую команду оболочки. Шаблон цикла см. на вкладках SDK; вызов CLI на каждом ходе идентичен разделу [Базовое использование](https://platform.claude.com/docs/ru/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 не предоставляет типизированный метод доступа .type(); считывайте его из исходного JSON.
            @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>

## Формат ответа

Поле `diagnostics` в ответе `Message` имеет три возможных значения:

| Значение                       | Смысл                                                                                                                                                                                        |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `null`                         | Запрос не включал объект `diagnostics`, `previous_message_id` был `null` (первый ход, сравнивать не с чем), или сравнение было выполнено и не обнаружило расхождений.                        |
| `{"cache_miss_reason": null}`  | Сравнение ещё выполнялось, когда ответ был сериализован. Это может произойти, когда ответ начинается очень быстро. Считайте результат неопределённым и проверьте следующий ход.              |
| `{"cache_miss_reason": {...}}` | Прикреплён `cache_miss_reason`. Для типов `*_changed` он указывает первую точку расхождения; `previous_message_not_found` и `unavailable` — это случаи, когда сравнение не было произведено. |

Когда `cache_miss_reason` не равен null, он выглядит так:

```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
    }
  }
}
```

## Типы причин промаха кэша

`cache_miss_reason` — это размеченное объединение (discriminated union) по полю `type`. Ответ сообщает только о самом раннем расхождении, поэтому исправьте его первым; более поздние могут быть скрыты за ним.

| Тип                          | Что это означает                                                                                                                                                                                                                                                                                                                                                                                                                                            | Что изменить                                                                                                                                                                                                                                                                                                                                         |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model_changed`              | `model` отличается от предыдущего запроса (например, маршрутизатор, A/B-тест или резервный механизм выбрал другую модель). Кэш ведётся отдельно для каждой модели.                                                                                                                                                                                                                                                                                          | Сохраняйте модель неизменной в рамках кэшируемого диалога.                                                                                                                                                                                                                                                                                           |
| `system_changed`             | Параметр `system` отличается. Обычно в системную подсказку была подставлена временная метка, идентификатор запроса или другое значение, уникальное для запроса.                                                                                                                                                                                                                                                                                             | Сделайте системную подсказку побайтово стабильной константой и перенесите динамические данные в первое сообщение `user` после вашей точки разрыва кэша.                                                                                                                                                                                              |
| `tools_changed`              | Массив `tools` отличается: инструменты были добавлены, удалены или переупорядочены между ходами, либо JSON `input_schema` инструмента был сериализован недетерминированно.                                                                                                                                                                                                                                                                                  | Отправляйте один и тот же список инструментов на каждом ходе в фиксированном порядке с детерминированно сериализованными схемами (например, сортируйте ключи).                                                                                                                                                                                       |
| `messages_changed`           | Модель, system и tools совпадают, но более ранняя запись в `messages` была изменена, переупорядочена или удалена, а не дополнена. Обычно история диалога была усечена или отредактирована, либо ходы ассистента и блоки `tool_result` были повторно сериализованы иначе при повторной отправке.                                                                                                                                                             | Относитесь к истории как к допускающей только добавление; возвращайте `content` ассистента и результаты инструментов дословно.                                                                                                                                                                                                                       |
| `previous_message_not_found` | Для указанного `previous_message_id` не существует сохранённого отпечатка. Это не свидетельствует о том, что ваш запрос изменился. Обычно предыдущий запрос не включал объект `diagnostics`, был выполнен в другом рабочем пространстве или с момента его отправки прошло слишком много времени.                                                                                                                                                            | Включайте объект `diagnostics` в каждый ход и отправляйте последовательные ходы с небольшими интервалами.                                                                                                                                                                                                                                            |
| `unavailable`                | Диагностическая информация была недоступна для этого запроса. Сюда входит случай, когда `model`, `system` и `tools` совпадают, но отличается другой влияющий на подсказку параметр запроса (`tool_choice`, `thinking`, `context_management`, `output_config`, `output_format` или набор активных заголовков `anthropic-beta`), а также очень длинные диалоги, где расхождение находится за горизонтом сравнения. Ваш запрос был обработан в обычном режиме. | Сохраняйте влияющие на подсказку параметры запроса неизменными на протяжении всего кэшируемого диалога. Если проблема сохраняется, примените ручные проверки из раздела [Устранение распространённых проблем](https://platform.claude.com/docs/ru/build-with-claude/prompt-caching#troubleshooting-common-issues) на странице кэширования подсказок. |

<Note>
  Четыре типа `*_changed` также содержат целое число `cache_missed_input_tokens`: оценку того, сколько входных токенов оказалось после точки расхождения, что даёт представление о том, какая часть кэшируемого префикса была потеряна. Оно вычисляется по длинам в байтах до токенизации, поэтому рассматривайте его как показатель порядка величины, а не как число для выставления счётов. Оно может отличаться от `usage.input_tokens` (и иногда превышать его).
</Note>

## Чтение диагностики вместе с usage

`diagnostics` отвечает на вопрос «изменился ли мой запрос?», а `usage.cache_read_input_tokens` — на вопрос «произошло ли попадание в кэш?». Их сочетание подсказывает, где искать.

Эта матрица применима к ходам, на которых вы передали реальный `previous_message_id`. На первом ходе (`previous_message_id: null`) `diagnostics` всегда равен `null`, а `cache_read_input_tokens` обычно равен нулю, поскольку кэш записывается, а не читается; устранение неполадок не требуется. Матрица также не применима, когда `cache_miss_reason` равен `null` (сравнение ещё выполняется; проверьте следующий ход) или когда его `type` — `previous_message_not_found` или `unavailable` (сравнение не было произведено).

| Результат диагностики                     | Токены чтения из кэша | Интерпретация                                                                                                                                                                                                                                     |
| ----------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `null`                                    | много                 | Работает как ожидается. Ваш префикс стабилен, и произошло попадание в кэш.                                                                                                                                                                        |
| `null`                                    | мало или ноль         | Ваши запросы совпадают, но запись кэша больше не была доступна. Рассмотрите сокращение промежутков между ходами или использование [TTL кэша в 1 час](https://platform.claude.com/docs/ru/build-with-claude/prompt-caching#1-hour-cache-duration). |
| `cache_miss_reason` имеет тип `*_changed` | мало или ноль         | Ваша ошибка. Запрос изменился; устраните причину, указанную в `type`.                                                                                                                                                                             |
| `cache_miss_reason` имеет тип `*_changed` | много                 | Редко. Изменение произошло ближе к концу подсказки, но более ранняя точка разрыва `cache_control` всё же сработала. Стоит исправить, но влияние невелико.                                                                                         |

## Ограничения

* **Только Claude API:** Недоступно на Amazon Bedrock или Google Cloud.
* **Ограниченное хранение:** Отпечатки для поиска по `previous_message_id` истекают через короткий период. Выполняйте диагностические сравнения между близко расположенными по времени запросами.
* **То же рабочее пространство:** Предыдущий запрос должен был выполняться в той же организации и рабочем пространстве. Для проверки сравните [заголовок ответа](https://platform.claude.com/docs/ru/api/overview#response-headers) `anthropic-workspace-id` в двух ответах.
* **Горизонт сравнения:** Для очень длинных диалогов, где единственное изменение находится глубоко в списке сообщений, ответ может быть `unavailable` вместо точного местоположения.
* **По мере возможности:** Диагностика никогда не блокирует и не приводит к сбою вашего запроса. Если диагностическая информация недоступна, ответ возвращает `unavailable` или `cache_miss_reason: null`, если сравнение ещё выполнялось.

## Хранение данных

Диагностика кэша соответствует требованиям ZDR (с оговорками). Anthropic не хранит исходный текст ваших подсказок или выходных данных Claude для этой функции.

API сохраняет отпечаток только для запросов, включающих объект `diagnostics`. Отпечаток состоит только из криптографических хэшей и оценок количества токенов, привязан к `id` ответа и ограничен вашей организацией и рабочим пространством. Отпечатки истекают через короткий период и не используются ни для каких других целей.

Сведения о соответствии ZDR для всех функций см. в разделе [API и хранение данных](https://platform.claude.com/docs/ru/manage-claude/api-and-data-retention).

## См. также

* [Кэширование подсказок](https://platform.claude.com/docs/ru/build-with-claude/prompt-caching)
* [Подсчёт токенов](https://platform.claude.com/docs/ru/build-with-claude/token-counting)
