---
title: Diagnóstico de caché
url: https://platform.claude.com/docs/es/build-with-claude/cache-diagnostics
description: Diagnostica fallos inesperados de la caché de prompts comparando solicitudes consecutivas e identificando exactamente dónde divergió el prefijo del prompt.
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
---

El [almacenamiento en caché de prompts](https://platform.claude.com/docs/es/build-with-claude/prompt-caching) ("prompt caching") reduce significativamente la latencia y el costo, pero solo cuando el comienzo de tu prompt es idéntico byte por byte a una solicitud reciente. Una herramienta reordenada, una marca de tiempo interpolada en tu indicación del sistema o una edición de un mensaje anterior pueden invalidar la caché silenciosamente. Sin el diagnóstico de caché, la única señal es que `usage.cache_read_input_tokens` cae a cero, sin ninguna indicación de qué cambió.

El diagnóstico de caché cierra esa brecha. Pasa el `id` de tu respuesta anterior, y la API compara las dos solicitudes y te dice dónde divergieron (el modelo, la indicación del sistema, las herramientas o el historial de mensajes) para que puedas corregir la causa raíz en lugar de adivinar.

## Cómo funciona el diagnóstico de caché

Para cada solicitud que incluye el objeto `diagnostics`, la API almacena un "fingerprint" (huella digital) ligero indexado por el `id` de la respuesta. No almacena nada para las solicitudes que omiten el objeto. En tu siguiente solicitud, incluye el `id` de la respuesta anterior como `diagnostics.previous_message_id`. La API reconstruye la huella digital para la nueva solicitud, la compara con la almacenada y adjunta a la respuesta un objeto `diagnostics` que describe el primer punto de divergencia.

La comparación se refiere a la estructura de la solicitud, independientemente de si la caché realmente acertó. Consulta [Leer el diagnóstico junto con el uso](https://platform.claude.com/docs/es/build-with-claude/cache-diagnostics#reading-diagnostics-alongside-usage) para saber cómo combinar el resultado de `diagnostics` con `usage.cache_read_input_tokens`.

Las huellas digitales contienen solo hashes y estimaciones de conteo de tokens (nunca el contenido sin procesar del prompt), se conservan durante un tiempo limitado, están restringidas a tu organización y espacio de trabajo, y no se utilizan para ningún otro propósito.

## Uso básico

Incluye el objeto `diagnostics` en cada turno. El objeto es la forma de activarlo: la API almacena una huella digital solo para las solicitudes que lo incluyen. En el primer turno, pasa `"previous_message_id": null` para activarlo sin un mensaje previo con el cual comparar. En los turnos siguientes, pasa el `id` de la respuesta anterior. El "beta header" (encabezado beta) `cache-diagnosis-2026-04-07` ya no es necesario, y las solicitudes que todavía lo envían funcionan como antes.

<CodeGroup>
  ```bash cURL
  # Turno 1: establece la caché y activa los diagnósticos
  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")

  # Turno 2: referencia el turno anterior para que la API pueda comparar prefijos
  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
  # Turno 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"

  # Turno 2: pasa el id del turno 1 como 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>"

  # Turno 1: actívalo con 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},
  )

  # Turno 2: haz referencia al id de la respuesta anterior
  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>";

  // Turno 1: activa la opción con 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 }
  });

  // Turno 2: haz referencia al id de la respuesta anterior
  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.")
          // Pasa null en el primer turno para activarlo sin un mensaje previo con el cual comparar.
          .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 no expone un accesor tipado .type(); léelo desde el JSON sin procesar.
      @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

En las respuestas con streaming, `diagnostics` aparece en el evento `message_start`.

<CodeGroup>
  ```bash cURL
  # Turno 2: haz streaming de la respuesta. diagnostics llega en el evento message_start;
  # un valor null significa que no se encontró ninguna divergencia.
  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
  # Turno 2: streaming. Con --stream, la CLI emite cada evento SSE como un objeto JSON.
  # diagnostics llega en el evento message_start; extráelo con 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
  # Turno 2: haz streaming, haciendo referencia al id de la respuesta anterior
  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 llega en message_start y se conserva hasta el mensaje 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#
  // Turno 2: haz streaming, haciendo referencia al id de la respuesta anterior
  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 llega en el evento 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
  // Turno 2: haz streaming, referenciando el id de la respuesta anterior
  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 llega en message_start; Accumulate lo traslada a 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
  // Turno 2: haz streaming, haciendo referencia al id de la respuesta anterior
  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 llega en message_start y se conserva hasta el mensaje acumulado
  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 no expone un accesor tipado .type(); léelo desde el JSON sin procesar.
      @SuppressWarnings("unchecked")
      var json = (Map<String, JsonValue>) reason._json().orElseThrow().asObject().orElseThrow();
      IO.println("cache_miss_reason: " + json.get("type").asStringOrThrow());
  }
  ```

  ```php PHP
  // Turno 2: haz streaming, haciendo referencia al id de la respuesta anterior
  $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 llega en el BetaMessage incrustado del evento 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
  # Turno 2: haz streaming, haciendo referencia al id de la respuesta anterior
  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 llega en message_start y se conserva en el mensaje acumulado
  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>

El evento `message_start` contiene el campo `diagnostics` completo; consulta [Formato de respuesta](https://platform.claude.com/docs/es/build-with-claude/cache-diagnostics#response-format) para ver los valores posibles.

## Propagar el diagnóstico a través de un bucle de conversación

En una conversación de varios turnos, lleva el `id` de la respuesta más reciente como `previous_message_id` en cada turno. La primera iteración pasa `null` para activar la función; cada iteración posterior pasa el `id` de la respuesta anterior.

<Tabs>
  <Tab title="cURL">
    <Info>
      Este flujo de trabajo no se traduce bien a un comando de shell único. Consulta las pestañas de los SDK para ver el patrón de bucle; la solicitud HTTP por turno es idéntica a la de [Uso básico](https://platform.claude.com/docs/es/build-with-claude/cache-diagnostics#basic-usage).
    </Info>
  </Tab>

  <Tab title="CLI">
    <Info>
      Este flujo de trabajo no se traduce bien a un comando de shell único. Consulta las pestañas de los SDK para ver el patrón de bucle; la invocación de la CLI por turno es idéntica a la de [Uso básico](https://platform.claude.com/docs/es/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 no expone un accesor tipado .type(); léelo desde el JSON sin procesar.
            @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>

## Formato de respuesta

El campo `diagnostics` en el `Message` de respuesta tiene tres valores posibles:

| Valor                          | Significado                                                                                                                                                                                                         |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `null`                         | La solicitud no incluyó el objeto `diagnostics`, `previous_message_id` era `null` (primer turno, nada que comparar), o se ejecutó una comparación y no encontró ninguna divergencia.                                |
| `{"cache_miss_reason": null}`  | La comparación aún se estaba ejecutando cuando se serializó la respuesta. Esto puede ocurrir cuando la respuesta comienza muy rápidamente. Trátalo como no concluyente y revisa el siguiente turno.                 |
| `{"cache_miss_reason": {...}}` | Se adjunta un `cache_miss_reason`. Para los tipos `*_changed`, esto identifica el primer punto de divergencia; `previous_message_not_found` y `unavailable` son casos en los que no se produjo ninguna comparación. |

Cuando `cache_miss_reason` no es nulo, tiene este aspecto:

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

## Tipos de motivos de fallo de caché

`cache_miss_reason` es una unión discriminada por `type`. La respuesta informa solo la divergencia más temprana, así que corrígela primero; las posteriores pueden estar ocultas detrás de ella.

| Tipo                         | Qué significa                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | Qué cambiar                                                                                                                                                                                                                                                                                                                                                       |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model_changed`              | El `model` difiere de la solicitud anterior (por ejemplo, un enrutador, una prueba A/B o un mecanismo de respaldo seleccionó un modelo diferente). La caché es por modelo.                                                                                                                                                                                                                                                                                                                    | Mantén el modelo constante dentro de una conversación en caché.                                                                                                                                                                                                                                                                                                   |
| `system_changed`             | El parámetro `system` difiere. Normalmente se interpoló una marca de tiempo, un ID de solicitud u otro valor por solicitud en la indicación del sistema.                                                                                                                                                                                                                                                                                                                                      | Haz que la indicación del sistema sea una constante estable a nivel de bytes y mueve los datos dinámicos al primer mensaje `user` después de tu punto de interrupción de caché.                                                                                                                                                                                   |
| `tools_changed`              | El arreglo `tools` difiere: se agregaron, eliminaron o reordenaron herramientas entre turnos, o el JSON de `input_schema` de las herramientas se serializó de forma no determinista.                                                                                                                                                                                                                                                                                                          | Envía la misma lista de herramientas en cada turno en un orden fijo con esquemas serializados de forma determinista (por ejemplo, ordena las claves).                                                                                                                                                                                                             |
| `messages_changed`           | El modelo, el sistema y las herramientas coinciden, pero una entrada anterior en `messages` fue alterada, reordenada o eliminada en lugar de agregarse al final. Normalmente el historial de la conversación se truncó o editó, o los turnos del asistente y los bloques `tool_result` se volvieron a serializar de forma diferente al reenviarlos.                                                                                                                                           | Trata el historial como de solo anexado; devuelve el `content` del asistente y los resultados de herramientas textualmente.                                                                                                                                                                                                                                       |
| `previous_message_not_found` | No existe ninguna huella digital almacenada para el `previous_message_id` proporcionado. Esto no es evidencia de que tu solicitud haya cambiado. Normalmente la solicitud anterior no incluyó el objeto `diagnostics`, provino de un espacio de trabajo diferente o ha pasado demasiado tiempo desde que se envió.                                                                                                                                                                            | Incluye el objeto `diagnostics` en cada turno y mantén los turnos consecutivos cercanos en el tiempo.                                                                                                                                                                                                                                                             |
| `unavailable`                | La información de diagnóstico no estaba disponible para esta solicitud. Esto incluye el caso en que `model`, `system` y `tools` coinciden pero otro parámetro de solicitud que afecta al prompt (`tool_choice`, `thinking`, `context_management`, `output_config`, `output_format` o el conjunto de encabezados `anthropic-beta` activos) difiere, y las conversaciones muy largas en las que la divergencia está más allá del horizonte de comparación. Tu solicitud se procesó normalmente. | Mantén constantes los parámetros de solicitud que afectan al prompt durante toda la vida de una conversación en caché. Si persiste, aplica las verificaciones manuales de [Solución de problemas comunes](https://platform.claude.com/docs/es/build-with-claude/prompt-caching#troubleshooting-common-issues) en la página de almacenamiento en caché de prompts. |

<Note>
  Los cuatro tipos `*_changed` también incluyen un entero `cache_missed_input_tokens`: una estimación de cuántos tokens de entrada quedaron después del punto de divergencia, lo que te da una idea de cuánto prefijo almacenable en caché se perdió. Se deriva de las longitudes en bytes antes de la tokenización, así que trátalo como un indicador de magnitud y no como una cifra de facturación. Puede diferir de (y ocasionalmente superar) `usage.input_tokens`.
</Note>

## Leer el diagnóstico junto con el uso

`diagnostics` responde a "¿cambió mi solicitud?", mientras que `usage.cache_read_input_tokens` responde a "¿acertó la caché?". Combinarlos te indica dónde buscar.

Esta matriz se aplica a los turnos en los que pasaste un `previous_message_id` real. En el primer turno (`previous_message_id: null`), `diagnostics` siempre es `null` y `cache_read_input_tokens` normalmente es cero porque la caché se está escribiendo, no leyendo; no se necesita ninguna solución de problemas. La matriz tampoco se aplica cuando `cache_miss_reason` es `null` (la comparación aún está pendiente; revisa el siguiente turno) ni cuando su `type` es `previous_message_not_found` o `unavailable` (no se produjo ninguna comparación).

| Resultado del diagnóstico                  | Tokens leídos de caché | Interpretación                                                                                                                                                                                                                                             |
| ------------------------------------------ | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `null`                                     | alto                   | Funciona como se espera. Tu prefijo es estable y la caché acertó.                                                                                                                                                                                          |
| `null`                                     | bajo o cero            | Tus solicitudes coinciden, pero la entrada de caché ya no estaba disponible. Considera acortar los intervalos entre turnos o usar el [TTL de caché de 1 hora](https://platform.claude.com/docs/es/build-with-claude/prompt-caching#1-hour-cache-duration). |
| `cache_miss_reason` es un tipo `*_changed` | bajo o cero            | Es tu error. La solicitud cambió; corrige la causa indicada por `type`.                                                                                                                                                                                    |
| `cache_miss_reason` es un tipo `*_changed` | alto                   | Poco frecuente. Ocurrió un cambio tarde en el prompt, pero un punto de interrupción `cache_control` anterior aún acertó. Vale la pena corregirlo, pero el impacto es bajo.                                                                                 |

## Limitaciones

* **Solo Claude API:** No disponible en Amazon Bedrock ni en Google Cloud.
* **Retención limitada:** Las huellas digitales para la búsqueda de `previous_message_id` expiran después de un período corto. Ejecuta las comparaciones de diagnóstico entre solicitudes cercanas en el tiempo.
* **Mismo espacio de trabajo:** La solicitud anterior debe haberse ejecutado en la misma organización y espacio de trabajo. Para verificarlo, compara el [encabezado de respuesta](https://platform.claude.com/docs/es/api/overview#response-headers) `anthropic-workspace-id` en las dos respuestas.
* **Horizonte de comparación:** Para conversaciones muy largas en las que el único cambio está muy adentro en la lista de mensajes, la respuesta puede ser `unavailable` en lugar de una ubicación precisa.
* **Mejor esfuerzo:** El diagnóstico nunca bloquea ni hace fallar tu solicitud. Si la información de diagnóstico no está disponible, la respuesta devuelve `unavailable`, o `cache_miss_reason: null` cuando la comparación aún se estaba ejecutando.

## Retención de datos

El diagnóstico de caché es elegible para ZDR (con condiciones). Anthropic no almacena el texto sin procesar de tus prompts ni las salidas de Claude para esta función.

La API almacena una huella digital solo para las solicitudes que incluyen el objeto `diagnostics`. La huella digital consiste únicamente en hashes criptográficos y estimaciones de conteo de tokens, indexada por el `id` de la respuesta y limitada a tu organización y espacio de trabajo. Las huellas digitales expiran después de un período corto y no se usan para ningún otro propósito.

Para conocer la elegibilidad para ZDR de todas las funciones, consulta [API y retención de datos](https://platform.claude.com/docs/es/manage-claude/api-and-data-retention).

## Ver también

* [Almacenamiento en caché de prompts](https://platform.claude.com/docs/es/build-with-claude/prompt-caching)
* [Conteo de tokens](https://platform.claude.com/docs/es/build-with-claude/token-counting)
