cocodot
← 返回教程
国内卡付不了海外 AI?cocodot 一张卡 + 一个 Key 搞定
成本控制更新于 2026-09

Claude prompt caching 怎么验证命中?机制、脚本与静默 miss 排查(2026)

Prompt caching 缓存的是有序前缀,不是给每个请求自动降价的开关。这篇说明缓存断点放在哪里、怎样用首次创建与后续读取证明命中,以及看起来相同的请求为什么仍可能 miss。

一句话结论:Anthropic prompt caching 复用的是有序提示词前缀:tools、system、messages,直到缓存断点。稳定材料放在前面,每次变化的内容放在断点后。新的验证必须顺序进行:第一次请求查看 usage.cache_creation_input_tokens;等响应返回后,用相同缓存前缀发送下一次请求,查看 usage.cache_read_input_tokens。两笔同时发可能读不到刚创建的条目。三个输入字段要分开看:cache_creation_input_tokens 是本次写入,cache_read_input_tokens 是本次读取,input_tokens 是最终断点后的未缓存输入,总输入为三者之和。200 响应不能证明缓存命中,要以这些 usage 字段为准。Anthropic 默认 ephemeral 缓存寿命为五分钟,命中会刷新;API 也定义了一小时 TTL。最低可缓存长度按模型变化,所以前缀太短时两个缓存字段都可能为零。本文只讲 Anthropic Messages 的字段语义,不要把公式直接套到其他协议或供应商计价。

1. 正确心智模型:缓存有序前缀,不是一袋文本

Anthropic 按固定顺序处理提示词:tools、system、messages。一个缓存断点代表从开头到该内容块的完整前缀;下一次请求只有在相关前序内容仍匹配时才能读取它。相同段落出现在另一个位置并不等价。若断点前的工具定义、系统指令、历史消息、模型选择或相关 thinking 设置发生变化,后续前缀可能需要重新创建缓存。因此,内容组织比缓存标记数量更重要:稳定的规则、工具 schema、示例或被反复查询的文档放在前面;时间戳、请求 ID、当前问题和经常变化的检索结果放在最后一个稳定断点之后。

2. 自动缓存与显式断点

Anthropic 当前支持两种启用方式。自动缓存使用顶层 cache_control,会随对话增长移动有效断点,适合普通多轮对话。显式缓存把 cache_control 放在具体内容块上,适合明确知道哪些材料长期稳定的批处理、RAG 和 Agent。不同部分更新频率不同时可以设置多个显式断点,但增加标记不能挽救不稳定的前缀。最容易诊断的起点是:在一段足够长且稳定的 system 内容最后放一个断点,把当前问题放在它之后。是否值得使用以及怎样计价,要结合所选模型和供应商的当前文档判断,不能套用固定倍率。

3. 正确读取三个输入字段

响应中的 usage 才是验证依据。新条目创建时,cache_creation_input_tokens 表示在断点写入的 token;命中时,cache_read_input_tokens 表示从已有条目读取的 token。缓存开启后,input_tokens 不是整个提示词,而是最终断点之后未缓存的输入。总输入应按 input_tokens + cache_creation_input_tokens + cache_read_input_tokens 计算。对一个新的合格前缀,首次响应通常应看到创建大于零、读取为零;TTL 内后续顺序请求应把可复用前缀计入读取字段。即使响应成功,两个缓存字段仍可能都是零,例如该前缀低于所选模型的最低可缓存长度。不要只凭延迟推断命中,也不要只用 input_tokens 推断总输入。

4. 把显式断点放在稳定 system 前缀末尾

下面的 Anthropic Messages 示例只设置一个显式断点。长而稳定的指令或文档放在 system 内容块中,cache_control 放在该稳定块末尾;每次变化的问题放进后续 user 消息。先用一个断点把顺序验证跑通,再根据真实的更新频率决定是否增加边界。本文的 usage 公式只适用于 Anthropic 的缓存字段;其他协议可能使用不同字段与包含关系,必须读对应官方文档。

