第 1 章 什么是上下文工程:超越提示词的系统视角 / What Is Context Engineering
第 1 章 什么是上下文工程:超越提示词的系统视角
1.1 一个真实的失败案例:当提示词"修不动"的时候
2024 年初,一家电商公司的客服团队找到了我们。他们的对话机器人上线已经六个月,团队成员一直在"调提示词"。每一次产品改版、每一次促销活动、每一次客服质检扣分,所有人都本能地打开 `system_prompt.txt`,加一条规则,再加一条规则,再加一条。六个月后,那个文件已经从最初的 600 字膨胀到 4 200 字。
结果呢?
- 机器人仍然会把"我的订单 #20240315 没收到"误识别成"我想下单 20240315 件";
- 当用户跨多轮提问时,它会忘记三句话前提到的优惠券编号;
- 每次模型升级到新版本,整个提示词都要重写一遍,因为之前精心调好的"if-else 链路"在新模型上完全失效;
- 客服主管说了一句让所有人沉默的话:**"我们不知道这个机器人现在到底知道什么。"**
这不是一个孤立的故事。在我接触过的几十个 LLM 应用团队里,80% 的生产事故都来自同一类问题:团队把所有精力都投入到了"提示词",却没有人系统地思考过——
> 在每一次模型调用发生的那个瞬间,模型究竟看到了什么?
这就是上下文工程(Context Engineering)要回答的核心问题。
上下文工程 不是 prompt engineering 的同义词,也不是 prompt engineering 的"升级版"。它是一门将"模型在推理那一刻所能看到的所有可计算信号"作为工程对象,研究其设计、组装、压缩、检索、淘汰、演化全过程的方法论。
要理解这句话,你需要先理解一个事实:现代大语言模型的"输入窗口"是一个被严重低估的工程对象。它的容量看起来很大——200K、1M、10M tokens——但它不是免费的午餐。窗口里的每一个 token 都在:
1. 占用推理成本(你按 token 付费); 2. 稀释关键信号的注意力权重(信号-噪声比下降); 3. 受限于模型的注意力机制(长上下文中部的召回率显著下降,这被称为 "lost in the middle" 现象); 4. 进入模型的 KV Cache,影响吞吐量和延迟。
所以,"把东西塞进上下文"和"把正确的东西以正确的方式放进上下文",是两种完全不同的工程能力。后者,就是本书要教给你的东西。
1.2 上下文工程的正式定义与系统边界
让我们给上下文工程一个可以被工程团队使用的精确定义。
> 上下文工程(Context Engineering):在每一次大语言模型调用发生之前,对所有进入模型输入窗口的可计算信号——包括但不限于 system prompt、用户输入、对话历史、外部检索文档、工具调用结果、结构化状态、记忆摘要——进行有目的的设计、有预算的组装、有度量的压缩、有索引的检索、有生命周期的管理,以使模型在给定的 token 预算与延迟约束下,产生可预测、可观测、可演化的输出。
这个定义里有几个关键短语,我需要逐一拆解:
"可计算信号"——这里排除了"业务背景""用户情绪""组织文化"这类不可被序列化进 prompt 的东西。我们只关心最终会被 token 化、会被送进模型输入窗口的内容。
"有目的的设计"——每一个进入上下文的字段,都必须回答"它为什么在这里?没有它会怎样?"这两个问题。如果回答不出来,那它就是噪声。
"有预算的组装"——上下文不是"能塞多少塞多少"。一个工业级的 LLM 应用通常有一个明确的 token 预算(比如 32K、128K),每个子系统(system、history、RAG、tools、memory)都必须在这个预算内竞争。预算分配本身就是一项工程决策。
"有度量的压缩"——长对话必须被摘要,历史消息必须被淘汰,文档必须被切片重排。这些操作不是"自然发生"的,它们需要算法、阈值、触发条件与回滚机制。
"有索引的检索"——RAG 不是"调一下向量数据库"那么简单。检索的 query 设计、chunk 策略、重排(rerank)逻辑、上下文窗口里的引用格式,都是上下文工程的一部分。
"有生命周期的管理"——上下文中的信息是有"年龄"的。一条昨天的天气查询今天还有用吗?一段两周前的会议摘要今天还要保留吗?上下文工程需要回答:什么信息该被记住、什么该被遗忘、什么该被升级到长期记忆。
现在,让我们用一张图把上下文工程的系统边界画清楚:
```mermaid flowchart LR A[Context Engineering] --> B[System Prompt] A --> C[User Input] A --> D[Conversation History] A --> E[Retrieval RAG] A --> F[Tool Calls] A --> G[Structured State] A --> H[Long-term Memory] B --> I[LLM Inference] C --> I D --> I E --> I F --> I G --> I H --> I I --> J[Output Tokens] I --> K[Observability Log] K --> A ```
这张图传达了三个信息:
1. 上下文工程不是某一个组件,而是一个汇聚层。system prompt、用户输入、对话历史、RAG、工具、状态、记忆,这些原本散落在系统不同位置的信号,最终都被上下文工程组装成一个统一的输入。 2. 输出不仅仅是文本。模型还会产生结构化动作(tool calls),同时整个过程必须被观测(observability log),观测结果反过来优化上下文的组装——这就是后面"可演化性"的雏形。 3. 没有箭头指向"微调"或"训练数据"。上下文工程专注于推理时发生的事情,它与模型权重层面的修改(fine-tuning)属于两个不同的工程层面。这一点我们后面会详细讨论。
1.3 与相邻概念的精确关系:提示工程、记忆、状态、微调
很多读者会把上下文工程和它"长得很像"的概念混为一谈。这一节我把它们拆开。
1.3.1 上下文工程 vs 提示工程(Prompt Engineering)
提示工程关注的是单一文本字段的最优表达:如何措辞、如何举例、如何分隔、如何用 few-shot 引导模型。提示工程的研究对象是"那一段字符串本身"。
上下文工程关注的是那个字符串所在的整个窗口的组装与生命周期:这段字符串应该多长?和它并列的 RAG 文档应该放前面还是后面?当对话轮数超过 20 轮时,它是否应该被摘要压缩?当用户切换话题时,是否应该清空它?
用一个类比:如果提示工程是"写一封完美的邮件",上下文工程就是"设计整个收件箱的归档、过滤、优先级与回复模板系统"。
在实践中,两者的关系可以这样表述:
> 提示词是上下文的子集;上下文工程关注的是这个子集及其同住者的生命周期与系统边界。
这一点决定了——当你只能"调提示词"的时候,你的优化空间是有限的;一旦你开始管理整个输入窗口,你才真正进入了上下文工程的领域。
1.3.2 上下文工程 vs 记忆(Memory)
记忆在 LLM 应用中通常指跨会话(cross-session)持久化的用户或任务状态。它的存储介质一般是外部数据库(向量库、KV 库、SQL),而不是模型上下文窗口。
上下文工程则在单次推理(single inference)的尺度上工作。记忆的内容只有在被"调入"上下文窗口的那一刻,才进入上下文工程的视野。
换句话说:记忆是仓库,上下文是货架。仓库里的东西再多,如果上架的逻辑不清晰,模型还是看不到;反过来,上下文工程也会决定哪些记忆应该被"提取"进当前窗口——这是记忆系统与上下文系统的接口。
很多团队失败的根源,就是只设计了"记忆怎么存",没设计"记忆怎么用"。后者本质上就是上下文工程的一部分。
1.3.3 上下文工程 vs 状态(State)
状态通常指结构化的、可被程序读写的运行时变量——比如当前任务的步骤编号、用户的认证信息、待办事项的勾选状态。
状态本身是非自然语言的,但它必须被序列化进上下文才能影响模型行为。例如:
``` { "task_stage": "collecting_payment_info", "remaining_fields": ["card_number", "cvv", "expiry"], "user_tier": "gold" } ```
这段 JSON 会通过 prompt 模板被渲染成自然语言塞进上下文——这个渲染过程就叫状态注入(state injection)。它属于上下文工程的核心动作之一。
一个常见的设计错误是:把状态放在 system prompt 里硬编码。这会导致状态更新极其笨拙(每次都要重新生成整个 prompt),而且会让 system prompt 变成一个杂物间。正确的做法是把状态作为独立的、有 schema 的模块,通过模板渲染动态注入。
1.3.4 上下文工程 vs 微调(Fine-tuning)
微调修改的是模型参数——它让模型"学到"新的知识或行为模式。
上下文工程不修改任何参数——它只是在推理时决定模型看到什么。
两者的工程成本、迭代速度、适用场景截然不同:
| 维度 | 微调(Fine-tuning) | 上下文工程(Context Engineering) | |---|---|---| | 修改对象 | 模型权重 | 输入窗口 | | 单次迭代成本 | 高(GPU 小时) | 低(CPU 毫秒级) | | 可回滚性 | 困难 | 极易 | | 适合学习的 | 风格、格式、领域语言 | 事实、规则、当前任务状态 | | 失败时定位 | 需要重新训练 | 改 prompt 即可 |
一个工业级的判断标准是:能用上下文解决的事,不要用微调。这条原则在本书第 14 章会被详细论证,但在这里先给你建立直觉——微调是"重武器",上下文工程是"手术刀"。前者适合长期、稳定的模式变化;后者适合快速、多变的运行时信息。
1.3.5 三段式心智模型:输入、处理、输出
把所有相邻概念梳理清楚之后,我们可以用一个清晰的心智模型来组织全书:
``` ┌─────────────────────────────────────────────────┐ │ INPUT(输入) │ │ System Prompt + User Query + History + RAG + │ │ Tools + State + Memory → Context Window │ └─────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────┐ │ PROCESSING(处理) │ │ LLM Inference + Tool Execution │ └─────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────┐ │ OUTPUT(输出) │ │ Text + Tool Calls + Structured Actions + │ │ Side Effects → 重新写回 State / Memory │ └─────────────────────────────────────────────────┘ ```
上下文工程的全部工作发生在 INPUT 这一段。但它必须与 PROCESSING(你选什么模型、什么推理参数)和 OUTPUT(你如何把模型的输出回流到状态与记忆)协同设计。
这就是为什么说上下文工程是"系统视角"——它不是孤立的输入处理,而是与模型推理、状态管理、记忆系统耦合设计的一门工程。
1.4 三类典型场景及其上下文需求差异
不是所有 LLM 应用都需要同样复杂的上下文工程。为了帮你判断"我的项目到底需要多深的上下文工程",我把现实中最常见的三类场景并列放在一起,对比它们的上下文特征。
1.4.1 场景一:客服对话机器人
任务特征:多轮对话、任务导向、需要外部数据(订单、用户资料)、有明确的"完成态"。
上下文需求:
| 上下文组件 | 重要性 | 说明 | |---|---|---| | 系统提示词 | 高 | 定义机器人身份、语气、不可越界的话题 | | 用户输入 | 高 | 当前轮次的 query | | 对话历史 | 高 | 必须保留,但需要压缩策略 | | RAG 检索 | 中 | 知识库(FAQ、政策文档)通常需要 | | 工具调用 | 高 | 查订单、查物流、退款等动作 | | 结构化状态 | 高 | 当前任务阶段、已收集字段 | | 长期记忆 | 中 | 用户偏好、历史投诉记录 |
典型失败模式:
1. 历史无限累积:第 50 轮对话时,前 49 轮全部挤在窗口里,关键信息被淹没; 2. 工具结果堆积:连续调用 5 个工具后,工具返回的 JSON 块占据大量上下文,导致 token 预算爆炸; 3. 状态散落:任务阶段信息写在 system prompt 里,但 prompt 没人维护,导致机器人永远停在"收集信息"阶段。
上下文工程的杠杆点:对话历史的滑动窗口与摘要、工具结果的裁剪、状态的 schema 化设计。
1.4.2 场景二:长篇文档分析
任务特征:单轮或少量轮次、输入侧文档极长(10K–1M tokens)、输出侧为摘要、问答或抽取。
上下文需求:
| 上下文组件 | 重要性 | 说明 | |---|---|---| | 系统提示词 | 中 | 只需定义输出格式 | | 用户输入 | 中 | 通常是简短的指令 | | 对话历史 | 低 | 单轮为主 | | RAG 检索 | 高 | 文档分块 + 检索 + 重排 | | 工具调用 | 低 | 偶尔需要外部知识 | | 结构化状态 | 低 | 任务状态简单 | | 长期记忆 | 低 | 通常不需要 |
典型失败模式:
1. "lost in the middle":用户问的问题答案藏在文档中段,模型却回答了文档开头或结尾的内容; 2. chunk 切分错误:把一个表格从中间切开,导致模型看不到完整列名; 3. 引用幻觉:模型说"根据文档第 X 段",但其实那段根本不存在。
上下文工程的杠杆点:文档预处理、chunk 策略、重排(rerank)、位置感知的 prompt 模板(如"重要信息放在窗口首尾")。
1.4.3 场景三:Agent 任务
任务特征:多步骤、自主决策、工具调用密集、长时程(可能持续数小时甚至数天)、目标导向。
上下文需求:
| 上下文组件 | 重要性 | 说明 | |---|---|---| | 系统提示词 | 高 | Agent 的目标、能力边界、决策原则 | | 用户输入 | 中 | 初始任务 + 偶发的人类反馈 | | 对话历史 | 高 | 但必须经过严格压缩 | | RAG 检索 | 中 | 任务相关知识 | | 工具调用 | 极高 | Agent 的"手"和"眼" | | 结构化状态 | 极高 | 任务计划、子任务进度、待办列表 | | 长期记忆 | 高 | 跨任务的知识沉淀 |
典型失败模式:
1. 上下文腐烂(context rot):随着任务推进,上下文窗口逐渐被低价值的中间结果填满,关键的原始目标被稀释; 2. 目标漂移(goal drift):在长链路中,模型逐渐忘记最初的目标,开始追求"完成子任务"而非"完成总目标"; 3. 工具调用循环:重复调用同一个工具,因为之前的结果没有被正确记忆或检索; 4. 状态爆炸:用自然语言描述状态,导致信息冗余且难以解析。
上下文工程的杠杆点:分层记忆(working memory / episodic memory / semantic memory)、目标锚定(re-inject original goal at intervals)、工具结果的标准化摘要、任务规划与执行的上下文分离。
1.4.4 三类场景的对比总结
| 维度 | 客服机器人 | 长文档分析 | Agent 任务 | |---|---|---|---| | 上下文窗口压力来源 | 对话轮数 | 文档长度 | 工具调用次数 | | 状态复杂度 | 中(有 schema) | 低 | 高(必须结构化) | | RAG 依赖度 | 中 | 极高 | 中 | | 记忆需求 | 中(用户级) | 低 | 高(任务级 + 知识级) | | 压缩难度 | 中 | 高(不能丢信息) | 极高(要保留决策依据) | | 核心风险 | 状态丢失 | lost in the middle | 上下文腐烂 |
理解了这些差异,你就不会拿"文档分析的 chunk 策略"硬套到客服机器人上,也不会把"客服机器人的对话摘要"用到 Agent 上。每一类场景都需要独立的上下文工程设计模式,这也是本书第 5–13 章会逐一展开的内容。
1.5 四项贯穿全书的工程原则
在深入具体技术之前,我先给你四个贯穿全书的工程原则。它们不是某一章的局部最佳实践,而是判断"任何上下文工程决策是否正确"的元标准。
1.5.1 可观测性(Observability)
定义:你能完整、实时地看到每一次模型调用实际收到的完整上下文,以及模型基于该上下文**实际产生的