从"金鱼记忆"说起:什么是AI的上下文
你刚和 AI 聊了二十分钟,问它五分钟前说过的那件事,它却一脸茫然——这种"对话失忆"现象,并不是 bug,而是一个根本性的设计特征。要看懂背后的原理,必须先回答两个基础问题:AI 的"上下文"到底是什么?为什么它必然有长度限制? 让我们从一个你我都熟悉的比喻开始。
1.1 金鱼记忆与人类短时记忆:AI 也会"忘事"吗?
据说金鱼的记忆只有 7 秒,7 秒前游过的鱼食它就忘了。这个笑话广为流传,背后却并非全无道理——工作记忆(working memory)的容量有限,是包括人类在内所有"有认知能力的系统"都面临的约束。
回想一下你自己的课堂经历:老师讲一道数学题,前 5 分钟讲的内容你通常还能复述;但如果这是一节持续两小时的课,老师在第 90 分钟突然回头问第 10 分钟的定义,你能确定自己还记得吗?多数人会犹豫一下。这不是因为你笨,而是因为人类大脑的工作记忆容量大约只有 7±2 个信息块(这个数字来源于心理学家 George Miller 在 1956 年的经典研究)。一旦信息量超过这个范围,最早的内容就会被"挤出去"。
AI 面临完全相同的处境。它也有一个容量有限的"工作空间":每说一句话,AI 就把这句话与之前的对话历史塞进这个工作空间。一旦装不下,最早的内容就会被"挤掉"。这个工作空间有正式的名字——上下文窗口(Context Window)。
> 想象你正在写作业,面前有一张一米宽的桌子。语文课本、数学卷子、英语词典都摊在桌上,桌子上的东西你都"看得见"、能"用得上";桌子下面塞进抽屉的东西,你暂时就不知道了。 > 这张桌子的宽度,就是 AI 的上下文窗口宽度。AI 只能"看到"落在它上下文窗口里的内容,看到的就是它此刻掌握的全部信息。
这意味着 AI 没有"长期记忆"。 它既不会主动记住昨天跟你说过的话,也不会因为跟你聊得久就更"了解"你。每一次新对话开始,它都从一张空白桌子重新工作——除非你把过去想让它记住的内容重新摊在那张桌子上。这就是为什么大家管 AI 的"记性差"叫"金鱼记忆"——它确实是在不断刷新工作内存。
那么,AI 这张"桌子"到底有多大?由什么决定?这就要说到下一个关键概念——Token。
1.2 Token:AI 读懂世界的最小积木
AI 不会直接读"中文"或"英文",它读的是 Token。Token 是 AI 用来组装语义的最小积木,把一段文本拆成 Token 的过程叫 Token 化(Tokenization)。
不同 AI 使用的 Token 化算法不一样,但目前主流模型(GPT-4、Claude、Llama 等)大多采用 BPE(Byte Pair Encoding,字节对编码)的某种变体。我们不必深究 BPE 的算法细节,只需要抓住它的核心思想:
> 出现频率高的字或词组合,会被合并成一个 Token;出现频率低的字符,会各自成为一个 Token。
举个例子(具体切分会随模型略有不同):
- 英文 `Hello` → `["Hello"]` (1 个 Token,因 "Hello" 是高频词)
- 英文 `unbelievable` → `["un", "believ", "able"]` (3 个 Token,因低频组合被拆解)
- 中文 `你好` → `["你", "好"]` (2 个 Token,每个字 1 个)
- 中文 `机器学习` → `["机器", "学习"]` (2 个 Token,因这是高频词组)
- 数字 `12345` → `["12", "34", "5"]` 或 `["1234", "5"]` (具体看实现)
粗略的经验法则是:
- **1 个汉字 ≈ 1~2 个 Token**
- **1 个英文单词 ≈ 1~3 个 Token**
- **1 个标点 / 空格 ≈ 1 个 Token**
这就解释了为什么同样的内容,中文往往比英文"更耗 Token"。例如:
> "今天天气真好,我们去公园散步吧。" > 14 个汉字 → 大约 16 个 Token > > "The weather is nice today, let's go for a walk in the park." > 16 个英文单词 → 大约 18 个 Token
信息密度的差异直接体现在 Token 消耗上:中文是一种"信息密度高"的语言——几十个字就能说完的事,英文可能要一两百个单词。但 AI 模型是按 Token 计费的,所以同样的预算下,中文能处理的字符总量往往更少。
要更直观地体验 Token 化,你可以去 OpenAI 提供的在线工具 Tokenizer(https://platform.openai.com/tokenizer)。把感兴趣的文本粘进去,它会实时告诉你每个 Token 是怎么切的。这是一款非常值得把玩的工具,因为它会让你对"AI 到底在看什么"建立具体印象——你会发现同一个汉字在不同上下文里,可能被切成不同的 Token。
理解了 Token,我们才能真正理解上下文窗口——这是 AI 当下能看到内容的 Token 数上限。下面我们来量化它。
1.3 上下文窗口:AI 的"工作记忆"有多窄?
上下文窗口(Context Window)就是 AI 一次能"看到"的最大 Token 数。这个数字因模型而异:
| 模型 | 上下文窗口 | 大约能装下的汉字 | |---|---|---| | GPT-3.5 Turbo(早期) | 4K Token | ~3000 字 | | GPT-3.5 Turbo(16K 版本) | 16K Token | ~12000 字 | | GPT-4(8K 版本) | 8K Token | ~6000 字 | | GPT-4(32K 版本) | 32K Token | ~24000 字 | | Claude 2 | 100K Token | ~75000 字 | | Claude 3 / GPT-4 Turbo | 200K Token | ~150000 字 | | Gemini 1.5 Pro | 1M~2M Token | ~75 万~150 万字 |
光看表格你会觉得"够用"——100K Token 听起来很夸张,能装下几十万汉字。但这里有几个常被忽略的事实:
事实一:上下文窗口是"输入 + 输出"的总和。
AI 不是只"读"不"写"。它读入你的历史消息后,还要"留出空间"给生成的回复。如果你用的模型是 128K 窗口,给 AI 输入塞了 120K Token,AI 留给回复自己的空间就只有 8K Token——多了它就生成不完,会被强制截断。
事实二:对话是线性累积的。
AI 不会"压缩"对话历史,每多一轮对话,新内容都被追加到上下文窗口里。来回 50 轮对话(每轮约 200 Token),仅"问题"和"回复"加起来就 20000 Token。如果其中有粘贴的长文档(代码、论文、报错日志),上万 Token 消耗得飞快。
事实三:系统提示也占空间。
每次调用 API 时,通常会给 AI 一个"系统提示"(System Prompt),告诉它扮演什么角色、遵守什么规则。这部分也算在上下文窗口里。一个精心设计的系统提示动辄上千 Token——再大的窗口也会被悄悄吃掉一块。
所以即使在最"大度"的模型下,上下文窗口依然是个硬约束,会真实影响你能让 AI 干什么。这就是为什么长对话时 AI 会"忘记"前文——不是它不努力,是它的桌子就那么大,最早摆上来的东西被迫要挪走了。
那么问题来了:为什么不能把上下文窗口做成无限大?
1.4 位置编码与注意力机制:Transformer 怎么知道"先后"?
这就要从 Transformer 架构的核心——自注意力机制(Self-Attention)说起了。
Transformer 不像人类那样按顺序阅读。它处理一段文本的方式更像一个"全景镜头":把上下文里所有 Token 摆在一起,然后让每个 Token 互相"对眼神",看看谁跟谁更相关。这种"对眼神"就是自注意力。
具体的计算流程是这样的:
> 1. AI 给每个 Token 算出三个向量:Query(查询)、Key(键)、Value(值)。 > 2. 每个 Token 的 Query 跟所有 Token 的 Key 计算相似度,得到一组权重(即"注意力分数")。 > 3. 用这组权重对所有 Token 的 Value 做加权平均,得到这个 Token 的"上下文表示"。 > 4. 重复多次(多头注意力),得到更丰富的语义。
听起来抽象,我们用一个具体例子感受一下:
> 输入:"小明借给小王一本书。" > 训练有素的 AI 应该识别出:"小明"是"借"的执行者,"小王"是接收者,"一本书"是借的对象。这种"谁对谁做了什么"的关系识别,靠的就是自注意力。每个 Token 都"看"一眼其他所有 Token,自己琢磨出"我和谁关系最密切"。
Transformer 强大之处在此——它能看到全局关系,比只能看相邻词的老式 RNN(循环神经网络)强太多。但有个致命问题:计算量爆炸。
数学上,如果上下文有 $n$ 个 Token,自注意力的计算复杂度是:
$$O(n^2)$$
也就是说,上下文长度翻倍,计算量变成 4 倍;再翻倍,变成 16 倍。从 4K Token 扩展到 128K Token,长度变 32 倍,计算量变 1024 倍。这就是为什么上下文窗口不能无限放大——背后是实打实的算力成本。每家公司都要在用户体验和服务器账单之间做平衡。
还有个关键问题:Transformer 怎么知道 Token 的"先后顺序"?
自注意力计算的是"集合里谁跟谁相关",跟顺序无关。如果你把"我打你"和"你打我"的 Token 序列完全打乱,交给自注意力计算,它在不借助额外信息的情况下,分不清这两句话!
解决方案就是 位置编码(Positional Encoding)。给每个 Token 加一个"位置标签",告诉 AI:"这是第 1 个词"、"这是第 2 个词"……一直编号到第 $n$ 个。
具体实现不是简单的 $[1, 2, 3, ...]$ 这样编号,而是一组精心设计的正弦余弦波:
$$PE_{(pos,\,2i)} = \sin\!\left(\frac{pos}{10000^{2i/d}}\right)$$
$$PE_{(pos,\,2i+1)} = \cos\!\left(\frac{pos}{10000^{2i/d}}\right)$$
其中 $pos$ 是位置,$i$ 是维度索引,$d$ 是向量维度。这套设计的精妙之处在于:AI 不仅能区分每个位置的"身份",还能通过向量之间的运算学到"相对距离"——比如位置 5 和位置 8 的距离,跟位置 10 和位置 13 的距离,在向量空间里是相似的。这种性质对 AI 处理长文本至关重要。
后来研究者又发明了相对位置编码(RoPE、ALiBi 等),让 AI 在面对"超出训练长度的位置"时表现得更好,但核心思想不变:没有位置编码,Transformer 就是个"失序"的阅读器;有了位置编码,它才能区分"谁先谁后"。
下面这张流程图展示了从一段对话输入到 AI 生成回复的完整上下文流:
```mermaid flowchart LR A["对话历史<br/>user + assistant<br/>messages 1...N"] --> B["Token 化<br/>BPE 算法拆分"] B --> C["位置编码<br/>pos 1, 2, 3..."] C --> D["嵌入向量<br/>Embedding"] D --> E["自注意力层<br/>O(n²
flowchart LR A["对话历史<br/>user + assistant<br/>messages 1...N"] --> B["Token 化<br/>BPE 算法拆分"] B --> C["位置编码<br/>pos 1, 2, 3..."] C --> D["嵌入向量<br/>Embedding"] D --> E["自注意力层<br/>O(n²) 注意力计算"] E --> F["输出 Token<br/>逐个生成"] F --> G{"已达上限?"} G -- 否 --> F G -- 是 --> H["生成终止<br/>或截断"] ```
理解了 Transformer 如何处理上下文,我们就能看清一个重要事实:AI 的"记忆"从来不是被动丢失,而是被新内容主动挤掉。它的"遗忘"不是因为时间太久,而是因为上下文窗口装不下新东西。
但这又引出一个更现实的问题——既然上下文窗口是硬约束,AI 又只能"看到"窗口里的内容,那么AI 在真实业务中到底会怎样失败?我们来看一个具体的案例。
1.5 真实案例:一个把客户逼疯的智能客服
让我们虚构一个真实场景——某电商平台的智能客服助手"小E",它的系统提示大致是这样的:
> 你是一名电商客服助手,态度要友善、专业、有耐心。请严格按知识库内容回答用户问题,遇到不确定的情况请引导用户转人工。
公司为了让它"更聪明",把整个产品手册、退换货政策、FAQ 都塞进了上下文(约 8000 Token)。上线第一天,效果看起来不错,AI 回答得头头是道。
但用户开始反馈各种"奇怪"的回答。我们挑一个典型的对话来看:
> 用户(第 1 轮):你们这款 A100 耳机支持无线充电吗? > 小E:支持的,A100 标配 Qi 无线充电底座,2 小时充满,可连续使用 30 小时。 > > 用户(第 2 轮):底座是单独卖还是套装里带的? > 小E:套装内含底座,无需单独购买。 > > 用户(第 3 轮):那如果底座坏了,能单独换吗? > 小E:可以单独补购,联系客服即可。 > > 用户(第 4 轮):补购价格多少? > 小E:99 元。 > > (中间经过几轮其他问题、几段政策讨论……大约聊了 30 轮之后) > > 用户(第 35 轮):你刚才说底座补购是 99 元,但我在你们 APP 上看到是 79 元,哪个对? > 小E:抱歉,根据知识库信息,A100 底座补购价格为 79 元。 > 用户:可你前面对我说的是 99 元啊! > 小E:我没有说过这个价格,请您以最新信息为准。
注意最后这一句——"我没有说过这个价格"。这就是典型的"上下文溢出导致的翻脸不认账"。发生的事实是:在第 30 轮之前,"99 元"那一轮早就被挤出上下文窗口了。从 AI 的视角看,自己确实"没说过"。但用户的体验是:这个客服撒谎。
更糟糕的是,如果第 35 轮这条消息本身也被塞到了一个接近上限的上下文,AI 给出的回答甚至可能被截断——句子说一半就停了,或者干脆出现"乱码字符":
> A100 底座补购价�为 79 元。
这时候用户就只能看到"乱码",还以为是 APP 出 bug。
类似的事故在真实生产环境里层出不穷。我们再举几个高频例子:
- **法律助手忘条款**:对话刚开始用户提到案件类型,到第 20 轮 AI 给出法律建议时,已经忘了案件类型,把"离婚纠纷"的建议套到"劳动仲裁"上。
- **编程助手忘报错**:用户粘贴了一段 5000 Token 的报错日志,AI 给出修改建议。来回几轮后用户问"为什么改完还是报错?",AI 已经忘了原报错是什么,给出与问题无关的建议。
- **陪聊机器人忘偏好**:用户第 1 轮说"我对花生过敏",第 50 轮 AI 推荐餐厅时,主动推荐了"花生酱拌面"——因为偏好早就被挤掉了。
这些事故的共同特征是:用户觉得 AI"应该有"的信息,AI 其实已经没有。这就是"上下文"的残酷真相——它不是 AI 的记忆,而是 AI 当下能看到的视野。视野之外的,都不存在。
理解了这一点,我们就能回答一个根本问题:怎么让 AI 不遗忘? 答案是——把关键信息反复摆到它"视野"里。
但在那之前,我们先用一段代码,把这个"遗忘"的过程亲手演示一遍。眼见为实。
1.6 实践:让 AI 亲手"忘记"你说的话
下面这段 Python 代码可以直接运行。我们要做的事很简单:
1. 准备一段较长的"用户资料"(约 6000 字)。 2. 让 AI 记住其中几个关键事实。 3. 继续对话,塞进越来越多的无关内容,把上下文窗口填满。 4. 再回头问 AI 最早的那几个关键事实,观察它的回答。
> 运行前提:本机已安装 Python 3.8+ 和 `openai` 库;并准备好一个 OpenAI API Key(或国内代理的 API Key,按需替换 `base_url`)。
```python
文件:observe_ai_forgetting.py
运行:python observe_ai_forgetting.py
import os from openai import OpenAI
1. 初始化客户端(按你的实际情况调整 base_url)
client = OpenAI( api_key=os.getenv("OPENAI_API_KEY", "sk-..."),
base_url="https://api.openai.com/v1", # 默认
)
MODEL = "gpt-4o-mini" # 用一个便宜、速度快的模型做演示
2. 构造一段"用户资料",包含 3 个关键事实
USER_PROFILE = """ 用户姓名:张三 职业:建筑设计师 关键事实1:他养了一只名叫"豆豆"的橘猫,豆豆 2021 年出生,最爱吃三文鱼。 关键事实2:他正在主持一个位于杭州的图书馆设计项目,甲方要求 2025 年 6 月交付。 关键事实3:他最近在自学 Rust 编程语言,目标是用 Rust 重写公司内部的 CAD 转换工具。 """
3. 构造若干轮"填充对话",把上下文窗口逐渐撑大
这段文字约 800 Token × 10 = 8000 Token,足以把早期消息挤出窗口
FILLER_ROUNDS = 10 FILLER_TEXT = """ 我们接着讨论一个无关的工程问题:某团队用 React 写了一个内部看板, 遇到了若干性能瓶颈。下面列出的 10 项是该团队遇到的问题,每一项 都附上了一些细节: