第 1 章 / 共 11 章
越跑越笨不是错觉
1.1 第 40 轮,它改了一个二十轮前改过的文件
shopfront 是一个多人维护的 Node + React 电商前台,大约 1200 个文件。这天你交给 agent 的任务很清楚:把结算流程从内部 v1 API 迁到 v2,并补齐测试。涉及的地方你心里有数——src/checkout/ 下的几个组件、src/api/client.ts 里的请求封装、tests/checkout/ 下的测试、还有 docs/api-migration.md 里那份迁移说明。
前十轮非常顺。它读了 docs/api-migration.md,找出 v1 和 v2 的字段差异,改掉了 src/checkout/CartSummary.tsx 里的两处调用,跑了一遍类型检查,给出的解释也对。你去泡了杯咖啡。
第 40 轮的时候,你回来看输出。它正在编辑 src/api/client.ts——这个文件它在第 18 轮已经改过一次,改得没问题。而这一次的 diff 里,它把一个已经删掉的 fetchCartV1() 调用又加了回来,理由写得一本正经:「为了兼容旧的购物车接口」。
你没换模型。你没改提示词。你甚至没打断过它。中间发生的唯一一件事,是这个对话变长了。
这不是幻觉,也不是运气差。它是一种可以被命名、被测量、被设计规避的现象。这一章要做的事,是把这个现象讲清楚,并把你对上下文窗口的直觉从「储物间」换成「注意力预算」。之后的每一章都建立在这个转换上。
1.2 三种越用越糟的补救动作
发现 agent 变笨之后,大多数人的第一反应落在三条路上,而这三条路指向的是同一个方向:往里面加更多东西。
第一条:把提示词写得更长更细。 你在 CLAUDE.md 里补上一段「注意:修改 src/api/client.ts 前先确认该文件是否已被本次任务修改过」。下一次它照做了。再下一次它遇到的是别的文件,于是你再补一条。三个月后 CLAUDE.md 变成一份两千行的判例集,每一条都是某次翻车的墓碑。它们常驻在每一轮请求里,占着预算,而其中绝大多数与当前这一轮无关。
第二条:把更多文件贴进去。 「它不知道 v2 的结构,那我把 src/api/ 整个目录贴给它。」这条路的问题不在于信息不够,而在于信息之间开始互相干扰。v1 的类型定义和 v2 的类型定义长得很像,字段名有一半重合。你贴进去的不是「更多知识」,是「更多长得像正确答案的东西」。
第三条:换一个窗口更大的模型。 从 20 万 token 换到 100 万 token,感觉像是买了更大的房子。但更大的窗口只是提高了上限,没有改变「填得越满、质量越差」这条曲线的形状。你只是把翻车的位置从第 40 轮推到了第 70 轮,同时让每一轮都更贵、更慢。
这三条路共享同一个隐含假设:上下文窗口是一个储物间,东西放进去就等于模型知道了。 只要这个假设不换掉,你补的每一条规则、贴的每一个文件,都是在给同一个问题加压。
1.3 窗口是注意力预算,不是储物间
换掉这个假设,从理解 Transformer 怎么处理长输入开始。
注意力机制的工作方式是:对 n 个 token,模型要在它们之间建立 n² 对关系。序列翻倍,关系数变成四倍。模型的参数量是固定的,用来分配这些关系的能力也是固定的。所以序列越长,每一对关系上分到的注意力就越稀薄。这不是工程实现的缺陷,是机制本身的性质。
叠加在上面的第二个事实是训练分布。训练数据里,超长序列本来就少。模型在短序列上见过的模式远比在长序列上多,所以它在接近窗口上限的地方,本来就处在自己不熟悉的区域。
这两件事合起来的可观测后果,有一个名字:上下文腐化(context rot)。Chroma 做过一组受控实验,覆盖 18 个前沿模型,包括 GPT-4.1、Claude 4、Gemini 2.5、Qwen3。结论是:输入越长,模型在简单任务上也会持续掉准。请注意这里的两个词。
一个是「简单任务」。掉准的不是复杂推理,是那些在短上下文里模型闭着眼都能做对的事——比如「找出这段文本里提到的那个函数名」。任务难度没变,变的只是它周围堆了多少东西。
另一个是「持续」。这不是断崖,不是「窗口没满就没事,一满就崩」。它是一条从很早就开始的、连续向下的曲线。你在第 15 轮感觉到的那点「它好像有点跟不上了」,不是你的错觉,是这条曲线上一个真实的点。
腐化的具体机制里,有两个和日常最相关:
中间迷失(lost in the middle):模型对上下文开头和结尾的内容注意得好,中间那一段最容易被稀释。你在第 18 轮说的「fetchCartV1() 已经删了,不要再引用」,到了第 40 轮正好躺在中间。
语义干扰项(distractors):和正确答案语义相似、但实际错误的内容,比完全无关的内容危害大得多。你贴进去的那份 v1 类型定义就是标准的干扰项——它和 v2 长得几乎一样,模型要在两个高度相似的候选里挑,而它的注意力已经被稀释了。
把这两条对回开头的场景:第 40 轮那个把 v1 调用加回来的动作,不是模型「忘了」,是上下文里同时躺着 v1 的定义、v2 的定义、以及一条被埋在中间的删除记录,而模型在稀薄的注意力下选错了。

