什么是上下文工程:AI的记忆科学 / What is Context Engineering: The Science of AI Memory
第1章:什么是上下文工程:AI的记忆科学
想象一下:你有一位非常聪明的朋友,他的知识储备堪比一座图书馆,解题能力堪比数学教授,写代码的速度比你快十倍。但是,这位朋友有一个奇怪的限制——每次和你对话时,他只能"看到"你最近说的几千个字。之前的聊天内容,他会逐渐忘记。如果你不把重要信息写下来递给他,他根本无法基于它们做判断。
这位朋友,就是我们今天要研究的大型语言模型(Large Language Model,简称 LLM)。
而"上下文工程"(Context Engineering),就是帮助这位朋友更好地使用他有限"记忆"的科学与艺术。
1.1 一个聪明但健忘的朋友:上下文的直观理解
在日常生活中,"context"(上下文)这个词随处可见。阅读时,前一段落是后一段落的 context;打电话时,对方刚才说的话是你下一句回应的 context;下象棋时,棋盘上的局势是你的 context。换句话说,context 就是让当前事件变得可以被理解的所有背景信息。
把这个概念搬到 AI 世界:当一个语言模型回答你的问题时,它并不是从"虚无"中生成答案的。它需要依赖某些输入信息——你刚才的问题、之前几轮的对话、系统提示词、检索到的外部文档……这些信息的总和,就是模型的"上下文"。
但这里有一个关键限制:模型在一次推理(inference)中能够"看到"的信息总量是有限的。这个上限,就叫做"上下文窗口"(Context Window)。
打个比方。如果你把语言模型想象成一个正在答卷的学生,那么:
- **模型权重(Model Weights)** ≈ 这名学生多年学习积累的知识和能力
- **上下文窗口(Context Window)** ≈ 这场考试时摆在他面前的、可以翻阅的参考材料
学生的"内在知识"是相对固定的,但"参考材料"是每次考试可以变化的。理解这一点,是理解上下文工程的第一步。
上下文窗口的大小一般用"token"来计量。Token 可以粗略地理解为一个"词片段"——英文里大约 1 个 token ≈ 0.7 个单词;中文里 1 个汉字 ≈ 1.5–2 个 token。一个 token 到底有多长?OpenAI 官方给过一个经验法则:1 token ≈ 4 个英文字符 ≈ ¾ 个英文单词。
GPT-3.5 的上下文窗口是 16K tokens(约 24,000 个英文单词,或约 12,000 个汉字)。GPT-4 Turbo 把它提升到 128K。Claude 3 系列进一步推到 200K。Gemini 1.5 Pro 甚至达到了 1M tokens。这意味着你可以一次性把一本中等厚度的书喂给模型。
但是——这是本书要反复强调的一点——"窗口大"不等于"用得好"。这是一个常见的误解。更大的窗口只是让你能装下更多信息,并不意味着模型能同样好地利用所有位置的信息。这是后文要讲的 Lost in the Middle 现象的伏笔。
1.2 上下文的精确定义:模型在一次推理中能"看到"的所有信息
让我们把"上下文"这个概念拆得更精确一些。
在一次标准的 LLM 推理中,模型实际上"看到"的输入通常由以下几个部分组成:
1. 系统提示(System Prompt):开发者预先设定的"人设"或"行为准则",例如"你是一个耐心的数学老师"。 2. 用户输入(User Input):你当前这一轮说的话或问的问题。 3. 对话历史(Conversation History):之前几轮你与模型的对话记录。 4. 外部检索内容(Retrieved Context):通过 RAG(Retrieval-Augmented Generation)等技术从外部知识库中检索到的相关文档。 5. 工具调用结果(Tool Outputs):如果模型调用了外部工具(比如计算器、搜索引擎、数据库查询),返回的结果也会进入上下文。 6. 少样本示例(Few-shot Examples):为了让模型"看样学样"而提供的输入-输出示例。
这六类信息,全部合在一起,构成了模型在某次推理中的"上下文"。它不是单一的东西,而是一个动态拼装的"信息包裹"。
下图展示了一次 LLM 推理中信息从用户输入到模型输出的完整流动过程:
```mermaid flowchart LR A[User Input<br/>用户输入] --> D[Context Assembly<br/>上下文窗口组装] B[System Prompt<br/>系统提示] --> D C[Conversation History<br/>对话历史] --> D E[RAG<br/>外部检索] --> D F[Tool Output<br/>工具结果] --> D D --> G[LLM Inference<br/>大语言模型推理] G --> H[Model Output<br/>模型输出] H --> I[Return to User<br/>返回用户] H --> C ```
注意右下角那个箭头:模型的输出本身也会进入下一轮的上下文,成为后续对话历史的一部分。这就是为什么多轮对话会逐渐"消耗"上下文窗口——每多一轮,新的输入和输出都要被加进 context,直到塞满为止。
理解了这一点,你就能看出"上下文工程"要做的事:不是直接修改模型本身,而是精心设计这个信息包裹的内容、结构和顺序,让模型在有限的窗口里做出尽可能好的判断。
1.3 人类工作记忆 vs 机器上下文窗口:一场有益的类比
在认知心理学中,有一个非常著名的概念叫"工作记忆"(Working Memory)。它指的是人类大脑在执行认知任务(比如心算、理解一段话、规划下一步行动)时,短时间内保持并主动操作信息的能力。
经典的工作记忆模型认为,人类一次只能在工作记忆里保持大约 7±2 个"块"(chunk)。后来的研究把这个数字下调到 4±1。不管是 7 还是 4,都说明人类的工作记忆是有限的、需要主动维护的、容易受干扰的。
LLM 的上下文窗口和人类工作记忆有惊人的相似性:
- 都是**有限的容量**(虽然 LLM 的"7"是 128K 或 200K,远超人脑)
- 都**受位置影响**——开头和结尾的内容通常被记得更清楚,中间容易被忽略(人类也有 primacy 效应和 recency 效应)
- 都**会被无关信息干扰**——给太多无关紧要的 context,模型和人一样会"分心"
- 都**会随时间衰减**——对话轮次多了,早期信息的影响力会下降
但是,这个类比也有几个关键区别,必须明确指出,否则会误导你:
第一,衰减机制不同。 人类的工作记忆如果不主动复述,几十秒到几分钟就会显著衰减。而 LLM 的上下文窗口是"原样保存"的——只要还在窗口内,每一个 token 都以完全相同的强度被模型访问。LLM 不会"忘记"窗口里的早期内容,但它可能会"忽略"它们(这是 Lost in the Middle 要讨论的现象)。
第二,可控性不同。 人类可以在工作记忆里主动"重排"信息,比如把一个数字从短期记忆移到长期记忆。LLM 没有这种主动管理机制——它只能"读取"当前窗口里的内容。你必须通过 prompt 设计、外部存储(向量数据库等)等手段来模拟这种"主动管理"。
第三,容量尺度不同。 人类工作记忆是 7±2 个 chunk,LLM 是数十万到上百万 tokens。差了五六个数量级。这个差异让我们有时会产生一种错觉:"反正窗口这么大,塞进去就行。"但研究表明,模型对超长上下文的利用效率并不线性增长——超过某个阈值后,每多塞一段内容,边际收益急剧下降。
把这三点结合起来看:LLM 的上下文窗口不是人脑工作记忆的简单放大版,而是一种全新的、需要专门工程方法去驾驭的信息结构。
这就是"上下文工程"作为一个独立学科存在的根本原因。
1.4 为什么大模型不是"全知全能"的
很多刚刚接触 LLM 的青少年朋友会有一种印象:"AI 什么都懂,问它什么都能答出来。" 这种印象在前几次使用时确实成立——因为 GPT-4 这类模型在训练时已经"读过"了人类互联网上很大一部分公开文本,从维基百科到 GitHub 代码,从学术论文到 Reddit 评论。
但这里有几个关键限制,常常被忽略:
第一,知识有截止日期(Knowledge Cutoff)。 模型的知识来自训练数据,而训练数据是在某个时间点之前抓取的。GPT-4 的训练数据大致截止到 2023 年 10 月(不同版本略有差异)。如果你问它"昨天谁赢了世界杯",它答不上来,因为它根本不知道"昨天"发生了什么。
第二,模型权重是"冻结"的。 训练完成后,模型的参数就固定了。你不能通过和它聊天来"教会"它新知识——至少不能直接地、永久地教会它。你今天告诉它"我家小狗叫豆豆",明天它会忘记,因为它没有把这个信息写进它的参数里。
第三,模型没有真实世界的"感官"。 它看不到你的表情,听不到你的语气,感受不到你的情绪。它只能处理 token 序列。所以如果你不把背景信息写进 context,它就完全不知道。
第四,模型会产生"幻觉"(Hallucination)。 在某些情况下,模型会"编造"看起来合理但实际错误的信息。比如它可能自信地告诉你某篇论文的作者和发表年份,但那是它"想象"出来的——因为它的训练目标只是"生成下一个 token",而不是"保证事实正确"。
第五,长上下文下的能力退化。 这是 2023 年以来学术界和工业界重点关注的问题,后面 1.6 节会专门讨论。
那么,模型怎么弥补这些缺陷?答案是:通过 context。
当我们想要让模型回答"昨天世界杯的结果"时,我们可以在问题旁边附上实时检索到的新闻摘要。当我们想要让模型知道"我家小狗叫豆豆"时,我们可以在每次对话中把这条信息重新放进 prompt(或者让它存储在外部记忆系统中,每次自动加载)。当我们想要减少幻觉时,我们可以在 context 里附上权威资料,让模型"基于这些资料回答"。
上下文是弥补模型"无知"的关键机制。 这就是为什么在 LLM 时代,"context" 的地位从"输入数据"升级成了"一种需要被设计、被管理、被优化的核心资产"。
1.5 从提示词工程到上下文工程:一场范式的演进
如果你在 2022 年底到 2023 年中接触过 AI 应用,你大概率听说过"提示词工程"(Prompt Engineering)这个词。当时的网红教程是"如何写出让 GPT 表现更好的 7 个魔法短语",各种"Prompt 工程师"培训班也应运而生。
提示词工程当然重要。但随着应用复杂度的提升,人们逐渐发现:仅靠优化用户那一句话的措辞,已经远远不够了。
举个真实场景。假设你在做一个"AI 法律助手",用户问:"这份租房合同里有没有对我不利的条款?" 这时模型需要的 context 包括:
- 整份租房合同的文本
- 当地的租房法规(比如某些城市禁止房东收取"钥匙押金")
- 之前几轮对话中用户提到的"我是学生,没有稳定收入"等个人情况
- 系统的角色设定("你是一个严谨的法律顾问,回答时需要明确指出风险等级")
- 必要的少样本示例("对于押金退还条款,请按以下格式输出风险评估……")
这些信息中,只有最后一个是用户自己写的"prompt"。其他都需要系统主动拼装到 context 里。
"上下文工程"就是要做这些事。学术界和工业界大致把它的工作分为以下几类:
| 工作内容 | 说明 | 典型技术 | |---|---|---| | 上下文写入(Writing Context) | 决定把哪些信息放进窗口 | Prompt 设计、模板生成 | | 上下文选择(Selecting Context) | 从海量候选中挑出最相关的信息 | RAG、向量检索、记忆系统 | | 上下文压缩(Compressing Context) | 在有限窗口里塞进更多信息 | 摘要生成、token 优化、KV-cache 复用 | | 上下文隔离(Isolating Context) | 让不同任务/不同用户的信息不互相干扰 | 多租户架构、对话分支 | | 上下文调度(Scheduling Context) | 决定信息进入窗口的顺序和时机 | 滚动窗口、注意力引导 |
与传统提示词工程的区别,可以用一句话概括:
> 提示词工程关心"如何把一句话问好";上下文工程关心"如何为模型构建一个完整的、可工作的信息环境"。
提示词工程是上下文工程的一个子集。但上下文工程还要处理对话历史、外部知识、工具调用、记忆管理、窗口优化等一系列更宏观的问题。这是从"问对问题"到"设计整个认知环境"的范式跃迁。
对于青少年读者来说,你可以这样理解:
- **写好一道题的问题** ≈ 提示词工程
- **设计一张试卷的出题策略、参考答案、评分标准** ≈ 上下文工程
前者重要,但后者决定了一场考试的总体质量。
1.6 Lost in the Middle:上下文退化的实证证据
2023 年 7 月,Stanford 和 Samaya AI 的研究者发表了一篇题为 *"Lost in the Middle: How Language Models Use Long Contexts"* 的论文(作者包括 Nelson F. Liu 等)。这篇论文后来成为上下文工程领域的奠基性引用之一。
它做了什么呢?研究者们设计了一系列实验,让模型阅读一段很长的文档(比如 50 个段落),然后回答关于其中一个特定段落的问题。他们系统性地改变"目标段落"在文档中的位置——有时在开头,有时在正中间,有时在结尾——然后观察模型的准确率变化。
结果令人震惊:
- 当**关键信息放在文档开头**时,模型表现最好。
- 当**关键信息放在文档结尾**时,模型表现次之。
- 当**关键信息放在文档中间**时,模型表现**急剧下降**。
把模型准确率作为"目标信息位置"的函数画出来,得到的是一条U 型曲线(有时也叫"浴缸曲线")。这是非常反直觉的——如果上下文窗口真的是"一视同仁地处理所有 token",那准确率应该是平的,不应该随位置变化。
这个现象被命名为 "Lost in the Middle"(迷失在中间)。它的含义是:模型对位于长上下文中间位置的信息利用效率显著低于首尾位置。
后续的研究(比如 Anthropic 2024 年关于"Context Rot"的内部研究)进一步证实了这一点,并补充了几个新发现:
1. 即使是 Claude 3 这种 200K 窗口的"长上下文之王",在处理超长输入时也会出现能力退化——只是退化得更平缓、阈值更高。 2. 无关信息的干扰比想象中严重。在 200K 上下文中塞入 1 条相关信息 + 大量无关信息,模型找到那条信息的概率会明显下降。 3. 上下文窗口的"有效利用率"远低于标称容量。标称 128K 不等于能高效利用 128K。
这些发现对实际工程有直接含义:
- **重要的指令和关键信息应该放在开头或结尾**,尽量避开中间位置。
- **不要迷信"窗口大"**。如果你的应用需要长 context,应该设计检索和重排机制,让关键信息优先出现在模型注意力最集中的位置。
- **结构化比堆叠更有效**。清晰的标题、分隔符、编号可以让模型"看见"信息的层次,提升检索和理解效率。
- **定期"刷新"上下文**。当对话变长时,主动做摘要、压缩,把旧信息浓缩后腾出空间。
Lost in the Middle 是上下文工程领域第一个被广泛接受的"反直觉定律"。它告诉我们:更大的窗口不是免费的午餐,工程上的精心设计仍然不可或缺。
1.7 动手实践:用 Python 观察上下文窗口
理论的介绍到此为止,现在让我们把双手弄脏,做一个真实的小实验。
本节将带你用 Python 调用 OpenAI 的 API(或兼容的 LLM API),观察两个现象: 1. 超出上下文窗口会发生什么? 2. 同样的关键信息放在不同位置,模型的回应是否不同?
实验一:超出上下文窗口的截断
步骤 1:准备环境
确保你已经安装了 `openai` Python 包:
```bash pip install openai ```
然后设置你的 API key(推荐用环境变量,避免硬编码):
```python import os from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) ```
步骤 2:构造一个超长输入
我们用一个简单的方法构造一个非常长的字符串——重复一段文本很多次:
```python base_sentence = "Python 是一种广泛使用的高级编程语言,强调代码的可读性和简洁的语法。"
重复这段话 5000 次,得到一个超长字符串
long_input = (base_sentence + " ") * 5000
估算 token 数(粗略估算:英文 1 token ≈ 0.75 个单词)
print(f"输入长度(字符数): {len(long_input)}") print(f"估算 token 数: {len(long_input) // 3}") ```
步骤 3:让模型阅读并回答
```python question = "请告诉我:Python 强调什么?(用一句话回答)"
messages = [ {"role": "system", "content": "你是一个简洁的助手。"}, {"role": "user", "content": long_input + "\n\n" + question} ]
response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, max_tokens=100 )
print("模型回答:", response.choices[0].message.content) ```
步骤 4:观察现象
你大概率会看到两种结果之一:
- **API 直接报错**:返回 400 错误,提示 "context length exceeded"。
- **模型返回的回答质量明显下降**:因为部分输入被截断