生成式软件工程
搜索
开始阅读/第 1 部分 · 机器开始会干活/第 4 章

上下文工程

模型的记性像一张课桌,能摊开的纸是有限的。这一章讲怎么摆这几张纸。

这一页讲什么
  • 上下文为什么是有限资源,聪明区与笨区分别指什么;
  • 上下文里该放四类东西:目标、约束、证据、示例,各自怎么给;
  • 把大任务拆成能检查的小步,每一步留下一份书面产出物;
  • 压缩与摘要的取舍,什么时候该主动开一个新会话;
  • 怎么判断一次改动到底变好了还是变坏了。

一、上下文是有限资源

context window(上下文窗口)指模型一次请求能看到的全部文本:系统提示、历史对话、工具返回的结果、你这一句问题,都以 token 计算。token 是模型处理文本的最小单位,一个英文单词大致是一个多 token,一个汉字大致一到两个 token。

关键判断在这里:哪怕模型号称支持很长的窗口,真正能高质量利用的只有前面一段。把窗口画成两段,前一段叫聪明区,后一段叫笨区,分界线大约在窗口的 ½ 到 ¾ 之间。

flowchart TB
  A["系统提示"] --> B["项目说明文件"]
  B --> C["本任务相关的上下文"]
  C --> L["分界线 大约在窗口的 ½ 到 ¾ 处"]
  L --> D["笨区:忘记约束 重复劳动 抓错重点"]

进入笨区之后会发生三件很具体的事:忘记早先定下的约束,例如「不要改对外接口」;重复做已经做完的事;在大量无关的工具输出里抓错重点。

这些现象并不限于某一款模型。2023 年的一项研究发现,关键信息被放在长上下文的中间位置时,模型的利用率最差,开头和结尾最好,这个现象叫中间迷失(lost in the middle)。2025 年的一项研究测试了多款主流模型,发现随着输入变长,即便是简单任务,表现也会持续下降,这个现象叫上下文腐烂。还有一种更激进的经验值来自工程团队:把上下文利用率保持在 40% 到 60% 之间。

那什么东西最占地方?按经验从多到少大致是四类:工具输出,例如一次打印一个大文件、一次完整的测试日志;搜索结果,例如关键词搜索返回的大量匹配;反复的读、改、测循环,每一轮都叠加新的改动和日志;外部工具的定义,接了很多工具服务器时,光是说明书就能吃掉上万 token。

生活类比 寝室那张书桌,摊得开的东西是有限的。左边放课本、右边放笔记本、中间放手写的那张草稿,找什么都一眼看到。 如果桌上堆了三十本书、五个外卖盒、两件没叠的衣服,你要找一支笔都得翻半天。桌子的面积没变小,能干活的部分变小了。

二、上下文里该放什么:目标、约束、证据、示例

既然位置有限,就要挑。挑法可以归成四类,缺哪一类,产出就会在对应的方向上跑偏。

第一类是目标,要做成什么。目标要写到能被检验的程度。「优化一下性能」算不上目标,「让首页在十万条数据时三秒内打开」才算目标,因为它自带验收方式。这一条看起来最简单,实际最常出问题:你自己没想清楚要什么,模型只能替你猜。

第二类是约束,不许碰什么。约束分两种。技术约束例如用哪个已有组件、不能引入新的第三方库、必须兼容现在的数据格式。边界约束例如哪些文件不许动、哪些接口的返回结构不能改。约束要写清楚,因为模型不知道你上周刚被一个线上故障教育过。计划模式的提示词里有一条值得直接抄:动手之前先搜索项目里已有的函数、工具和写法,不要重复造轮子。这条属于约束,写进去就能省掉大量重复代码。

第三类是证据,现在的实际情况。证据指真实的文件路径、行号、报错原文、测试结果、数据结构。让模型引用文件路径和行号有个额外好处:它没法轻易编造,因为路径和行号你可以立刻核对。这也是减少凭空捏造最便宜的手段。

第四类是示例,照着这个样子写。给一个已经写好的例子,比写十句形容词管用。「参照 src/components/UserCard.tsx 的写法新建一个组件」这句话,同时交代了目录位置、命名习惯、组件风格、样式方案。

关键结论 这四类的顺序有讲究。目标在最前,因为目标错了后面全错;约束紧跟,因为它决定哪些方案根本不用讨论; 证据和示例放在后面,它们的作用是替模型省掉猜测和探索。

三、把大任务拆成能检查的小步

一个功能太大,单个上下文装不下;一个任务太糊,做完之后没法验证。拆解的目标是让每一步同时满足两个条件:一个会话能做完,有明确的验证方式。

把这件事拆成五个阶段,每个阶段留一份文件,是最省心的做法。下面这张表把每个阶段产出什么、里面写什么、人在这里做什么列全了。

