速成课 · No. 26

LLM 既聪明又空白:两次调用之间它什么都不记得,而每次调用它只知道你摆到它面前的文本。Context engineering 就是把恰到好处的文本组装进那个窗口的功夫——指令、事实、历史、工具,不多也不少。LLM 的质量真正活在这里,而它大多无关乎巧妙的措辞。

只讲精髓 · 每个想法一个画面 · 工程胜于魔法

§ 01

这门课的一切都源自关于语言模型如何工作的一个事实:它没有记忆,任何一次调用中它的全部觉知就是你发给它的文本。理解了这点,其余都是显而易见的。

模型在两次调用之间什么都不记得

一位才华横溢却彻底失忆的顾问——每次会面,你都得把每份文件重新递给他一遍,因为他对上次的事毫无记忆。

语言模型是 stateless 的:它在两次请求之间什么都不保留。聊天机器人仿佛记得你对话的那种错觉,只不过是每次都把整段历史重新发送一遍而已。模型本身在每次调用时都从头开始,只知道此刻摆在它面前的东西。所以「模型知道什么?」从来无关乎模型——它完全取决于你这次选择放进去了什么。

context window 是它唯一的工作记忆

一个工人只能依据钉在他面前白板上的便签行事——把白板擦干净,就仿佛什么都没发生过。

context window 是模型在一次调用中看到的那块文本——你的指令、对话、任何数据、工具。它是模型的全部工作记忆,而且大小有限。如果一个事实、一条规则或一条过往消息不在窗口里,模型就根本不知道它。模型能推理的一切,都是从窗口出发去推理的——这就使得窗口成为你真正在做工程的东西。

所以质量由你放进去的东西决定

一位天才只依据你递给他的那一个文件夹作答——给他对的文件夹,答案就精彩;给他错的,他就自信地一无是处。

因为模型只从窗口出发工作,它输出的质量大多在模型运行之前就已决定——取决于你组装了什么。同一个模型给出绝妙答案还是无可救药的答案,完全取决于对的 context 是否摆在了它面前。这重新定义了整件事:你不是在从模型里哄出智能,你是在精选它能看到的东西。 那份精选就是 context engineering。

模型是 stateless 的,只知道它的 context window。输出质量由你组装进那个窗口的东西决定——所以你做工程的对象是窗口,而不是模型。

§ 02

人们谈论「prompt engineering」,但随着应用变得严肃,真正的功夫转移到了别处——从措辞一个请求,转向围绕它组装对的信息。这一转变正是这门课的核心。

措辞请求 与 组装 context

律师取胜靠的不是巧妙的措辞,而是把恰到好处的文件、按正确的顺序、不掺无关之物地递给法官——案情在卷宗里,而不在陈词里。

prompt engineering 关乎你如何措辞那条指令。context engineering 是更大的活儿:把模型所需的所有信息——相关的事实、历史、示例、工具——组装进每次调用的窗口。随着应用成长,措辞越来越不重要,而组装越来越重要。大多数「AI 给了个糟糕答案」的时刻并非措辞不当;而是模型缺了它从未被给到的 context。

大多数质量问题都是 context 问题

一位助手给出了错误答案,只因你忘了提那个改变了一切的唯一约束——他不蠢,只是没被告知。

当一个 LLM 功能表现不佳时,人的本能是去微调 prompt 的措辞或责怪模型。可真正的根源远更常是 context 缺失或错误:相关文档没被检索到、一条关键规则没被纳入、陈旧的历史把它搞糊涂了。在你重新措辞任何东西之前,先问模型实际面前有什么。修好 context 所修复的 bug,比修措辞所能修复的多得多。

模型再好也好不过它的输入

最顶尖的厨师也只能用台面上的食材下厨——给错了食材,手艺也救不了这道菜。

一个能力强但 context 差的模型,会输给一个能力弱但 context 出色的模型。这就是为什么 context engineering 而非选模型,通常是最值得投入精力、杠杆最高的地方:同一个模型的输出会随你喂给它的东西剧烈摆动。你不是靠更用力地要求来得到更好的答案——你是靠把更好、更锋利、更相关的材料放进窗口来得到它。输入才是那根杠杆。

