Claude Code 学习站
Mingyu's Library

学习站 / 系统课程 / D2

D2 · 上下文窗口与 prompt caching

搞懂上下文窗口怎么构成、装满时发生什么,以及 prompt caching 为什么决定了你每一轮交互的速度和费用。

每发一条消息,Claude Code 都会把整个会话从头到尾重发一遍。本课讲清这件事为什么可行:窗口的三层结构、接近上限时的自动压缩(compaction),以及让「重发」变便宜的前缀缓存机制——最后落到你随时能自查的 /context/usage

为什么这天学这个

D1 讲了 agentic loop,以及用 /context 能看到上下文里都装了什么。D2 接着回答三个问题:这个窗口有多大、装满了会发生什么、为什么每轮都重发全部内容却不至于又慢又贵。答案分别对应本课的三个机制:窗口上限、compaction 和 prompt caching。

这课是后面几课的地基:D6 会话管理/clear/compact/rewind 的取舍,全部建立在「什么会打掉缓存、什么能活过压缩」之上;实战 Tip D 簇(缓存失效额度去哪了长会话变笨)是本课机制的问题式速查。跳过这课,你会在「为什么切个模型突然变慢了」「为什么改了 CLAUDE.md 没反应」这类问题上反复踩坑。

概念讲清楚

三层结构,每轮全量重发

官方模型在两次请求之间不记得任何东西。所以你每发一条消息,Claude Code 都发起一次全新的 API 请求,重发完整上下文:系统提示、项目上下文、之前的每一条消息和工具结果,再加上你的新消息。新内容永远追加在末尾,因此每次请求的绝大部分和上一次是一模一样的。为了配合缓存,Claude Code 把请求内容按「变化频率」排序,越少变的越靠前:

内容什么时候变
系统提示层核心指令、工具定义、output style加载的工具集变化,或升级 Claude Code
项目上下文层CLAUDE.md、auto memory、无 paths 的 rules会话开始,或 /clear/compact 之后
对话层你的消息、Claude 的回复、工具结果每一轮
系统提示层 核心指令 · 内置工具定义 · output style 项目上下文层 CLAUDE.md · auto memory · 无 paths 的 rules 对话层(每轮增长) 你的消息 · Claude 的回复 · 工具结果 (文件读取的内容通常占大头) 几乎不变:升级 Claude Code 或工具集变化时才变 会话开始时读入;/clear、 /compact 后从磁盘重新加载 每一轮都在增长,是占用 与费用的主要来源 接近上限 → 自动压缩(auto-compact)触发区 上下文上限(常见 200K token,部分模型可达 1M)
一次 API 请求里的上下文,自上而下就是请求里的先后顺序:越少变的内容越靠前,这是缓存能命中的前提。

官方关于上限:官方交互演示以 200K token 为示意上限;Fable 5、Sonnet 5、Opus 4.6 及之后的模型和 Sonnet 4.6 支持 1M token 窗口(按套餐提供,一般需选 [1m] 模型变体,Sonnet 5 则直接以 1M 运行)。用 /context 随时查看当前会话各类内容的真实占用。

自动压缩:什么时候发生,什么能活下来

官方窗口装满不会终结会话:接近上限时 Claude Code 自动做 compaction——用一份结构化摘要替换掉整段对话历史。你也可以主动出手:/compact 聚焦某话题 手动压缩并指定保留重点;/autocompact 500k(或启动参数 --autocompact、环境变量 CLAUDE_CODE_AUTO_COMPACT_WINDOW)把触发线提前,可设 100K 到 1M。没有另行设置时,默认到模型上限才压;云端会话和部分以 200K 窗口运行的模型会提前触发。

官方压缩后各类内容的命运不同:系统提示与 output style 不变(它们不在消息历史里);项目根 CLAUDE.md、无 paths 的 rules 和 auto memory 从磁盘重新注入;带 paths: 的 rules 和子目录 CLAUDE.md 会丢失,直到 Claude 再次读到匹配文件;已调用的 skill 正文重新注入,但单个截断到 5,000 token、总计 25,000,最旧的先丢。本站观点所以「必须永远在场」的规则应放项目根 CLAUDE.md(D4 展开),别放路径规则里。

prompt caching:按前缀精确匹配

官方API 把每次请求的开头部分(前缀)与最近处理过的内容做精确匹配:命中的部分直接复用,按缓存读取价计费——约为标准输入价的 10%;只有末尾新增的部分按全价处理。匹配是逐字精确的,前缀里任何一处变化,其后的所有内容都要重算;不存在按文件或按片段的缓存。此外,模型和 effort 等级各有独立缓存(它们是缓存 key 的一部分),切换任何一个都等于从零重建。

全价处理 从缓存读取(约为标准输入价 10%) 第 1 轮 全部首次处理并写入缓存 第 2 轮 前缀命中,从缓存读 新增 只有尾部按全价处理 第 3 轮 前缀命中,从缓存读 新增 会话越长,缓存省得越多 第 4 轮前:系统提示层变了(如升级、工具集变化)或切换了模型 第 4 轮 前缀不再匹配 → 整段全价重算 一次性变慢、变贵 缓存按前缀精确匹配:靠前任何一处变化,其后全部重算
正常轮次里,前一轮的整个请求就是这一轮的前缀,只有最新一问一答是新内容;前缀一变,缓存全部作废。

