上下文工程:把「塞得越多越好」这句话拆开

About 19 min read

Anthropic 把它定义成有限预算的分配问题;Chroma 用 18 个模型、194,480 次调用测出:信息怎么摆放,比信息在不在更重要。

Contents · 9 sections · jump to a heading

2025 年 7 月 14 日,Chroma 发了一份叫《Context Rot》的技术报告。他们测了 18 个模型,跑了 194,480 次 LLM 调用,只为回答一个问题:输入变长的时候,模型到底是怎么退化的。

2025 年 9 月 29 日,Anthropic 发了一篇《Effective context engineering for AI agents》。文章里有一句对上下文工程的定义:它是在 LLM 推理期间,策展并维护那组最优 token 的策略,包含 prompt 之外所有可能落进上下文里的信息。

两篇放在一起看,指向同一件事:上下文是一个预算,不是一条管道。

这篇文章想回答三个可以被验证的问题:

  1. 上下文变长时,模型具体怎么退化?
  2. 哪些内容该预加载,哪些该等到运行时再取?
  3. 压缩、笔记、子智能体这三种长任务手段,各自的代价是什么?

我做不到什么

  • 我没有复现 Chroma 的实验。 下面每一个数字都是他们发布的,我只做了把它们排在一起这件事。我自己加的判断会明确标出来。
  • 我没有自己的 token 账单可以贴。 所以本文不出现「省了百分之多少」这类数字,因为没有测量就没有数字。
  • 我读的是官方文档和论文原文,不是二手解读。 每一节末尾都有链接,你可以自己去核对措辞。
  • 最后那套「做得好不好」的判断标准是我自己的。 它不是行业标准,只是我用下来觉得可操作的一组线。

退化不是断崖,是斜坡

先看结论。Chroma 报告里最反直觉的一句是:

模型并不会均匀地使用它们的上下文;输入越长,性能越不可靠。

他们把这句话落到了具体数字上。在 LongMemEval 这组对话式问答里,完整 prompt 平均约 113k tokens,聚焦后的 prompt 平均约 300 tokens。所有模型在聚焦版本上的表现都显著更高,而且即使开启 thinking 模式,两种输入长度之间的差距依然存在。

Anthropic 那篇给出了机制层面的解释,用的是他们自己的措辞:

上下文必须被当作一种有限资源,边际收益递减。LLM 有一个「注意力预算」,每引入一个新 token 都会消耗掉一部分。

预算这个词不是修辞。Transformer 的注意力机制让每个 token 都能注意到上下文里的其他所有 token,n 个 token 就有 n² 组两两关系。序列变长,这组关系的捕捉会被摊薄——他们用的说法是「上下文长度与注意力聚焦之间产生了天然的张力」。

这两段合起来解释了一个常见误解:长上下文窗口不是把短上下文的问题解决掉了,是把问题从「装不下」换成了「装得下但读不准」。 100 万 token 的窗口能让你把整个仓库塞进去,装进去之后模型对第 10 万个 token 的检索精度不如第 100 个。

三个可验证的退化机制

Chroma 那 194,480 次调用测出了三个机制,每一个都能单独复现。

第一,干扰项会叠加。 报告里,单个干扰项就能让性能相对基线下降,四个干扰项进一步叠加退化。而且干扰项的影响不均匀——同一组实验里,第 3 个干扰项造成的下降比其他几个都大。失败分析显示,第 2 和第 3 个干扰项在模型的幻觉回答里出现得最频繁。

这条对写 prompt 的直接含义:你往上下文里加的每一段「参考信息」,都可能是一个干扰项。 加之前要问的是「没有它模型会做错吗」,不是「有它会不会更好」。

第二,模型家族之间的差异是量级上的。 同一组带干扰项的任务里,Claude 系列的幻觉率最低,Sonnet 4 和 Opus 4 尤其保守,不确定时倾向直接说找不到答案;GPT 系列在存在干扰项时更容易给出自信的错误回答。

