为什么上下文工程如此重要?
LLM 有一个固定大小的上下文窗口(Claude 当前最大 1M token)。而一次真实的编码会话,可能涉及几十次文件读取、数百次工具调用,产生的原始文本量轻松超过百万 token——很容易逼近甚至超出上下文窗口的容量。
这意味着系统必须做出艰难的取舍:哪些信息留在上下文中,哪些被压缩或丢弃。如果取舍不当,模型会忘记刚才编辑了哪个文件、重复读取已经看过的内容、或者产生与之前决策矛盾的输出。
可以把上下文窗口想象成一张办公桌:桌面有限,你必须把最重要的文档放在手边,其他的归档到抽屉里。上下文工程就是这套"文档管理系统"——决定桌上放什么(上下文构建)、什么时候把旧文档收进抽屉(压缩)、以及如何让归档的文档在需要时快速取回(持久化与恢复)。
但上下文工程面临的挑战不止于此。Claude Code 的每次 API 请求,光系统提示词和工具定义就可能有 50-100K token。为了避免每次都从零处理这些内容,Claude Code 依赖服务端的前缀缓存(Prefix Caching / KV Cache)——服务端记住之前处理过的前缀,后续请求只处理新增部分,省去重复消化整段前缀的延迟和成本。
但前缀缓存有一个残酷的约束:前缀必须字节级完全一致才能命中缓存。不是"差不多就行",而是任何一个字节的变化——哪怕只是换了一个请求头、改了一个工具的顺序——都会导致整个前缀的缓存失效,50-100K token 全部需要重新处理。
这给上下文工程带来了一种"带着镣铐跳舞"的感觉:你不能随意调整提示词顺序,不能随意增删工具定义,不能中途改变请求元数据……每一个设计决策都必须同时满足两个目标——给模型最好的上下文,同时不打破缓存。本章中你会反复看到这种张力:很多看起来"过度设计"的机制,背后的驱动力都是缓存稳定性。
Claude Code 在这方面的工程量远超大多数人的预期。本章将深入分析它的完整上下文管理体系。
关键文件:src/context.ts(190 行)、src/utils/api.ts、src/services/compact/