阶段产出物里面写什么人在这里做什么
调研research.md相关文件路径与行号、数据从哪来到哪去、现有组件、测试怎么跑读一遍,确认它没有认错地方
提方案proposal.md两到三种做法的具体步骤、改动范围、风险、推荐哪种以及理由由你来选方案
定计划plan.md分阶段的带勾选清单,每步写明改哪个文件、改什么、怎么验证这一步做对了仔细审计划
实现代码与进度记录一次只做一个阶段,做完立刻跑该阶段的验证看验证结果
测试测试结果自动化测试、类型检查、格式检查,界面类改动要真的打开看一眼判断失败的原因在哪一层

调研阶段有一个最容易犯的错:让模型一边调研一边改代码。调研只描述现状,不提出修改,也不动手。这条规矩写进要求里,能省掉后面大量的返工。

为什么前面的步骤更值钱?有一句话把杠杆说清了:代码里的一行错误,就是一行错误的代码;计划里的一行错误,可能导致上百行错误代码;调研里的一行错误,比如误解了某个功能在代码库里的位置、误解了数据的组织方式,可能导致上千行错误代码。

flowchart TB
  R["调研:错一行 可能上千行坏代码"] --> P["计划:错一行 可能上百行坏代码"]
  P --> C["代码:错一行 就是一行坏代码"]

所以人的注意力应该压在上面两层:认真读调研、认真审计划,代码那一层更多交给自动化检查。这也是计划模式值得单独占一层外壳的原因。

生活类比 做课程设计。开题报告写错方向,最后交上去的是一整套跑偏的东西,推倒重来要一个月。 中期检查发现方案不对,还有时间换,损失两周。答辩前一天发现某个函数写错了,改那一行只要两分钟。 越早的那一行字,代价越大。

四、压缩与摘要:把八十页笔记整理成三页提纲

任务做长了,历史一定会堆满。压缩的做法是把冗长的历史交给模型,让它提炼成一份短摘要,然后用新的窗口从摘要继续干活。

压缩该有多贵、值不值,有一份真实使用数据的统计值得看一眼。一位作者追踪了自己 162 天里的 2451 个智能体会话、287748 次接口调用、873 次压缩,并人工审计了 201 个步骤。

第一组数字关于成本增长速度。上下文第一次达到 10 万 token 时,累计消耗的中位数是 140 万 token;达到 20 万时是 900 万;达到 80 万时是 3.38 亿。窗口只大了 8 倍,累计消耗变成 250 倍。拟合出来的规律是上下文每翻一倍,成本大约变成 6 倍。原因是智能体在 50 万上下文时做 25 次工具调用,付出的相当于 25 次 50 万,总量还在随时间增长。缓存能降低单价,改变不了这个增长形状。

第二组数字关于窗口大了之后有多少内容真的有用。上下文小于 5 万时,窗口里实际被用到的 token 占 47.5%;上下文大于 60 万时,这个比例降到 2.5%。窗口扩大了 16.6 倍,有用的部分几乎没变,都在 8000 token 左右。另外只有 14% 的步骤真正需要旧上下文的原文,而且集中在「必须逐字复制某个字符串」这类场景;如果依赖的是决策、约束、结论,一份摘要就够。

第三组数字是压缩的代价。压缩之后的 5 步内,用户消息里带纠正的比例达到 41.9%,而平时是 17.7%,约为 2.37 倍;这种升高会持续至少 30 步,到 120 步仍未完全恢复。工具调用的报错率没有变化,说明调用本身没错,变差的是背后的判断。原因很直接:一次压缩的中位数是把 574875 个 token 压成 4382 个 token 的摘要,只剩不到 1%。

这三组数字推出一个反直觉的结论:不要过早压缩。过早压缩的代价比过晚更大,因为你会长时间待在压缩后的迷雾里。可以用的经验有三条:低于约 20 万 token 时压缩过于频繁;高于约 60 万 token 时付出的溢价找不到对应收益,拿不准的时候宁可晚一点压缩;比调阈值更有效的一招是压缩时保留最近 3 万到 6 万 token 的原文尾巴,仅靠一份四千字的摘要撑不住。

生活类比 一节课上到一半换教室,你只带走一张便签:课上讲到第三章、作业周五交、下节课带计算器。新教室里你接着听,没问题。 但如果便签上漏了「周五交」,你在新教室会觉得一切正常,直到周五才发现作业已经过期。压缩就是这张便签,写漏一条,后果在很久之后才显现。

五、什么时候该开一个新会话

与其等工具自动压缩,不如自己挑时机主动压缩。这个做法叫有意的压缩:在合适的节点把进度写成一份文件,然后开一个新会话从文件继续。

时机很好判断,出现下面任何一种情况就该动手:

一份好的进度文件包含五块,可以直接照抄成模板:任务目标是什么;已完成的部分,包括哪个接口能用、哪个测试通过了;关键发现与决策,例如数据存在哪张表、为什么选了方案 B;下一步要做什么,写到能直接执行的程度;注意事项,例如哪些接口的返回结构不能动、移动端还在用。

这套做法背后有一条原则:每完成一项,把工作记录成书面产出物,它就成为继续下一项的上下文。文件在硬盘上,不占窗口;等到需要时再读进来,读多少由你决定。

