上下文工程:不把窗口撑大,而是教模型怎么用草稿纸
上下文窗口扩不动——算力、显存和注意力各有一道硬边界。
但真正决定 Agent 表现的,不是窗口有多大,而是你往里面放什么、什么时候压缩、什么时候扔掉。
这套手艺叫上下文工程。它管的不是容量,是利用率。
一、堆硬件是死胡同
最直接的想法是把窗口做大:加卡、加显存、换更长的模型。
这条路走不通,原因有三层。
第一层是成本。窗口扩十倍,算力大约涨一百倍;显存虽然只线性增长,也会很快撞上硬上限。这两条都不必展开,这里只留结论。
第二层是效果。信息过载会让模型更容易出错,像让一个专家在一万页废纸里找一个线索——不是他能力不行,是信噪比太低。
第三层是方向。堆硬件解决的是"能装下",可任务需要的往往是"找得到"。
第三层最容易被忽略,因为它不体现在账单上,体现在结果里。你花了大价钱把资料全塞进去,模型反而答得更差,还很难解释为什么。
举个示意场景。做一份合同审查,把四十份历史合同一起塞进窗口,希望它"参考先例"。
结果它引用了三年前已经作废的条款版本,因为那份文档在上下文里出现得更靠后、写得更详细。
真正有用的,其实只有当前这一份和两条仍然有效的先例。剩下的三十七份,全是噪声。
窗口是按量计费的桌子,不是仓库。你把仓库里的东西全摊到桌上,桌面只会更乱。
除了钱,还有延迟。上下文越长,模型读完再吐出第一个字的时间越长;交互式场景里,用户感受到的就是"它卡住了"。
多花的钱还能报销,多花的等待时间不能。
有意思的是,人自己也不是靠"装下全部"来做复杂任务的。
人的工作记忆很小,开会时记不住整段发言,但能靠笔记、清单、流程,把一件复杂的事做完。
工程师排查线上问题,靠的也不是记住全部日志,而是按顺序查几个关键指标。
所以"一次装下所有资料"从来不是智能的必要条件,按需查阅才是。
这也是长会话值得投入的原因:把 AI 当可以反复追问的搭档,比每次重新交代一遍背景更省事。
但价值不在"它记住了所有",而在"它记住了该记的"。
问题的重点因此转移:不能扩,那具体能改什么?
二、容量与利用率是两件事
窗口标称的容量,和模型实际能用到的容量,不是一回事。
最典型的现象是中间迷失:同一份材料,关键信息放在开头或结尾,模型用得比较好;放在中段,召回明显下降。
这个现象不是"偶尔看漏",而是有稳定方向的位置效应。
放到具体任务里,就是关键约束的放置问题。
| 约束放置位置 | 结果(示意,非实测) |
|---|---|
| 开头 | 通常被遵守 |
| 中段 | 容易被忽略,或被记成别的值 |
| 结尾 | 通常被遵守,且对最近一次输出影响最大 |
所以"有效容量"比标称容量小。你以为塞进去了,其实只是放进了一个不太会被读到的位置。
位置效应不是玄学,可以实测。把同一条约束分别放在开头、中段、结尾,各跑十来次,看遵守率差多少。
这个测试很便宜,却能直接告诉你这条约束该放在哪。
第二个问题是噪声。上下文里无关内容一多,相关内容的注意力权重会被摊薄。
塞十份文档,不如放一份对的。这不是态度问题,是机制问题。
推论很实用:关键约束要么放在边缘位置,要么在中间和结尾各重复一次;无关材料宁可不放。
第三个问题是重复。同一段内容在历史里出现三次,不只是浪费空间,还会让模型误以为它特别重要。
所以压缩不只是删,也包括去重。
放到具体的提示结构里,位置安排可以很直接。
系统提示和角色设定放开头,当前这一步的指令放结尾,中间放素材。因为结尾离生成最近,模型对它的敏感度最高。
如果一条约束在整段任务里都有效,那它应该在开头出现一次、在临近执行时再出现一次,而不是只出现一次。
一个可以自查的估算方式:把这一轮真正必须遵守的约束列出来,再加上当前步骤需要的素材,看它占标称窗口的多少。
占不到一半,说明还有余量可以放更多相关材料;占到七八成,就该压缩了,而不是继续加。
留余量的另一个理由是输出:输出也要占窗口,塞满了输入,回答就会被截断。
三、压缩:把早期对话炼成决策链
先分清一件事:把上下文压短的技术是一回事,怎么组织内容放进窗口是另一回事。前者关心"能不能更短",后者关心"该留什么"。
历史总会撑爆窗口,第一个手段是压缩。
压缩的目标不是变短,是保留决策链。
一份好的摘要至少要有五样东西:谁在做事、要达成什么、已经决定了什么、否掉了什么以及为什么、还差什么。
示意如下,这不是某个产品的真实摘要格式:
任务:把一篇初稿写到 4800 字
已定:按大纲分节写;不写未核实的基准数字
已否:不用"先解释 Harness 定义"的开场,太慢
待办:补一张取舍表
约束:标签最多 3 个,发布前查真实 ID
五行动,顶得上十轮对话。因为它留下的是"为什么",不是"说过什么"。
反例是直接截断。把前面几轮删掉,模型就不知道"方案 B 为什么被否",过一会儿又会把方案 B 提出来。
这不是它笨,是你把理由一起删了。
人的记忆也是这么工作的。人按"事件边界"切分经历:一次会议、一次争吵、一次上线,边界处记得清,中间流水账记不住。
这叫事件分割。压缩做的事,本质上就是模拟事件边界。
所以好的压缩点应该选在"一件事干完了"的位置,而不是按字数平均切。
平均切会把一件事切成两半,前后各留半句,模型反而更困惑。这个细节比压缩算法本身更影响效果。
但压缩有代价,得说清楚。
摘要要额外花一次模型调用;摘要会失真,写摘要的模型也可能漏掉关键约束。
所以摘要里最好留原文指针:哪份文件、第几节。需要细节时能取回,而不是只剩一句概括。
还有一个顺序问题:先压缩再继续做,还是等快满了再压缩。
我倾向于留出余量,在窗口用到七成左右就开始压缩。等到真的满了再压,通常已经没有空间放摘要本身了。
压缩由谁来做,也值得想一下。
交给模型自己总结,方便,但它可能顺着自己的错误往下写,把错的当成已定的结论。
用固定的模板加规则来生成,稳定,但覆盖不全,容易把细节压成一句空话。
我的做法是两者结合:先用模板抽取硬信息(任务、已决、待办、约束),再让模型补一句"为什么"。硬信息可核查,理由可以模糊。
压完还要检查。抽三个问题问自己:这条摘要能不能让一个新人接着干?关键约束在不在?被否掉的方案有没有写原因?
三个都答得上来,才算压住了。
四、卸载与隔离:挪走不用的,挡住无关的
有些东西压缩也压不下去,比如一份必须逐字遵守的规范、一段不能改的代码。
那就不要在窗口里硬塞,把它挪出去。
卸载的载体很朴素:进度文件、结构化文档、代码仓库的提交历史、外部存储。
需要的时候按指针取回。取回的是原文,不是摘要,所以不会失真。
进度文件的价值在"接手":换一个会话、换一个人、隔一天再来,都能从同一份状态继续。
所以它要写的是状态,不是过程。写"第 3 批已完成、第 4 批失败、原因是超时",比写"今天做了很多工作"有用得多。
卸载的另一个常见对象是"索引"。整份 API 文档不必进窗口,进窗口的是函数名和一句话说明;需要细节时再取正文。
这和一个工程师查文档的顺序一样:先看目录,再翻到那一页。
隔离解决的是另一个问题:不该进来的,别让它进来。
输入前就筛,只注入与当前步骤最相关的片段。写第三节时,只放第三节的素材,不要把整份大纲和全部访谈记录一起灌进去。
筛选可以分两级:文件级先筛掉明显无关的目录,片段级再按段落挑出相关的那几段。
只做文件级,往往还是塞不下;只做片段级,又会把上下文切得太碎,模型看不出因果关系。
检索可以作为手段之一,但怎么建索引、怎么召回是另一个问题,这里只说它的代价。
检索不是免费的:多一次查询就多一段延迟,多一批候选就多一份噪声。每轮都检索,通常是在浪费窗口和钱。
还有一个更隐蔽的代价:检索结果之间可能互相矛盾。两段来自不同版本的文档同时进来,模型不会告诉你它看到了冲突。
判断条件可以写得很直白:
与当前步骤无关,挡掉;超出预算,摘要;暂时用不到,移出。
三条各管一段,不互相替代。压缩不能代替隔离——把无关内容压短,它还是无关;隔离也不能代替卸载——挡掉的东西如果以后要用,就得有地方存。
五、分层记忆:近期、中期、长期
三种手段组织起来,就是一套分层记忆。
近期靠窗口:当前任务正在处理的细节,保证连贯。
中期靠摘要:任务主线、已定结论、约束条件,压缩成可续接的几行。
长期靠外部档案:用户偏好、项目背景、跨会话的事实,放在窗口之外,用的时候取。
三层各有自己的失效方式,这一点常被忽略。
近期层的失效是溢出:一超限就截断,关键信息直接消失。
中期层的失效是失真:摘要写偏了,后面几轮全跟着偏。
长期层的失效是过期:三年前的项目约束,今天可能已经不适用,取回来反而是干扰。
反例是把所有东西都塞进窗口:看起来"什么都记得",实际上没有分层,也没有优先级。
窗口满了之后,最先被挤掉的往往是最该留的那条约束。
分层之后还有一个好处:每层可以用不同的策略更新。近期每轮都变,中期每几轮重写一次,长期按事件更新。
用一个节奏去管三种时效的信息,本身就是浪费。
三层的读写规则也可以定得很具体。
近期只读当前任务的状态,写完就随窗口消失;中期在阶段性节点重写,节点由任务结构决定,不由字数决定;长期只在确认有价值时写入,并记录来源和日期。
长期层最怕的是"什么都存"。存进去的每一条都要在检索时被比较,条目越多,命中率反而越低。
所以长期记忆需要清理机制:过期的事实标日期,被推翻的结论标状态,不再使用的偏好直接删。
跨会话恢复可以做成一套固定动作:先读长期档案里的项目背景,再读中期摘要里的当前主线,最后加载当前步骤的素材。
顺序固定下来,恢复过程就不依赖某个人的记忆,也方便排查"它为什么一上来就搞错了方向"。
六、认知仿生:AI 在模仿人的哪三件事
这套做法不是新发明,它更像是在模仿人处理长任务的方式。
第一件是工作记忆的容量限制。
人的工作记忆很小,具体能同时装几个信息块,学界一直有争议,几种经典说法之间也有出入。所以这里只讲结论:它是有限的。
正因为有限,人才需要把复杂任务拆成段,一段一段做。
第二件是记笔记。
人不会把所有事都记在脑子里,而是记在本子上,需要时翻。这就是卸载:外部存储加上按需调入。
笔记还有一个隐含作用:它逼你把模糊的东西写成明确的句子。写不清楚,说明还没想清楚。
第三件是选择性注意。
人会自动抑制干扰,把注意力放在与当前目标相关的信息上。对应到上下文工程,就是隔离与筛选。
还有一层更细的机制:门控检索。
不是每一轮都去查资料,而是在模型对下一个词不确定、或者任务明确需要外部事实时才触发检索。这样检索成本和噪声都被压到最低。
触发条件可以写得比较具体:问题里出现了需要最新数据的关键词、模型对某个实体没有把握、当前步骤明确要求引用来源。
这三条都符合,检索才是必要的;都不符合,检索只是习惯。
不过类比的边界也要说清。人脑不是按"窗口"和"token"运转的,类比只是帮我们理解为什么要压缩、要卸载、要过滤。
把它当成设计原则可以,当成实现方案就会走偏。
比如人的遗忘有生理机制,AI 的摘要没有等价的机制,它只是把原文换成了别人写的概述。所以"像人一样遗忘"在工程上要靠规则保证,不会自动发生。
这三件事合起来,说明一个反直觉的结论:会遗忘不是缺陷,是设计。
一个什么都记得的上下文,和一个知道该记什么的上下文,后者通常更好用。
上下文窗口决定了模型一次能想多少,上下文工程决定了它在这点空间里能想出什么。
空间省出来了,但还有个更根本的问题:知识到底该临时塞进窗口,还是永久写进参数?回答它,要从 RAG、Skill、微调三条路里挑。
图注:内容进出窗口要过三道闸门;近期、中期、长期三层记忆对应三种粒度。