Prompt engineering 是措辞;context engineering 是组装对的信息。质量活在后者——而大多数糟糕答案是 context 缺失,不是措辞不当。

§ 03

进入窗口的东西有些是持久的,有些是每次请求各不相同的,而少数几样具体的配料干了大部分的活。知道它们是什么,会让组装 context 这件事变得具体。

system prompt 设定恒常的行为

餐厅的恒常规定——「我们只做素食,十点打烊」——对比今晚的具体点单。一个为一切定框架;另一个是当下的诉求。

system prompt 装着贯穿整场交互的持久指令:模型的角色、它必须遵守的规则、语气、输出格式。user message 则是那个具体的请求。把你的人设、护栏和恒常规则放进 system prompt,能在用户的问题千变万化时让它们保持稳定。这是模型在每次调用中是谁与它这一次被要求做什么之间的区别。

展示,而不只是讲述:few-shot 示例

教人一种格式,展示三个做好的范例,比用文字描述、再指望他们脑补对,要更快也更清楚。

通常,得到你想要的输出形态最可靠的办法,是在 context 里直接放进几个输入与理想输出的示例——这叫 few-shot prompting。模型对示例做模式匹配,远胜于它遵循一段抽象的描述。两三个锋利的示例能赛过一整段指令,而当模型几乎做到了你想要的却差那么一点时,它们往往是最快的修法。

从各个部件组装窗口

为一场会议打理公文包:议程、两份相关的报告、一张约束条件的便条——都是有意挑选的,而不是把整个文件柜倒进去。

一个真实的 context window 是每次调用从各个部件组装起来的:system prompt、相关的历史、检索到的事实(RAG)、示例、可用的工具,最后才是用户的请求。Context engineering 就是——刻意地、每一次——决定哪些部件进去、哪些留在外面。把窗口看作你从部件搭建出来的东西,而不只是你敲进去的一段 prompt,正是让其余一切豁然开朗的那个思维转变。

System prompt 设定持久的行为;user message 是请求;few-shot 示例展示你想要的形态。窗口由这些部件组装而成,刻意地,每一次。

§ 04

Context engineering 中新手最大的误区,是以为越多越好。事实恰恰相反:你加进去的每一个无关 token,都在实实在在地把答案变糟。

更多的 context 不等于更好的 context

一份只有一页、锋利精炼的简报,胜过一份 200 页的倾倒——读者找得到信号而不是淹没其中,决策更快也更好。

把一切可能相关的东西「以防万一」塞进窗口,很有诱惑力。但更多并不更好——通常更糟。每个 token 都在争夺模型的注意力,而无关的材料会稀释信号、把模型拉向附近的任何东西,并使它更可能抓错对象。功夫在于挑出那少数要紧的东西,而不是把一切可能相关的都放进去。

噪音抬高给出错误答案的概率

把那唯一相关的事实藏进一座几乎全是无关纸张的大山里,连细心的读者都会开始引用错误的那一页——是噪音本身造成了错误。

冗长、注水的 context 不只是浪费空间;它实实在在地拉低质量。被埋进无关材料里,模型的注意力会摊薄,而在长 context 中,这会可测量地抬高幻觉和出错的比率——它自信地用了某样本不该是答案的东西。「以防万一」加进去的 context,可能正是导致失败的那样东西。更少、更锋利的 context 才是更可靠的 context。

为这一次调用狠心地精选

好的编辑不做加法——他们做删减,只把那些配得上这一页的句子递给你。

这门功夫是狠心的精选:为这一次具体的调用,放进模型真正需要的那少数几样,把其余一切都留在外面。这意味着只检索最相关的片段、把历史修剪到要紧之处、丢掉已经完成使命的 context。相关性胜过完整性是那条统御性的原则——你不是想给模型一切,你是想给它恰好够用的东西。

更多的 context 并不更好——无关的 token 会稀释注意力、抬高幻觉。狠心地精选:给模型这一次调用恰好需要的东西,别的一概不给。

