提示缓存(prompt caching)可以显著降低延迟和成本,但前提是您的提示开头与最近的某个请求逐字节完全相同。工具顺序的调整、插入到系统提示中的时间戳,或对较早消息的编辑,都可能悄无声息地使缓存失效。如果没有缓存诊断,唯一的信号就是 usage.cache_read_input_tokens 降为零,而没有任何关于发生了什么变化的提示。

缓存诊断(cache diagnostics)填补了这一空白。传入您上一个响应的 id,API 会比较这两个请求并告诉您它们在哪里发生了分歧(模型、系统提示、工具或消息历史),这样您就可以修复根本原因,而不是靠猜测。

缓存诊断的工作原理

对于每个包含 diagnostics 对象的请求,API 会存储一个以响应 id 为键的轻量级 “fingerprint”(指纹)。对于省略该对象的请求,API 不会存储任何内容。在下一个请求中,将上一个响应的 id 作为 diagnostics.previous_message_id 传入。API 会为新请求重新构建指纹,将其与已存储的指纹进行比较,并在响应中附加一个 diagnostics 对象,描述第一个分歧点。

该比较针对的是请求结构,与缓存是否实际命中无关。有关如何将 diagnostics 结果与 usage.cache_read_input_tokens 结合使用,请参阅结合 usage 解读诊断结果。

指纹仅包含哈希值和令牌数量估计值(绝不包含原始提示内容),保留时间有限,作用域限定在您的组织和工作区内,并且不会用于任何其他目的。

基本用法

在每一轮中都包含 diagnostics 对象。该对象即表示选择启用:API 仅为包含该对象的请求存储指纹。在第一轮中,传入 "previous_message_id": null,以便在没有可供比较的先前消息的情况下选择启用。在后续轮次中,传入上一个响应中的 id。不再需要 cache-diagnosis-2026-04-07 beta 请求头,仍然发送该请求头的请求也会照常工作。

cURL

# 第 1 轮:建立缓存并启用诊断
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}
  }'

# 第 2 轮:将第 1 轮响应中的 id 作为 previous_message_id 传入,以便 API 比较前缀。
# 在响应中,diagnostics: 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,
    "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"'"}
  }'

CLI

# 第 1 轮:建立缓存并启用诊断
ant beta:messages create <<'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

# 第 2 轮:将第 1 轮响应中的 id 作为 previous_message_id 传入
ant beta:messages create <<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

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

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

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 BetaUserMessageParam { 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 BetaUserMessageParam { Content = "Summarize section 1." },
            new BetaAssistantMessageParam
            {
                Content = r1.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
            },
            new BetaUserMessageParam { 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.Raw()}",
});

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

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

$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

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

流式传输

在流式传输(streaming)响应中,diagnostics 出现在 message_start 事件上。

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"'"}
  }'

CLI

# 第 2 轮:流式传输。使用 --stream 时,CLI 会将每个 SSE 事件输出为一个 JSON 对象。
# diagnostics 随 message_start 事件一同到达。
ant beta:messages create --stream <<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

# 第 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

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

C#

// 第 2 轮:流式传输(streaming),并引用上一个响应的 id
BetaDiagnostics? diagnostics = null;

var stream = client.Beta.Messages.CreateStreaming(
    new()
    {
        Model = Messages::Model.ClaudeOpus5_5,
        MaxTokens = 1024,
        CacheControl = new(),
        System = system,
        Messages =
        [
            new BetaUserMessageParam { Content = "Summarize section 1." },
            new BetaAssistantMessageParam
            {
                Content = r1.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
            },
            new BetaUserMessageParam { 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.Raw()}",
});

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

// 第 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

// 第 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 位于 message_start 事件内嵌的 BetaMessage 中
            $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

# 第 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

message_start 事件携带完整的 diagnostics 字段;有关可能的取值,请参阅响应格式。

在对话循环中传递诊断信息

在多轮对话中,每一轮都将最新的响应 id 作为 previous_message_id 向前传递。第一次迭代传入 null 以选择启用;之后的每次迭代传入上一个响应的 id。

cURL

说明

此工作流不太适合转换为一次性的 shell 命令。请使用 Claude SDK 来运行循环;每一轮的 HTTP 请求与基本用法中的完全相同。

CLI

说明

此工作流不太适合转换为一次性的 shell 命令。请使用 Claude SDK 来运行循环;每一轮的 CLI 命令与基本用法中的完全相同。

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

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

C#

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 BetaUserMessageParam { 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.Raw()}");
    }

    messages.Add(
        new BetaAssistantMessageParam
        {
            Content = r.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
        }
    );
    prevId = r.ID;
}

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

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.ofUser(prompts.get(turn)));

    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();
}

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

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

