Claude Code Prompt Cache 深度解析:工作原理、5 个失效陷阱与配置实战
用 Claude Code 一年,最大的技术认知更新就一句话:Prompt Cache 没配好,你的 token 账单里 80% 是冤枉付的。本文整理我在实战中踩过的坑、读源码和官方博客后理解的工作原理,以及一套实测有效的配置方案。
一、为什么 Claude Code 对 Prompt Cache 特别敏感
普通 LLM API 调用,输入输出大致 1:1,prompt 短,缓存收益有限。
Claude Code 的请求结构反过来——单次请求的输入 token 远大于输出。我抓了一个实际请求的 payload 看:
单次「修一个 bug」请求的 token 分布:
├─ system prompt(系统提示 + 24 个内置工具定义) ≈ 3,200 tokens
├─ CLAUDE.md(项目级指令) ≈ 800 tokens
├─ 历史消息(5 轮对话累积) ≈ 8,500 tokens
├─ 当前文件内容 ≈ 2,000 tokens
└─ 用户新消息 ≈ 50 tokens
─────────────────────────────────────────────
输入总计:14,550 tokens
输出:≈ 400 tokens
输入/输出 比:36:1
结论:Claude Code 场景下,每个输入 token 的实际单价决定总账单。
而 Prompt Cache 提供的优惠是 90% 折扣——缓存命中的部分只收基础输入价的 10%。这不是"小幅优化",是数量级差异。
二、Prompt Cache 工作原理:前缀匹配,不是"内容去重"
很多教程把 Prompt Cache 描述成"系统检测到重复内容就打折",这个理解是错的。
2.1 真实机制:前缀完全匹配
Anthropic 服务端会保存你最近发送的 prompt 前缀。下次请求时,只要前缀(从第一个 token 开始)完全一致,就从缓存读取,跳过重新计算。
关键约束:前缀任何一处变化,后面所有内容的缓存全部失效。
举例说明:
请求1: [Sys][Tools][CLAUDE.md v1][对话1-5][新消息A]
↓ 缓存写入
请求2: [Sys][Tools][CLAUDE.md v1][对话1-5][新消息B]
↓ 命中缓存,A 之前部分按 0.1× 计费
请求3: [Sys][Tools][CLAUDE.md v2][对话1-6][新消息C]
↑ CLAUDE.md 改了一行
从 CLAUDE.md 开始,包括对话 1-5(之前已缓存的部分)全部失效
全部按 1.0× 重新计算 + 写入新缓存
2.2 cache_control 是怎么放的
Anthropic API 的 cache_control 标记是放在 message 块上的。Claude Code 的实际请求体大致长这样(精简后):
{
"model": "claude-opus-4-7",
"system": [
{
"type": "text",
"text": "You are Claude Code...",
"cache_control": {
"type": "ephemeral" }
}
],
"tools": [...],
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "<system-reminder>CLAUDE.md content...</system-reminder>",
"cache_control": {
"type": "ephemeral" }
}
]
},
{
"role": "assistant", "content": "..." },
{
"role": "user", "content": "新消息" }
]
}
注意两个 cache_control 标记位置——一个在 system prompt 末尾,一个在 CLAUDE.md 那条消息末尾。这意味着有两个缓存断点(cache breakpoint):
- 断点 1:system + tools 是稳定层,第一次请求后整个 session 命中
- 断点 2:CLAUDE.md 是项目层,跨 session 但同项目复用
2.3 CLAUDE.md 不在 system prompt 里(重要细节)
读 Claude Code 的请求结构会发现一个反直觉的设计:
CLAUDE.md 不是拼接在 system prompt 里发送的,而是通过 <system-reminder> XML 标签注入到 messages 数组的第一条用户消息中。
为什么这样设计?因为 system prompt 在所有 Claude Code 用户之间共享——同一版本 CLI 的系统提示词字字相同。Anthropic 在服务端做了"共享前缀缓存",所有用户共用一份 system prompt 缓存。如果把 CLAUDE.md 拼进 system prompt,每个用户的 system prompt 不一样,共享缓存就没了。
把 CLAUDE.md 单独放在 messages 中,可以独立设置 cache_control,既不破坏 system prompt 的全局共享缓存,又能让你自己的 CLAUDE.md 独立缓存。
三、5min vs 1h 两档缓存:怎么选
Anthropic 提供两档 TTL 的缓存:
| 档位 | Cache Write 价格 | Cache Read 价格 | 回本条件 |
|---|---|---|---|
| 5 分钟 | 1.25× 基础输入价 | 0.1× | 1 次命中即回本 |
| 1 小时 | 2.0× 基础输入价 | 0.1× | 2 次命中回本 |
回本计算逻辑:
写入溢价 = 写入价 - 1.0×(基础输入价)
节省 = 1.0× - 0.1× = 0.9×(每次命中)
- 5min 档:溢价 0.25× → 0.25 / 0.9 ≈ 0.28 次命中即回本
- 1h 档:溢价 1.0× → 1.0 / 0.9 ≈ 1.12 次命中回本
实战选择策略:
高频连续工作(< 5 分钟一次请求)→ 5min 档
任务切换或思考间隔较长(> 5min)→ 1h 档
session 跨午餐、会议等 → 1h 档
Claude Code 内部对 system prompt + tools 默认用 5min(高频复用),messages 部分根据时间戳和上下文自动选档。
四、5 个让缓存失效的隐藏陷阱
这部分是官方文档没明确说,但实战中最容易踩的坑。每个陷阱都能让你的 token 成本瞬间翻倍甚至 10 倍。
陷阱 1:中途修改 CLAUDE.md
最常见的坑。你以为加几行项目说明无伤大雅,实际上:
# session 开始
$ claude
> 帮我重构这个模块
# 几轮对话后,你想到 CLAUDE.md 应该补充一条规则
$ vim CLAUDE.md # 加了一行 "用 pnpm 不用 npm"
# 继续对话
> 接着改
# 这一轮请求:CLAUDE.md 内容变了,从 CLAUDE.md 起的所有缓存失效
# 之前 5 轮对话的输入 token 全部按 1.0× 重新计费
对策: session 开始前规划好 CLAUDE.md,开始后只读不改。需要改时先 /new。
陷阱 2:动态时间戳/随机内容
# ❌ 这种 system prompt 让缓存永远失效
SYSTEM = f"""
你是代码助手。当前时间:{datetime.now()}
请帮助用户...
"""
任何在前缀里塞动态内容(时间戳、随机 ID、用户名、UUID 等)都会让缓存命中率归零。每次时间戳都不同 → 前缀不同 → 缓存必然 miss。
对策: 动态信息放在 messages 末尾、最后一条用户消息里。前缀部分(system / tools / 早期 messages)保持稳定。
陷阱 3:中途切换模型
请求1: model=claude-opus-4-7 → 缓存写入 A
请求2: model=claude-sonnet-4-6 → 缓存 A 完全不可用,重新写入 B
不同模型的缓存是隔离的。中途切模型 = 抛弃当前缓存。
对策: 同一任务保持模型一致。需要切模型时,主动 /new 开新 session(避免在脏缓存上继续)。
陷阱 4:/compact 命令的隐藏成本
/compact 触发的总结请求使用专门的总结 system prompt(不是日常那个),且不带工具定义。这意味着:
日常请求前缀: [Claude Code 系统提示][24 个工具定义][CLAUDE.md][对话历史]
/compact 请求: [总结 system prompt][对话历史]
↑
从第 1 个 token 就和缓存的 prefix 不同
整个对话历史在 /compact 这一次按全价计费。
对策:
- 不要积累几十轮再 compact,做完一个子任务就 compact 一次(被全价计费的内容更少)
- 如果只是想清空,
/new反而更划算(彻底清空,下次 session 重新建立缓存) - compact 之后的"压缩摘要"会成为新 session 的起点,需要重新写入缓存
陷阱 5:/resume 恢复会话破坏缓存
--resume 或 /resume 命令在多个版本中存在缓存失效问题。恢复 session 后,前几轮请求按全价计费,导致 10–20 倍成本暴增。
原因是 resume 时序列化/反序列化的 messages 结构和原始结构有微小差异(额外的 metadata、空格、序列化时丢失的标记),导致服务端认为这是新前缀。
对策: 长任务尽量在一个连续 session 完成。不得不中断时,宁可在新 session 简短复述上下文,也不要 resume。
五、CLAUDE.md 最佳实践
CLAUDE.md 是缓存命中率最敏感的文件。我的实践:
5.1 保持极简(< 50 行)
# 项目说明
## 关键命令
- 安装: pnpm install
- 开发: pnpm dev
- 测试: pnpm test
- 部署: pnpm deploy
## 代码约定
- 用 pnpm 不用 npm
- 用 TypeScript strict 模式
- API 路由放 src/api/
## 关键文件
- 配置: config/index.ts
- 数据库 schema: prisma/schema.prisma
- 路由表: src/router.ts
## 不要做
- 不要修改 .env.production
- 不要 commit lockfile 之外的 .pnpm 目录
5.2 当作"索引"而非"文档"
CLAUDE.md 的作用是告诉 Claude 去哪里找信息,而不是把信息塞进来。详细文档放 docs/,让 Claude Code 主动 Read。
5.3 不写动态内容
不要写"今日更新""本周计划""临时方案"这类内容——你会改它,缓存就会失效。
六、实测节省效果
我用同一个项目(中等规模 React + Node 后端,约 200 个文件)做对比测试:
测试方法: 模拟典型工作流,连续提 100 个修改/重构请求,记录 token 消耗。
| 场景 | 输入 token 总量 | 理论成本(按 Opus 计) |
|---|---|---|
| 不开 Cache(强行禁用) | ≈ 1,800,000 | $9.00 |
| 默认开启 Cache(命中率 ~70%) | ≈ 540,000 等效 | $2.70 |
| 优化 CLAUDE.md + 避坑(命中率 ~92%) | ≈ 280,000 等效 | $1.40 |
等效 token 算法: 缓存命中部分 × 0.1 + 写入部分 × 1.25 + 未命中部分 × 1.0
实测最终成本是不开缓存的约 15.6%,节省 84%。
七、配置实战
7.1 settings.json 推荐配置
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.anthropic.com/",
"ANTHROPIC_AUTH_TOKEN": "sk-ant-..."
},
"effortLevel": "medium"
}
effortLevel: medium 减少推理 token 消耗(对日常代码任务质量影响很小,但能减少 20–30% token)。
7.2 国内开发者的实际困境与变通方案
国内直连 api.anthropic.com 在大部分网络环境下不稳定(IP 限制、TLS 握手失败、TTFT 抖动),加上 Anthropic 官方账号注册需要美国信用卡,国内开发者通常采用两种变通方案:
方案 A:自建代理(HTTP Proxy / 工作流隧道)
# 通过 ANTHROPIC_BASE_URL 指向自建代理
export ANTHROPIC_BASE_URL=https://your-proxy.example.com/
要点是代理需要透明转发请求(不能改 body),否则 cache_control 标记会丢失,缓存机制全部失效。我用 Cloudflare Worker 写过一个轻量转发器,关键代码:
// worker.js — 透明转发,必须保留所有 headers 和原始 body
export default {
async fetch(request) {
const url = new URL(request.url);
url.hostname = 'api.anthropic.com';
return fetch(new Request(url, request));
}
};
但自建代理方案有个隐患:Anthropic 服务端的缓存是按账号隔离的,如果用别人的 key 走自己的代理,缓存命中率会和那个账号的其他用户共享,命中率反而可能受影响。
方案 B:使用国内中转服务
笔者实际使用的是灵眸AI(一家国内 Claude API 中转 clawapi.fulitimes.com),它对外提供 Anthropic 兼容的接口:
{
"env": {
"ANTHROPIC_BASE_URL": "https://clawapi.fulitimes.com/",
"ANTHROPIC_AUTH_TOKEN": "your-token"
},
"effortLevel": "medium"
}
选这家的核心原因和本文主题相关——它透明转发官方 API,cache_creation_input_tokens 和 cache_read_input_tokens 字段完整透传,Cache 行为与官方一致。这点很关键,因为市面上有些中转走的是逆向接口(不是真实的 Anthropic API),那种平台完全不支持 Prompt Cache,本文讲的所有优化技巧都白搭。
判断方法见下一节 7.3。
7.3 验证缓存是否真的命中
在 Claude Code 内执行 /status 或查看响应中的 usage 字段:
{
"usage": {
"input_tokens": 245,
"cache_creation_input_tokens": 3120,
"cache_read_input_tokens": 8450,
"output_tokens": 412
}
}
关注三个字段:
cache_creation_input_tokens:本次写入缓存的 token(按 1.25× 或 2.0× 计费)cache_read_input_tokens:本次缓存命中的 token(按 0.1× 计费)input_tokens:未命中、按全价计费的部分
理想情况下,连续 session 的第 N 轮(N>1)应该 cache_read_input_tokens 远大于 input_tokens。如果你发现 cache_read 一直为 0,前缀肯定哪里被破坏了——按上面 5 个陷阱排查。
7.4 工作节奏
开新任务 → /new (清空,建立新缓存)
连续对话 → 充分利用 Cache 命中(最便宜的状态)
做完子任务 → 趁早 /compact (被全价的内容少)
长间隔后 → 优先 /new,避免 resume
绝不中途 → 改 CLAUDE.md / 切模型 / 加时间戳
八、总结
Prompt Cache 是 Claude Code 单项最重要的成本机制,但官方文档没把"怎么避免缓存失效"讲清楚,导致很多人开了缓存还是花冤枉钱。
记住三条核心原则:
- 保持前缀稳定 —— 不要中途改 CLAUDE.md、加时间戳、切模型
- 同任务一气呵成 —— 长 session 比频繁 resume 更划算
- /compact 早用而不是攒着用 —— 越晚 compact,被全价计费的内容越多
照这三条做,配合 effortLevel: medium 和合理的 CLAUDE.md,token 成本可以做到不开缓存场景的 15–20%。
思考题: 如果你的 Claude Code 项目同时有多人协作,每个人的 CLAUDE.md 都不一样,团队层面有没有可能共享缓存?欢迎评论区交流。
本文基于 Claude Code 1.x 和 Anthropic API 2026年5月 文档撰写。如有错误欢迎指正。