§ 05

除了质量,窗口还是一种有限的、要付费的资源。把它当作一份稀缺的预算、而非一个无底的倾倒场,正是玩具与产品的分水岭。

窗口是有限的,每个 token 都有成本

一只有重量上限、按公斤收费的行李箱——你只装真正用得上的,而不是整个衣柜,因为有硬性上限,也有价钱。

context window 有一个最大尺寸,其中每一个 token 都耗费延迟和金钱——更大的 context 在每一次调用上都更慢也更贵。所以 context 不是供你填满的免费空间;它是一份你要花的预算。一个在每次请求上都塞满窗口的功能,在规模上既慢又贵,甚至在质量受损之前就已如此。窗口是一种要去分配的资源,而不是一个要去填满的虚空。

把它花在配得上其位置的东西上

一笔紧凑的旅行预算逼你做真正的取舍——你把钱花在对这趟行程要紧的事上、跳过其余,而正因这份纪律,行程反而更好。

把窗口当作预算,会改变你的搭建方式:你修剪对话历史、把旧的回合总结起来而不是逐字带着走、只检索此刻相关的东西而不是你手头的一切。每个 token 都该挣得它的位置。这与做性能优化或缓存是同一种纪律——刻意地花掉那稀缺的资源,花在真正能撬动结果的东西上。

更大的窗口并不终结这份纪律

租一辆更大的卡车,并不意味着你就该往里塞垃圾——更大的容量是用来装更多有用东西的余地,而不是停止仔细打包的许可证。

模型不断推出更大的 context window——一百万 token,甚至更多——于是人很容易以为预算问题就此消失。它并没有。更大的窗口在每个 token 上仍然更贵也更慢,而关键是,更满的窗口仍然稀释注意力、招致错误。更大的容量不是倾倒的许可;它只是更多你仍得明智花掉的余地。精选窗口的这份纪律,熬得过任何尺寸的增长。

窗口是有限的,每个 token 都耗费延迟和金钱。把它当作预算来花——修剪、总结、只检索所需——别让更大的窗口终结这份纪律。

§ 06

在漫长的、多步骤的交互里,context 不是静静待着的——它会累积也会腐坏。管好这份腐坏,是规模化 context engineering 中最难也最要紧的部分。

长 context 会累积陈旧、矛盾的状态

沿着长长一排传话——到末了,消息已悄悄变了样,而每个人都自信地复述着略有出入的东西。

在一场漫长的对话或一项多步骤的 agent 任务里,窗口会被历史填满:更早的回合、工具输出、做了一半的推理。久而久之这堆东西变得杂乱——陈旧的事实挨着新鲜的、矛盾不断累积、最初的目标渐渐滑出焦点。业界把由此产生的质量腐坏称为 context rot:随着模型自身累积的 context 越来越嘈杂,同一个模型变得越来越不可靠。

边走边压缩

好的记录者不会把一场长会的每个字都记下来——他们把它提炼成决定和悬而未决的问题,带着走的是结论,而不是逐字记录。

修法是在 context 增长时主动地压缩它:把已完成的步骤总结进一份简短的运行状态,而不是把完整的逐字记录一路拖着走;一旦从工具输出里提取出要紧的,就把它丢掉;保留一份紧凑的工作摘要而非原始历史。模型带着的是结论,而不是产生它的每一个字。长跑的 agent 就是这样保持连贯的——它们管理自己的 context,而不是任它越堆越高。

重新锚定目标

在一段漫长曲折的徒步中,你会定期重新核对地图和目的地——否则你就会漂移,一个看似合理的转弯接一个,渐渐偏离你本想去的地方。

经过许多步骤,最初的目标会在此后发生的一切的重压下被侵蚀。所以你要重新锚定它:在每一步都把目标和关键约束在窗口里重述一遍,好让模型始终对准真正的靶子,而不是随对话漂走。更大的窗口让这件事更糟,而非更好——它给漂移留下了更多累积的余地。让一项长任务不脱轨的,是主动的管理,而不是原始的容量。

