AI的“工作台”:理解上下文的第一课
第1章:AI的"工作台":理解上下文的第一课
想象一个场景
你走进一家繁忙的餐厅厨房。大厨正在灶台前飞快地翻炒,动作行云流水。你注意到一个有趣的细节:无论大厨的厨艺多么高超,他面前的工作台——那个放满食材、调料和工具的台面——永远只有那么大。
如果一份菜需要的配料太多,工作台放不下怎么办?大厨只能先做一部分,把已经用完的食材挪走,再放上新的。换句话说,大厨的视野始终被限制在这个有限的工作区域内。超出工作台范围的东西,无论多么重要,大厨在当下这一刻都"看不见"。
这个场景,正是理解大型语言模型(Large Language Model,简称LLM)如何工作的钥匙。在AI的世界里,这个"工作台"有一个专门的名字:上下文窗口(Context Window)。而理解它如何运作,将是你掌握"上下文工程"这门技艺的第一课。
让我们从最基本的问题开始:什么是上下文,为什么它对AI如此重要?
---
1.1 厨房的启示:什么是上下文
上下文的直观定义
上下文(Context)这个词来源于拉丁语"contextus",意思是"编织在一起"。在日常生活中,上下文无处不在。当你听到朋友说"它跑了!",你需要上下文来判断他说的是狗跑了、快递跑了,还是某个程序崩溃了。不同的上下文,完全改变了这句话的含义。
对于大型语言模型来说,上下文就是它在处理当前任务时能够看到和使用的全部信息范围。这包括:
- **用户输入的内容**:你刚才问了什么问题
- **对话历史**:你和AI之前交流过什么
- **系统提示**:AI被设定的角色和规则
- **相关的背景知识**:任何被提前加载的信息
用厨房的比喻来说,上下文就是大厨工作台上摆放的所有东西——新鲜蔬菜、调味品、烹饪工具,以及手边的食谱笔记。没有这些,大厨就无法完成一道菜;这些东西不齐全或不清晰,做出来的菜就会走样。
为什么上下文对AI如此重要
你可能会问:AI不是应该很聪明吗?它难道不能像人一样"记住"所有对话内容吗?
这里有一个关键点需要理解:现代的大型语言模型本身并不具备真正的"记忆"功能。这听起来可能有些反直觉,让我们来解释一下。
当你和ChatGPT对话时,你可能以为它在"记住"你们的对话。但实际上,每次你发送一条新消息,AI并不是从它的"记忆"里提取之前的内容。取而代之的是,你的整段对话历史——从第一条消息到最新这条——会被重新组织,作为新的输入发送给模型。
这就像大厨做菜:他并不是在"回忆"上次做菜的过程,而是每次都要把所有食材重新摆上工作台。如果一道菜需要连续做十份,大厨每次都要重新备料、重新开始。这就是为什么在一次长对话中,AI消耗的"资源"会越来越多——它需要处理的数据量在不断累积。
这带来一个根本性的限制:上下文窗口的大小是固定的。无论模型多么强大,它每次能处理的"工作台面积"是有限的。超出这个范围的信息,模型就"看不见"了。
一个简单的实验
让我们用一个思维实验来说明。假设你问AI:
> "小明有5个苹果,给了小红2个,又买了3个,现在有几个?"
这道数学题对人类来说很简单。但如果我们改变一下顺序:
> "小明今天很开心。他有5个苹果。他给了小红2个。她很感谢。后来他又买了3个。请问现在小明有几个苹果?"
人类仍然能轻松回答——答案是6个(5-2+3=6)。但你会注意到,我们的思维过程中需要同时记住最初的数量、给出去的数量、后来买的数量。这是一个需要"放在脑子里"同时处理的信息集合。
对于AI来说,这个"脑子里同时放东西"的能力,就受限于上下文窗口。当对话变得很长,或者需要同时考虑很多信息时,如果超过了上下文窗口的限制,模型就无法准确处理了。
---
1.2 语言的积木:Token的秘密
什么是Token
要理解上下文窗口是如何限制AI的,我们需要先了解AI处理文本的基本单位:Token。
在大型语言模型的世界里,文本并不是按"字"或"词"来处理的。模型会把输入的文字切分成更小的片段,这些片段叫做Token(令牌/标记)。你可以把Token想象成构建语言大厦的"积木块"。
不同的模型使用不同的分词方式,我们以最广泛使用的GPT系列模型为例来说明。GPT使用的是一种叫做Byte Pair Encoding(BPE)的中文可称为"字节对编码"的分词方法。
BPE的工作原理很有意思:它从最基础的字符开始,不断合并出现频率最高的字符对,形成新的"token"。这样做的结果是:
- **英文单词**:通常1-4个token。简单的单词如"hello"可能就是一个token,但长单词可能被切成多个部分,比如"indivisible"可能被分成"ind"+"ivis"+"ible"三个token
- **中文**:由于中文字符数量多且含义丰富,每个汉字通常对应**1-2个token**,具体取决于词语的常见程度
- **标点符号和空格**:通常也算作独立的token
- **数字**:长数字可能被切分,例如"2024"可能被分成"20"+"24"两个token
为什么Token很重要
理解Token的概念对于"上下文工程"至关重要,原因有三:
第一,Token是LLM计费的单位。当你使用各种AI服务时(如OpenAI的API),你不是按照"字数"付费,而是按照处理的token数量计费。1000个token通常被称作1K token,这就是为什么长对话会越来越贵——你需要为越来越多的历史消息付费。
第二,Token数量直接影响响应速度。处理1000个token和处理10000个token,模型需要的时间和计算资源相差巨大。在实际应用中,如果你的输入加上输出的token总和超过了模型限制,请求就会失败。
第三,Token数量决定了你能塞进上下文窗口的内容量。上下文窗口的大小,本身就是用token数量来衡量的。GPT-4 Turbo的上下文窗口是128K tokens,这意味着它理论上可以处理多达12.8万个token的输入——大约相当于10万中文字符或一部长篇小说。
常见文本的Token估算
对于实际估算,以下经验法则很有用:
| 文本类型 | 大约Token数 | |---------|-------------| | 1个英文单词 | 0.75个token | | 1个中文字符 | 1-2个token | | 1页中文(约500字) | 600-1000个token | | 1页英文(约300词) | 400个token | | 一本《哈利·波特》 | 约100万token |
这个表格告诉我们:中文的token消耗大约是英文的两倍左右。这是一个非常重要的实际差异。当你在设计中文AI应用时,同样的上下文窗口能容纳的内容量会明显减少。
---
1.3 工作台的边界:上下文窗口
上下文窗口的定义
上下文窗口(Context Window)是LLM处理能力最核心的限制之一。它指的是模型在单次推理(即生成一次回复)过程中最多能处理的token数量。
这个"最多能处理"包含两层含义:
1. 输入上限:你发送给模型的全部内容(问题、对话历史、系统提示等)加起来不能超过这个上限 2. 输出上限:模型生成的回复也不能超过这个上限(虽然输出上限通常远小于输入上限)
用一个更精确的术语来说,上下文窗口限制的是上下文总量,即输入tokens + 输出tokens的总和。
主流模型的上下文窗口对比
让我们看看当前主流模型的上下文窗口大小:
| 模型 | 上下文窗口 | 约等于中文量 | |------|-----------|-------------| | GPT-3.5 Turbo | 16K tokens | 约1.2万字 | | GPT-4 | 8K / 32K tokens | 约0.6-2.4万字 | | GPT-4 Turbo | 128K tokens | 约10万字 | | Claude 3 | 200K tokens | 约15万字 | | Claude 3.5 Sonnet | 200K tokens | 约15万字 | | Gemini 1.5 Pro | 1M tokens | 约75万字 |
这个对比揭示了一个重要趋势:上下文窗口在短短几年内增长了几十倍。GPT-3在2020年发布时只有约4000个token的限制,而到了2024年,主流模型的上下文窗口已经达到几十万tokens。这意味着AI能"同时看到"的信息量大幅增加,但这并不意味着限制消失了——只是边界被推得更远。
窗口内的信息不平等
一个关键但常被忽视的事实是:上下文窗口内的信息并不是平等对待的。
研究发现,LLM对上下文中不同位置的信息,处理能力是不同的。对于较长的上下文,模型往往表现出"最近偏见"(Recency Bias)——它更容易关注和处理靠近对话末尾的信息,而较早的信息可能会被"淡化"甚至"遗忘"。
这就像大厨的工作台:虽然理论上整个台面都在视野范围内,但实际操作中,大厨肯定会更关注手边正在处理的食材,而台面边缘的东西——尽管确实"能看到"——往往不会被充分关注。
这对于上下文工程来说意义重大:你不能仅仅依靠"把所有信息都塞进窗口"来解决问题。如何在有限的窗口内有效组织信息,让最重要的内容得到模型充分的"注意力",这是一个需要精心设计的工程问题。
---
1.4 消失的记忆:为什么AI会"忘记"
一个真实的困惑场景
让我们用一个具体的例子来说明这个问题。假设你使用AI来进行一个写作项目,对话过程如下:
第一轮: > 你:我想写一篇关于人工智能在教育领域应用的文章,大约2000字。
> AI:当然可以,我来帮你规划这篇文章的结构...
第五轮: > 你:根据我们之前的讨论,把第一部分展开写出来。
第十五轮: > 你:把第三部分中提到的那个案例详细描述一下。
你可能会注意到,在第十五轮时,AI有时会"困惑"——它可能不完全确定"第三部分"具体指的是什么,或者"那个案例"是指哪个案例。随着对话越来越长,这种困惑会越来越明显。
这不是因为AI变笨了,而是因为上下文窗口正在被填满。在继续输入新内容时,早期的对话内容可能已经接近或超过了窗口的限制。
为什么模型本身不"记忆"
这里有一个技术上的关键点需要澄清:LLM的工作方式是无状态的(Stateless)。
当你使用一个LLM API时(除了那些专门设计的"保持会话"的服务),模型本身不会保存任何关于你的对话的信息。每次API调用都是独立的——你发送什么,模型就处理什么。
这和人类大脑的工作方式完全不同。当你和朋友聊天时,你朋友的大脑会自然地记住对话内容,下次见面时还会记得你们之前聊过什么。但LLM没有这样的"大脑记忆"——它只有处理输入的"临时工作区"(上下文窗口)。
这就解释了为什么:
1. 每次对话开始时,模型对之前发生的一切一无所知 2. 对话过程中,你需要重复提供所有必要的信息 3. 如果忘记提供某个关键信息,模型就无法知道
长上下文的技术挑战
即使上下文窗口足够大,能够容纳整个对话历史,长上下文的处理本身也面临技术挑战:
注意力分散问题:Transformer架构(大多数现代LLM的基础)的核心是"注意力机制"(Attention Mechanism)。理论上,注意力可以连接上下文中任意位置的信息。但实际上,当上下文变得非常长时,模型处理远处信息的能力会下降——就像在一间巨大的仓库里找东西,东西越多、越远,就越难找到。
计算成本问题:注意力的计算复杂度是O(n²),其中n是序列长度。这意味着如果序列长度翻倍,计算量会增加四倍。即使技术上能处理超长上下文,实际应用中也会面临成本和延迟的挑战。
信息检索问题:当给模型一个很长的上下文时,它需要从海量信息中"找到"与当前问题相关的部分。这就像让大厨在堆满食材的工作台上找到特定的调料——如果东西太多太杂,找到正确东西的难度就会增加。
这些技术现实意味着,上下文管理不是简单地"塞更多东西",而是一门需要精心设计的工程技艺。
---
1.5 实践出真知:计算你的文字花了多少"积木"
为什么需要亲手计算
了解了Token和上下文窗口的理论,现在让我们动手实践一下。没有什么比亲手操作更能建立直观理解了。
在这一节中,我们将使用Python代码来计算任意文本的token数量。你将看到:
- 一段普通中文大约消耗多少token
- 不同类型内容(纯文本、代码、对话)的token密度差异
- 如何在实际工作中估算token消耗
环境准备
首先,你需要安装`tiktoken`库。`tiktoken`是OpenAI开发的一个高效tokenizer库,专门用于计算GPT系列模型使用的token数量。
```python
安装tiktoken库
pip install tiktoken ```
安装完成后,我们就可以开始了。
基础计算:单段文本
让我们从最简单的例子开始:
```python import tiktoken
使用cl100k_base编码器,这是GPT-4和GPT-3.5 Turbo使用的编码方式
encoder = tiktoken.get_encoding("cl100k_base")
def count_tokens(text): """计算给定文本的token数量""" tokens = encoder.encode(text) return len(tokens)
测试几段不同的文本
test_texts = [ "你好,世界!", # 简短中文 "人工智能正在改变我们的生活方式。", # 中等长度中文 "Python是一种广泛使用的高级编程语言," "由Guido van Rossum于1991年首次发布。" "它支持多种编程范式,包括结构化、过程式、反射式、面向对象和函数式编程。", # 长中文 ]
for text in test_texts: token_count = count_tokens(text) char_count = len(text)
计算token密度:每个token代表多少字符
density = char_count / token_count if token_count > 0 else 0 print(f"文本:{text[:30]}...") print(f" 字符数:{char_count}") print(f" Token数:{token_count}") print(f" Token密度:{density:.2f} 字符/token") print() ```
运行这段代码,你可能会看到类似这样的输出:
``` 文本:你好,世界!... 字符数:6 Token数:6 Token密度:1.00 字符/token
文本:人工智能正在改变我们的生活方式。... 字符数:17 Token数:25 Token密度:0.68 字符/token
文本:Python是一种广泛使用的高级编程语言,... 字符数:90 Token数:104 Token密度:0.87 字符/token ```
有趣的结果:即使是同样的人类语言,token密度也不是固定的。中文的token密度大约在0.7-1.5之间波动,这取决于具体使用的中文词汇。
进阶计算:对话历史
现在让我们模拟一个更真实的场景:计算一段完整对话的token消耗。
```python def count_conversation_tokens(messages, model="gpt-4"): """ 计算对话历史的总token消耗
messages格式示例: [ {"role": "system", "content": "你是一个有帮助的助手"}, {"role": "user", "content": "你好"}, {"role": "assistant", "content": "你好!有什么可以帮助你的吗?"}, ] """ if model.startswith("gpt-4"): tokens_per_message = 3 # 每个消息前的标记 tokens_per_name = 1 # 如果有名字字段,加1个token else: # gpt-3.5-turbo tokens_per_message = 4 tokens_per_name = -1
total_tokens = 0
for message in messages: total_tokens += tokens_per_message for key, value in message.items(): if key == "content": total_tokens += count_tokens(value) elif key == "name": total_tokens += count_tokens(value) total_tokens += tokens_per_name
total_tokens += 3 # 回复前缀的额外token
return total_tokens
模拟一个真实对话
conversation = [ {"role": "system", "content": "你是一个专业的Python编程导师,擅长用简单易懂的方式解释编程概念。"}, {"role": "user", "content": "什么是函数?为什么我们需要使用函数?"}, {"role": "assistant", "content": "函数就像是一个预设的食谱。你可以事先准备好一份食谱(定义函数),写清楚需要什么材料(输入参数)和具体怎么做(函数体)。之后每次想做这道菜时,只需要调用这个食