4、上下文窗口管理:Token计算与预算、滑动窗口策略、关键信息压缩、长文本摘要与检索增强
上下文窗口,说白了就是AI的「短期记忆」。
我刚开始做Agent时,总觉得上下文越大越好。直到有一次,我的Agent在处理一个长达50页的合同审核任务时,直接「失忆」了——它忘了前面讨论过的关键条款,开始胡言乱语。嗯,从那以后,我彻底明白了:上下文窗口不是越大越好,而是要用得巧。
4.1 Token计算与预算:你的「内存条」有多粗?
先搞清楚一个概念:Token。它不是字符,也不是单词。中文里,一个Token大约对应1-2个汉字。英文里,一个Token大约对应0.75个单词。
我习惯用一个简单公式来估算:
中文文本:Token数 ≈ 字符数 × 0.6
英文文本:Token数 ≈ 单词数 × 1.3
混合文本:建议直接用工具计算
为什么要做预算?因为Token就是钱啊!
| 模型 | 上下文窗口 | 输入价格(每千Token) | 输出价格(每千Token) |
|---|---|---|---|
| GPT-4 Turbo | 128K | $0.01 | $0.03 |
| Claude 3 Opus | 200K | $0.015 | $0.075 |
| 本地模型(如Llama 3) | 8K-32K | 免费(但吃显存) | 免费 |
你看,一个128K的上下文,如果全塞满,一次对话可能就要花掉几块钱。要是你的Agent每天跑几千次……嗯,老板会找你谈话的。
4.2 滑动窗口策略:让AI保持「新鲜感」
滑动窗口,说白了就是「只保留最近N条对话」。就像你刷朋友圈,只看最近几天的,太旧的就划走了。
我常用的滑动窗口策略有三种:
- 固定大小窗口:保留最近K轮对话,旧的直接丢弃。
- Token数窗口:保留最近N个Token,超出的部分从最旧开始删。
- 语义窗口:保留与当前问题最相关的M条历史。
举个例子,一个客服Agent的滑动窗口实现:
class SlidingWindow:
def __init__(self, max_tokens=4000):
self.max_tokens = max_tokens
self.history = []
self.current_tokens = 0
def add_message(self, message, token_count):
self.history.append((message, token_count))
self.current_tokens += token_count
# 超出预算,从最旧开始删
while self.current_tokens > self.max_tokens:
oldest_msg, oldest_tokens = self.history.pop(0)
self.current_tokens -= oldest_tokens
def get_context(self):
return [msg for msg, _ in self.history]
你想想看,如果不做滑动窗口,一个对话持续100轮后,光历史记录就能吃掉几万Token。模型哪还有精力去处理你当前的问题?
4.3 关键信息压缩:把「废话」扔掉
压缩,不是让你删内容,而是让你提炼精华。
我常用的压缩技巧:
- 摘要压缩:让模型自己把长文本总结成几句话。
- 结构化压缩:把段落变成列表、表格、JSON。
- 去冗余压缩:去掉重复的、无关的、过时的信息。
举个例子,原始对话可能是这样的:
用户:你好,我想问一下我的订单什么时候发货?
客服:您好,请问您的订单号是多少?
用户:订单号是123456。
客服:好的,我查一下。您的订单目前显示已发货,预计明天到达。
用户:好的,谢谢。
客服:不客气,祝您生活愉快!
压缩后,变成这样:
{"order_id": "123456", "status": "shipped", "eta": "tomorrow"}
你看,从100多个Token压缩到不到20个Token。信息一点没少,但体积缩小了5倍。
4.4 长文本摘要与检索增强:让AI「翻书」而不是「背书」
长文本处理,是上下文窗口管理的终极挑战。
你想想看,如果让AI直接读一本500页的书,它肯定记不住。怎么办?两个思路:
- 先摘要,再处理:把长文本切成块,每块生成摘要,然后把这些摘要拼起来。
- 检索增强(RAG):不把全文塞进上下文,而是让AI自己去「翻书」找答案。
RAG的流程,我用一张SVG图来说明:
你看,RAG的核心思想就是:不把所有信息塞进上下文,而是让AI学会「查资料」。
我做过一个项目,需要处理1000份技术文档。如果全塞进上下文,一次对话就要花掉几十块钱。用了RAG后,每次只检索最相关的3-5段,成本降到了原来的1/10,而且回答质量反而更高了。
- 需要精确查找某个事实 → RAG
- 需要整体理解 → 先摘要,再分析
- 需要多步推理 → 混合使用:先RAG找证据,再摘要整合
4.5 实战:一个完整的上下文管理方案
说了这么多,来一个我实际用过的方案:
class ContextManager:
def __init__(self, model_max_tokens=128000):
self.model_max = model_max_tokens
self.safety_margin = 0.8 # 留20%给输出
self.max_input = int(self.model_max * self.safety_margin)
# 三个缓冲区
self.system_prompt = "" # 系统提示(固定)
self.short_term = [] # 短期记忆(滑动窗口)
self.long_term = [] # 长期记忆(压缩后)
def add_to_context(self, message, token_count):
# 1. 先尝试加入短期记忆
self.short_term.append((message, token_count))
# 2. 检查总Token数
total = self._count_total_tokens()
if total > self.max_input:
# 3. 超出预算,压缩短期记忆
self._compress_short_term()
def _compress_short_term(self):
# 把最旧的一半对话压缩成摘要
old_half = self.short_term[:len(self.short_term)//2]
summary = self._summarize(old_half)
# 存入长期记忆
self.long_term.append(summary)
# 删除旧对话
self.short_term = self.short_term[len(self.short_term)//2:]
def get_context(self):
# 组装最终上下文
context = [self.system_prompt]
context.extend(self.long_term) # 长期记忆(压缩后)
context.extend(self.short_term) # 短期记忆(原始)
return context
嗯,上下文窗口管理,说白了就是在「记住所有」和「记住关键」之间找平衡。你想想看,人类开会时,不也是记笔记、划重点、扔废纸吗?AI也一样。
最后送你一句话:别让AI的「记忆」成为它的「负担」。管理好上下文,你的Agent才能跑得又快又准。