滑动窗口丢上下文,向量召回劝退,混用才是AI记忆的正解
你的 AI Agent 记性差?聊聊短期记忆管理的三种方案
上周我的 AI 助手在第 12 轮对话时突然问我:“对了,你刚才说的那个 Bug 是用什么框架来着?”——那一刻我意识到,这家伙的记性比我还差。
好吧,可能跟我连续写了 6 小时代码也有关系。
今天聊聊这个让无数开发者抓狂的问题:怎么让 AI Agent 在长对话里记住事儿,又不把 Token 烧光。我踩过不少坑,总结出三种方案。不一定都对,但至少能让你少走点弯路。
先说结论
- 滑动窗口:简单,但丢上下文
- 摘要压缩:省钱,但细节会被“磨平”
- 向量召回:精准,但工程复杂度劝退
- 实际项目里,我都是混着用
嗯...这个我后面细说。
为什么短期记忆是个大问题?
想象一下这个场景。
你在用 AI Agent 调试一个分布式系统的 Bug。前 20 轮对话你告诉它:系统架构、报错日志、你试过的三个方案、为什么都不行...然后到第 21 轮,它开始胡言乱语。
不是它笨。
是它的“工作记忆”爆了。
以 GPT-4 为例,128K 上下文看着挺大对吧?但实际跑起来,一个稍微复杂的 Agent 任务——比如代码审查 + 多轮推理 + 调用几个工具——轻松吃掉 30K-50K Token。我测过,GPT-4-turbo 在 2024 年 4 月那版,跑一个带工具调用的 debugging 任务,平均消耗 42K Token。
不做记忆管理的话,就两条路:丢信息,或者烧预算。
我去年 9 月做一个客服 Agent 项目,用的是当时最新的 gpt-4-0125-preview。上线第一天,客户就怼过来:“你们的 AI 怎么跟金鱼一样,7 秒记忆?”——原话。我当时那个脸啊...
方案一:滑动窗口——简单但不傻
思路简单到爆:只保留最近 N 轮对话,旧的直接扔掉。
class SlidingWindowMemory:
def __init__(self, max_turns=10):
self.max_turns = max_turns
self.messages = []
def add_message(self, role, content):
self.messages.append({"role": role, "content": content})
# 超过窗口大小就截断
if len(self.messages) > self.max_turns * 2:
self.messages = self.messages[-(self.max_turns * 2):]
def get_context(self):
return self.messages实际体验
我在一个内部工具里用了这个方案,窗口设的 8 轮。
效果嘛...有好有坏。
- ✅ Token 消耗稳得一批,绝不会超预算
- ✅ 实现就 10 行代码,5 分钟搞定
- ❌ 用户在第 9 轮提“之前说的那个”,Agent 直接懵圈
- ❌ 没法跨窗口推理
等等,这里我要更正一下——上面说“10 行代码”其实不太准。如果你要处理 system prompt、要区分 user 和 assistant 消息的角色标记,实际得 30 行左右。我上面那个代码是简化版的。
适合什么场景?
任务明确、不需要长期上下文的。比如单次代码生成、翻译、或者就聊两句就结束的那种。如果你的 Agent 需要“记住用户是谁”,光靠这个不够。
我一般拿它当兜底方案。
方案二:摘要压缩——把历史“蒸馏”一下
这个思路是:用 LLM 把历史对话压成一段摘要,只留关键信息。
class SummaryMemory:
def __init__(self, llm_client):
self.llm = llm_client
self.summary = ""
self.recent_messages = []
def compress_history(self, messages):
prompt = f"""
将以下对话历史压缩为关键摘要,保留:
- 用户的核心需求
- 已做出的决策
- 重要的技术细节
对话:
{json.dumps(messages, ensure_ascii=False)}
"""
self.summary = self.llm.complete(prompt)
self.recent_messages = []我踩过的坑
去年 11 月,我尝试用摘要压缩做个“长期陪伴型”学习助手。想法很美:每次学习要点压缩一下,下次对话时注入摘要。
结果呢?
翻车翻得很彻底。我喝了三杯咖啡才搞明白问题在哪:
1. 细节丢失严重。用户说“我喜欢用 VS Code 的 Vim 插件,keybinding 改成了 Ctrl+J”,压缩后变成“用户使用 VS Code”——Vim 插件和自定义 keybinding 全丢了。这就导致后续对话里,Agent 推荐快捷键时完全不考虑用户的肌肉记忆。
2. 压缩成本不低。每次压缩都要额外调一次 LLM。我用的是 Claude 3 Haiku 做压缩(便宜),但如果频繁压缩,一个月下来也多了 200 多刀。
3. 错误累积很可怕。这是最让我头疼的。如果摘要本身有偏差——比如把“用户在考虑用 Redis”压缩成“用户已使用 Redis”——后续对话会在错误的基础上继续,越跑越偏。我遇到过连续 5 轮都在讨论一个不存在的 Redis 配置,用户最后直接打了“???”。
一个意外的补救方案
后来我改成分层摘要。
把“用户画像”(长期不变的偏好、习惯、技术栈)和“任务上下文”(当前这次任务的目标、进展)分开压缩。用户画像压缩一次管很久,任务上下文每次任务结束再压缩。
效果提升了,但工程复杂度直接翻倍。我跟同事吐槽说:“这玩意儿维护起来比我前女友的情绪还复杂。”
方案三:向量召回——需要的时候再找
向量召回不压缩也不丢弃,而是把所有历史存进向量数据库,需要的时候检索最相关的片段。
class VectorMemory:
def __init__(self, vector_store):
self.store = vector_store
def add_interaction(self, user_msg, assistant_msg):
doc = f"User: {user_msg}\nAssistant: {assistant_msg}"
embedding = get_embedding(doc)
self.store.insert(embedding, doc)
def retrieve_context(self, query, top_k=5):
query_embedding = get_embedding(query)
results = self.store.search(query_embedding, top_k)
return "\n".join(results)一个让我凌晨 3 点被叫醒的案例
三个月前,2024 年 12 月,我在做一个代码审查 Agent。用户可能随时提到“上次那个 PR 里的 SQL 注入问题”。用滑动窗口肯定不行——可能已经是 50 轮之前的事了。用摘要也不行——摘要不会保留具体代码细节。
向量召回看起来是完美方案。
我用的是 ChromaDB + text-embedding-3-small。上线第一周,凌晨 3:07,PagerDuty 响了。
出了什么问题?
用户说“那个 SQL 的问题”,向量检索返回了所有包含“SQL”的对话片段。12 个历史片段里有 8 个跟当前问题完全不相关——用户之前讨论过 SQL 优化、SQL 索引、甚至一个 SQL 相关的烂梗。但 Agent 不知道“那个”指的是哪个,直接返回了一堆无关信息。
还有一个更隐蔽的问题:召回的相关性严重依赖 embedding 模型质量。text-embedding-3-small 在处理技术术语时,会把“SQL injection”和“SQL optimization”当成相似度 0.85 的东西——据我了解,这是因为它对专业术语的语义区分不够细。
我的补救方案:
- 给每条记忆加了时间戳和会话 ID,检索时优先近期和同会话的。用的是简单的衰减权重,24 小时内的权重 ×2,同会话 ×1.5
- 混合检索:关键词匹配(BM25)+ 向量相似度,两者加权。BM25 对精确术语匹配更准
- 召回的片段会加上元信息:“这是 3 天前在第 5 轮对话中提到的”
现在这个系统终于稳定了,召回准确率大概在 87% 左右。但说实话,调参调得我想转行卖咖啡。
到底该怎么选?
我现在都是组合拳,根据场景搭配:
| 场景 | 推荐方案 | 原因 |
|------|---------|------|
| 简单问答 / 单次任务 | 滑动窗口 | 够用、省事 |
| 长期用户画像 | 摘要压缩 | 信息密度高 |
| 知识密集型任务 | 向量召回 | 精准检索 |
| 复杂 Agent 系统 | 三者结合 | 各取所长 |
我现在的实战配方:
滑动窗口(最近 8 轮,保底)
+ 摘要压缩(每 20 轮压一次,用便宜的 Haiku 做)
+ 向量召回(存在 ChromaDB,检索 top 5)
= 成本可控,记忆不丢Token 消耗大概上涨了 15%,但用户满意度从 3.2 涨到了 4.5。我觉得值。
最后想说的
做记忆管理这事儿,没有银弹。
我花了半年时间在各种方案之间反复横跳,LangChain 的 Memory 模块试了个遍,LlamaIndex 的那套也搞过,最后发现关键是想清楚你的场景——用户在什么情况下会说“上次那个”?你需要在多大范围内保持一致性?
我现在的做法是:先上线最简单的滑动窗口,跑一周看数据。然后根据用户反馈,看哪些信息丢了最要命,再有针对性地加摘要或召回。别一上来就搞大而全的架构,大概率会后悔。
好了,我去续杯咖啡了。最近迷上了云南保山的豆子,做冷萃很不错。
你的 Agent 项目用的什么记忆方案?有没有遇到过“金鱼脑”的尴尬时刻?评论区聊聊,我会认真看每一条。尤其想知道你们在生产环境里是怎么处理这个问题的。
#AI #Agent #LLM #Memory #工程实践
读者评论 3