你好,AI!你好,上下文! / Hello, AI! Hello, Context!
第 1 章 你好,AI!你好,上下文!
Hello, AI! Hello, Context!
> 🎯 本章核心问题:什么是上下文(Context)?为什么 AI 需要上下文才能好好说话? > > 🧒 目标读者:9–14 岁的小朋友,以及所有想知道"AI 到底是怎么读懂我说话"的大朋友。 > > 🛠️ 本章工具:Python 3.10+、tiktoken 库、一颗好奇心。
---
1.1 故事开始:第一次遇见 AI 的小 C
小明今年 11 岁,是小学五年级的学生。他最近收到了一份生日礼物——一个叫做"小 C"的 AI 助手。小 C 住在爸爸的笔记本电脑里,像一个会打字的精灵。
第一天,小明满怀期待地打开了对话框,敲下了第一行字:
> 小明:`你好!`
屏幕上的小 C 几乎是瞬间就回复了:
> 小 C:`你好呀!很高兴认识你,我可以帮你做什么吗?😊`
小明开心极了,接着问:
> 小明:`你知道我叫什么名字吗?`
这一次,小 C 的回复让小明有点困惑:
> 小 C:`抱歉,我暂时不知道你的名字。你可以告诉我吗?`
小明皱起了眉头:"咦?刚才我不是已经跟你说话了吗?你怎么不知道我叫什么?"
他又试了一次,这次他特意在开头写下了自己的名字:
> 小明:`我叫小明,今年 11 岁。我最喜欢的颜色是蓝色。请你记住。` > 小 C:`好的,小明!我记住了:你 11 岁,最喜欢蓝色。有什么我可以帮你的吗?`
"原来是这样!"小明恍然大悟——原来 AI 并不是像人一样"一直记得"所有事情。它只在这一次对话里知道你说的话。一旦你关掉对话框,或者开始一段新的对话,它就又变成"什么都不知道的小 C"了。
这个小 C 在和你"说话的当下"能看到的全部信息,就叫做 上下文(Context)。
> 🌟 核心定义(中英对照) > > Context is everything the AI knows when it talks to you. > 上下文就是 AI 在和你说话时知道的所有信息。
这一章,我们就来揭开"上下文"的神秘面纱:它从哪里来?有多大?是怎么组成的?为什么 AI 没有它就像失忆的鹦鹉?
---
1.2 上下文的三个层次:你以为的 vs 真实的
很多人以为,AI 知道世界上所有事情。但其实,AI 只知道"你告诉它的"和"它在训练时学到的"。我们可以用一个三层的蛋糕来描述 AI 看到的世界:
```mermaid flowchart TD A[AI 眼中的世界 = 三层蛋糕] --> B[第一层:训练知识层] A --> C[第二层:上下文窗口层] A --> D[第三层:用户输入层] B --> B1[训练时学到的事实、语法、世界常识] C --> C1[本次对话所有消息 系统提示 工具返回] D --> D1[你刚刚发的那条消息] D1 --> C1 C1 --> B1 ```
让我们一个一个看:
1.2.1 训练知识层(Training Knowledge)
这是 AI 在被"制造"出来的时候,工程师们喂给它的大量书籍、网页、新闻、代码、对话……比如:
- 维基百科上的所有文章
- 图书馆里成千上万的书
- GitHub 上的开源代码
- 新闻网站、论坛、博客
AI 通过阅读这些材料,学会了语法、事实、推理方式,甚至学会了"如何像人一样聊天"。这部分知识在 AI 完成训练之后就被冻结了,AI 不会自动更新——你今天告诉 AI 明天有日食,它在下次对话里依然会"忘记"。
> 🧠 小贴士:训练知识是 AI 的"长期记忆",但它是只读的——AI 自己改不了,你也不容易直接改。
1.2.2 上下文窗口层(Context Window)
这是我们这一章要重点讲的"工作记忆"。它就像是 AI 的桌面——所有"正在用的文件"都摊开在上面。桌面的面积是有限的(这就是后文要说的"上下文窗口大小"),所以当文件太多,旧的就会被挤出桌面,AI 就"忘记"了。
桌面上通常放着这几样东西:
| 文件类型 | 英文名 | 举例 | |---------|--------|------| | 系统提示 | System Prompt | "你是一个友善的助手,回答要简洁" | | 用户消息 | User Message | "小明:今天天气怎么样?" | | 助手回复 | Assistant Message | "小 C:我没有实时数据……" | | 工具返回 | Tool Response | 搜索引擎返回的 5 条结果 | | 多模态内容 | Multimodal | 图片、音频(被转成文字后) |
1.2.3 用户输入层(User Input)
这是你刚刚按下回车的那条消息。它会被追加到上下文窗口的末尾,然后整个上下文被一次性喂给 AI,AI 根据这整段文字预测下一个回复。
> 🎯 关键洞察:AI 在生成回复的那一刻,只能看到上下文窗口里的内容。窗口之外的信息,无论多重要,它都不知道。
所以,当我们设计一个 AI 应用时,最重要的事情之一就是:如何把正确的信息放进上下文窗口。这是后文每一章都会反复出现的主题。
---
1.3 超级鹦鹉:LLM 到底是怎么说话的?
要理解上下文,先要理解谁在读这个上下文。答案是:大语言模型(Large Language Model, LLM)。
1.3.1 一个让你惊讶的事实
LLM 做的事情,本质上只有一件:
> 给定一段文字,预测下一个最有可能出现的词。
就这样?没有别的了吗?真的没有了。
但神奇的是,当这个"预测下一个词"的工作被重复几十亿次,并且模型被做得足够大、训练数据足够多之后,它就会"涌现"出惊人的能力:写诗、做数学题、写代码、解释科学概念、模拟角色……
我们可以用一个比喻来理解:
> 🦜 超级鹦鹉比喻 > > 想象有一只超级鹦鹉,它读过图书馆里所有的书。当你对它说话时,它不会"思考"你说的话是什么意思——它只是在找规律:根据前面这些字,接下来最可能出现的字是什么? > > 因为它读的书太多,所以它的"找规律"能力极强,强到看起来像"真的懂了"。但本质上,它仍然只是在续写。
1.3.2 用数学语言描述"预测下一个词"
假设我们有 $N$ 个词的历史:$w_1, w_2, \ldots, w_N$,LLM 要做的事情就是估计下一个词 $w_{N+1}$ 的概率分布:
$$P(w_{N+1} \mid w_1, w_2, \ldots, w_N)$$
然后从这个分布里采样一个词,输出。再把新词加入历史,重复上面的过程。
这叫做自回归生成(Autoregressive Generation)。我们用 Python 伪代码来表示:
```python def generate(history_tokens, llm, max_new_tokens=50): output = [] for _ in range(max_new_tokens):
计算下一个 token 的概率分布
logits = llm.forward(history_tokens) # shape: [vocab_size] probs = softmax(logits) # shape: [vocab_size]
按概率采样一个 token
next_token = sample(probs) output.append(next_token)
把新 token 加入历史
history_tokens = history_tokens + [next_token]
如果遇到结束符,提前停止
if next_token == END_TOKEN: break return output ```
> 💡 小贴士:在真实的 LLM 里,模型是并行计算整个序列的(用一种叫 Transformer 的结构),但每生成一个词都要把它"喂回去"重新算一遍,这就是为什么生成长文本会比较慢。
1.3.3 为什么这跟"上下文"有关?
既然 LLM 只能看到当前输入的整段文字来预测下一个词,那么这段"输入的整段文字"就是它的"上下文"。也就是说:
> 上下文 = LLM 用来预测下一个词的依据。
如果上下文是空的,LLM 就只能瞎猜;如果上下文里有详细的信息,LLM 就能给出更精准的回复。所以——
> 🌟 黄金法则:想让 AI 回答得好,就给它对的上下文。
这是后 14 章我们要反复训练的核心能力:上下文工程(Context Engineering)。
---
1.4 上下文窗口:AI 的"短期记忆"有多大?
既然 LLM 是"读一段文字然后续写",那它一次能"读"多长?这就是 上下文窗口(Context Window) 的概念。
1.4.1 一个生动的类比:书桌 vs 房间
我们再用一个比喻来理解:
- **训练知识** → 你**一辈子**学到的所有知识(住在你的长期记忆里)。
- **上下文窗口** → 你**书桌**上摊开的资料(短期记忆)。你只能看到桌面上摊开的东西,桌面有多大,就能摊多少。
如果桌面上只摊得开 1 张 A4 纸,那 A3 大小的资料就只能卷起来放到一边,你"看不到"——对 LLM 来说就等于不存在。
1.4.2 主流模型的上下文窗口对比
让我们看看 2024–2026 年主流 LLM 的上下文窗口大小(数字会不断更新,请以官方文档为准):
| 模型 | 上下文窗口(tokens) | 大约能装下 | |------|---------------------|-----------| | GPT-3.5 | 16 K | 一本短篇小说 | | GPT-4 | 128 K | 一本厚厚的平装书 | | Claude 3.5 Sonnet | 200 K | 整整一本《哈利·波特》全集 | | Gemini 1.5 Pro | 2 M | 几千页的财报或代码库 | | Kimi (月之暗面) | 200 K–2 M | 同上 |
> ⚠️ 注意:这里的"token"还不是"字"或者"单词"。在第 1.5 节我们会详细讲 token 是什么。先记住:上下文窗口衡量的是"token 数量",不是"字数"。
1.4.3 上下文窗口里的"竞争"
上下文窗口虽然是"一张大桌子",但桌子的面积是有限的。当我们往里塞东西时,会发生"竞争":
```mermaid flowchart LR A[系统提示 500 tokens] --> F[上下文窗口 200K tokens] B[工具描述 2000 tokens] --> F C[历史对话 8000 tokens] --> F D[当前用户输入 300 tokens] --> F E[检索回来的文档 50000 tokens] --> F F --> G{是否溢出?} G -- 否 --> H[正常生成] G -- 是 --> I[裁剪最旧的内容] ```
也就是说,系统提示、工具描述、历史对话、检索文档、当前问题这些会"争抢"上下文窗口的空间。如果一个应用的设计者不懂得管理这个窗口,就会出现:
- 最早的对话被裁掉 → AI 忘了用户姓名
- 工具描述太长 → 没空间放真实内容
- 检索文档塞太满 → AI 没空间"思考"
所以,优秀的上下文工程,本质上是一门"空间管理"的艺术。
1.4.4 长上下文的"陷阱"
听起来上下文窗口越大越好,是不是?不一定。长上下文 ≠ 高质量输出。这里有几个常见陷阱:
1. 大海捞针问题(Needle in a Haystack):当上下文很长时,模型可能会"忽略"中间或尾部的重要信息,注意力集中在开头和结尾。 2. 位置偏差(Position Bias):模型对上下文的不同位置有"偏好",开头和结尾的信息往往被利用得更充分。 3. 成本飙升:API 通常按 token 收费,200K 上下文的成本可能是 8K 上下文的 25 倍。 4. 延迟增加:处理更长的上下文需要更多计算,响应会变慢。
> 🎯 实战经验:在大多数实际应用中,8K–32K 的高质量上下文比 200K 的"灌满"上下文效果更好。
---
1.5 Token:文字的乐高积木
现在我们要回答一个非常关键的问题:AI 看到的"文字"到底是什么?
1.5.1 一个反直觉的事实
AI 看到的不是"字",也不是"单词",而是 token。你可以把 token 想象成乐高积木:
- 一篇文章 = 一堆积木
- 每一个 token = 一块拼好的小积木
- AI 处理的是**这些小积木**,而不是原始的"字"或者"单词"
举几个例子(以 GPT 系列的 BPE 分词器为例):
| 英文 | tokens(简化展示) | |------|-------------------| | `Hello` | `["Hello"]` | | `Hello world` | `["Hello", " world"]` | | `I love AI` | `["I", " love", " AI"]` | | `unbelievable` | `["un", "bel", "ievable"]` | | `🙂` | `["🙂"]` | | `你好` | `["你", "好"]` | | `人工智能` | `["人", "工", "智能"]`(不同模型不同) | | `今天天气真好` | `["今天", "天气", "真", "好"]` |
你会发现,token 不一定等于一个字或一个单词。常用的字词会被合并成一个 token,生僻的词会被拆成更小的片段。
1.5.2 为什么 AI 不直接处理"字"?
这个问题问得好!主要有三个原因:
1. 效率:英文里如果用"字母"作为单位,那么一个单词可能有 10+ 个字母,会让序列变得非常长,模型计算成本飙升。 2. 稀有词问题:如果用"单词"作为单位,那"unbelievable"和"inconceivable"就完全不一样,模型见不到几次就学不会。而 token 可以把"un"、"bel"、"ievable"这些片段作为通用积木,组合出无数新词。 3. 跨语言统一:同一个分词器可以同时处理英文、中文、代码、emoji。
1.5.3 字节对编码(BPE):token 是怎么切出来的?
大多数现代 LLM 使用 BPE(Byte Pair Encoding,字节对编码) 算法来切分 token。流程如下:
```mermaid flowchart TD A[准备大量训练文本] --> B[把每个字符当作一个 token] B --> C[统计相邻 token 出现的频率] C --> D{最频繁的相邻对是什么?} D --> E[把最频繁的相邻对合并成新 token] E --> F{达到词表大小?} F -- 否 --> C F -- 是 --> G[得到最终词表] G --> H[用这个词表切分新文本] ```
用一个极简的例子说明。假设语料库里有这些词:
``` low low low low low lowest lowest newer newer newer newer newer newer wider wider wider ```
开始时每个字符是一个 token。我们反复找"出现频率最高"的相邻对,把它们合并:
1. `e r` 出现 13 次(`er` 在 newer/wider 中) → 合并成 `er` 2. `er` + `n` 在 `n` 后面出现 → `n er`?其实更可能是 `ne` + `w` 这种。 3. 经过多轮合并……
最终,词表里就会有 `low`、`lower`、`newest`、`widest` 这些"语义完整的"token。这就是为什么"常用词"是一个 token,而"生僻词"会被拆碎。
1.5.4 中英文 token 数量差异
这是一个非常重要的实战知识点:
> 同样的"字数",中文文本需要的 token 数量通常是英文的 1.5–2 倍。
为什么?因为中文每一个字都"信息量更大",分词器倾向于把每个字单独切出来。粗略估算:
| 文本 | 字数 | 大约 tokens | |------|------|-------------| | `Hello, world!` | 13 个字符 | 4 tokens | | `你好,世界!` | 6 个字 | ~10 tokens | | 一本 10 万字的中文书 | 100,000 字 | ~150,000–200,000 tokens | | 一本 10 万词的英文书 | ~120,000 字符 | ~150,000 tokens |
> ⚠️ 预算提示:做中文应用时,token 预算要比英文多留 30%–50% 的余量。
1.5.5 token 的"身份证":词表与编号
每个 token 在词表(vocabulary)里都有一个整数编号(ID)。比如:
``` "Hello" → token id 15496 " world" → token id 995 "!" → token id 0 "你" → token id 53901(不同模型不同) "好" → token id 16143 ```
LLM 内部处理的,其实就是这些整数 ID,而不是字符串。字符串只是给我们人类看的"皮肤"。
---
1.6 为什么上下文是有限的?—— GPU 内存的物理边界
小朋友可能会问:"为什么 AI 不能一次读一亿字呢?"这是一个特别好的问题,答案藏