长 context 会腐坏——陈旧、矛盾的状态不断累积,质量随之衰退。把已完成的步骤压缩进一份运行摘要,并重新锚定目标,因为更大的窗口只会给漂移留下更多余地。

§ 07

做得好的 context engineering 是刻意的组装,并像对待其他一切那样去度量。整套实践归结为:有意地搭建窗口,并核查它是否帮上了忙。

有意地搭建窗口,而非碰巧搭成

厨师有意地为一道菜摆盘——每个元素都有理由地放置——而不是把台面上随便什么刮到盘子里。

核心习惯是把窗口当作你刻意搭建的东西:决定 system prompt 装什么、保留还是总结哪些历史、检索什么、展示哪些示例、暴露哪些工具。许多 LLM 应用是碰巧攒出 context 的——手边凑巧有什么就发什么。有意地搭建它、让每个部件都说得出理由,正是可靠功能与不稳功能之间大部分的差别所在。

像度量代码那样度量 context

你不会去猜一处改动有没有帮上忙——你会去测它。你放进窗口的东西也一样:一个答案变好了还是变糟了,你应当知道是哪一种。

Context 的改动是实实在在的改动,所以要验证它们:当你调整进入窗口的东西——更多示例、不同的检索、修剪过的历史——用 evals 去核查效果,别只凭肉眼看一个输出。没有度量的 context engineering 是凭感觉调参,而一处「无害」的添加正是这样悄悄拉低质量的。把窗口的内容当作一样你要去测的东西,与那门 evals 课同一种纪律。

在你上线一个 LLM 功能之前
  • 模型所需的一切是否都在窗口里——还是你在指望它知道你从未给过的东西? - system 还是 user——持久的规则在 system prompt 里,请求在 user message 里? - 示例会不会有帮助——这是不是 few-shot 胜过更多指令的情形? - 每一块是否都相关——还是注水正在稀释注意力、抬高错误? - 你是否 在预算之内——是在修剪和总结,而非把一切都倒进去? - 对于长任务,你是否在压缩并重新锚定以对抗 context rot?
你如今掌握的词
  • stateless / context window——模型什么都不记得;窗口是它唯一的工作记忆。 - prompt engineering / context engineering——措辞请求,对比组装信息。 - system prompt / user message——持久的恒常行为,对比那个具体的请求。 - few-shot——靠在 context 里展示示例来教会输出的 形态。 - 相关性胜过完整性——精选那少数要紧的东西,而非把一切都塞进去。 - context budget——窗口 是有限的,每个 token 都耗费延迟和金钱。 - context rot / 漂移——长 context 会累积陈旧状态并腐坏;压 缩并重新锚定。
你善于做 context engineering 的迹象
  • 你修糟糕答案时,先核查窗口里有什么,再去重新措辞 prompt。 - 持久的规则活在 system prompt 里;当形态要紧时你会伸手去拿 few-shot 示例。 - 你狠心地精选——相关性胜过完整性——而不是给窗口 注水。 - 你把窗口当作一份预算,在修剪和总结,而非倾倒。 - 对于长任务你会压缩并重新锚定,而且你 会度量 context 的改动,而非靠猜。

Context engineering 是对窗口的刻意组装:对的规则、对的事实、对的示例,精选到相关、保持在预算内、并被管理以对抗腐坏——有意搭建并经过度量,而非敲进去再听天由命。

速成课终 · 7 章 · 工程胜于魔法

接下来是实践:拿一个有时给出糟糕答案的 LLM 功能,在动 prompt 措辞之前,先把一次糟糕调用时窗口里到底有什么记录下来——你通常会发现缺失、陈旧或无关的 context,而非糟糕的措辞。然后修好组装:补上缺失的事实、剪掉噪音、总结历史。当一个搭建得更好的窗口胜过一个更巧妙的 prompt 时,这门功夫便豁然开朗。但请把一个想法置于其余之上:模型只知道窗口里有什么。别再试图从它身上哄出智能,转而去为它能看到的东西做工程——质量自始至终就在那里。