Claude Code 的 Prompt Cache 到底怎么工作? 5 个让缓存失效的坑

发布于

用 Claude Code 一段时间后我发现,同样的工作量,token 账单能差好几倍——差别几乎全在 Prompt Cache 有没有用对。官方文档把"有这个功能"讲了,但"怎么才不会踩坑"基本没说。这篇把我踩过的坑和读源码/官方博客理解的机制整理一下。

纯技术,不涉及任何平台推荐,就事论事。

---

## 一、为什么 Claude Code 对缓存特别敏感

普通 API 调用输入输出大致 1:1 ,prompt 短,缓存收益有限。

Claude Code 反过来——**单次请求的输入 token 远大于输出**。一次"改个 bug"的对话,输出可能就 200 token ,但输入要带:

- 系统提示词(约 3000 token ,含全部工具定义)
- [CLAUDE.md](http://claude.md/) (项目指令,几百到几千 token )
- 历史对话(几千到几万 token )
- 当前文件内容

**典型一次请求:输入 15K ,输出 500 ,输入是输出的 30 倍。** 这种结构下,输入 token 的单价几乎决定总账单。而 Prompt Cache 对命中部分给的是 **90% 折扣**(只收 10%),不是小优化,是数量级差异。

---

## 二、工作原理:前缀匹配,不是"重复内容打折"

很多人以为是"系统检测到重复内容就打折",这是错的。

真实机制:**服务端保存你最近发送的 prompt 前缀。下次请求只要前缀完全一致(一字不差),就从缓存读取、跳过重算。**

关键点:**前缀任何一处变化,后面所有内容的缓存全部失效。**

```plaintext
请求 1:[系统提示][工具定义][CLAUDE.md][对话 1-5][新消息]
请求 2:[系统提示][工具定义][CLAUDE.md][对话 1-6][新消息]
前面完全相同 → 命中,只有"对话 6+新消息"按全价
```

但如果你在请求 2 里给 [CLAUDE.md](http://claude.md/) 偷偷加了一行,从 [CLAUDE.md](http://claude.md/) 往后**全部失效**,包括之前已经缓存的对话历史。

### 一个反直觉的设计细节

[CLAUDE.md](http://claude.md/) 不是拼在 system prompt 里发送的,而是通过 `<system-reminder>` 标签注入到 messages 数组。为什么?因为**同版本 Claude Code 的 system prompt 在所有用户之间字字相同**,服务端可以做全局共享缓存;如果把每个人不同的 [CLAUDE.md](http://claude.md/) 拼进 system prompt ,这个共享缓存就没了。拆开放,既保住全局共享,又让你的 [CLAUDE.md](http://claude.md/) 独立缓存。

---

## 三、5min vs 1h 两档,怎么选

| 档位 | 写入价格 | 读取价格 | 回本条件 |
| -------- | ---------------- | --------- | ------------------ |
| 5 分钟 | 1.25× 基础输入价 | 0.1× | 1 次命中即回本 |
| 1 小时 | 2× 基础输入价 | 0.1× | 2 次命中回本 |

- 高频连续工作(<5min 一次请求)→ 5min 档
- 任务间隔长、跨会议/午餐 → 1h 档

Claude Code 内部对 system prompt + 工具定义默认 5min (高频复用),用户上下文按 session 长度自动选档。

---

## 四、5 个让缓存失效的坑(最实用的部分)

### 坑 1:中途修改 [CLAUDE.md](http://claude.md/)

最常见。几轮对话后随手给 [CLAUDE.md](http://claude.md/) 加条规则——从 [CLAUDE.md](http://claude.md/) 往后全部失效,之前几轮的输入全按全价重算。 **对策**:session 开始前配好,开始后只读不改,要改先 `/new`。

### 坑 2:prompt 里塞动态内容

```plaintext
"当前时间 2026-07-21 15:23:45 ,请…"
```

时间戳、随机 ID 、UUID 只要进了前缀,缓存命中率直接归零(每次都不一样)。 **对策**:动态信息放最后一条用户消息里,别混进 system 或前置。

### 坑 3:中途切模型

不同模型缓存隔离。opus 切 sonnet ,之前的缓存直接作废。 **对策**:同任务保持模型一致,要切先 `/new`。

### 坑 4:/compact 的隐藏成本

`/compact` 的总结请求用的是**专门的总结 system prompt 、且不带工具定义**,前缀从第一个 token 就和日常缓存不同,**整个对话历史按全价计费一次**。 **对策**:别攒到几十轮才 compact ;做完一个子任务就 compact 一次(被全价的内容少);只想清空的话 `/new` 更划算。

### 坑 5:/resume 破坏缓存

`--resume` / `/resume` 在多个版本存在缓存失效——恢复后前几轮全价,可能 10–20 倍成本暴增。原因是序列化/反序列化后 messages 结构有微小差异,服务端当成新前缀。 **对策**:长任务尽量一个连续 session 做完;不得不断,宁可新 session 简短复述,也别 resume 。

---

## 五、怎么确认缓存真的命中了

看响应里的 usage 字段:

```json
{
"usage": {
"input_tokens": 245,
"cache_creation_input_tokens": 3120,
"cache_read_input_tokens": 8450,
"output_tokens": 412
}
}
```

- `cache_creation_input_tokens`:本次写入缓存( 1.25× 或 2×)
- `cache_read_input_tokens`:本次命中( 0.1×)
- 连续 session 第 N 轮( N>1 ),`cache_read` 应远大于 `input_tokens`
- 如果 `cache_read` 一直是 0 ,前缀肯定哪里被破坏了,照上面 5 个坑排查

---

## 六、三条核心原则

1. **保持前缀稳定**——别中途改 [CLAUDE.md](http://claude.md/) 、加时间戳、切模型
2. **同任务一气呵成**——长 session 比频繁 resume 划算
3. **/compact 早用别攒**——越晚 compact 被全价的内容越多

[CLAUDE.md](http://claude.md/) 我个人保持在 50 行以内,当"索引"用(列关键文件路径、命令、约定),详细文档放 `docs/` 让 Claude Code 自己 Read——既省 token 又不容易失效。

以上是我自己的实践,有不同经验欢迎评论区交流。

*基于 Claude Code 1.x 与 Anthropic 官方文档,如有错误欢迎指正。

---

原文链接:[点击查看](https://www.v2ex.com/t/1229144)

评论(2)

针对优化缓存命中率这个问题,我自己写了个网关来解决,项目地址:https://github.com/alexazhou/gt_ai_gateway 。它可以监控每个请求的缓存命中情况,并且自动把 Claude Code 产生的动态标记剔除掉,实测能大幅提升缓存命中率。

· 0 个赞

感觉这篇内容有点水啊。关于 `/compact` 的建议,把十个任务打包处理,和分十次每次处理一个,本质上有什么区别吗?实在没看懂。

· 0 个赞