如果你在写 AI Agent——不管是接 Claude Code 这类自主编码工具,还是自己撸一个 loop——迟早会撞到同一个问题:token 账单涨得比你想象快得多。多数人第一反应是"换个更便宜的模型",但真正的杠杆在另一个地方:Prompt Cache(提示词缓存)的命中率

这篇教程把 Claude API 的 cache_control 协议、前缀稳定原则,以及一次真实把命中率从 27% 拉到 90% 的优化过程,按步骤讲清楚,读完你能直接照着配。

先把关键词摆这:claude apiprompt cachecache_controlclaude codetoken 成本。下面开始。


一、先算笔账:Prompt Cache 为什么能省好几倍

一个自主代码 Agent(Claude Code、Aider,或你自己写的 loop),典型的一次任务是这样累积上下文的:

Turn 1:  用户提问 (500 token) + system prompt (5000 token) + tools (3000 token)
Turn 2:  加上 Turn 1 的 assistant 回复 (2000 token) + 用户新消息 (300 token)
Turn 3:  再加 assistant + user
...
Turn 10: prompt 累积到 30K-100K token

不做缓存,每一轮都把整段 prompt 完整送给模型。10 轮总输入 ≈ 30K × 10 = 300K token。按 Opus 4.8 官方输入价 $5/M 算,仅输入这一项就是 $1.5 一次任务,跑 100 个任务就是 $150。

做对缓存,从 Turn 2 开始,前面 25K+ 的固定前缀(system + tools + 已固化历史)每次都命中缓存,只按 0.1x 单价计费。同样 10 轮:

  • 1 次全额输入:30K × $5/M = $0.15
  • 9 次命中读取:25K × 9 = 225K × $0.5/M(read = 0.1x)= $0.11
  • 增量输入(每轮新问答):5K × 10 = 50K × $5/M = $0.25
  • 合计 ≈ $0.51,降到不做缓存的约 1/3。

把场景放大:一次 200K 上下文的 Agent 循环跑 10 步 ≈ 200 万 token,不做缓存按 $5/M 就是 $10;做对缓存后大概只要 $1.5–2。这 5 到 10 倍的差距,几乎全部来自"前缀稳不稳定"这一件事。

这不是"能不能省"的问题,是"产品能不能规模化活下去"的问题。


二、Claude API 的缓存协议:精确到字节的前缀匹配

Anthropic 的 Prompt Caching 是 messages API 的第一等公民,协议很简洁,但有几个必须精确理解的细节。

2.1 触发方式:cache_control

在任意一个 content block 上加一个字段:

{
  "type": "text",
  "text": "这里是很长的 system prompt...",
  "cache_control": { "type": "ephemeral" }
}

这个 block 及其之前的所有内容(按 tools → system → messages 的自然顺序)被标记为「一个缓存前缀」。下次请求如果前缀逐字节相同,就命中。

cache_control 还支持一个 ttl 字段。下面以 Opus 4.8 官方输入价 $5/M 为基准换算价格:

ttl 计费倍数 折算单价($/M) 使用场景
"5m"(默认) 输入价×1.25 $6.25/M 交互式对话、Agent 短循环
"1h" 输入价×2.0 $10/M 大规模文档 RAG、长 session
读取(cache read) 输入价×0.1 $0.5/M 命中即用,通用

Break-even 心算:1 次 5m 写入(1.25x)+ N 次读取(0.1x) vs N+1 次全额输入。
1.25 + 0.1N ≤ N+1N ≥ 0.28只要同一前缀被复用一次以上,缓存就赚

2.2 硬性约束

约束 说明
最小缓存单元 1024 token(多数模型)/ 2048 token(Haiku) 太短的 block 加了 cache_control 也不缓存
每请求最多断点数 4 要精心规划哪 4 个位置最有价值
前缀匹配 逐字节 JSON key 顺序、时间戳、随机 ID 都会击穿
生效顺序 tools → system → messages 越靠前的部分越应该稳定

2.3 usage 返回字段(必须监控的指标)

每次响应的 usage 对象里,有 4 个决定成本的字段:

