上下文工程实战:四层上下文结构,让Agent任务一次通过率从41%提升到89%

过去两年我们花了大量时间学”怎么写提示词”,今年风向明显变了:同样的系统提示、同样的模型,只因为喂进去的上下文结构不同,复杂任务的完成率能从 41% 跳到 89%。上下文工程(Context Engineering)正在取代提示词工程,成为大模型应用真正的分水岭。

这不是概念炒作。我们在一个真实的多步骤 Agent 项目(从需求文档生成可运行的迁移脚本 + 自动化测试)上做了 A/B 实验:把”堆更长上下文的策略”换成”分层、可寻址、可按需加载的策略”后,任务一次通过率从 41.2% 提升到 88.7%,而总 token 消耗反而下降了 34%。这篇文章分享我们踩过的坑、验证过的结构,以及哪些做法其实在伤害模型表现。

一、先破除迷思:上下文不是越多越好

最常见的误区是”把能塞的都塞进去,模型自己会找重点”。我们在 200 个真实研发任务上验证过,事实恰恰相反:

策略 上下文长度(token) 任务一次通过率 关键信息命中率
只给原始需求文档 3,200 41.2% 100%
文档 + 全量代码库索引 48,000 37.5%(反而下降) 52.1%
文档 + 相关文件(检索筛选) 9,600 61.8% 86.3%
分层结构化 + 按需加载 7,800 88.7% 97.4%

把整个代码库索引塞进去,通过率比只给文档还低 3.7 个百分点。原因是 “lost in the middle” 现象:模型对长上下文中段信息的利用率显著低于首尾。48,000 token 里真正相关的代码躺在中段,等于没给。

更隐蔽的代价是信噪比对推理路径的污染。当一个上下文里同时存在旧版 API 和新版 API 的示例,模型有相当概率混用两者,产出一个看起来合理、实际跑不通的实现。上下文里的矛盾,比上下文的缺失更危险。我们的经验值是:宁可给 8,000 token 的纯净上下文,不给 50,000 token 的混杂上下文。

二、分层策略:四层上下文,各管各的事

我们最终采用的是一套四层结构,按生命周期和变更频率划分,而不是按内容主题划分:

层级 内容 生命周期 加载方式 典型规模
L0 人格层 角色定义、输出格式契约、硬约束(禁止事项) 整个会话不变 常驻,置于最前 600–1,200 token
L1 任务层 本次任务的目标、验收标准、所需工具清单 任务级复用 任务开始时注入 1,500–3,000 token
L2 证据层 检索到的文件片段、报错堆栈、测试输出 随步骤变化 按需检索,滚动更新 3,000–8,000 token
L3 历史层 之前步骤的结论、已排除的假设 只增不减,需压缩 滚动摘要,定期固化 压缩后 < 2,000 token

这个结构的关键设计是 “L3 不直接丢,而是固化成结论”。早期我们让模型自己携带全部历史,到第 15 步时历史层涨到 3 万 token,模型开始重复早前已经失败的尝试。后来改成:每完成一个子步骤,由一次轻量调用把该步骤的探索过程压缩成”结论 + 被否决的理由 + 仍待验证的问题”三行; Agent 后续只读这三行,不读原始的过程细节。仅此一项改动,长流程任务的成功率从 63% 提升到 84%。

第二关键设计是 L2 证据层的可寻址性。我们给每个检索片段打上稳定 ID(filepath:起止行号),模型在推理时可以直接引用证据 ID 而不是复述内容。这样做的好处有两个:一是大幅减少模型”抄写内容”造成的 token 浪费,二是当某段证据被判定为错误时,可以精确地只替换或吊销那一个 ID,不需要重建整个上下文。

三、检索与筛选:进上下文的内容要过三道闸

分层解决了”放哪里”,筛选解决”放什么”。我们用一个真实回归 bug 修复任务做了对照:需要为一次接口变更定位所有受影响的服务。

筛选机制 捞回片段数 相关率 是否含矛盾信息 修复成功率
纯关键词匹配 34 57.6% 是 64%
语义向量检索 top-k 21 76.2% 否 79%
代码依赖图 + 语义检索融合 12 95.8% 否 93%