常见坑:把「开新会话」当成「重新开始」 新会话不带旧记忆,如果开之前没先把进度写下来,你会从零解释一遍,模型的判断力也跟着回到起点。 开新会话之前先写文件,这个顺序不能反。

六、怎么判断一次改动变好了

改上下文最常见的误区是凭感觉:这次回答看起来顺眼,就认为改好了。感受有用,但不足以支撑判断,因为同一套写法在不同任务上的表现可以完全相反。

可操作的方法有五条,都不需要任何工具。

  1. 固定一组任务,别只试一个。挑五到十个你真实做过的任务,每次改动都拿同一组重跑。一个任务上的改善可能只是巧合。
  2. 看可数的指标,不看印象。例如:要返工几次、要补几轮追问、第一次给出的方案能不能直接用、改动涉及几个文件、检查有没有通过。这些都能数。
  3. 把失败分门别类。是理解错了目标、漏掉了约束、找错了文件,还是猜错了数据格式。分类之后你会发现问题大多集中在同一处,改那处比改整段要求有效。
  4. 一次只改一处。同时改三处,改好了也不知道是哪一处的功劳,改坏了也不知道该退回哪一处。
  5. 记进实验日志。任务、我怎么问的、结果哪里好哪里不对、下次改什么。三个月后回头看,能看出自己的哪类问法一直在踩坑。

还有一套按任务难度选打法的分类,判断标准很简单:你在脑子里能不能完整想清楚要改哪几行?能,就直接说;不能,但改动只落在少数几个熟悉的文件里,先让它写计划;再复杂一点,走一次调研、计划、分阶段实现;跨多个代码仓库的大改动,拆成多个子问题,每个子问题各走一遍。用重型流程处理小事是浪费时间,用一句话处理大事是等着返工。

关键结论 判断一次改动好不好,靠一组固定的任务和几个能数的指标,靠一次对话的印象得不出结论。 改一处、测一轮、记一笔,这套动作花的时间比反复重写要求少得多。

七、任务描述的五段写法与常见误用

把前面几节收成一个可以照着念的顺序:先在纸上写清目标与验收,再收集证据与示例,然后按阶段拆开,接着开始实现,最后用检查收尾。前三步都在动手之前,它们的产出都是文字,都占硬盘而不占窗口。

常见的三种误用也能对应上。

三种误用背后是同一个问题:把上下文当成了越满越好。占满一个窗口的信息量,远大于模型在这个窗口里能真正用上的信息量。挑出此刻做决定真正用得上的那几样,剩下的放到硬盘上,需要时再取。

现在就做 十分钟,做一次可对比的上下文实验。
① 打开第 1 章建的「实验日志.txt」,新起一段;
② 挑一个你这两天要做的真实小任务,先用一句话直接问 AI,把回答原样保存下来;
③ 同一件事再问一遍,这次把四类内容补齐:目标写成可检验的一句话、约束写两条、证据给出具体的文件名或报错原文、示例给一个「照着这个写」的参考;
④ 对比两次结果,在日志里记下三件事:哪一次需要你补充追问、哪一次可以直接用、你补的哪一条信息最关键。
全程不需要安装任何东西,用平时那个聊天窗口就能完成。

自测

1. 什么是上下文窗口里的笨区?它大概从哪里开始?
窗口靠后那一段,模型的表现明显变差,分界线大约画在窗口的 ½ 到 ¾ 之间。进入之后会出现忘记早先的约束、重复做已经做过的事、在大量无关输出里抓错重点这三种情况。
2. 上下文里该放哪四类内容?各举一个自己场景里的例子。
目标、约束、证据、示例。目标是可检验的一句话,例如「班级名单导入后三秒内出结果」;约束例如「不能改动现有的表格格式」;证据例如具体的文件名、报错原文、数据结构;示例例如「照着上次那份活动通知的格式写」。
3. 为什么说审计划比审代码更有杠杆?
因为错误的放大倍数不同:代码里的一行错误就是一行错误代码,计划里的一行错误可能导致上百行错误代码,调研里的一行错误可能让你得到上千行错误代码。所以人的注意力应该压在调研和计划这两层,代码层更多交给自动化检查。
4. 那份真实数据统计里,压缩之后会发生什么?给出的缓解办法是什么?
压缩后 5 步内用户消息里带纠正的比例从 17.7% 升到 41.9%,约 2.37 倍,持续至少 30 步,到 120 步仍未完全恢复;工具调用报错率没变,变差的是背后的判断。缓解办法是不要过早压缩,低于约 20 万 token 时压缩过于频繁;压缩时保留最近 3 万到 6 万 token 的原文尾巴,仅靠一份四千字的摘要撑不住。
5. 判断一次改动是否有效,你会怎么设计对比?
固定一组五到十个真实任务,每次改动都用同一组重跑;看可数指标,例如返工次数、需要补充追问的轮数、改动涉及的文件数、检查是否通过;把失败分类,找到问题集中的那一处;一次只改一处,这样结果才能归因;把每次结果记进实验日志,积累成自己的经验。