这条的含义不是「用哪个模型」,而是同一份上下文在不同模型上的可靠性不同。换基模的时候,上下文里那些「差不多够用」的部分需要重新验一遍。

第三,也是我最没想到的一条:结构连贯的草堆会持续伤害性能。 报告的原话是,在全部 18 个模型和所有针-草堆配置下,模型在打乱顺序的草堆上的表现都优于逻辑结构化的草堆。

这条结论要先划边界再往下用。它测的是「在一大段文本里找一根针」这个动作,不是「让模型理解一份结构化文档」。它说明的是:当上下文长到模型只能部分利用时,语义上的连贯性会掩盖位置信号。 打乱之后,针和周围的差异反而更明显。

用它的时候别越界。如果你的上下文只有两三千 token,结构化的收益是正的,因为那时候不存在「部分利用」这个前提。

预加载还是运行时取

把上面三条合起来,能推出一个决策规则:只预加载那些「没有它模型连问题都理解不了」的内容,其余全部改成运行时取。

Anthropic 那篇把这两种做法分开讲了。预加载是嵌入检索那一套,在推理前把相关数据取好塞进去。运行时取的做法是另一条:

采用「just in time」策略的 Agent 不预先处理所有相关数据,而是维护轻量标识符(文件路径、已存的查询、网页链接等),在运行时用工具把这些数据动态加载进上下文。

他们举的例子是 Claude Code:模型可以自己写定向查询、存结果,用 head 和 tail 这类 Bash 命令分析大量数据,而不必把完整的数据对象加载进上下文。

代价在这句话的同一段里写着:运行时探索比取预计算结果慢。 而且没有指引的话,Agent 会浪费上下文去误用工具、追死胡同、找不到关键信息。

所以真实系统用的是混合策略。Claude Code 自己的做法是:CLAUDE.md 这种文件在启动时直接放进上下文,而 glob 和 grep 这类原语让它按需导航取文件。Anthropic 对这套混合策略的总结是一句很短的话:做最简单那件能跑通的事。

判断哪些内容该预加载,我按三类分:

类别该预加载吗理由
流程规范(分支策略、测试铁律、提交契约)是每轮都需要,且体量小,放前面还能吃到 prompt 缓存
环境基准(运行时版本、包管理器、环境变量模板)是一行一条,缺了就会产出「本地能跑、线上暴毙」的代码
代码上下文(目录结构、工具库索引、数据结构)部分目录树和关键 schema 预加载;具体实现文件运行时取
具体业务数据(订单、日志、代码文件正文)否体量大、每轮不同,预加载等于给每一轮都塞一遍噪音

这张表里最常被做错的是最后一行。把整个代码库的正文灌进上下文,等于让模型在 113k tokens 里找那 300 tokens 真正有用的东西,也就是上面那组 LongMemEval 数字里表现更差的那一边。

长任务的三件工具,和各自的代价

任务长度超过上下文窗口时,Anthropic 给了三种手段。三种都能用,也都有账单。

压缩(compaction)。 做法是对话快到窗口上限时,让模型把内容总结成一份摘要,用摘要开一个新的上下文窗口。Claude Code 的实现里,摘要保留架构决策、未解决的 bug 和实现细节,丢掉冗余的工具输出,然后带着最近访问的 5 个文件继续。

代价写在同一个段落里:过度压缩会丢掉那些「重要性后来才显现」的细节。 调参建议是先最大化召回,确保压缩提示不漏掉关键信息,再逐步提升精度、删掉多余内容。他们提到的风险最低的一种压缩形式是「工具结果清理」——只丢掉工具吐出的原始文本,不动对话本身。

结构化笔记。 让 Agent 把笔记写到上下文窗口之外,需要时再读回来。Claude 玩 Pokémon 的例子把这件事量化了:Agent 能跨数千个游戏步骤维持精确计数,比如记下「过去 1,234 步我都在 1 号道路练级,皮卡丘涨了 8 级,目标是 10 级」,上下文重置之后读回自己的笔记继续。这条的成本最低,因为它只是写文件,但它的收益取决于你的任务有没有清晰的里程碑。