Anthropic 原生:把缓存断点打在稳定部分的末尾
{
  "model": "你的模型名",
  "max_tokens": 256,
  "system": [
    {
      "type": "text",
      "text": "这里是很长的系统提示词……(要超过最小长度门槛)",
      "cache_control": { "type": "ephemeral" }
    }
  ],
  "messages": [{ "role": "user", "content": "这里是每次都不同的短问题" }]
}

5. 顺序运行验证脚本,保留可审计证据

下面脚本把同一份足够长的稳定前缀顺序发送三次,并打印三个输入字段。必须等前一笔响应返回后再发下一笔,因为新条目要到第一笔响应开始后才可被读取。新前缀首次通常应出现 cache_creation_input_tokens,后续请求应出现 cache_read_input_tokens;若条目已存在,首次也可能直接读取。若缓存字段一直为零,先检查最低长度、断点位置、模型与路由支持、TTL 和模型可见前缀是否变化。脚本只帮助验证 Anthropic Messages 的实际效果,不能据此推断其他协议字段或最终账单价格。

同一份前缀连发三次,看命中数有没有跳上去
import json, time, urllib.request

BASE  = "https://cocodot.co/api/ai"   # Anthropic 原生,不带 /v1
KEY   = "你的-key"
MODEL = "你的模型名"

# 本次运行只生成一次标记,两笔之间保持完全相同
RUN_TAG = str(time.time_ns())
PREFIX = RUN_TAG + "\n" + ("你是一个严谨的代码审查助手。以下是团队的编码规范,请严格遵守。\n"
          "规范条目:每个函数必须有类型注解;禁止裸 except;日志必须结构化。\n") * 220

def call(question):
    body = {
        "model": MODEL,
        "max_tokens": 64,
        "system": [{"type": "text", "text": PREFIX,
                    "cache_control": {"type": "ephemeral"}}],
        "messages": [{"role": "user", "content": question}],
    }
    req = urllib.request.Request(
        BASE + "/v1/messages",
        data=json.dumps(body).encode(),
        headers={"x-api-key": KEY,
                 "anthropic-version": "2023-06-01",
                 "content-type": "application/json"},
    )
    with urllib.request.urlopen(req, timeout=120) as r:
        return json.load(r)["usage"]

for i in range(3):
    u = call(f"第 {i+1} 次:用一句话回答 1+1 等于几")
    read  = u.get("cache_read_input_tokens", 0)
    write = u.get("cache_creation_input_tokens", 0)
    plain = u.get("input_tokens", 0)
    total = read + write + plain
    rate  = read / total * 100 if total else 0
    print(f"第{i+1}次  未缓存={plain:6}  写入={write:6}  命中={read:6}  命中率={rate:5.1f}%")

6. 六类常见静默 miss 原因

第一,每次调用都在断点前加入时间戳、nonce、会话 ID 或请求 ID。第二,工具定义的顺序变化,或其 schema 被改动。第三,动态 RAG 结果放进 system 前缀,但它会随查询变化。第四,较早的对话消息被编辑、删除、总结或重排。第五,标记的前缀短于所选模型的最低可缓存长度。第六,下一次调用到来时条目已经过期。模型或相关 thinking 设置变化也可能使有效提示词不同。修复方向与原因一一对应:把易变元数据移到断点后,让工具顺序稳定,把稳定规则放在动态检索之前,追加新对话而不是改写历史,用明确足够长的前缀测试,并记录请求间隔。HTTP JSON 的无关空格或键排序不是这里要判断的核心;应比较模型实际看到的内容与顺序。

7. TTL、最低长度与并发会制造很多假警报

