Token 与上下文窗口:理解 AI 模型的「记忆」与限制

一、为什么 Token 是你必须理解的概念
如果你只用 AI 编程工具做简单的代码补全(Tab 补全),那你可能不需要太关心 Token 是什么。但如果你想要:
- 让 AI 理解和修改一整个文件
- 让 AI 跨多个文件做重构
- 编写精确的 Prompt 来控制 AI 输出
- 理解为什么 AI 有时会"忘记"你之前说的话
那你就必须理解 Token 和上下文窗口这两个核心概念。
在我使用 AI 编程的早期,遇到过一个让我困惑的问题:我给 Claude 贴了一个 1500 行的代码文件,然后在对话中讨论了前面几个函数的优化方案。但当我让它修改文件末尾的一个函数时,它好像"不记得"这个函数的内容了------它的修改建议明显是错误的。
后来我才明白,这不是 AI 的 Bug,而是上下文窗口的特性。当你理解了 Token 和上下文窗口的机制后,这种问题就是完全可以预判和避免的。
二、什么是 Token------AI 的"原子单位"
2.1 Token 的定义
Token(词元)是大语言模型理解和生成文本的最小单位。类比一下:
- 英语中的"字母"是最小书写单位,但人类不会一个字母一个字母地读------我们会把字母组合成"词"
- Token 就是 AI 的"词"------AI 不一个一个字符地读,也不一个一个单词地读,而是把文本切分为一定大小的"Token块"
2.2 Tokenization 的工作机制
文本转为 Token 的过程叫做 Tokenization(分词)。目前主流模型(GPT、Claude)使用的是一种叫 BPE(Byte Pair Encoding)的算法。
BPE 的工作方式是:从字符级别开始,统计最常见的"字符对",将它们合并为一个新的 Token。反复执行这个过程,直到达到预设的词汇表大小。
让我们看一个具体的例子:
原始文本: "const userName = 'hello world';"
BPE Tokenization 过程(简化示意):
Step 1: 先按常见编程符号拆分
["const", " ", "user", "Name", " ", "=", " ", "'", "hello", " ", "world", "'", ";"]
Step 2: 高频组合合并
"user" + "Name" 经常一起出现 → 作为一个 Token
"hello" + " " + "world" → 由于"hello world"是高频短语,"hello"和"world"可能各为一个Token
最终 Token 序列(近似):
["const", " ", "userName", " ", "=", " ", "'", "hello", " ", "world", "'", ";"]
约 12 个 Token
2.3 英文 vs 中文的 Token 消耗差异
这是一个很多中文开发者没意识到的"隐性成本"。不同语言的 Token 效率差异很大:
英文的 Token 效率:
文本: "The quick brown fox jumps over the lazy dog"
Token 数: ~10
比例: 1个单词 ≈ 1.2 个 Token
中文的 Token 效率:
文本: "快速的棕色狐狸跳过了懒惰的狗"
Token 数: ~15-20
比例: 1个汉字 ≈ 1.5-2 个 Token
代码的 Token 效率:
Python: def calculate_sum(a, b): return a + b
Token 数: ~12
中文注释: # 这个函数用来计算两个数的和
Token 数: ~15-20(比代码本身还多!)
📊 各语言 Token 消耗对比表(以表达相同语义为准):
| 语言 | 相对 Token 消耗 | 说明 |
|---|---|---|
| 英文 | 1x(基准) | 拉丁字母与BPE天然契合 |
| 中文 | 1.5-2x | 每个汉字通常占1-2个Token |
| 日文/韩文 | 1.5-2.5x | 类似中文,字符集大 |
| 代码(英文关键词) | 0.8-1.2x | 常见关键字和模式Token化效率高 |
| 代码(含中文注释) | 1.2-1.8x | 中文注释增加额外消耗 |
💡 实用建议:如果使用按 Token 计费的 API(如 GPT-4 API、Claude API),用英文 Prompt 性价比更高。中文 Prompt 的效果可能同样好(甚至在某些情境下更好),但 Token 消耗明显更大。
2.4 代码的 Tokenization 示例
让我们看看一段实际的 JavaScript 代码是如何被 Token 化的:
javascript
// 源代码
function calculateDiscount(price, userLevel) {
if (userLevel === 'vip') {
return price * 0.8;
}
return price;
}
// 大致的 Token 分解(GPT-3.5/4 的 Tokenizer)
// "function" → 1 Token(高频关键字)
// " " → 1 Token
// "calculate" → 1 Token
// "Discount" → 1 Token
// "(" → 1 Token
// "price" → 1 Token
// "," → 1 Token
// " " → 1 Token
// "user" → 1 Token
// "Level" → 1 Token
// ")" → 1 Token
// " " → 1 Token
// "{" → 1 Token
// "\n " → 1 Token
// "if" → 1 Token
// 依此类推...
// 总计约 30-35 个 Token
💡 高频出现的关键字(function、if、return)、常见变量名、常用模式(=> 箭头函数)在 Token 表中有自己的"专属位置",通常一个 Token 就够了。
三、上下文窗口------AI 的"工作记忆"
3.1 上下文窗口的概念
上下文窗口(Context Window)是模型在一次推理中能"看到"的 Token 最大数量。它决定了 AI 的"视野范围"。
用一个类比来理解:上下文窗口就像你在一个白板上写字。白板的大小是固定的------当白板写满后,你若要写新内容,就必须擦掉一些旧内容。同样,当 Token 数量超出上下文窗口后,模型就会"忘记"最早的内容。
上下文窗口示意图:
[最早的内容] ... [中间的内容] ... [最新的内容]
|<-------------- 上下文窗口 --------------->|
超出窗口的内容会被"截断"------模型完全看不到了
3.2 上下文窗口的历史演进
上下文窗口的增长速度是 AI 进步最显眼的指标之一:
| 时间 | 模型 | 上下文窗口 | 约等于 |
|---|---|---|---|
| 2018 | GPT-1 | 512 | 一段话 |
| 2019 | GPT-2 | 1,024 | 一页文字 |
| 2020 | GPT-3 | 2,048 | 一篇短文 |
| 2022 | GPT-3.5 | 4,096 | 一篇长文 |
| 2023 | GPT-4 | 8,192 | 一个章节 |
| 2023 | GPT-4 Turbo | 128,000 | 一本小说 |
| 2024 | Claude 3 | 200,000 | 300页的书 |
| 2024 | Gemini 1.5 Pro | 1,000,000 | 一部《战争与和平》 |
从 512 到 1,000,000------增长近 2000 倍,仅用了 6 年时间。这个速度甚至超过了摩尔定律。
3.3 200K Token 意味着什么
Claude 3 的 200K Token 上下文窗口,对编程来说意味着:
≈ 150,000 个英文单词
≈ 300 页的技术文档
≈ 50,000-80,000 行代码(取决于语言和注释密度)
≈ 可以一次性放入以下内容:
- 一个中型 Next.js 项目的所有源文件
- React 官方文档 + Express.js 文档 + Prisma 文档
- 整个项目的 package.json + tsconfig + 所有组件的完整代码
3.4 "近因效应"------不是所有 Token 都同等重要
虽然模型有大的上下文窗口,但研究表明:模型"注意"得最多的是靠近末尾的内容。这被称为"近因效应"(Recency Bias)或"Lost in the Middle"现象。
上下文中各部分的"注意权重"分布(示意):
高权重:开头部分("首因效应")
中低权重:中间部分("Lost in the Middle")
最高权重:结尾部分("近因效应")
建议:把最重要的信息放在开头或结尾,不要让核心指令"沉"在中间
💡 实用建议:
- 将核心指令和约束放在 Prompt 的最后(最新位置)
- 将背景信息和示例放在 Prompt 的前部
- 在长对话中,定期重申关键要求
- 对于特别重要的约束,在对话的各个环节反复强调
四、上下文窗口对编程的影响
4.1 单文件代码理解
当你让 AI 理解一个代码文件时,首先需要确保整个文件的内容能放进上下文窗口。
一个 1000 行的 JavaScript 文件 ≈ 10,000-15,000 Token
→ 在 GPT-4(8K)中放不下 → 在 GPT-4 Turbo(128K)和 Claude 3(200K)中完全没问题
4.2 多文件代码修改
这是上下文窗口最能体现差异的场景。假设你要做一个涉及 5 个文件的修改:
文件1(页面组件): 200 行 ≈ 2,500 Token
文件2(API路由): 150 行 ≈ 1,800 Token
文件3(数据模型): 100 行 ≈ 1,200 Token
文件4(类型定义): 80 行 ≈ 1,000 Token
文件5(工具函数): 120 行 ≈ 1,500 Token
对话历史 + Prompt + AI响应: ≈ 4,000 Token
总计: ~12,000 Token
这个总数在 8K 的窗口(GPT-4)中已经很拥挤了------可能放不下完整的 5 个文件加上充分的对话讨论。但在 128K(GPT-4 Turbo)和 200K(Claude 3)中,还有大量空间可以添加相关文档、API 规范等额外上下文。
4.3 长对话的"记忆丧失"
当你与 AI 进行长时间的多轮对话时,早期交换的信息会逐渐被"推"出上下文窗口。
第 1 轮对话:讨论项目架构(占 5,000 Token)
第 2 轮对话:讨论数据库设计(占 8,000 Token)
第 3 轮对话:开始写代码(累计 13,000 Token)
...
第 10 轮对话:修改 bug(累计 35,000 Token)
此时,第 1 轮讨论的架构细节可能在 GPT-4(8K)中完全消失了,
只在 GPT-4 Turbo 和 Claude 3 中还能被"记住"。
⚠️ 这就是我在文章开头提到的那个问题的原因------当我把 1500 行代码放进会话,经过多轮讨论后,文件末尾的内容虽然一开始在上下文中,但随着新内容的不断加入,AI 对较早位置的"注意力"减弱了。
五、高效管理 Token 的实用策略
5.1 文件拆分策略
不要把一个 3000 行的文件直接丢给 AI。相反,采用"分层递进"策略:
步骤①:给 AI 项目的骨架
"这是我的项目结构:[目录树]
我要修改 [具体功能] 模块,请先帮我理解相关的文件。"
步骤②:给 AI 关键部分
"这是 [具体文件] 的核心部分(前 200 行),请分析..."
步骤③:选择性提供上下文
"这是和当前修改相关的 [类型定义/接口/配置],请参考..."
步骤④:总结代替完整代码
"文件中剩余的 800 行是 [功能描述],如果你需要某部分的具体代码,请告诉我。"
5.2 选择性上下文(Selective Context)
给 AI 的上下文不是"越多越好",而是"越相关越好":
❌ 不好的做法:
把整个项目的 50 个文件全部放进 Prompt
→ 浪费 Token,AI 反而可能被无关信息干扰
✅ 好的做法:
只放与当前任务直接相关的文件:
- 正在修改的文件
- 被修改文件的依赖(它 import 了什么)
- 类型定义和接口
- 相关的配置文件
5.3 Prompt 压缩技巧
一些减少 Token 消耗的实用技巧:
去掉不必要的空白和注释(仅在你发送给 AI 的代码中):
javascript
// 原始(包含格式化空白和开发注释)
function getUserById(id) {
// 查询用户信息
const user = await db.user.findUnique({ // Prisma 查询
where: { id: id },
include: { profile: true }
});
return user;
}
// 压缩后(去掉注释和多余空白,AI 仍然能理解)
function getUserById(id) {
const user = await db.user.findUnique({
where: { id },
include: { profile: true }
});
return user;
}
用简洁的描述替代完整的文件内容:
"项目的 ESLint 配置是标准配置 + React plugin + TypeScript plugin"
(而不是粘贴整个 .eslintrc.json 文件)
在大文件场景中,用 // ... (略) 标记省略部分:
javascript
function firstFunction() { /* ... */ }
function secondFunction() { /* ... */ }
// ... (此处省略 500 行业务逻辑函数)
// 以下是要修改的核心部分:
function processOrder(order) {
// 这个函数有 150 行
// ...
}
5.4 "分而治之"策略
对于大型任务,不要试图在一个对话中完成所有工作:
# 错误做法:一个长对话处理一切
对话1(Token 0 → 50K):讨论需求 → 设计架构 → 生成所有代码 → 修改 → 测试
→ 后期 AI 的"记忆"已经严重过载
# 正确做法:分阶段、分对话
对话1(Token 0 → 15K):需求分析和架构设计 → 输出:设计文档
对话2(Token 0 → 20K):基于设计文档,实现模块 A → 完成并测试
对话3(Token 0 → 20K):基于设计文档,实现模块 B → 完成并测试
对话4(Token 0 → 10K):集成测试和问题修复
→ 每个对话都有充足的上下文空间
5.5 利用工具自动管理上下文
一些工具已经内置了智能的上下文管理:
- Claude Code:自动选择相关文件放入上下文,而非全部文件
- Cursor:通过 .cursorrules 和项目索引来优选上下文
- GitHub Copilot:分析最近查看和编辑的文件,自动构建上下文
💡 经验法则:让工具帮你管理上下文,你只需要关注"当前这个对话中聊什么"。
六、常见 Token 管理错误与避免方法
错误一:把所有文件都放进 Prompt
错误示范:
"我有以下文件:[粘贴了10个文件,总计5000行代码]。
帮我给所有文件的函数加错误处理。"
问题:AI 被过多的信息淹没,可能漏掉某些文件或做出不准确的修改。
✅ 改进方法:分文件逐个处理,并在需求中明确具体文件。
错误二:忽视 Token 消耗的"隐形大户"
错误示范:
在 Prompt 中粘贴了一个 200 行的 JSON 配置文件(包含了 AI 不需要知道的
所有细节),而这个文件的"精华"信息其实只有 5 行。
✅ 改进方法:提取关键信息,而非全量粘贴。对于 JSON 配置,只给出结构或关键字段。
错误三:在长对话中依赖 AI "记住"所有内容
错误示范:
第 1 轮:我告诉 AI 使用 async/await 风格
...(50 轮对话后)
第 51 轮:我让 AI 写一个新函数,期望它还记得 async/await 的偏好
→ AI "忘了",因为它已经被推出上下文窗口
✅ 改进方法:在关键指令被"遗忘"前,主动重申。或在工具层面设置(如 CLAUDE.md 中的规范会在每次对话开始时重新加载)。
错误四:中文 Prompt 中不必要的啰嗦
错误示范:
"请帮我写一个能够处理用户登录请求的 Express.js 路由处理函数,
这个函数需要接受用户名和密码两个参数,然后..."
Token 数:~100(中文)+ 代码
改进版:
"写 Express.js 登录路由:接收 username + password,JWT 认证,bcrypt 加密"
Token 数:~30(中文+英文混排)+ 代码
✅ 改进方法:Prompt 中使用简洁的关键词 + 英文技术术语(API、框架名等),减少 Token 浪费。
七、总结
Token 和上下文窗口是大语言模型最基础的"物理限制",理解它们就像理解计算机的内存和硬盘------你不需要成为硬件专家,但了解基本概念能在你编程时做出更好的决策。
核心要点:
- ✅ Token 是 AI 处理文本的最小单位,中英文的 Token 效率不同
- ✅ 上下文窗口决定了 AI 一次能"看到"多少内容
- ✅ 模型对靠近末尾的内容"注意"得更多(近因效应)
- ✅ 大型上下文窗口(128K+)让理解整个项目成为可能
- ✅ 高效管理上下文的关键是:选择性提供信息 + 分层递进 + 工具辅助
- ✅ 避免常见错误:信息过载、忽视隐形 Token 消耗、依赖 AI 的长时记忆
掌握了 Token 和上下文窗口的知识,你就理解了为什么有时候 AI 的"记忆"好像不稳定------这不是 Bug,而是机制。下一章我们将探讨 AI 编程的三种核心模式。
下一篇:AI 编程的三种模式:代码补全、对话生成与智能体自动编程