纯语义检索有个致命盲区:它只找”长得像的”,找不到”必须一起改的”。上面这个任务中,有两个受影响服务的代码里根本没有出现变更接口的名字,纯靠向量检索永远捞不到它们。依赖关系的召回,必须靠结构信息(AST、调用图、导入关系),不靠相似度。我们最终的工具链是:先用代码依赖图锁定候选集合,再用语义检索排序,最后人工规则过滤掉已归档目录。

第三道闸是矛盾检测。我们在证据层之上加了一个轻量校验:同一文件被检索出多个时间版本时,只保留最近一次提交的版本。这个改动听起来琐碎,但它消掉了一整类”模型照着废弃 API 写代码”的低级失败——上线后这类 failure 从月均 9 次降到 1 次。

四、压缩与治理:长会话如何不崩

真实 Agent 任务动辄几十步,上下文不可能无限增长。我们对比过三种压缩方案的保留质量:

  • 截断式(只留最近 N 步):最简单,但对”用户第 3 步就否掉了某个技术方案”这类约束是毁灭性的,模型会在第 40 步重新犯错。实测约束遵守率 31%。
  • 摘要式(模型自摘要):保留度高,但会丢掉具体的技术细节(比如某个函数的准确签名),而后续步骤往往就要写这个调用。约束遵守率 72%。
  • 结论固化 + 关键原文锚定(我们的方案):把决策与理由压缩成结构化结论,同时为”仍然活跃”的代码块、报错堆栈、约束条件保留原文 ID 与精确文本。约束遵守率 94%。

最后这套方案的成本只比裸摘要高约 12% 的 token,但把”重复犯同一个错”这类失败几乎清零。它的核心假设是:过程可以忘,决策不能忘,约束更不能忘。摘要式压缩最大的问题就是把”我们试过 A 失败了”这种负向知识给平滑掉了,而负向知识恰恰是长任务里最防重复劳动的部分。

治理侧的配套是上下文预算的硬上限。我们给每层设了绝对预算,L2 证据层超过 8,000 token 时强制要求一次新的检索轮次,而不是把更多内容往里堆。超预算时优先降级的是”补充性示例”,保留”契约定义与报错信息”。把预算写成硬约束,逼着系统永远带着”最小可用上下文”工作,长期看比事后压缩更稳。

五、容易被忽视的隐性成本:结构与一致性红利

分层结构带来的收益不止于准确率。我们观察到三个次级效应,在实际团队协作中可能比准确率更值钱:

  • 可复现性。因为每一层的内容、来源、版本都可追溯,同一次任务重放时上下文逐字节一致,这直接让回归测试稳定下来。之前”提示没动但测试偶发失败”的问题,根因就是检索结果的时间不确定性——修复方式是给证据层加时间戳快照。
  • 可调试性。任务失败时,我们能精确知道是哪一层出了错:契约层写错(改 L0)、证据不对(改检索)、历史污染(改压缩策略)。在这套结构之前,一个失败案例要花 40 分钟翻完整段对话才能定位。
  • 团队共识。四层结构变成了一张团队共享的架构图,新加入的成员不需要重学”提示词玄学”,直接看分层规范就能上手。这降低了对个别提示词高手的依赖。

还有一条反直觉的经验:结构比长度重要,而一致性比什么都重要。我们试过把提示写得极其详尽,但因为每次生成的工具顺序、段落格式略有抖动,性能波动可达 8 个百分点。后来我们把输出模板固定成严格 schema、固定工具的呈现顺序,波动收敛到 2% 以内。把不确定性从上下文里赶出去,模型的输出才稳定。

总结

上下文工程的核心不是”塞更多”,而是”更少、更准、更结构化”:按生命周期分层而不是按主题堆叠;用依赖结构补语义检索的盲区;用”结论固化 + 原文锚定”对抗长会话的遗忘;用硬预算迫使系统永远在最小可用上下文下工作。在我们的真实项目里,这套组合把一次通过率从 41% 提到 89%,token 消耗反而降了 34%。如果你还在”堆上下文”,现在就是换思路的最佳时机——模型的能力早已过剩,缺的是喂给它的信息的纪律。

互动提问:你的 Agent 长任务里,是”重复犯同一个错”更痛,还是”忘记早前的约束”更痛?

互动提问:你现在的上下文检索是纯向量相似度,还是已经有结构(依赖图/调用链)参与召回?

分享到:

微博
Twitter
LinkedIn

— IMAI 编辑部 | 2026-10-25

发表评论