Foundations of Context Engineering / 上下文工程基础:从 Prompt 到 Context
第 1 章:Foundations of Context Engineering / 上下文工程基础:从 Prompt 到 Context
为什么一个在 Jupyter Notebook 中只需二十行代码就能跑通的 LLM 应用,一旦部署到生产环境便开始出现"模型遗忘"、"指令漂移"、"账单爆炸"这些几乎不可能在 PoC 阶段预见的问题?这些症状并非某个提示词写得不漂亮,也不是某次 temperature 设得不对,而是源自一个被长期低估的事实:对现代 LLM 系统而言,context(上下文)不是一段文本,而是一种与计算、内存、网络同等重要的一等工程资源。本章将围绕这一核心命题展开,从自回归生成的物理机制出发,论证为什么 token 序列天然地同时承担输入、状态与输出三重角色;继而梳理从 Prompt Engineering 到 Context Engineering 的范式跃迁,给出"Context as a Resource"的四维心智模型与 OAG(Operation–Argument–Goal)三元组视角;最后用 LangChain 复现三种上下文管理策略并量化其成本—质量曲线,作为后续十四章的方法论基石。
1.1 自回归生成与 token 序列的三重身份
要理解"上下文为何值得被工程化",必须先回到 LLM 最底层的运行机制。几乎所有现代大语言模型——无论是 GPT 系列、Claude 系列,还是开源的 LLaMA、Qwen、Mistral——其核心推理过程都可以被刻画为同一个自回归公式:
$$P(y_1, y_2, \ldots, y_T \mid x) = \prod_{t=1}^{T} P(y_t \mid y_{<t}, x)$$
其中 $x$ 是输入,$y_{<t}$ 是已经生成的前缀,$y_t$ 是当前要预测的下一个 token。这一公式看似朴素,却蕴含了一个对工程师至关重要的设计哲学:模型没有显式的"内存结构"——它对世界的全部认知、对任务的全部理解、对历史的全部记忆,都被线性化地塞进了那个名为"上下文窗口"的 token 序列。换言之,token 序列在自回归框架下同时承担了三种彼此不可分割的角色:
1. 输入数据(Input):用户问题、检索到的文档、工具返回值、系统提示——所有"被喂给模型"的内容都以 token 形式进入序列; 2. 运行时状态(Runtime State):模型自身的隐藏状态(KV Cache)虽然物理上独立存储,但其内容完全由当前上下文序列所决定;模型"现在在想什么",等价于"它刚刚看到了哪些 token"; 3. 输出(Output):模型每一步生成的 $y_t$ 也会立即被追加到序列的末尾,作为后续生成的上下文。
这种"输入—状态—输出三者同构于同一条线性流"的性质,是 LLM 系统与传统冯·诺依曼架构最深刻的差异。在传统程序里,输入、状态、输出分别由 stdin、内存、stdout 承载,彼此解耦;而在 LLM 中,三者被强制折叠进同一个容量受限的容器——上下文窗口。这一折叠带来了巨大的灵活度(任何信息只要能转成 token 就能参与推理),也带来了工程上的脆弱性(任何超载、噪声或漂移都会同时污染三种角色)。
可以用一个更具象的类比:把 LLM 想象成一位只能用一块小卡片做笔记的速算选手。卡片正面写着他要回答的问题(输入),背面写着他此前计算出的中间结果(状态),而他的每一笔推导都必须把答案原样擦回卡片正面(输出→再输入)。卡片总面积固定,擦多了会丢失上下文,擦少了会没有空间继续演算。Context Engineering 的全部工作,本质上就是设计这张卡片的写入、擦除、压缩与检索策略。
理解这一同构性后,便能解释很多 PoC 阶段看不到的生产问题。例如,用户抱怨"模型答非所问",表面是输出错误,根因往往是状态被无关输入污染;用户抱怨"模型记不住自己刚说过的话",表面是状态丢失,根因是输出被截断或未被回填到下一轮上下文。当问题可以从"上下文序列的资源管理"角度被统一解释时,一门独立的工程学科便有了立足之地。
1.2 从 Prompt Engineering 到 Context Engineering:范式跃迁
过去三年,LLM 应用开发的关注点发生过三次明显的重心迁移,每一次迁移都对应着系统复杂度的阶跃式上升:
- **2022 年前后:Prompt Engineering 时代**。核心问题是"如何用一段自然语言指令让模型完成目标",典型工具是 prompt 模板、few-shot 示例、CoT 推理链。上下文规模通常 < 2K tokens,任务以单轮 Q&A 为主。
- **2023 年前后:In-Context Learning 与 RAG 时代**。随着 GPT-4、Claude 2 将窗口扩展到 32K–100K,工程师开始把外部知识(文档、数据库、API 响应)注入上下文。核心问题变为"如何在固定窗口内塞入尽可能相关的证据",LangChain、LlamaIndex 等框架应运而生。
- **2024 年至今:Context Engineering 时代**。当应用进入多轮、多工具、多智能体阶段,开发者意识到:单条提示词的质量已经不再是系统性能的主要瓶颈,**真正的瓶颈是"如何在长时序、多源异构、动态演化的信息流中,持续为模型提供一份'刚好够用'的上下文"**。
```mermaid flowchart LR A[Prompt Engineering<br/>单轮 / 2K tokens] --> B[ICL + RAG<br/>检索增强 / 32K-100K] B --> C[Memory + Tool Use<br/>多轮状态 / 函数调用] C --> D[Context Engineering<br/>长时序 / 多源 / 动态] D --> E[Long-Context Systems<br/>百万 token / 流式] E --> F[Multi-Agent State<br/>分布式上下文] ```
这张演进图揭示了一个关键事实:Prompt Engineering 是 Context Engineering 的子集。一段写得很精妙的提示词,本质上仍然是一种"手工编排的静态上下文";而 Context Engineering 要求把这种手工编排上升为一套可度量、可调优、可观测的工程系统。两者的差异不是"提示词写得更好",而是设计粒度从"句子"跃迁到了"资源流"。
用一个工程上的对比来固化这一区分:
| 维度 | Prompt Engineering | Context Engineering | |------|---------------------|----------------------| | 单位 | 一段字符串 | 一份资源清单 | | 设计目标 | 最大化单次输出质量 | 最大化长时序任务的成功率与单位 token ROI | | 主要工具 | 模板、示例、CoT | 压缩器、检索器、状态机、预算分配器 | | 失败模式 | 指令不清 | 上下文墙(遗忘、漂移、失控、爆炸)| | 度量方式 | 人工评测 | token 用量、信号密度、延迟、成本曲线 |
进入 Context Engineering 阶段,意味着工程师必须像对待 CPU、内存、带宽一样,对上下文进行预算、调度、回收和监控。这是本书的工程立场,也是后续所有章节的出发点。
1.3 Context as a Resource:四维心智模型
既然上下文是一等资源,就需要一个能工程化操作它的心智模型。本书提出 Context as a Resource 的四维框架——Capacity(容量)、Bandwidth(带宽)、Noise(噪声)、Lifetime(寿命)——分别回答"装得下吗"、"送得进吗"、"信号够纯吗"、"留得住吗"四个根本问题。
```mermaid flowchart TD CR[Context as a Resource] CR --> CAP[Capacity 容量] CR --> BW[Bandwidth 带宽] CR --> NO[Noise 噪声] CR --> LT[Lifetime 寿命]
CAP --> CAP1[Max tokens per request] CAP --> CAP2[Effective attention span] CAP --> CAP3[KV Cache footprint]
BW --> BW1[Ingest tokens per second] BW --> BW2[Retrieval recall latency] BW --> BW3[Pre-fill vs decode throughput]
NO --> NO1[Signal-to-noise ratio] NO --> NO2[Distractor robustness] NO --> NO3[Instruction-data interference]
LT --> LT1[Cross-turn persistence] LT --> LT2[Decay / staleness] LT --> LT3[Eviction policy] ```
Capacity(容量) 是最直觉的一维,指模型单次请求能容纳的最大 token 数。GPT-4o 支持 128K,Claude 3.5 支持 200K,Gemini 1.5 Pro 支持 1M,部分开源模型通过 RoPE/YaRN 外推可达 10M+。但"窗口大小"并不等于"有效容量"——大量实验表明,模型在长上下文中的有效注意力会随位置显著衰减,所谓的"lost in the middle"现象(Liu et al., 2023)说明:第 1000 个 token 与第 50000 个 token 对最终输出的影响力并不相同。因此 Capacity 维度实际由两个子指标共同决定:物理窗口大小与有效注意力跨度。后者只能通过 needle-in-a-haystack 这类基准实测,无法从模型规格表直接推断。
Bandwidth(带宽) 度量的是"上下文以多快的速度被送进模型"。在 LLM 推理中,延迟由 prefill 阶段(处理输入)与 decode 阶段(生成输出)共同决定,而 prefill 时间与输入长度近似线性相关。一个 100K token 的输入,在某些模型上需要 5–10 秒才能完成 prefill,这构成了"带宽"瓶颈。检索增强系统还会叠加额外的检索延迟。当用户对端到端延迟敏感时(典型阈值 < 2 秒),带宽维度会反过来约束容量——你可能"装得下"但"送不进"。
Noise(噪声) 是最容易被低估的一维。信息论视角下,上下文序列的"信号"是指与当前任务目标直接相关的内容,"噪声"则是其余部分。设上下文总长为 $L$,信号长度为 $S$,则信号密度可定义为 $\rho = S / L$。当 $\rho$ 下降时,模型的输出质量遵循一条近似的负相关曲线——这一关系比容量本身更直接地决定系统表现。实践中,噪声来源包括:无关检索片段、过期对话历史、工具返回的冗余字段、用户闲聊内容、模型自身的前后矛盾输出。"塞得越满越好"是新手最常见的误区。
Lifetime(寿命) 回答"上下文在系统中能存活多久"。在单轮调用中,寿命就是一次请求的输入输出;在多轮会话中,寿命跨越用户可能随时离开又回来的若干小时乃至若干天;在多智能体系统中,寿命更涉及跨进程的持久化。寿命的工程含义是 eviction policy(淘汰策略):当新信息涌入而容量不足时,谁该被丢弃?LRU?按相关性评分?按任务阶段?这一决策会直接塑造用户对系统"记忆力"的感知。
四维之间存在显著的耦合:增 Capacity 会放大 Noise(窗口越大越容易塞入噪声);压 Noise 通常消耗 Bandwidth(需要重排序与压缩);延长 Lifetime 必须用 Capacity 买单;提高 Bandwidth 则反过来放宽 Capacity 约束。优秀的 Context Engineering 不是单独优化某一维,而是在四维之间做联合寻优。这一多目标优化的视角将贯穿全书。
1.4 OAG 三元组:分离静态与动态上下文
有了资源视角,下一步是找到一种结构化方法,把一团混沌的 token 序列拆成可独立管理的子集。本书引入 OAG(Operation–Argument–Goal)三元组 作为上下文结构的最小单元:
- **Operation(操作)**:模型被要求执行的原子动作,如 `summarize`、`translate`、`extract_field`、`call_tool`;
- **Argument(参数)**:执行该操作所必需的输入,如待摘要的文本、待提取的字段名、待调用的工具入参;
- **Goal(目标)**:本次操作需要达成的最终目的,常以自然语言或结构化 schema 表达。
OAG 的关键洞察在于:同一个上下文,可以被拆分为"静态上下文"与"动态上下文"两类。静态上下文由 Operation 模板、Goal 描述、系统提示等"在整次会话甚至整个产品生命周期中基本不变"的内容构成;动态上下文则由 Argument 及其衍生的检索结果、工具返回值、中间推理链等"每次调用都不同"的内容构成。两者在 OAG 三元组中的对应关系如下:
| 组件 | 主要归属 | 变化频率 | 典型管理策略 | |------|----------|----------|----------------| | Operation | 静态 | 极低 | 模板预编译、token 缓存 | | Goal | 静态 | 中 | KV-stable prefix 优化 | | Argument | 动态 | 高 | 检索、压缩、淘汰 |
分离静态与动态,是 Context Engineering 的第一项核心工程动作。一旦分离完成,工程师便可以独立优化两个子集:静态部分争取最小化重复 token、最大化 prefix-cache 命中率(这对成本与延迟都有数量级的影响);动态部分则集中精力做相关性排序、压缩与生命周期管理。
举个具体例子:一个企业知识库问答系统,其 Operation 是 `answer_question_with_citations`,Goal 是"基于提供的文档片段,给出准确、引用清晰的回答"。这两个部分可以固化为系统提示,几乎不随用户查询变化。Argument 则是"用户当前问题 + 检索到的 top-k 文档片段",每次调用都不同。如果不分静态动态,每次调用都会把整段系统提示重新 tokenize 一次,并在 KV cache 中重新计算 prefix 部分——这在大规模部署中是显著浪费。
更深一层,OAG 三元组还提供了一种 可测试的接口契约。Operation 与 Goal 的稳定性,使得我们可以为同一组动态 Argument 编写单元测试("无论检索到什么文档,回答格式都应满足 schema"),这在传统 Prompt Engineering 中几乎不可能做到。
1.5 上下文墙:从 PoC 到生产的四种典型症状
为什么几乎所有 LLM 应用都会在规模化阶段撞上某堵"墙"?本节给出四种最典型症状的工程化解释,并指出它们在 OAG 三元组中的病灶位置。
症状一:遗忘(Forgetting)。用户连续多轮对话后,模型"忘了"自己在第 3 轮说过的关键信息。根因几乎总是动态 Argument 部分的容量溢出:旧对话被裁剪,Operation 与 Goal 虽然还在,但关键的 Argument 历史已经丢失。修复方向:引入显式的 Memory Store,把被淘汰的 Argument 持久化到外部存储,再在需要时按 Goal 检索回来。
症状二:漂移(Drift)。随着会话延长,模型开始偏离最初的 Goal,甚至自相矛盾。根因是静态 Goal 部分的"信号"被大量动态 Argument 稀释——信号密度 $\rho$ 跌落到临界值之下,模型开始"猜"Goal 是什么。修复方向:定期把 Goal 摘要重新注入上下文,或在每轮调用前用一个小模型做 Goal 守门检查。
症状三:失控(Cost Runaway)。月末账单比预期高出数倍,根因往往是 Bandwidth 维度的隐性放大:每个用户会话平均积累了 50K token 的历史,每次调用都把它们完整送进模型做 prefill。修复方向:建立 token 预算(ContextBudget),强制做压缩与淘汰。
症状四:爆炸(Latency Explosion)。P95 延迟从 800ms 飙升到 12 秒,根因是 Capacity 与 Bandwidth 的乘积超出了系统设计上限。修复方向:把长上下文任务拆成"分块检索 + 增量推理",或切换到支持 streaming prefill 的推理引擎。
这四种症状看似独立,实则同源:它们都是 OAG 三元组在四维资源上失衡的不同投影。理解这一点,是从"逐个 debug 症状"升级到"系统化设计 Context Engineering"的关键。
1.6 Context Engineering 五大设计原则
基于前文的资源模型与 OAG 框架,本书凝练出 Context Engineering 的五大设计原则,它们既是后续章节的方法论索引,也是工程师日常评审系统设计 checklist 的最小集合。
原则一:预算(Budget)。永远为每一次调用设定一个显式的 token 预算,并在 OAG 三个组件之间分配配额(典型比例:Operation 10%、Goal 20%、Argument 70%)。预算应作为运行时约束而非文档建议——一旦超出,触发压缩或淘汰。`ContextBudget` 类(见 1.7 节)是这一原则的代码化实现。
原则二:压缩(Compression)。任何进入上下文的 token 都应经过显式压缩。压缩分三个层次:(a)无损压缩——去重、归一化、移除冗余空白;(b)语义压缩——用摘要、向量、引用替代原文;(c)有损压缩——剪枝、量化、淘汰低相关片段。压缩是 Context Engineering 区别于 Prompt Engineering 的最显性技术。
原则三:检索(Retrieval)。当动态 A