Day 3 详细展开:Transformer 直觉(下)—— 上下文窗口与温度采样
欢迎来到第三天!今天我们将完成 Transformer 直觉的最后一块拼图,并动手实验两个对实际开发至关重要的概念:上下文窗口(Context Window)和温度(Temperature)。理解它们将帮助你更好地控制模型的行为,避免踩坑。
一、今日学习目标
- 理解上下文窗口是什么,为什么它决定了模型能“记住”多少内容。
- 掌握 Temperature 参数的作用:它如何控制模型输出的随机性与确定性。
- 通过代码实验,直观感受不同 Temperature 值对生成文本的影响。
- 学会使用
tiktoken计算输入文本的 Token 数,并处理超长输入(截断或摘要)。 - 了解
max_tokens与上下文窗口的关系,以及如何处理输出截断问题。
二、详细实现步骤
步骤 1:上下文窗口(Context Window)的概念
大语言模型在处理文本时,并不是逐字逐句地“阅读”,而是将整个输入(包括系统提示、历史对话、用户问题)拼接成一个 Token 序列,然后一次性输入到 Transformer 中。这个序列的最大长度就是上下文窗口。
- DeepSeek-chat 的上下文窗口是 64K Token(即约 64000 个 Token)。这意味着你的
messages列表中所有内容的 Token 总数 + 模型将要生成的 Token 数不能超过这个限制。 - 如果输入超过了窗口,API 会报错(通常提示
maximum context length)。 - 上下文窗口决定了模型能“看到”多少信息。对于 Agent 来说,这意味着对话历史不能无限累积,必须进行管理(裁剪、摘要或使用外部记忆)。
为什么重要?在构建 Agent 时,你经常需要将工具返回的结果、历史对话、知识库片段拼接起来。如果不加控制,很容易撑爆窗口,导致请求失败。
步骤 2:动手实验 —— 不同 Temperature 下的生成差异
Temperature 控制采样时的随机性。值越低,模型越倾向于选择概率最高的词(确定性高);值越高,模型越可能选择概率较低的词(创造性高,但可能跑题或产生胡言乱语)。
新建temperature_experiment.py:
importosfromdotenvimportload_dotenvfromopenaiimportOpenAI load_dotenv()client=OpenAI(api_key=os.getenv("DEEPSEEK_API_KEY"),base_url="https://api.deepseek.com")prompt="请写一首关于秋天的五言绝句,要求押韵。"temperatures=[0,0.5,1.0,1.5]fortempintemperatures:response=client.chat.completions.create(model="deepseek-chat",messages=[{"role":"user","content":prompt}],temperature=temp,max_tokens=60,seed=42# 设置随机种子(DeepSeek 支持 seed 参数,可使结果可复现))content=response.choices[0].message.contentprint(f"--- Temperature ={temp}---")print(content)print()运行脚本:
python temperature_experiment.py观察与思考:
- Temperature = 0:模型几乎是贪婪解码,输出非常确定,通常每次运行结果相同(如果设置了
seed则完全一致)。诗句可能比较“保守”,但可能缺乏新意。 - Temperature = 1.5:模型开始“放飞自我”,可能会出现不常见的词语、奇怪的搭配,甚至语句不通顺。创造性高,但质量不稳定。
- Temperature = 1.0:是默认值,通常是一个平衡点。
- 对于需要事实准确性的任务(如数学计算、知识问答),应该使用较低的 Temperature(0 或 0.2);对于创意写作、头脑风暴,可以适当提高(0.7~1.0)。
步骤 3:处理上下文窗口 —— 超长输入的测试与截断
现在我们编写一个脚本,演示如何检测输入长度并处理超长情况。
新建context_window_demo.py:
importosimporttiktokenfromdotenvimportload_dotenvfromopenaiimportOpenAI load_dotenv()client=OpenAI(api_key=os.getenv("DEEPSEEK_API_KEY"),base_url="https://api.deepseek.com")# 使用 tiktoken 进行 Token 估算(DeepSeek 推荐使用 cl100k_base 近似)enc=tiktoken.get_encoding("cl100k_base")defcount_tokens(text:str)->int:returnlen(enc.encode(text))deftruncate_text(text:str,max_tokens:int)->str:"""将文本截断到 max_tokens 个 Token 以内"""tokens=enc.encode(text)iflen(tokens)<=max_tokens:returntext truncated_tokens=tokens[:max_tokens]returnenc.decode(truncated_tokens)# 模拟一个很长的用户输入(重复一段话)long_text="人工智能正在改变世界。"*2000# 大约 2000 * 10 = 20000 字符,Token 数可能更多print(f"原始文本长度(字符):{len(long_text)}")print(f"原始文本 Token 数:{count_tokens(long_text)}")# 假设我们设定一个安全阈值,例如只保留前 5000 Token 作为上下文MAX_INPUT_TOKENS=5000truncated_text=truncate_text(long_text,MAX_INPUT_TOKENS)print(f"截断后 Token 数:{count_tokens(truncated_text)}")# 现在用截断后的文本调用模型(这里我们只截取一部分,实际可能还需要加系统提示等)try:response=client.chat.completions.create(model="deepseek-chat",messages=[{"role":"system","content":"你是文本摘要助手,请总结用户输入的核心内容。"},{"role":"user","content":truncated_text}],temperature=0.2,max_tokens=100)print("模型摘要:",response.choices[0].message.content)exceptExceptionase:print("调用失败:",e)# 另外,故意发送超过上下文窗口的文本(可选,注意可能会产生费用,谨慎操作)# 这里我们模拟一个超过 64K Token 的文本,但为了不真的发送,只演示计算huge_text="测试 "*40000# 大约 40000 个 Token? 实际可能更多print(f"\n模拟超长文本 Token 数:{count_tokens(huge_text)}")ifcount_tokens(huge_text)>64000:print("警告:此文本超过了 DeepSeek 的 64K 上下文窗口,直接发送会报错。")# 实际可以尝试发送,但会报错,不建议花这个钱运行脚本:
python context_window_demo.py观察与思考:
- 我们使用了
tiktoken来估算 Token 数,这是防止上下文溢出的第一步。 truncate_text函数简单粗暴地截断了尾部,但在实际应用中,你可能需要更智能的策略(如保留开头和结尾,或使用摘要模型)。- 在实际开发中,你应该根据模型的最大上下文窗口减去
max_tokens(输出预留)来设置输入的安全阈值。 - 测试超长文本时,可以先计算 Token 数而不发送请求,避免浪费。
步骤 4:max_tokens与输出截断
max_tokens参数控制模型最多生成多少个 Token。如果生成的文本达到这个上限,模型会停止输出,可能导致句子不完整。你需要根据上下文窗口预留足够的输出空间。
修改temperature_experiment.py,将max_tokens设得很小(例如 10),观察输出是否被截断:
# 在上述脚本中,将 max_tokens 改为 10,再运行一次response=client.chat.completions.create(model="deepseek-chat",messages=[{"role":"user","content":prompt}],temperature=0.7,max_tokens=10)print("截断输出:",response.choices[0].message.content)print("结束原因:",response.choices[0].finish_reason)# 可能为 'length'finish_reason为length表示因为达到max_tokens而停止,这通常不是我们想要的结果,需要增加max_tokens或让模型在有限 Token 内完成。
三、常见问题与调试
Q1:设置temperature=0后,为什么输出仍然不完全确定?
→ 理论上temperature=0应该使用贪婪解码(每次选择概率最高的词),但实际 API 可能仍然存在浮点计算差异或模型服务端的微小随机性。此外,如果你没有设置seed参数,多次调用可能因服务器负载导致细微差异。对于确定性要求高的场景,可以设置seed(DeepSeek 支持),并固定所有参数。
Q2:如何知道我的输入到底有多少 Token?
→ 使用tiktoken的encode方法进行估算。对于非 OpenAI 模型,比如 DeepSeek,官方推荐使用cl100k_base编码,虽然可能不是 100% 精确,但足够用来规划。
Q3:我的对话历史太长,除了截断还有什么办法?
→ 常见策略有:
- 滑动窗口:只保留最近的 N 条消息。
- 摘要:使用模型将较旧的历史摘要成一段简短的文本,再拼接到新的上下文中。
- 向量检索(我们后面会学到):将历史对话存入向量数据库,只检索最相关的部分。
Q4:上下文窗口是不是越大越好?
→ 上下文窗口越大,模型能一次处理的信息越多,但也有缺点:更长的输入意味着更多的计算量(成本更高、速度更慢),而且模型对长文本中间部分的注意力可能下降(“迷失在中间”现象)。因此,合理管理上下文比盲目追求大窗口更重要。
四、今日总结与作业
今天你完成了:
- ✅ 理解了上下文窗口的概念及其限制。
- ✅ 通过实验对比了不同 Temperature 值对生成结果的影响,学会了如何选择适合任务的温度。
- ✅ 使用
tiktoken计算 Token 数,并实现了简单的文本截断。 - ✅ 了解了
max_tokens与输出截断的关系。
今日作业(必做):
- 写一个 Python 脚本,让模型用三种不同的 Temperature(0、0.7、1.5)分别为同一个产品(比如“智能手表”)写一句广告语,对比风格差异。
- 编写一个函数
safe_chat(messages, max_input_tokens=5000),该函数接收消息列表,计算总 Token 数,如果超过max_input_tokens则自动裁剪最早的消息(或采取其他策略),然后调用模型并返回结果。测试该函数。 - (思考题)在构建一个客服 Agent 时,如果用户连续问了 20 个问题,对话历史很可能超过上下文窗口。你打算如何设计记忆管理策略?用文字描述你的思路(不需要代码)。
明日预告:我们将进入 Prompt 工程的基础篇,学习 Zero-shot 和 Few-shot 提示技巧,并通过实验感受提供示例的重要性。
有任何问题欢迎随时提问!