图 1.1:左边是「储物间」心智模型——东西放进去就算模型知道了,窗口没满就是安全的;右边是「注意力预算」心智模型——每多一个 token,其他所有 token 分到的注意力就少一点,质量曲线从很早就开始下滑。注意右图的下滑是连续的,没有一个「安全线」。
1.4 提示词工程和上下文工程的分界
有了这个心智模型,就能把两件经常被混在一起的事分开。
提示词工程(prompt engineering) 解决的是「怎么把话说对」。它的产物是一段写好的指令,写完就固定下来,主要在一次性的、单轮或少轮的任务里起作用。你调整措辞、加个角色设定、给两个例子,然后收工。
上下文工程(context engineering) 解决的是「这一轮该让模型看见什么」。它的产物不是一段文本,是一套每一轮推理都重新执行的决策:这一轮,系统指令要不要全带上?哪几个工具定义需要在场?上一次 grep 返回的 800 行还要留着吗?三十轮前的探索过程能不能压成三句话?
差别的核心在于:提示词是你写完就不动的东西;上下文是每一轮都在变、都需要你(或你设计的机制)做取舍的东西。在 shopfront 这种跨越几十轮工具调用的任务里,真正决定成败的几乎全是后者。
| 提示词工程 | 上下文工程 | |
|---|---|---|
| 管的是什么 | 一段指令怎么措辞 | 系统指令 + 工具定义 + MCP + 外部数据 + 消息历史的总和 |
| 什么时候做决定 | 写的时候做一次 | 每一轮推理都要重新决定 |
| 典型动作 | 改措辞、加角色、给示例 | 决定这一轮放什么、留什么、压什么、隔离什么 |
| 失效的信号 | 单次回答跑偏、格式不对 | 任务跑长了质量滑坡、重复劳动、自相矛盾 |
在 shopfront 里 | 让它按团队风格写测试 | 让它在第 40 轮还记得第 18 轮改过什么 |
| 产物形态 | 一段文本 | 一套机制(笔记、检索、压实、子代理) |
它们不互斥。上下文工程包含提示词工程——系统指令本身就是上下文的一部分,第 4 章会专门讲怎么把它写在合适的海拔上。但只做提示词工程,你处理不了长任务。
1.5 全书唯一的核心原则
后面十章会讲很多具体机制:任务笔记、渐进式披露、压实、子代理。它们全都是同一条原则的不同落地形态。这条原则值得你现在就记住:
找到能最大化目标结果概率的、最小的一组高信号 token。
拆开看有三个词在承重。
最小的一组——不是「够用就行」,是主动往下砍。每一个留在窗口里的 token 都在稀释其他 token 的注意力,所以「多带一点没坏处」这个直觉是错的,多带一点一定有坏处,只是你不一定当场看见。
高信号——信噪比是判据,不是信息量。一份 800 行的 grep 输出里可能只有 3 行有用,那它的信号密度是 0.4%。把这 800 行换成那 3 行加一句「其余 797 行是测试快照」,token 少了两个数量级,信息没少。
目标结果的概率——判断标准始终是「这能不能让任务做成」,不是「这看起来是不是相关」。相关但不影响决策的内容,是最难删掉的那一类噪声,因为它每一条看上去都有道理。
所以每当你要往上下文里加东西,问题不是「这有用吗」,而是「在挤掉别的东西之后,这还值得吗」。这是一道预算题,不是一道收纳题。
一个顺带的好处:注意力预算和钱是同向的。更少的 token 意味着更低的成本、更快的响应。第 10 章会讲到,在生产环境里这两件事甚至可以差出一个数量级。
1.6 动手:给你上次那场翻车做一次噪声归因
现在把这套东西用在你自己身上。
- 翻车发生在第几轮左右? 不用精确,量级就行——第 5 轮、第 20 轮、还是第 50 轮。这个数字告诉你腐化曲线走到了哪。
- 当时上下文里最可能的三个噪声来源。 具体到东西,不要写「历史太长」。写成这样:「第 12 轮那次
grep -r 'checkout' src/返回的约 400 行,其中真正用上的是 6 行」「CLAUDE.md里那 30 条历史判例,这次任务只碰到其中 2 条」「MCP 订单服务器带进来的 11 个工具定义,整个任务一个都没调用」。 - 有没有语义干扰项? 上下文里有没有两份长得很像、但一份已经作废的东西——旧版接口定义、被替换掉的旧实现、两份互相矛盾的文档。有的话,把它们写出来。
写完这三条,你手里就有了一份粗糙但真实的诊断。第 2 章会给你一张正式的盘点表把它系统化,第 3 章会告诉你每一类噪声该用写、选、压、隔里的哪一个动作去治。
答得上来的标准是,你的回答里要同时出现两件事:一是腐化是连续下滑而不是撞到上限才发生的,所以更大的上限不改变曲线形状,只是把翻车点往后推;二是加窗口治不了信噪比——干扰项和无关内容的比例没变,模型要在更多相似候选里做选择,反而更难。如果你的回答只说了「窗口大了还是会满」,那还差一层,回到 1.3 再读一遍。