上下文管理
Agent 上下文的选择、压缩与组织。
每次模型上下文窗口从 128K 涨到 1M,我做的第一件事都不是往里塞更多东西,而是重新算一遍:这次任务真正需要被模型「看到」的到底是什么。
答案通常比我以为的要少一个数量级。
塞满上下文是最容易的失败方式
上下文窗口是一个上限,不是目标。我见过太多这样的写法:把整个代码库附上,把全部历史对话拼进去,把十几篇文档接在一起,然后问一句「帮我修一下这个 bug」。
输出质量不会随输入线性上升,它会在某个点之后断崖下跌。原因并不神秘:注意力是有限的预算,塞得越多,每一份被认真读到的比例就越低。
更麻烦的是信噪比。一段 2000 行的代码里真正出问题的是第 3 行,但你把它和另外 1999 行平等地放进了上下文。模型要自己在噪声里定位信号,而这个定位过程本身就在消耗它读其他部分的注意力。
上下文的价值不取决于它有多少,取决于里面有多少是模型非读不可的。
真正的做法:先做减法,再做加法
我现在的工作流是反过来的。
第一步,先明确结束条件。 这次任务的「完成」长什么样?如果我说不出来,那不管塞多少上下文都是错的——模型会朝着一个模糊的方向堆砌看起来合理的东西。
第二步,只放决策必需的原始材料。 不是整个文件,而是出问题的那几个函数加上它的直接调用方;不是全部文档,而是和这个 bug 直接相关的那两段。
第三步,把判断结果固化下来,而不是保留推理过程。 这是压缩幅度最大的一次。如果探索阶段跑了 20 次工具调用,其中 18 次是走弯路,我不需要保留这 18 次的完整输出,我需要的是「已确认 X」这个结论,加上支撑它的证据。
// 不要这样:把思考过程和结论一起塞回去
messages.push(...allToolCalls);
messages.push(userMessage);
// 这样:结论 + 证据
messages.push({
role: "assistant",
content: `已确认根因:${rootCause}\n关键证据:${evidence.join("\n")}`,
});
上下文预算是一种工程纪律
我把它类比成团队的时间预算。你当然想让工程师了解所有背景,但如果你把他八小时里七小时花在读上下文上,剩一小时写代码,他交付的东西一定不好。
所以我现在会在 prompt 里显式写出边界:
- 必读:出问题的那几个函数、相关的类型定义
- 不用读:其他目录、构建产物、历史实现
- 已经确认:前面几轮已经排除的可能
这三条边界,比多给 50K token 有用得多。
什么时候反而该给更多
不是永远做减法。有两种情况反而需要主动加。
跨文件的隐式约束。 比如一个全局的错误处理约定,代码里没有注释,但三十个文件都遵守它。这时把约定显式写出来,比让模型从三十个文件里归纳要可靠得多。
需要判断品味的情况。 比如「这个交互设计对不对」,把几个「对」的参照实现一起给它,比讲十条原则有用。设计判断靠样例,不靠描述。
这两种情况有个共同点:你给的每一样东西都是经过筛选的,不是「整个项目」。
一个可以马上用的自检
在发送一个复杂的 Agent 任务前,问自己三个问题:
- 如果我是那个接手的人,我需要读什么才能做完?
- 我给的东西里,有多少是我已经知道「可以不看」的?
- 我能说清楚任务完成的判据吗?
三个都能答上来,任务大概就能跑。答不上来的那个,通常才是真正的问题所在。
上下文工程的核心不是「给模型更多」,是替模型做决定。它本质上是在替一个非常聪明但没有上下文的人,整理一份刚好够用的材料。