{
  "usage": {
    "input_tokens": 300,                    // 未命中缓存的新输入
    "cache_creation_input_tokens": 5200,    // 本次新写入缓存的 token
    "cache_read_input_tokens": 25000,       // 本次命中读取的 token
    "output_tokens": 800
  }
}

健康度判据cache_read / (cache_read + cache_creation) 应稳定 > 80%。长期低于 50%,说明前缀在被反复击穿——问题几乎肯定出在输入未标准化上。

2.4 白嫖式测量:count_tokens API

Anthropic 提供了 /v1/messages/count_tokens 端点,不消耗任何 credit,可以在真正发请求前算出这一次会被计几个 token。做缓存优化的工程都应该把它当基线工具——猜测是缓存优化的头号大敌


三、Agent 缓存设计的五条黄金原则

从「协议」跨到「工程」,这五条决定你能不能真正把命中率吃到 80%+。

原则 1:稳定的东西放前面

Prompt 的物理结构应该是同心圆

最稳定  → [ tools 定义 ]        永远不变
        → [ system prompt ]     每次任务前拼好就锁死
        → [ 历史消息 ]           Agent 循环里追加式增长
        → [ 用户当前输入 ]        变化
最易变  → [ 工具结果 / 观察 ]      变化

cache_control 断点应刚好放在每层的尾部——tools 尾、system 尾、最后一条已完成对话的尾部。这样一次请求可以在多个层级同时命中

原则 2:输入必须标准化(Standardized Input)

前缀击穿的头号元凶,是被忽略的"隐形变量"

  • "timestamp": "2026-07-21T14:32:11.827Z" ← 每次都变
  • "session_id": uuid() ← 每次都变
  • ❌ JSON 序列化时 key 顺序不同
  • ❌ system prompt 里嵌了当前 CWD、git branch、TODO 列表
  • ❌ tool schema 里写了 “as of {{today}}”

修复:所有 volatile 字段全部塞到 messages 的最后一条 user 消息里。前缀部分,任何时候序列化出来的字节都必须完全一致。

原则 3:断点是稀缺资源,精心分配 4 个

断点 1:  [ tools 尾部 ]        → 命中率 99%+
断点 2:  [ system 尾部 ]       → 命中率 95%+
断点 3:  [ history[-5] 尾部 ]  → 命中率 ~70%
断点 4:  [ history[-1] 尾部 ]  → 命中率 ~30%(短循环内命中)

不要把断点浪费在"当前问题"上——那注定是新内容,不会有前缀。

原则 4:多意图分离到不同 conversation

同一个用户既让 Agent 写代码又让它做 SQL 查询,不要塞在一个对话里——不同意图的历史消息互相击穿彼此的前缀。给不同意图开不同对话线程,命中率立刻起飞。

原则 5:监控 read / creation 比例,而非绝对量

命中率是结构性指标,总 token 是流量指标。流量涨的时候 cache_creation 跟着涨很正常。真正要报警的是比例:一旦 cache_read / (cache_read + cache_creation) 从 90% 跌到 50%,说明结构性问题出现——赶紧看是不是有人往 system prompt 里塞了当天日期。


四、实战:从 27% 到 90% 命中率的一次优化

下面是我们在自用 AI 网关(灵眸AI,api.lmuai.com)上做的一次真实上游缓存优化,数据脱敏但结构真实。

4.1 起点

某个 Claude Code 类客户端接入后,后台监控发现:

  • 上游端的 cache read 命中率仅 27%
  • 每次请求平均 5–8K token 走了 cache_creation_input_tokens(冷写入)
  • 客户端行为看起来正常——loop 循环、tool 使用、system prompt 也稳定

4.2 根因定位

打开完整请求 dump,对比连续两次请求的前缀字节,发现 3 处 volatile:

  1. 每次都有一个新的 agentContinuationId(客户端生成的 UUID,放在 conversationState 根)
  2. history 里每条 user message 都带 modelId 字段(大小写偶尔漂移)
  3. system prompt 尾部 auto-inject 了"当前时间戳"(客户端为 debug 加的)

任何一处变化,都让整个前缀失效。三处叠加,命中率被反复砸下去。

4.3 修复(在网关层做,客户无感)

网关侧写了一个 stable_prefix_normalizer 中间件,核心逻辑就三步:

def stable_prefix_normalizer(payload):
    # 1. 剥离上游不看的 volatile 根字段
    payload["conversationState"].pop("agentContinuationId", None)

    # 2. 历史 user message 里的 modelId 统一移除,只保留 currentMessage
    for msg in payload["conversationState"]["history"]:
        if msg.get("role") == "user":
            msg.pop("modelId", None)

    # 3. 把 system 尾部的 volatile 时间戳挪到 messages 末尾
    payload["system"], volatile = split_volatile_tail(payload["system"])
    if volatile:
        append_to_last_user_message(payload, volatile)

    return payload

同时补上 cache_control 断点:tools 尾、system 尾、history[-5] 尾各放一个。

4.4 效果

指标 优化前 优化后
cache_read 命中率 27% 90%
单请求平均输入 credit 8.4 4.5
单请求成本 基线 降 46%
Agent 循环 10 步总成本 $0.10 $0.054

客户端根本不需要改代码,网关帮它把前缀稳定化 + 断点安置好,命中率就上来了。


五、成本口径:官方 vs 网关,怎么对齐

很多人纠结"用官方还是用网关谁便宜",其实命中率的影响远大于单价差。先把两边价格摆清楚(Opus 4.8):

Anthropic 官方(美元)

  • 输入 $5/M、输出 $25/M、缓存读 $0.5/M(0.1x)

灵眸AI(人民币,两档)

  • 按量档:输入 ¥6/M、输出 ¥30/M、缓存读 ¥0.6/M(约官方美元价 ×1.2 计价),典型负载加权约 ¥22.8/M
  • 月套餐档:综合加权约 ¥18/M,约官方价 1.4 折,适合调用量稳定的团队

重点是:同样一段 Agent 负载,命中率从 27% 提到 90% 省下的钱,比在供应商之间比单价省下的多得多。缓存优化是先做的事,选供应商是后做的事。


六、给 Agent 开发者的 8 条 Checklist

打印出来贴在显示器上:

  • 每次请求前先跑一次 count_tokens,建立"预期发多少 token"的直觉
  • system prompt 是不是完全稳定?(有没有嵌入日期 / session_id / user profile)
  • tools 定义是不是完全稳定?(有没有动态 tool_use_id 出现在 schema 里)
  • cache_control 断点是不是放在了"稳定尾部"而不是"变化头部"?
  • 多意图对话是不是拆到了不同 conversation?
  • 监控 cache_read / (cache_read + cache_creation) > 80%,低于就报警
  • JSON 序列化用了固定 key 顺序(sort_keys=True
  • volatile 字段全部挪到 messages 尾部,不污染 tools / system

七、常见误区

误区 1:缓存越多越好,把所有东西都标 cache_control。
错。断点只有 4 个,每个还必须满足最小 token 数(1024/2048)才生效,滥用只会浪费断点。

误区 2:用 1h TTL 更保险。
1h 写入价是 2.0x($10/M)。会话不到 30 分钟就结束,那额外 60% 的写入成本白花。默认 5m 已覆盖 80% 的交互式场景。

误区 3:命中率低是模型 API 的问题。
99% 的低命中率是你自己的输入没标准化。用 diff 工具比对连续两次请求的前缀字节,答案自己会浮出来。


八、局限与写在最后

得说清楚 Prompt Cache 不是银弹:

  • 它只帮你省重复前缀的钱。如果你的场景每次请求内容都不一样(一次性总结、随机检索),缓存基本无用武之地。
  • 断点只有 4 个、最小 1024 token,短 prompt 优化空间有限。
  • 逐字节匹配很脆,上游一次协议变更就可能让你的 normalizer 失效,得持续跟进。

但对 Agent 类应用,规律很清楚:用户越用越黏、单会话越用越长、后台任务越跑越多,起步时随手写的 timestamp、随便设的 UUID,会以每月成倍的账单差回来找你。在 Day 1 就把"前缀稳定 + 断点分层"做对的团队,能把命中率长期锁在 80%+,这是复利。

以上是我们踩过的坑和量出来的数,欢迎在评论区交流你的命中率数据和优化思路。

关键词:claude api、prompt cache、cache_control、claude code、token 成本、ai agent、anthropic、prompt caching

更多推荐