子智能体。 主 Agent 拿高层计划,子 Agent 各自带干净的上下文去深挖。Anthropic 给的数字是:每个子 Agent 可能消耗数万 token 甚至更多,但只返回一份压缩后的摘要,通常 1,000 到 2,000 tokens。

代价是最明显的一类:信息在返回时就已经丢失了。 子 Agent 找到但没有写进摘要的那部分,主 Agent 永远不知道它存在过。用这套结构的前提是你相信「摘要里没有的细节本来就不重要」——在探索类任务里这个前提经常不成立。

把规则写成文件:静态前缀这一层

前面讲的是怎么取、怎么丢。这一层讲的是最死的那部分:那些每一轮都必须出现、永远不变的内容,放哪里。

答案是把它们放进一个文件,然后每次都放在请求的最前面。原因有两条,第二条比第一条重要。

第一条是注意力:系统提示词的位置是注意力最集中的位置之一。Anthropic 对系统提示词的措辞是找一个「正确的海拔」,在两种失败之间取平衡——把脆弱复杂的逻辑硬编码进去会让它变脆,写得太笼统又给不出具体信号。

第二条是成本:前缀稳定,缓存才能命中。 把静态内容钉死在请求最前端、后面只做单向追加,缓存读取的价格通常是首次计算的一小部分。

我这台机器上的 craft-garden 仓库就是这么组织的。根目录有 AGENTS.md,里面是技术栈、目录结构和发布流程;CLAUDE.md 只写「先读 AGENTS.md,再读这两条规则」;.cursor/rules/ 下面按语言和主题拆成五个 .mdc。最上面那层只做索引,不重复内容。

CLAUDE.md

1
# CLAUDE.md
2
3
先读 `AGENTS.md`(技术栈、目录结构、部署方式),再读下面这两条规则,写代码前必看:
4
5
- `.cursor/rules/tokens.mdc` — 设计令牌、Stitches styled、玻璃拟态写法
6
- `.cursor/rules/react.mdc` — 组件约定、MDX widget、motion 动画

这个结构的代价要说清楚:它要求人维护。 规则文件不会自己知道 package.json 里换了图表库,也不会知道发布脚本换了入口。三个月不更新的 AGENTS.md 比没有 AGENTS.md 更糟,因为它会以权威的语气给出过期的指令,而模型会照做。

怎么判断做得好不好

标准是三条线,从低到高。

及格线:不再产出语法正确但违反项目约定的代码。 这条可以靠读 diff 判断,不需要测量。

良好线:每轮会话的 token 增长是平缓的线性,不是加速的二次方。 这条可以测量——把每轮请求的 prompt token 数打出来,看曲线。如果第 20 轮的增量比第 5 轮大很多,说明历史没有被折叠,工具输出还在滚雪球。

优秀线:换掉底层基模,Agent 仍然按同一套约束稳定交付。 这条是三条里唯一真正的检验。上下文工程做得好,意味着约束写在文件和工具契约里,而不是写在某个模型特有的提示技巧里。Chroma 那份报告里不同模型家族在干扰项下的差异,正好说明依赖模型特性的提示技巧有多脆。

三条线里,我只稳定做到第一条。第二条我打过日志,但没有做成持续监控。第三条我没测过。这不是谦虚,是这三条的可测量性依次下降,而第三条需要你至少跑过一次基模切换的对照。

收尾

这两篇文章里的模型清单和数据都会过期。Chroma 测的那批模型已经不算最新,Anthropic 那篇顶部的编者注也提示了工具生态的变化。

但有一件事现在就能确定:上下文工程的度量单位是信噪比,不是长度。 从 113k tokens 到 300 tokens 的那组对比,测的不是「多一点还是少一点」,测的是「哪些 token 在承担决策」。这个判断不随模型版本变化,因为它描述的是信息本身。

剩下的距离,全在写上下文的那个人这一侧。

参考来源

Share this note on Twitter / X · Browse all articles · Feedback via @qianyuhe.

Thanks for reading.

– 千羽鹤