响应格式

响应 Message 上的 diagnostics 字段有三种可能的取值:

值 含义
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 非空时,它看起来像这样:

{
  "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 是一个基于 type 的可辨识联合类型。响应仅报告最早的分歧,因此请先修复它;后面的分歧可能被它掩盖了。

类型 含义 需要更改的内容
model_changed model 与上一个请求不同(例如,路由器、A/B 测试或回退机制选择了不同的模型)。缓存是按模型区分的。 在一个已缓存的对话中保持模型不变。
system_changed system 参数不同。通常是时间戳、请求 ID 或其他每次请求都不同的值被插入到了系统提示中。 使系统提示成为字节稳定的常量,并将动态数据移到缓存断点之后的第一条 user 消息中。
tools_changed tools 数组不同:在轮次之间添加、删除或重新排序了工具,或者工具的 input_schema JSON 被非确定性地序列化。 每一轮都以固定顺序发送相同的工具列表,并使用确定性序列化的 schema(例如,对键进行排序)。
messages_changed 模型、系统提示和工具全部匹配,但 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 请求头集合)不同;以及分歧超出比较范围的超长对话。您的请求已正常处理。 在已缓存对话的整个生命周期内保持影响提示的请求参数不变。如果问题持续存在,请应用提示缓存页面上常见问题排查下的手动检查。

注意

四种 *_changed 类型还携带一个 cache_missed_input_tokens 整数:这是对分歧点之后有多少输入令牌的估计,让您了解丢失了多少可缓存的前缀。它是在分词之前根据字节长度推导出来的,因此请将其视为量级指标而非计费数字。它可能与 usage.input_tokens 不同(偶尔会超过它)。

结合 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 低或为零 您的请求匹配,但缓存条目已不再可用。请考虑缩短轮次之间的间隔,或使用 1 小时缓存 TTL。
cache_miss_reason 为 *_changed 类型 低或为零 请求发生了变化;请修复 type 所指示的原因。
cache_miss_reason 为 *_changed 类型 高 罕见。变化发生在提示的后部,但较早的 cache_control 断点仍然命中。值得修复,但影响较小。

限制

  • 仅限 Claude API: 在 Amazon Bedrock 或 Claude Models on Google Cloud (Google managed) 上不可用。
  • 有限的保留期: 用于 previous_message_id 查找的指纹会在短时间后过期。请在时间间隔较近的请求之间运行诊断比较。
  • 同一工作区: 上一个请求必须在同一组织和工作区中运行。要进行检查,请比较两个响应上的 anthropic-workspace-id 响应头。
  • 比较范围: 对于唯一变化位于消息列表深处的超长对话,响应可能是 unavailable 而非精确位置。
  • 尽力而为: 诊断绝不会阻塞您的请求或使其失败。如果诊断信息不可用,响应会返回 unavailable;如果比较仍在运行,则返回 cache_miss_reason: null。

数据保留

缓存诊断符合 ZDR 资格(有条件)。Anthropic 不会为此功能存储您的提示或 Claude 输出的原始文本。

API 仅为包含 diagnostics 对象的请求存储指纹。指纹仅由加密哈希值和令牌数量估算值组成,以响应 id 为键,作用范围限定在您的组织和工作区内。指纹会在较短时间后过期,并且不会用于任何其他目的。

有关所有功能的 ZDR 资格,请参阅 API 和数据保留。

另请参阅

可用性

缓存诊断在所有模型上可用。

API 支持

Messages API

  • 可用性:可用
状态 云平台
可用 Claude API

Managed Agents

  • 可用性:不可用

数据处理

数据处理概览

项目 零数据保留 HIPAA
状态 具备资格(有条件;不包括 涵盖的模型) 不具备资格
详细信息 您的提示和 Claude 的输出不会被存储。由加密哈希和令牌数估算值组成的指纹会被短暂保留,以便与下一个请求进行比较。 了解更多 —

适用的端点:

  • /v1/messages 使用 diagnostics