上下文工程导论:从 Prompt 到 Context 的范式跃迁
第 1 章 上下文工程导论:从 Prompt 到 Context 的范式跃迁
2024 年,一家头部电商平台在双十一大促中将客服对话系统从单轮 FAQ 机器人升级为基于 GPT-4 的多轮 Agent。模型选型、Prompt 模板、工具调用链路都经过了 A/B 测试,看上去一切就绪。上线第三天,客诉量却比预期激增 4.7 倍。复盘日志显示,用户反复投诉的核心不是"答错",而是"同一个问题问三遍"——系统对五分钟前用户明确陈述过的偏好("我只要深色款,不要赠品")完全失忆,仍然在第二轮、第三轮重新询问尺码、颜色和赠品偏好。事后技术复盘认定:模型本身没有问题,Prompt 模板也没有问题,问题出在系统从未真正"理解"过对话的历史。提示词是一次性的字符串,而用户与系统之间的关系是连续的、有状态的——把连续的运行时状态塞进一次性的字符串里,本身就是范式错配。
这就是上下文工程(Context Engineering)必须独立成学的根本原因。本章将围绕"为什么 Prompt 已经不够"这条主线,从概念辨析、失败模式、案例复盘、能力支柱和架构蓝图五个层面,建立读者对上下文工程的整体认知,为后续 14 章的技术展开提供共同语言。
1.1 为什么 Prompt 工程已经不够用了
Prompt Engineering 在 2022—2023 年几乎是 LLM 应用工程的全部。当时主流范式是:选定一个模型,写好一段 System Prompt,把用户问题拼进 `User` 槽位,调用 `chat.completions.create()`,把返回的字符串渲染给用户。整个数据流是"无状态请求—响应",Prompt 是这段请求中唯一可控的变量。在这一范式下,Prompt Engineering 优化的是"如何用更好的指令激活模型潜能"。
但当应用从"问答玩具"走向"生产级 Agent",这一范式遭遇了三重不可调和的瓶颈。
第一重瓶颈:状态连续性。 真实对话天然是累积的。用户在前一轮告知的所有事实——身份、偏好、上下文、情绪曲线——都应当成为下一轮的推理前提。Prompt 工程的默认假设是"每次调用独立",这意味着开发者必须手工把所有相关历史拼接到当前 Prompt 中。一旦拼接策略出错——漏掉一条、错位一条、过期一条——应用就会出现"健忘"。在工业级系统中,这种拼接工作量占整个对话管理代码的 60% 以上,且极易引入 bug。
第二重瓶颈:信息密度与窗口成本。 一旦开启多轮、工具调用、检索增强(RAG),Prompt 的体量会迅速膨胀。把过去 50 轮对话、20 个工具返回值、5 篇检索原文原封不动地拼接,会导致:(a) 上下文窗口超限;(b) 关键信息被淹没在噪声中("Lost in the Middle" 现象);(c) 单次推理成本随轮次线性增长,账单失控。Prompt Engineering 的工具箱——CoT、ReAct、Few-shot——没有任何一项是为"管理一段长达数十万 token 的动态文本"而设计的。
第三重瓶颈:可观测与可调试。 Prompt 是字符串,字符串可以打印、可以 diff、可以回放。但一段上下文包含的内容远不止字符串:它包括系统状态、用户档案、长期记忆、外部世界快照、工具 I/O、中间推理步骤。这些异构信息以何种顺序、何种格式、何种粒度被组装,本身就是一个工程问题,而 Prompt 工程缺乏相应的建模语言与诊断工具。
这三重瓶颈指向同一个结论:当 LLM 应用的复杂度从"单次问答"跃迁到"长程任务",可控变量从"提示词字符串"扩展为"运行时环境"。这个"运行时环境"就是上下文(Context)。Prompt 只是上下文的一个子集。
1.2 Context 与 Prompt 的本质区别
让我们给出一组可操作的工作定义,这是后续所有章节的术语基础。
> Prompt(提示词):一次模型调用中显式传入模型的指令与问题文本,通常是 `system / user / assistant` 三段式或单条字符串。
> Context(上下文):在一次模型调用之前,由应用层为该调用所装配的全部运行时信息集合。它包含但远不止 Prompt。
一个生产级 LLM 调用的 Context 通常由以下几类异构信息构成:
1. 指令层(Instructions):对应传统 Prompt 的 system 段,定义角色、目标、约束、输出格式。 2. 记忆层(Memory):跨越本次会话甚至跨会话的用户相关事实——偏好、历史任务、既往结论。 3. 状态层(State):当前会话与任务的运行时变量——当前阶段、已完成步骤、待办项、错误码。 4. 工具层(Tooling):可用工具的 schema 定义、历史调用记录、本轮返回值。 5. 检索层(Retrieval):从外部知识库中按当前查询动态召回的文档片段。 6. 感知层(Perception):当前输入的多模态内容——文本、图像、结构化数据、传感器读数。
这六层信息通过上下文组装(Context Assembly)这一核心动作汇聚成一段有限长度的文本(或一组结构化消息),再交给模型推理。Prompt Engineering 关心的是"指令层怎么写更好";Context Engineering 关心的是"六层信息如何按当前任务需要被选择、压缩、排序、注入"。
二者的差异可以用一句话概括:Prompt 是静态的文本;Context 是动态的运行时环境。一个把 Prompt 优化到极致的应用,仍然可能在 Context 层全面崩溃——这正是客服案例的本质。
1.3 AI Agent 失败的三大根因模式
我们在多个企业级项目的故障复盘中发现,AI 应用的上线后故障有相当比例(不同样本下落在 55%—78% 区间,取决于样本选取方式)可被归到三种上下文缺陷之一。需要强调的是,这里的因果链是应用层的,不是模型层的——模型权重不变、推理参数不变、Prompt 模板不变,故障依然会发生。
1.3.1 上下文丢失(Context Loss)
定义:本应进入本轮推理的信息,由于过期、剪枝、超限截断或检索缺失,未被纳入 Context。
典型症状:
- 用户:"我说过不要辣。"
Agent:"请问您需要什么口味?"
- 系统声明的工具列表中缺少本轮任务必需的工具,导致 Agent 反复请求用户确认。
- 长会话超过窗口上限,最早几轮的事实被静默丢弃。
判断方法:检查当前轮 Context 中是否包含完成本轮任务所必需的全部前提。如果"忘记"是确定可复现的——把那条信息重新注入后行为恢复正常——即可判定为上下文丢失。
1.3.2 上下文污染(Context Pollution)
定义:无关、过期、错误或低置信度的信息进入 Context,干扰模型推理。
典型症状:
- 检索模块召回了不相关文档,模型基于错误事实给出答案。
- 旧的失败尝试结果被注入当前推理,模型重复犯同样错误。
- 工具返回的错误信息被无差别保留,导致后续步骤基于错误状态继续执行。
- 多轮对话中,用户的临时性陈述("先随便看看吧")被当作长期偏好固化。
判断方法:检查 Context 中是否存在与本轮任务目标无关或方向相反的信息片段。如果"删除某些片段后行为反而改善",即可判定为上下文污染。
1.3.3 上下文冲突(Context Conflict)
定义:Context 中存在相互矛盾的多条信息,模型无法判断应优先采纳哪一条。
典型症状:
- 系统指令与用户消息冲突(Prompt 注入场景)。
- 不同来源的检索结果给出矛盾事实。
- 长期偏好与本轮明示需求冲突(如长期档案显示用户喜欢深色,但本轮明确说"这次想试试浅色")。
- 不同时序的工具返回值给出不一致状态。
判断方法:检查 Context 中是否存在同一变量、多个不同取值且缺乏优先级标注。冲突与污染的关键区别是:污染是"多了不该有的",冲突是"该有的但互相矛盾"。
丢失、污染、冲突三者常常耦合出现——例如一次失败的检索既污染了 Context(召回了无关内容)又引发了冲突(与已知事实矛盾)。在故障诊断时需要区分主因与伴生现象,主因决定了修复路径。
1.4 案例复盘:智能客服的"健忘症"生产事故
为把抽象的失败模式落到工程层面,我们对前述电商客服事故做完整复盘。这是一份"现场级"的事故分析,读者可以从中看到 Prompt 工程视角与 Context 工程视角的诊断差异。
1.4.1 系统架构(事故前)
- 模型:GPT-4(8K 上下文窗口)
- 框架:单轮 `chat.completions` 调用,无状态
- Prompt:手工拼接的字符串模板,包含 system 角色定义、若干 Few-shot 示例、当前用户消息
- 工具:三个只读工具(订单查询、物流跟踪、退款政策查询)
- 多轮管理:客户端维护会话 ID,每次调用携带完整历史消息数组
1.4.2 事故时间线
- **T+0**:用户进入会话,陈述需求"我要退订上周三买的深色款冲锋衣,不要赠品,订单号 X123"。
- **T+1**:系统调用订单查询,确认存在订单,回应"已为您查询到订单,请问您要退订吗?"(注:用户已经说过要退订)
- **T+2**:用户回复"是的,刚才说了退订。请问退到哪里?"
- **T+3**:系统调用退款政策查询,正确返回"原路退回,3—5 工作日"。同时**再次询问订单号**(该信息已在 T+0 提供)。
- **T+4**:用户投诉"你是金鱼吗?前面说过的话全部忘了。"
1.4.3 Prompt 工程视角的诊断(错误路径)
按 Prompt Engineering 思路检查:
- System Prompt:清晰,角色与目标定义明确。
- Few-shot 示例:覆盖了订单查询场景,未显示遗漏。
- 用户消息:携带了完整历史。
- 模型输出:未见明显幻觉。
诊断结论往往是"模型对长对话的支持不够"或"Prompt 还需要进一步优化",继而陷入反复改写 Prompt 的循环。
1.4.4 Context 工程视角的诊断(正确路径)
按 Context Engineering 思路逐层检查:
| 层级 | 现象 | 根因 | |------|------|------| | 指令层 | 正常 | 无 | | 记忆层 | 缺失 | 用户偏好(不要赠品)从未被结构化提取,仅作为普通字符串存在;T+2 之后 Prompt 长度超过窗口或被截断,导致 T+0 完整陈述丢失 | | 状态层 | 缺失 | 用户的核心意图"退订"从未被提升为状态变量,每轮都被当作新问题处理 | | 工具层 | 正常 | 工具调用结果被保留 | | 检索层 | 缺失(场景不需要) | 无 | | 感知层 | 污染 | T+2 用户带有情绪的"刚才说了"被原样保留,模型在 T+3 一定程度上被情绪化措辞分散注意力 |
根因汇总: 1. 上下文丢失:用户的订单号、退订意图、不要赠品偏好,在多轮拼接过程中未得到稳定保留; 2. 状态层缺位:应用没有显式的"意图状态机",每轮都重新走一遍"询问→确认"流程; 3. 记忆层缺失:用户的长期偏好与本轮关键事实混在普通消息流里,没有任何优先级保护。
1.4.5 修复路径(Context 工程方案)
修复动作分为三步: 1. 结构化提取:在每轮对话结束后,把用户的事实陈述抽取为 `<user_facts>` 结构化字段,注入下一轮 Context 的固定位置。 2. 意图状态机:将"退订"建模为显式状态(`INIT → CONFIRMED → PROCESSING → DONE`),每轮推理前先查询当前状态。 3. 关键事实锁:对订单号、退款方式等核心字段做"位置保护"——始终放在 Context 的靠前或靠后位置(模型对首尾位置注意力最强),不被新消息挤出。
修复后同类场景的用户重述率从 38% 降至 4%。
这个案例的核心教训是:Prompt 写得再好,也无法弥补 Context 层的结构性缺陷。后者是工程问题,需要的是架构与机制,而不是更巧妙的措辞。
1.5 上下文工程的五大支柱
如果说 Context 是一栋大楼,那么这栋大楼由五根结构性支柱支撑。理解这五根支柱及其相互依赖,是后续所有技术章节的认知地图。
1.5.1 长上下文承载(Long Context Carrying)
指模型与应用协同提供的、可被一次性输入的 token 容量。它是 Context 的物理基础,决定了一次推理"看得到多少"。本章涉及的关键判断包括:
- 不同模型在 8K、32K、128K、1M 上下文下的**真实有效注意力范围**(许多模型在超过某一阈值后出现明显的"中间遗忘");
- 长上下文的**成本曲线**(多数 API 按 token 计费,长上下文并非免费午餐);
- **位置敏感度**——模型对首部和尾部内容的注意力通常强于中部。
这一支柱解决的是"放不放得下"的问题。
1.5.2 压缩(Compression)
指在保留关键语义的前提下,把一段长 Context 转换为更短表示的技术。它与"摘要"的区别在于:压缩目标是无损或近无损地减少 token 数量,不追求可读性,常见手段包括:
- 去除冗余空白、标点、模板文本;
- 用结构化键值对替代自然语言叙述;
- 借助小模型做语义等价改写;
- 数值与枚举值的归一化。
这一支柱解决的是"装不下时怎么瘦身"的问题。
1.5.3 摘要(Summarization)
指把一段 Context 浓缩为对人类(或模型)可读的自然语言概要。与压缩的关键差异是:摘要追求可读性与意图保真,允许丢弃细节以保留主线。摘要本身又分层次:
- **滚动摘要**:跨多轮的累积式摘要,每轮更新;
- **层级摘要**:树形结构,越往上越抽象;
- **查询感知摘要**:根据当前问题动态生成侧重大纲。
这一支柱解决的是"如何让长程历史可被快速理解"的问题。
1.5.4 检索记忆(Retrieval Memory)
指从外部存储中按当前任务需要动态召回信息的能力。它与传统 RAG 的差别在于:检索记忆是面向长程对话与跨会话的,召回源包括但不限于向量数据库,还可能包括:
- 结构化用户档案;
- 历史任务的结果缓存;
- 知识图谱子图;
- 上一会话的滚动摘要。
这一支柱解决的是"如何让模型'记起'没有进入当前窗口的信息"的问题。
1.5.5 状态管理(State Management)
指对应用运行时变量(任务阶段、用户意图、待办项、错误状态、工具调用栈等)的显式建模与持久化。它是五根支柱中最容易被低估的一根——许多团队习惯于把状态隐藏在自然语言里(例如让模型自己"记住"自己在哪一步),结果就是 1.3 节的客服案例。状态管理要求:
- **显式建模**:用类型化数据结构描述状态,而非依赖模型自由发挥;
- **持久化**:跨调用持久存在,不因上下文截断而丢失;
- **可序列化**:可被外部系统读写、审计、回放;
- **可观测**:状态转移路径可被日志记录与回溯。
这一支柱解决的是"系统对自己的当前位置是否心中有数"的问题。
1.5.6 五大支柱的依赖与数据流
五根支柱不是平行堆叠,而是构成一条数据处理流水线。下面用一张 Mermaid 流程图描述它们之间的依赖关系与数据流方向。
```mermaid flowchart LR In[用户输入] --> SM[状态管理] SM --> Ret[检索记忆] Ret --> LC[长上下文承载] LC --> Comp[压缩] LC --> Summ[摘要] Comp --> Asm[上下文组装] Summ --> Asm SM --> Asm Asm --> LLM[LLM推理] LLM --> Out[响应输出] Out --> SM Out --> Mem[(长期记忆库)] Mem --> Ret ```
图中关键依赖关系:
1. 状态管理是枢纽:所有其他支柱都从状态层查询"当前在哪一步、需要什么信息",所有其他支柱的输出也写回状态层。 2. 检索记忆依赖状态:检索的查询条件、召回范围、重排策略都由状态决定。 3. 压缩与摘要并列:二者都作用于长上下文之上,但目标不同——压缩追求 token 效率,摘要追求可读性。两者结果都汇入组装阶段。 4. 长上下文承载是物理底座:它的容量上限直接决定了压缩、摘要、检索三者必须把 Context 收敛到多窄。 5. 响应输出双写:输出既回到状态层(更新意图、阶段),又写回长期记忆库(为未来的检索记忆提供素材)。
这张图是后续十四章的导航。每一章都会深入其中一根支柱或多根支柱的交叉地带。
1.6 分层上下文架构的总体蓝图
掌握五根支柱之后,下一步是回答"它们如何在同一个应用中协作"。本节给出本书贯穿使用的分层架构蓝图,读者在阅读后续章节时可以随时回看。
整个 Context 栈自下而上分为五层,每层有明确的职责边界与接口约定:
| 层级 | 职责 | 典型实现 | |------|------|----------| | L0 物理层 | 模型可接受的最大 token 数、定价、首尾注意力偏差 | 模型 API 文档 | | L1 承载层 | 长上下文的写入、滑动窗口、位置保护 | 消息数组构建器 | | L2 加工层 | 压缩、摘要、检索召回 | 函数/工具调用链 | | L3 状态层 | 显式状态变量、转移规则、持久化 | 类型化状态机 + KV 存储 | | L4 编排层 | 跨会话记忆、用户档案、跨任务知识 | 长期记忆库 + 用户档案库 |
L0—L2 关心"一段对话的内部结构",是单次调用的内部流水线;L3—L4 关心"跨调用的外部结构",是跨会话的状态与记忆基础设施。这种"上下分层"的妙处在于:每一层可以独立替换、独立演进——例如把 L2 的压缩函数从启发式换成小模型,对 L3、L4 完全透明;把 L3 的存储从 Redis 换成数据库,对 L0—L2 也完全透明。
本书后续章节的技术地图:
- **第 2—4 章**聚焦 L1—L2:长上下文机制、压缩、摘要的算法与工程。
- **第 5—7 章**聚焦 L3:状态机设计、变量建模、持久化方案。
- *