Prompt Caching:一句话概括
Claude Code 并不是简单地存储对话记录,而是通过对 Anthropic API cache_control 参数的精密编排,将系统提示词、工具定义和对话历史动态地“钉”在模型服务器的 KV 缓存中,实现毫秒级的首字响应和极低的 Token 重复开销。
核心概念
在 Claude Code 的语境下,Prompt Caching 不是客户端的磁盘缓存,也不是简单的字符串匹配。
它本质上是一种服务端状态保持机制。你可以把它想象成在远程服务器上维护了一个“上下文热分区”。当你发送一个长达 30k tokens 的项目上下文时,Claude Code 通过 cache_control 标记告诉服务端:“这一段内容在接下来的几分钟甚至一小时内大概率还会被用到,请不要把它从内存中踢出去。”
它不是一种持久化的数据库存储,而是一种带有 TTL(生存时间)的、基于计算前缀匹配的临时驻留。如果你的下一次请求前缀与缓存内容一致,API 就会直接“复用”这部分计算结果,而不是重新处理一遍。
代码里的真实逻辑
Claude Code 对 Prompt Caching 的实现核心在于 src/services/api/claude.ts 和 src/services/api/promptCacheBreakDetection.ts 两个文件。其逻辑流程可以归纳为以下三个层面:
1. 系统提示词的“动静分离”
在 splitSysPromptPrefix 函数中,Claude Code 会检查系统提示词中是否存在 SYSTEM_PROMPT_DYNAMIC_BOUNDARY(动态边界标识符)。
- 静态部分:通常包含 CLI 的基础指令、核心规则。这部分被赋予
cache_scope: 'global'或org,在多个会话间甚至是跨组织共享。 - 动态部分:包含当前文件内容、临时上下文。这部分不设缓存或采用更窄的范围。 这种设计确保了最通用的指令永远在缓存中“预热”,而不会因为你打开了一个新文件就导致整个系统提示词缓存失效。
2. 对话历史的“端点推进”
在 addCacheBreakpoints 函数中,Claude Code 会动态地向消息流中插入 cache_control 断点。
- 常规模式:默认在消息列表的最后一条消息打桩。这样每次对话结束后,整个历史记录都会被标记为可缓存。
- Fork 模式(skipCacheWrite):当 Claude Code 进行后台预测、总结或子任务时,它会设置
skipCacheWrite: true。此时,断点会被前移到倒数第二条消息。这样做能利用已有的历史缓存,但不会让这些临时生成的预测内容“污染”主对话的 KV 缓存。
3. TTL(生存时间)的智能升级
在 getCacheControl 中,Claude Code 会根据用户身份和 GrowthBook 配置决定缓存时长。
- 5 分钟:默认时长。
- 1 小时:针对高级用户或特定任务源(如
repl_main_thread)。 通过延长 TTL,Claude Code 保证了你在长达一小时的深入开发过程中,不需要反复支付庞大上下文的“入场券”Token 开销。
踩坑指南
Prompt Caching 极其强大,但它不是万能的,以下是你必须了解的物理限制:
- 断点限额:Anthropic API 限制单个请求最多只能有 4 个
cache_control标记。Claude Code 通常极其节省,只在系统提示词和消息末尾使用 1-2 个,以确保最高的命中率。 - 前缀强一致性:缓存是基于前缀匹配的。如果你修改了系统提示词中间的一个字,或者在工具定义里多加了一个空格,该断点之后的所有缓存都会立即失效(即 Cache Break)。
- Token 门槛:Claude Code 的缓存失效监测(
PromptCacheBreakDetection)通常只在 Token 波动超过 2000 时才会发出警告。对于微小的上下文变动,它倾向于保持沉默。 - Microcompact 手术式剔除:Claude Code 内部有一个
CachedMCEdits机制。当上下文太长需要压缩时,它不会直接清空缓存,而是通过cache_reference指向特定的工具输出,进行精准的局部剔除。
接下来看什么
- 阅读
src/services/api/promptCacheBreakDetection.ts:了解 Claude Code 是如何通过哈希算法(djb2 或 Bun.hash)来追踪每一个导致缓存失效的微小因素(如 Beta 头部变化、工具定义修改)。 - 研究
src/services/api/claude.ts中的addCacheBreakpoints:这是操纵cache_control插入位置的最高机密。 - 查阅官方
Prompt Caching文档:理解 API 层面对ephemeral类型的计费差异和性能基准。
源码锚点
claude-code-opensource/src/services/api/claude.ts: 定义了cache_control的生成逻辑(getCacheControl)和消息断点设置。
📄 src/services/api/claude.ts — 定义了 `cache_control` 的生成逻辑(`getCacheControl`)和消息断点设置。
cache_control: getCacheControl({ querySource }),claude-code-opensource/src/services/api/promptCacheBreakDetection.ts: 负责监测并记录缓存命中失败的原因,是性能调试的关键。
📄 src/services/api/promptCacheBreakDetection.ts — 负责监测并记录缓存命中失败的原因,是性能调试的关键。
function getCacheBreakDiffPath(): string {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
let suffix = ''
for (let i = 0; i < 4; i++) {
suffix += chars[Math.floor(Math.random() * chars.length)]
}
return join(getClaudeTempDir(), `cache-break-${suffix}.diff`)
}claude-code-opensource/src/utils/api.ts: 包含splitSysPromptPrefix,揭示了系统提示词如何进行动静分离。
📄 src/utils/api.ts — 包含 `splitSysPromptPrefix`,揭示了系统提示词如何进行动静分离。
export function splitSysPromptPrefix(
systemPrompt: SystemPrompt,
options?: { skipGlobalCacheForSystemPrompt?: boolean },