第一章:什么是上下文工程 —— Context Engineering 入门 (Introduction to Context Engineering)
第一章:什么是上下文工程 —— Context Engineering 入门
2024 年 3 月,某电商平台的客服机器人上线了新一代版本。它基于当时最强的大模型 GPT-4,看起来无所不能:能聊天、能查订单、能处理退款、能安抚情绪。用户最初 5 轮对话体验堪称完美——客服机器人准确记住了订单号、用户姓名、问题描述。
但到了第 20 轮,一切都变了。
用户问:"我刚才说的那个订单,就是上次退货失败的那个,能帮我再看看吗?"
客服机器人回答:"您好,请问您要查询哪个订单?能否提供订单号?"
——它把第 1 轮的信息全部忘了。
这不是科幻故事,这是真实发生在生产环境中的"上下文失忆"事故(Context Amnesia Incident)。事后排查发现:技术团队完全有能力写出一个完美的系统提示词(System Prompt),但他们忽略了一件事——大模型的上下文窗口(Context Window)是有限的。当对话轮次增加到 30+ 轮后,早期的关键信息被新信息"挤出"了窗口,或者模型的注意力(Attention)已经无法稳定地锁定在那些久远的信息上。
这个事故的核心问题不是 Prompt 写得好不好,而是:如何让大模型在有限的空间里,始终"记住"它应该记住的东西。
这就是本书要回答的核心问题,也是"上下文工程"(Context Engineering)这门新兴学科存在的全部理由。
1.1 上下文的定义:输入给大模型的"全部信息"
1.1.1 从一次最简的 LLM 调用说起
要理解"上下文"(Context),我们必须先回到最朴素的起点:一次大模型调用(LLM Call)究竟发生了什么?
假设你调用 OpenAI、Anthropic 或开源模型(如 Llama 3)时,最简单的请求看起来是这样的:
```python from openai import OpenAI client = OpenAI() response = client.chat.completions.create( model="gpt-4", messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is Context Engineering?"} ] ) ```
在这个 `messages` 数组里,包含了本次调用中大模型能"看到"的全部信息。这些信息合在一起,就构成了这次调用的"上下文"。
1.1.2 上下文的四个组成部分
为了帮助初学者建立全景认知,我们将"上下文"拆解为四个基本组件(Four Components of a Single LLM Call):
组件一:系统提示(System Prompt) 系统提示是放在消息数组最开头、以 `"role": "system"` 标识的一段文本。它通常用来设定模型的角色、行为规范、输出格式、约束条件。例如:
```text 你是一位专业的图书编辑,回答必须引用具体章节。 ```
系统提示的设计是 Prompt Engineering 的核心战场,但 Context Engineering 关心的是更深一层的问题:系统提示应该多长?应该放在哪里?当它和其他组件冲突时怎么办?
组件二:用户输入(User Input) 当前这一轮用户发送的消息,以 `"role": "user"` 标识。它是上下文中"最鲜活"的部分,通常被模型赋予最高的注意力权重。
组件三:历史对话(Conversation History) 所有之前轮次的 user 和 assistant 消息,按时间顺序排列成数组。这是"上下文"中最容易膨胀的部分——多轮对话每多一轮,就要多两条消息。
组件四:外部数据(External Data / Retrieved Documents) 通过 RAG(Retrieval-Augmented Generation,检索增强生成)或工具调用(Tool Use)注入到上下文中的、与当前问题相关的外部信息。例如知识库片段、数据库查询结果、API 返回值。
1.1.3 全景视图:一次调用的上下文结构
下图展示了一次典型 LLM 调用中上下文的完整组成结构:
```mermaid flowchart TD A[LLM Call Context] --> B[System Prompt] A --> C[User Input Current Turn] A --> D[Conversation History] A --> E[External Data / Retrieved Docs] B --> B1[Role definition] B --> B2[Output format] B --> B3[Constraints and rules] D --> D1[Earlier user messages] D --> D2[Earlier assistant replies] E --> E1[Knowledge base chunks] E --> E2[Tool / API results] E --> E3[Database query output] ```
请记住这张图。当你看到任何 LLM 应用出错时,第一反应应该是:"上下文里现在到底装了什么?"——然后按图索骥去检查四个组件中的每一个。
1.1.4 一个常常被忽略的事实:上下文是有"成本"的
很多初学者会忽略一个关键事实:上下文的每一 Token 都在消耗资源。这里的资源包括三种:
1. 算力成本(Compute Cost):输入 Token 越多,推理(Inference)时的 FLOPs(浮点运算次数)就越大,API 调用单价也随之升高。例如 GPT-4 的 8K 上下文版本和 128K 上下文版本,单价相差近 10 倍。 2. 延迟成本(Latency Cost):输入长度和首 Token 时间(Time To First Token, TTFT)几乎呈线性关系。上下文越长,用户等待的时间越久。 3. 注意力成本(Attention Cost):Transformer 的自注意力机制(Self-Attention)的时间复杂度是 $O(n^2)$,其中 $n$ 是序列长度。上下文长度翻倍,计算量翻四倍。
这意味着,上下文不是"塞得越多越好",而是一种需要被精心设计的稀缺资源(Scarce Resource)。这也是 Context Engineering 区别于传统 Prompt Engineering 的根本所在。
1.2 Context Engineering vs Prompt Engineering:不是替代,是升维
1.2.1 一个常见的误解
在 LLM 应用的早期阶段(2023 年前后),"Prompt Engineering"(提示工程)几乎是万能钥匙。开发者们热衷于研究如何写出一个"魔法提示词",让模型乖乖听话输出 JSON、调用工具、扮演角色。这股浪潮催生了大量教程和最佳实践。
但随着应用从"单轮问答"走向"多轮智能体"(Multi-turn Agent),Prompt Engineering 的边界开始显现。
我们用一个真实场景对比一下:
场景:开发一个法律咨询助手,能根据用户描述给出初步法律建议。
Prompt Engineering 的思路:
- 写一段 2000 Token 的系统提示,规定模型的角色、知识边界、输出格式、风险免责声明
- 通过 few-shot examples(少样本示例)让模型学会判断
- 测试发现:单轮问题回答得很好
Context Engineering 的思路:
- 同样的系统提示,但同时考虑:提示词会随着历史对话的膨胀而被"稀释"吗?
- 用户在第 5 轮提到了"我是和公司签的合同",但第 1 轮提到了"我朋友也有类似问题"——这两条信息该如何在上下文中平衡?
- 如果用户的问题需要查阅《劳动合同法》具体条款,是检索后整段塞进上下文,还是只塞关键句?
- 当上下文接近窗口上限时,如何决定保留哪些、丢弃哪些?
看出区别了吗?Prompt Engineering 解决的是"这一轮如何问得好",而 Context Engineering 解决的是"整个会话生命周期内如何管理好进入窗口的全部信息"。
1.2.2 核心差异:三张对比表
我们用三张对比表,把 Prompt Engineering 和 Context Engineering 的差异讲透。
对比一:关注对象(Object of Focus)
| 维度 | Prompt Engineering | Context Engineering | |------|-------------------|---------------------| | 核心关注 | 提示词的措辞、格式、示例 | 进入模型窗口的"全部信息"的设计 | | 优化目标 | 单次调用的输出质量 | 整个会话生命周期内的输出质量 | | 失效模式 | 提示词写得不好 | 上下文结构混乱、信息过时、注意力分散 |
对比二:时间维度(Temporal Dimension)
| 维度 | Prompt Engineering | Context Engineering | |------|-------------------|---------------------| | 设计时点 | 主要在开发期设计 | 必须在每次调用时动态调整 | | 运行时干预 | 通常不需要 | 需要实时决策(保留什么、丢弃什么、检索什么) | | 演化方式 | 静态优化(改提示词) | 动态管理(滚动窗口、增量更新) |
对比三:技能栈要求(Required Skills)
| 维度 | Prompt Engineering | Context Engineering | |------|-------------------|---------------------| | 主要技能 | 自然语言写作、指令设计 | 系统设计、信息架构、状态管理 | | 工具链 | Prompt 版本管理、Few-shot 库 | 向量数据库、缓存、压缩算法 | | 评估指标 | 单次回答准确率 | 长程任务完成率、成本、延迟 |
1.2.3 一个有用的心智模型
为了让你在工作中能快速判断"现在该用 Prompt Engineering 还是 Context Engineering",我们给出一个简单的心智模型:
> 如果你的问题可以在一轮调用内解决 → 优先 Prompt Engineering。 > 如果你的问题必须在多轮、多智能体、长时间跨度内解决 → 必须升级到 Context Engineering。
具体来说,当你遇到以下任一情况时,说明 Prompt Engineering 已经不够用了:
- 对话轮次经常超过 10 轮
- 需要引用很久之前的对话内容
- 需要整合外部知识(文档、数据库、API)
- 多个 Agent 之间需要共享信息
- 单次调用的成本或延迟已经无法接受
这些场景,正是 Context Engineering 的主战场。
1.3 上下文工程的五大支柱(The Five Pillars of Context Engineering)
从本节开始,我们将进入本书的技术核心。
我们将 Context Engineering 的全部技术,归纳为五大支柱(Pillars)。掌握这五大支柱,就掌握了 Context Engineering 的完整地图。
1.3.1 支柱一:长上下文(Long Context)
定义:充分利用大模型不断扩展的上下文窗口(从最早的 2K 到今天的 1M+ Token)。
核心问题:
- 当上下文窗口足够大时,是不是就可以"无脑塞"全部历史对话?
- 长上下文中,模型的表现真的线性提升吗?
关键洞察(Key Insight):Lost in the Middle(Liu et al., 2023)这篇论文揭示了一个反直觉的现象:在长上下文中,模型对开头和结尾的信息关注度最高,对中间部分的信息容易"视而不见"。这意味着:
> 长上下文 ≠ 长记忆。把所有历史对话塞进上下文,模型依然可能忽略关键信息。
本章不深入展开技术细节(我们会在后续章节详细讨论),但请记住这条原则:长上下文是基础设施,不是解决方案。
1.3.2 支柱二:压缩(Compression)
定义:在保持语义完整性的前提下,减少上下文中的 Token 数量。
核心方法: 1. Token 级压缩:使用专门的 tokenizer-aware 压缩算法,去掉冗余的停用词、空格、重复内容。 2. 结构化压缩:将冗长的自然语言转换成结构化表示(如 JSON、表格、列表)。 3. 语义级压缩:使用模型本身对历史对话进行重写,保留核心信息,丢弃冗余表达。
一个简单的例子:
- 原始文本:"那个用户其实在最开始的时候就告诉我们说他的订单号是 20240315001,这个订单是他 3 月 15 日下的单,然后他说他遇到了一个问题就是说他想要退货,因为商品有质量问题,所以他联系了我们希望能够进行退货处理。"
- 压缩后:`{"order_id": "20240315001", "issue": "退货", "reason": "质量问题"}`
压缩后的 Token 数量约为原文的五分之一,且关键信息完全保留。
1.3.3 支柱三:摘要(Summarization)
定义:当历史对话过长时,使用模型生成一段"对话摘要",作为上下文的"记忆锚点"(Memory Anchor)。
核心方法: 1. 滚动摘要(Rolling Summary):每隔 N 轮,将之前的对话总结成一段固定长度的文本。 2. 分层摘要(Hierarchical Summary):在不同时间尺度上生成不同粒度的摘要(如:每轮摘要 + 每 10 轮摘要 + 每 100 轮摘要)。 3. 增量摘要(Incremental Summary):每次新对话进来时,只更新摘要的增量部分,而非重新生成全文。
与压缩的区别:压缩追求"等长信息密度提升",摘要追求"任意长度的历史 → 固定长度的概要"。如果把压缩比作"把句子写得更紧凑",那摘要就是"把整本书写成一段摘要"。
1.3.4 支柱四:检索记忆(Retrieval Memory)
定义:将历史对话、外部知识存储在向量数据库(Vector Database)中,需要时按相关性检索注入上下文。
核心思想:不要把所有记忆都装进上下文,只装当前任务需要的。
这是 RAG(Retrieval-Augmented Generation)思想在记忆管理上的延伸:
- 把每一条历史消息都嵌入(Embed)成向量
- 当用户提问时,先用问题检索最相关的 K 条历史消息
- 只把这 K 条消息注入上下文,而不是全部历史
优势:
- 突破上下文窗口限制(理论上可以"记住"无限长的历史)
- 显著降低成本和延迟
- 自然支持跨会话、跨用户的长期记忆
挑战:
- 检索的精度(Precision)和召回率(Recall)
- 嵌入模型的语义理解能力
- 检索结果与当前上下文的融合策略
1.3.5 支柱五:状态管理(State Management)
定义:对 Agent 的"心智状态"(Mental State)进行显式建模和持久化。
核心思想:一个真正强大的 LLM 应用,不应该每次都"从头开始思考"。它应该有:
1. 工作记忆(Working Memory):当前任务相关的临时信息 2. 长期记忆(Long-term Memory):跨会话保留的用户偏好、历史行为 3. 任务状态(Task State):多步任务的执行进度、已完成步骤、待办事项 4. 元状态(Meta State):反思、规划、目标、信念
与单纯"对话历史"的区别:
- 对话历史是被动记录的"流水账"
- 状态管理是主动建模的"心智模型"
一个简单的判断标准: > 如果你的应用重新启动后,模型表现得像"失忆"——那就是状态管理没做好。
1.3.6 五大支柱的关系
五大支柱不是孤立的,它们在真实系统中协同工作:
```mermaid flowchart LR P1[Long Context] --> M[Memory System] P2[Compression] --> M P3[Summarization] --> M P4[Retrieval Memory] --> M P5[State Management] --> M M --> Out[Optimized Context for LLM] ```
一个完整的 Context Engineering 系统,会同时使用这五种技术来处理不同类型的信息:
- 最近几轮对话 → 直接装入长上下文
- 较早的历史对话 → 压缩后保留
- 跨越多个会话的用户偏好 → 摘要 + 检索
- 复杂任务的执行状态 → 显式状态管理
五大支柱的协同,本质上回答了一个问题:"在每一次 LLM 调用时,进入窗口的应该是什么?"
1.4 为什么上下文是有限资源:成本、延迟与注意力衰减
我们已经多次提到"上下文是有限的",但为什么要反复强调?因为只有理解了限制的来源,才能做出正确的工程决策。
1.4.1 成本视角:每一 Token 都在烧钱
我们用真实数字来说明:
假设你使用 GPT-4o(截至 2024 年中):
- 输入价格:约 \$2.5 / 1M Tokens
- 输出价格:约 \$10 / 1M Tokens
一个典型的多轮 Agent 应用,每次调用输入 8K Tokens、输出 2K Tokens:
- 单次调用成本:约 \$0.04
- 如果每天 10 万次调用:每天 \$4,000,每年约 \$1.46M
如果你的 Agent 因为上下文管理不当,把上下文从 8K 膨胀到 8