本站观点换个讲法:把会话想成一摞不能抽页的稿纸,阅卷人读过的页数会记住,每次只续读新页;但只要中间某页改了一个字,他就得从那页开始把后面全部重读一遍——这就是「前缀」的含义,也解释了为什么改动越靠前代价越大。

官方缓存有有效期(TTL):每次命中都会重置计时,持续干活就一直是热的。订阅用户自动使用一小时 TTL(超限改用 usage credits 时降为五分钟);API key 或云厂商默认五分钟,可用 ENABLE_PROMPT_CACHING_1H=1 开启一小时。缓存在 Claude Code 里的作用域约等于「一台机器 + 一个目录」,同一仓库的不同 worktree 目录各建各的缓存。

什么会打掉缓存,什么不会

官方失效动作的共性是「改了前缀或缓存 key」,保住缓存的共性是「只往对话末尾追加」:

动作缓存说明
/model 切模型全部失效每个模型独立缓存;opusplan 进出 plan mode 也算切模型
/effort 调力度全部失效缓存按 effort 分 key,会话中改会弹确认
开启 fast mode全部失效一次性成本,越早开越便宜;关掉再开不再触发
MCP server 连接/断开看情况工具定义被延迟加载(默认)则保住;载入前缀则失效(D9)
deny 整个内置工具全部失效仅裸工具名(如 Bash);Bash(rm *) 这类范围规则不影响
/compact对话层失效系统提示层复用;CLAUDE.md 没改过则项目层仍命中
升级 Claude Code全部失效升级后 resume 长会话,第一轮可能是最贵的一次请求
编辑仓库文件保住只追加一条 <system-reminder>,不回改历史
会话中改 CLAUDE.md保住但改动也不生效,要等 /clear/compact 或重启
切权限模式、调 skill、/recap保住都是往对话末尾追加内容
/rewind保住截断回一个已缓存的前缀,比 compaction 便宜
派生 subagent保住子代理自建缓存,父会话前缀不动(D10)

误区澄清

  • 「改了 CLAUDE.md 马上生效」——实际它在会话开始时读一次、存在内存里,会话中的编辑既不打缓存、也不生效。判断方法:/clear/compact 或重启后再观察。官方
  • 「切模型 / 切 effort 是免费的」——内容一个字没变也要整段重算,下一轮明显变慢变贵。判断方法:切换后看 /usage 里 cache write 是否突增。官方
  • 「缓存是按文件缓存的」——只有前缀整体匹配这一种机制,没有按文件、按片段的缓存;文件内容只在被读取时进入对话层。官方

机制怎么落到速度和账单上

官方三条最常见的「费用怎么来的」都能用本课机制解释:其一,长会话里问一句「这个函数叫什么」,消耗远大于这一句本身——因为每轮都携带全部历史,整卷按缓存价重读;其二,离开超过 TTL 后回来,第一条消息缓存全失,整卷按全价重算,所以「休息后的第一轮」明显慢;其三,/compact 本身要把待摘要的整段对话读一遍,趁缓存还热时做便宜得多,而想彻底换任务时 /clear 一分钱不花。/usage 会在 cache miss、长上下文等行为占近期用量 10% 以上时给出提示。

官方建议会话开头就定好模型和 effort,/compact 留到任务间隙的自然断点再用——会话中途改得越少,缓存命中率越高。官方

当天能做完的实操

  1. 启动 claude,先不干活,直接运行 /context。预期:对话层几乎为零,但系统提示、工具定义、CLAUDE.md、memory 已经占了不少——这就是 D1 说的「你还没说话,窗口已经不空了」。
  2. 把下面的 prompt 原样发给 Claude,让它读一个大文件,然后再运行 /context。预期:对话层(Messages)明显变大,涨幅主要来自文件内容。
  3. 运行 /usage,找到按模型分列的那一行。预期:cache read 远大于 input——说明大部分上下文在按约 1 折的缓存价重读。
  4. 故意打一次缓存:/model 换一个模型,随便问一句「继续」,再看 /usage。预期:cache write 突增,因为整段会话在新模型下重写了缓存。(这一步真实消耗额度,在小会话里做。)
  5. 运行 /compact 只保留与刚才那个文件相关的结论。预期:出现 Conversation compacted 提示;再看 /context,对话层缩回一小段摘要。
帮我做一个上下文实验:
1. 挑本项目里较大的一个源码文件读一遍(自己挑,不用问我);
2. 用两三句话总结它;
3. 估计这次读取大约往上下文里增加了多少内容。
之后我会用 /context 和 /usage 核对,请把总结控制在五行以内。

过程中会反复用到的命令:

/context    # 当前上下文各类内容的占用
/usage      # 本会话 token 用量与 cache read / cache write
/model      # 切换模型(本课用它演示缓存失效)
/compact 只保留与 X 相关的结论   # 带焦点的手动压缩

验收标准

  • 能不看笔记说出上下文的三层结构,以及每层分别在什么时候变化。
  • 能各举出三个「会打掉缓存」和「不会打掉缓存」的动作,并用「前缀 / 缓存 key」解释原因。
  • 能解释为什么会话中修改项目根 CLAUDE.md 不生效、什么时候才生效。
  • 实操跑通:能在 /usage 里指认 cache read 与 cache write,并解释 /model 之后 cache write 为什么突增。
  • 自测题:一个开了一整天的会话里,你只问了一句「这个函数叫什么」,为什么消耗远大于这句话本身?(参考答案:每轮请求都携带全部历史,整卷按缓存价重读;若离开时间超过缓存 TTL,还会整卷按全价重算。)