What Is Context Engineering?
什么是上下文工程
1.1 一例生产事故:聊天机器人失去主线
2024 年 8 月,一家在欧洲运营的家居电商公司的值班工程师收到一份被标记为 "P1" 的事故报告:客服聊天机器人在过去三十六小时内出现了大面积"言行不一"的现象。在某些多日持续对话中,助手会推翻自己几小时前确认过的退款政策,会突然忘记客户订单编号,会把上一会话的上下文混入当前对话——而最令客户运营团队担忧的是,助手会主动承认"我之前的回答有误",但当客户试图调出所谓"错误回答"的具体内容时,聊天记录中根本不存在这段话。
这起事故最初的归因方向完全错误。前两位介入的工程师假设是新上线的模型版本存在回归,于是回滚了部署、把流量切回旧模型,但 P1 警报并未消失。他们接着检查了提示词模板,发现业务方近期并未修改过 system message;检查了输出日志,没有发现任何异常格式或截断。在事故升级到第三天时,一位资深工程师从一段被标记为"成功"的会话中发现了关键线索:助手在第四轮对话中,引用了一个用户在第一天才提过的收货地址,但该地址并不属于当前客户;进一步检查上下文窗口的拼装日志后,他意识到这段对话的 prompt 中同时包含了三条历史工单的内容,且这些工单彼此在事实层面相互冲突。
根因报告最终定性的结论并不复杂:模型没有问题。问题在于拼装到上下文窗口里的内容——即那段被送入大语言模型的输入——存在三种错误同时叠加的情况:来自不同用户的工单被误并入同一会话的提示词(状态管理失效)、历史工单中的过时政策未从上下文中移除(检索/记忆失效)、以及为了把历史塞进有限窗口而启用的压缩操作丢失了关键约束(压缩失效)。这次事故的直接修复手段同样朴素:给状态机加上用户级隔离、给历史检索加上时间衰减、为压缩设置最低保真度阈值。但它揭示了一个远比"提示词写错了"更结构化的问题——在 LLM 应用的生产实践中,输入的构造方式 本身就是一个需要被当作工程对象来对待的复杂系统。
这正是本书的核心主张:在大语言模型逐步从"对话玩具"演化为"生产基础设施"的过程中,什么被送入上下文窗口、如何被送入、什么被剔除 已经变成与"调用哪个模型、用什么参数"同等重要、乃至更重要的工程决策。把这一决策过程系统化、可度量、可迭代的工程实践,我们称之为 上下文工程(Context Engineering)。
1.2 上下文工程与邻近概念的边界
在 LLM 的工具链术语中,"上下文工程"常常与"提示工程(Prompt Engineering)""检索增强生成(RAG)""微调(Fine-tuning)"以及"代理设计(Agent Design)"混用。这种术语上的重叠并非偶然——它们都关注"如何让模型给出更好的输出"——但各自所指代的工程对象、抽象层级和可调参数是截然不同的。澄清这些边界,是进入本书正题的前提。
提示工程 处理的是"一段给模型的自然语言指令"内部的微观结构:措辞、示例、约束、格式、思维链。提示工程师关心的是同一段输入中词与词之间的相对位置如何影响输出。提示工程有一个隐含假设:输入已经被决定了,剩下的工作是在固定输入上做文本编辑。上下文工程则处理"输入本身是怎么被决定下来的":哪些文档被拉入、哪些对话状态被保留、哪些工具结果被压缩或丢弃、哪些用户偏好被注入 system message。上下文工程关心的是信息如何从外部世界流入模型,而不是这些信息在文本内部如何排列。简言之,提示工程是文字的编辑学,上下文工程是信息的装配线。
检索增强生成(RAG) 在大多数文献中被定义为"在生成时从外部知识库中检索相关片段并拼接进提示词"的技术。RAG 是上下文工程的一个重要子集——具体而言,是四大支柱中的"检索/记忆"支柱——但二者并不等价。一个使用 RAG 的系统可能完全没有设计状态管理、可能完全没有压缩策略、可能完全没有利用长上下文窗口的注意力特性;反过来,一个不使用向量数据库的纯对话系统依然可能做出精细的上下文工程,例如基于规则的上下文路由、基于对话阶段的提示词切换、基于语义的摘要压缩。RAG 是上下文工程的一种常见实现,但上下文工程的视野更宽。
微调 调整的是模型参数本身——通过梯度更新让模型的权重对特定分布的数据更敏感。微调解决的问题是"模型的归纳偏置与任务不匹配",而上下文工程解决的问题是"送入模型的信息与任务不匹配"。两者可叠加,但成本结构和失败模式都不同:微调是一次性或周期性的大额投入,调整的是模型的"先天倾向";上下文工程是每次推理都在发生的实时投入,调整的是模型的"当前视野"。在本书的框架下,我们假设模型权重是给定的——这与"上下文工程是模型无关的"这一性质高度吻合,也使得上下文工程中的所有讨论都可以无缝迁移到新版本、新供应商、新架构的模型上。
代理设计 处理的是"模型在多轮、可能多步、可能调用工具的执行循环中如何行动"。代理设计涉及工具选择、规划循环、错误恢复、自我批评等机制。表面上看,代理设计与上下文工程有大量重叠——事实上,每一轮代理循环都包含一次上下文工程的过程:把工具结果、历史动作、规划笔记、用户输入拼装成新的提示词。但在抽象层级上,代理设计关心的是"控制流",上下文工程关心的是"数据流"。一个常见的错误是把代理框架当成上下文工程的全部;这种过度泛化的代价是,工程师会忽略那些不在代理循环中的、但同样关键的上下文决策——例如初次进入对话时的用户画像注入、超长会话的滚动窗口管理、跨会话的偏好学习。
| 概念 | 抽象层级 | 调整对象 | 一次推理中的发生频率 | | --- | --- | --- | --- | | 提示工程 | 文本内部 | 措辞、示例、格式 | 每次推理 | | 上下文工程 | 输入装配 | 内容、长度、来源、顺序 | 每次推理 | | RAG | 输入装配的一个子集 | 检索片段的选择与排序 | 每次推理(若启用) | | 微调 | 模型权重 | 归纳偏置 | 一次性或周期性 | | 代理设计 | 控制流 | 行动规划、工具选择 | 每次推理(若启用) |
一个有用的心算规则:当你面对一个 LLM 应用的质量问题时,先问"问题出在模型看到的输入上,还是出在模型本身上?" 如果你能描述出一段"如果输入是 X,输出就会变好"的具体反例,那么问题几乎肯定在上下文侧——这正是上下文工程要管辖的领地。
1.3 上下文窗口:有限、注意力受限的资源
把上下文窗口视为"无限大的记事本"是一种常见但危险的误解。在底层实现上,上下文窗口的容量受三种相互独立的约束同时限制:序列长度、注意力计算的复杂度、键值缓存的显存占用。前者由模型架构决定(位置编码的最大支持长度、训练时的最大序列长度),中者由注意力机制的算法性质决定(标准 Transformer 的注意力是 $O(n^2)$ 复杂度,$n$ 为序列长度),后者由部署时的硬件决定(在自回归解码阶段,键值缓存的显存占用与序列长度呈线性关系,但常数项较大)。
这三种约束共同决定了上下文窗口是一个真实存在的预算。超出窗口的输入会被截断或拒绝;填满窗口的输入会显著拖慢推理速度;填满窗口的输入还会让模型表现出"上下文中部信息被忽略"的现象——这一现象最早由 Liu 等人在 2023 年的 "Lost in the Middle" 研究中系统性地记录和命名:当相关信息被放置在上下文的中段而非两端时,模型回忆和利用这些信息的准确率会下降十到二十个百分点。这意味着"上下文窗口大"并不等价于"上下文利用率高"——窗口只是容器,决定内容物能否被有效使用的是装配过程。
成本与延迟的线性放大器:在所有主流云端 LLM 服务的定价模型中,输入 token 数是计费的基本单位之一,并且与输出 token 数往往采用不同的费率;输入越长,单次推理的账单越大。在延迟方面,预填充(prefill)阶段——即把输入 token 编码进键值缓存——其耗时近似与输入长度成正比。当一个应用在每次对话中都要送入 50,000 个 token 的"全量历史"时,账单和首 token 延迟会很快把"用大模型做更多事"的设想击穿。压缩、检索、选择性拼装,这些上下文工程的核心动作,其首要动机往往不是"提高模型表现",而是"在不牺牲必要信息的前提下把账单和延迟压回可接受范围"。
注意力作为稀缺资源:即便不引入任何压缩或检索,只要把大量信息塞进上下文窗口,模型对不同部分的注意力也会出现"竞争"——这种现象在长上下文场景下尤为显著。经验上,系统提示词(system prompt)、当前用户输入、最近几轮对话这三部分会自然获得较高注意力;而更早的历史对话、检索片段的尾部、工具返回结果中的非关键字段则容易被弱化。这意味着上下文工程不能只考虑"信息是否在窗口中",还要考虑"信息在窗口中的位置和形式是否让它有竞争力地进入模型的注意力"。
工程上的隐含契约:在生产 LLM 应用中,上下文窗口实际上是一个多方共享的有限资源池——它需要同时容纳系统约束、当前指令、历史对话、外部知识、工具结果、用户偏好、输出格式要求。上下文工程的核心问题,就是如何在这个资源池中做调度:哪些内容获得资源、哪些内容被降级、哪些内容被彻底丢弃、哪些内容被摘要后保留、哪些内容被引用而不内联。这与操作系统中的内存管理、数据库中的查询优化在结构上高度相似——都是有限资源下的多目标调度问题。
1.4 四大支柱:组织本领域整体框架
把上下文工程从一组零散的技术("我们做了 RAG""我们做了摘要")升级为一门可教学的工程学科,需要一个稳定的分类框架。本书将全部上下文工程问题归入四个相互正交又彼此协作的支柱:
1. 长上下文(Long Context)——如何有效利用具有较大上下文窗口的模型; 2. 压缩(Compression)——如何在不丢失关键信息的前提下缩减上下文; 3. 检索/记忆(Retrieval / Memory)——如何把外部或历史信息按需引入上下文; 4. 状态管理(State Management)——如何在多轮、多会话、多用户场景下维护一致的上下文。
这四者并不互斥,任何一次推理中的上下文,都是这四个支柱协同作用的结果。一次典型的 LLM 请求会经历如下生命周期:用户输入抵达 → 状态管理根据当前用户、当前会话加载相关上下文 → 检索/记忆根据输入查询向量数据库或长期记忆库 → 压缩把检索结果、历史对话、工具输出裁剪到预算之内 → 拼装后的长上下文与系统提示词一起送入模型 → 模型生成输出 → 状态管理把新事实写回长期记忆。下面的图示呈现了这一循环,以及四大支柱在其中的位置。
```mermaid flowchart LR A[User Query] --> B[State Load] B --> C[Retrieval] C --> D[Compression] D --> E[Assembly] E --> F[Long Context Window] F --> G[LLM Inference] G --> H[Response] H --> I[State Update] I --> B P1[Long Context] -.-> F P2[Compression] -.-> D P3[Retrieval / Memory] -.-> C P4[State Management] -.-> B P4 -.-> I ```
支柱一:长上下文 关注的是"当窗口本身变大时,模型行为会发生什么变化"。2023 年起,多家模型供应商陆续发布支持百万 token 上下文的模型;2024 年起,部分模型宣称在"针在干草堆中(needle-in-a-haystack)"等检索基准上接近 100% 准确率。但长上下文带来的并不是"可以无脑塞东西"的自由——它带来了三组新问题:位置偏置(首尾效应)、延迟成本(线性增长)、指令稀释(关键约束被淹没)。本支柱下的工程杠杆包括:把关键信息固定放置在强注意力位置、对长上下文做分块读取后让模型多轮聚焦、针对长上下文设计专门的提示结构。
支柱二:压缩 关注的是"如何在信息保真度与 token 数量之间做权衡"。压缩可以是无损的(结构化裁剪、字段过滤、对话截断),也可以是有损的(摘要、改写、抽取)。压缩的工程难点不在于"能不能做",而在于"在什么位置做、保留什么、丢弃什么、用什么评估压缩是否过度"。本书将介绍基于规则的硬压缩、基于模型的摘要压缩、基于嵌入的语义压缩,以及如何用小模型做大模型上下文的"过滤器"。
支柱三:检索/记忆 是被讨论得最多、也最容易被误用的支柱。检索 是从外部语料中按相关性拉取信息,记忆 是从应用自身的运行历史中拉取信息。两者在技术实现上常常共用基础设施(向量数据库、关键词索引、图结构),但在语义上不同:检索是"事实的来源",记忆是"历史的回声"。本支柱下的核心工程杠杆包括:分块策略、嵌入模型选择、混合检索、查询改写、记忆的去重与冲突解决、记忆的过期与衰减。
支柱四:状态管理 是最容易被忽视的支柱。状态管理的对象包括:当前会话的多轮对话历史、跨会话的用户偏好、跨用户的隔离边界、跨工具调用的中间变量、应用层的业务流程控制。状态管理失效往往表现为最戏剧性的故障——模型"人格分裂"、跨用户信息泄露、对话穿越(前会话内容出现在后会话中)。本书将在后续章节中系统讨论有状态 LLM 应用的状态机设计、持久化策略、隔离机制。
四个支柱的协作不是串行而是耦合的:例如一次对话中拼接的内容,可能同时经过"按用户隔离(状态)→ 按时间衰减(记忆)→ 按相关度排序(检索)→ 按窗口预算压缩(压缩)→ 按位置策略放置(长上下文)"五道工序。判断一道工序做得好不好,需要看它对最终输出的影响,而不是看它的局部指标——这是上下文工程与传统软件工程最大的不同:评估函数不在工序内部,而在工序的下游,取决于模型行为的最终表现。
1.5 失效模式分类:把生产故障映射到支柱
如果上下文工程是"四支柱的协同",那么 LLM 应用的失效就可以被分类为"四支柱中的某一根或多根出了问题"。这种分类对工程师的核心价值在于:当你接到一份"模型答错了"的 bug 报告时,分类本身就能告诉你应该先去看哪里的代码。没有这种分类,调试会变成在拼装好的整段 prompt 上反复改写文本——这本质上是把工程问题降级为提示词调参。
下面给出在生产部署中观察到的四类典型失效模式,每一类都对应到特定的支柱。
第一类:过时事实(Stale Facts)。模型在回答中引用了一条不再正确的事实——价格已经调整、政策已经更新、产品已经下架。在 LLM 应用中,模型权重对世界知识的"冻结"特性,使得任何带时间敏感性的事实都必须在运行时由上下文提供。过时事实 在支柱上的归属是 检索/记忆:事实确实被送进了上下文,但送进去的是旧的副本,记忆系统缺乏过期机制或冲突解决机制。常见的工程杠杆是给记忆加时间戳与有效期、在检索时按时间衰减相关性、给关键事实设置"必须最新"的元数据。判断标准:在拼装好的提示词中是否能找到"过时版本的事实"——能找到,则属于此类。
第二类:丢失指令(Lost Instructions)。模型在回答中违反了一段被显式声明的约束——"不要提及价格""用中文回答""字数不超过 200"。这类失效的特征是:指令确实在系统提示词中,但模型在生成时没有忠实遵循。支柱归属首先是 长上下文:当上下文窗口很大时,位置偏置和注意力稀释会使得位于中段或末段的指令被弱化。其次也涉及 压缩:某些压缩操作会把指令一并摘要,破坏了指令的精确性。常见的工程杠杆是把关键指令放在强注意力位置(如 system prompt 的开头与结尾同时声明)、避免对指令区做语义压缩、为指令声明增加显式格式(如加粗、编号、加 `<instruction>` 标签)。判断标准:在拼装好的提示词中是否能找到完整的、未被截断的指令——能找到且在弱位置,则属于此类。
第三类:冲突记忆(Conflicting Memories)。模型在回答中同时引用了相互矛盾的两条信息——例如同一位客户在两条不同的历史工单中分别被标为"VIP"和"普通",模型随机选择了其一。更隐蔽的情况是,模型为了"调和"两条冲突信息而虚构了一段不存在的解释。支柱归属是 检索/记忆:记忆系统中存在重复或冲突的条目,且没有去重或冲突解决机制。常见的工程杠杆是建立记忆的写入时校验(写入前查询是否已有相似条目)、引入冲突解决策略(时间优先、来源权威性优先、显式覆盖标记)、限制同时进入上下文的记忆条数并优先保留最相关的几条。判断标准:在拼装好的提示词中是否能找到两条相互否定的事实——能找到,则属于此类。
第四类:预算超支(Budget Overrun)。模型推理被截断、响应时间飙升、账单爆表。表面看是基础设施问题,但根因常常是装配层缺乏预算控制。支柱归属首先是 压缩:没有任何机制阻止上下文无限膨胀。其次是 检索/记忆:检索结果未做截断、未做相关性过滤、未做总长度约束。再次是 状态管理:历史对话无限制累积。常见的工程杠杆是引入 token 预算、设置多级熔断(按预算分档采取不同压缩策略)、监控每次请求的实际输入 token 分布、给检索结果硬性设置 top-k 上限。判断标准:观测到单次请求的输入 token 接近或超过模型窗口,且其中包含大量可被裁剪的冗余信息——则属于此类。
一个调试心法:拿到 bug 报告时,不要直接修改提示词文本,而是先问"是哪根支柱出了问题?" 这一问往往能节省数小时的盲目调试。如果你不能明确归类,问题大概率不是单支柱的,而是多支柱同时失效——此时应该从最长的那根支柱开始排查,因为长链路上的问题更可能是根因。
1.6 全书贯穿的研究助手:一个不断演进的示例
抽象讨论容易让读者失去方向感。为此,本书将围绕一个具体的应用——个人研究助手(Personal Research Assistant)——展开后续所有章节。这个示例不是玩具,而是一个在功能上与生产系统非常接近的、端到端可运行的 LLM 应用:用户提出一个研究问题,助手从用户过往阅读过的论文、笔记、网页书签中检索相关信息,融合当前会话上下文,生成既有事实依据又有个性化风格的答复。
在本书的演进过程中,这个研究助手将经历从"最简单的提示词调用"到"完整的有状态 LLM 应用"的完整生命周期:
- **第 1 章**(本章)定义了它的需求与边界;
- **第 2 章至第 4 章** 分别深入四大支柱的第一根——**长上下文**——讨论如何让助手处