Anthropic 默认 ephemeral 缓存寿命为五分钟,从写入或读取请求开始计时,命中会刷新寿命。API 也定义了一小时 TTL,是否采用应查看供应商和模型的当前文档。最低可缓存长度随模型变化,所以小测试即使语法正确也可能让两个缓存字段都为零。并发是另一个陷阱:新条目要等首次响应开始后才可用,同时启动的相同请求未必能读取正在创建的条目。验证时应等待第一笔完成再发第二笔;生产并发可以先预热稳定前缀或控制首批请求。计价同样应以当前供应商文档和实际 usage 为准,不要把某一家倍率硬编码成通用结论。

8. 供应商这一层会不会把缓存弄丢

上游发生 Anthropic prompt caching 的必要条件,是链路保留 cache_control 与稳定的模型可见前缀。客户端若要验证命中并正确核算,响应还必须保留缓存 usage 字段。协议转换层可能忽略字段而仍返回 200;但缓存字段为零也可能来自前缀太短、模型或路由不支持、TTL 已过、工具顺序变化或并发时机。cocodot 的 Anthropic 格式端点会在上游响应包含相应字段时返回 cache_creation_input_tokens 与 cache_read_input_tokens;这是传输能力,不承诺每个前缀都会命中。以顺序测试验证实际效果,并用供应商当前计价文档解释账单。需要辅助时,probe.cocodot.co 可帮助组织可复现检查;对任何第三方诊断工具都使用临时、低额度 key,测完删除。

哪些工作负载适合缓存,以及什么必须保持稳定

工作负载适合缓存的前缀变化内容主要风险
编程 Agent工具定义与系统规则用户任务与工具结果工具顺序或 schema 变化
文档问答被反复查询的同一文档每次问题每次调用都替换文档
长对话较早的对话轮次最新一轮改写或压缩历史消息
RAG 应用稳定指令与共享语料每次检索结果把动态检索放到断点前
批处理共享规则与示例每条记录前缀低于模型最低长度
低频任务任何可复用前缀定时输入下一次请求晚于 TTL

常见问题

第一次和第二次请求分别应该看到什么?

对一个新的、足够长的前缀,首次响应应检查 cache_creation_input_tokens。等待它返回后,使用相同缓存前缀发送第二次请求,再检查 cache_read_input_tokens。验证时不要把两笔同时启动。

为什么两个缓存字段都是 0?

常见原因包括前缀低于所选模型的最低可缓存长度、没有有效断点,或请求路径没有保留 Anthropic 缓存字段。先用明确足够长的稳定前缀和 Messages 端点复现,再逐项排查。

input_tokens 包含缓存 token 吗?

在 Anthropic 的缓存 usage 拆分中不包含。它表示最终断点后的未缓存输入;总输入是 input_tokens、cache_creation_input_tokens 与 cache_read_input_tokens 之和。

看起来相同的请求为什么仍然 miss?

比较断点前的模型、工具定义及顺序、system 内容、较早消息与相关 thinking 设置,并检查 TTL 是否已经过期。只要变化内容位于稳定断点之后,答案文本或最新问题可以不同。

应该用自动缓存还是显式断点?

普通增长型对话使用自动缓存更方便。Agent、RAG、重复文档分析与批任务通常更适合显式断点,因为稳定边界可见且更容易诊断。

cache_read 大于 0 就能证明最终账单更低吗?

它能证明 API 复用了缓存输入,不能单独证明某个供应商怎样计价。应把返回的 usage 与该供应商当前公开计价规则一起核对,不要把一家供应商的倍率套到另一家。

关于 cocodot

cocodot 是面向中国大陆开发者与出海团队的支付与 AI 接入服务:由持牌机构发行的美国卡段虚拟卡(用于在海外网站完成订阅与广告扣费),以及 OpenAI 兼容的 AI API 中转(在中国大陆直连调用 Claude、GPT、Gemini)。两者共用同一个钱包,支持支付宝充值、以美元记账。卡费率:开卡 $9.9、充值到卡 3%、每张活跃卡每月 $1;消费:单笔 <$20 收 $0.60 结算费;发卡方按笔收费时另收相应费用。

服务范围、价格与能力边界 →
Claude Prompt Caching:验证命中与排查 Miss · cocodot