被用户骂“金鱼”后,我重新设计了Agent的记忆机制
上周三下午两点,我在调试一个客服Agent,眼睁睁看着它在第47轮对话时把用户3轮前说的“退货”给忘了,转头推荐了同款商品。用户直接甩了句“你是金鱼吗?”
扎心。但真说到点上了。
长序列决策里的记忆管理,这就是AI Agent的死穴。我从去年11月开始折腾一个方案,到现在快8个月了,总算有点能用的东西:渐进式状态摘要+遗忘机制。不扯论文,就说落地时踩的坑和那些歪打正着的解法。
为什么长序列这么要命
先看三个真实场景,都是我自己遇到的:
案例1:客服Agent的7天对话
我们有个电商客服Agent,用的是gpt-4-0125-preview这个版本,平均对话轮次在30-50轮。用户会反复提之前的订单号、退换货诉求、优惠券使用情况。传统做法是把所有历史消息塞进context window,结果GPT-4在超过15轮后开始“选择性失明”——第8轮用户说过“已经退货了”,第23轮Agent又去查物流状态。日志里一堆 order_status_check 的重复调用,token烧得我肉疼。
案例2:游戏NPC的剧情记忆
帮朋友做的一个文字RPG,NPC需要记住玩家在200+轮中的关键选择。一开始用全量记忆+向量检索(用的Qdrant,相似度阈值设的0.82),结果NPC经常把“你曾经偷过药水”和“你曾经买过药水”搞混。因为向量相似度太高了,两个句子就差了两个字。
案例3:代码审查Agent的上下文丢失
自己用的代码审查工具,分析一个大型PR时需要在3000行diff里追踪某个函数的调用链。Agent经常分析到一半“忘记”了函数签名,开始胡编参数类型。最离谱的一次,它把 def calculate_total(items: List[OrderItem]) -> Decimal 记成了 def calc(items: list) -> float,然后基于这个错误签名做了一整套类型检查。
这三个问题指向同一件事:信息不是越多越好,而是该记的记牢,该忘的果断忘。
我踩过的三个大坑
坑1:把摘要当成压缩,而不是提炼
最开始我用了最粗暴的方案——每10轮对话让LLM总结一次,把1000字的对话史压缩成200字的摘要。看着挺合理对吧?
翻车了。
Agent在后续对话里变得跟失忆似的,只能记住摘要里的东西,细节全丢了。用户说“我上次提到的那个红色的”,Agent完全不知道他在说什么,因为摘要里只写了“用户询问商品”,把“红色”这个细节给压缩没了。
后来改成分层摘要:
- **即时层**:保留最近5轮完整对话,不碰
- **短期层**:每10轮生成一个结构化摘要,JSON格式,包含意图、实体、决策
- **长期层**:跨会话的持久记忆,存用户画像、偏好、历史决议
等等,这里我要更正一下——后来我把即时层从5轮改成了动态的3-7轮,看话题稳定性。话题稳定就留少点,话题跳跃就留多点。固定5轮在很多场景下要么多了要么少了。
关键insight:摘要不是压缩,是重新组织信息结构。客服场景的摘要格式我改过四版,最后稳定成这样:
{
"当前意图": "退货咨询",
"涉及订单": "ORD-20240115",
"用户情绪": "不满-已安抚",
"已确认信息": ["退货原因:尺码偏小", "退货方式:上门取件"],
"待解决问题": ["退款金额需核实优惠券"]
}这种结构化摘要让Agent后续能快速定位关键信息。之前用非结构化文本,Agent经常在500字的摘要里瞎找,现在直接读字段就行。
坑2:遗忘机制太死板
这个坑最贵。真的。
我一开始设计了很“工程师”的遗忘规则:超过30轮的信息直接丢弃、非关键实体立即遗忘、冲突信息以最新为准。逻辑清晰,边界明确。
上线第一周就出事了。有个用户在对话第5轮提到“我对花生过敏”,第35轮时Agent推荐了花生酱产品。因为我的规则把超过30轮的“非订单相关信息”全清掉了。用户发邮件投诉,客服主管找我谈话。
后来改成重要性评分+软遗忘:
- 每条信息有个importance_score(1-10),由LLM在生成时打分
- 高重要性信息(>7分)进长期记忆,永不主动遗忘
- 中重要性信息(4-7分)随时间衰减,但可通过检索重新激活
- 低重要性信息(<4分)在摘要生成时直接丢弃
衰减函数用了指数衰减,半衰期大概设在15轮左右。但还加了个“唤醒机制”——如果后续对话出现了语义相似的内容,衰减值重置。这样既避免内存爆炸,又不会丢掉关键信息。
我觉得这个半衰期参数还挺关键的。设太短了信息丢太快,设太长了内存扛不住。目前15轮是我试出来的经验值,不一定普适。
坑3:状态摘要的更新时机
固定窗口(每10轮)看起来很合理。
实际上很蠢。
两个问题:一是用户在第9轮说了关键信息,第10轮就生成摘要,但第11轮用户又补充了相关细节——摘要里信息不完整。二是用户在3轮内快速切换了3个话题,固定窗口把三个不相关的事硬塞进一个摘要。
后来改成事件驱动的动态窗口:
- 检测到话题切换时(用embedding相似度判断,阈值设的0.65),立即生成当前话题摘要
- 检测到决策节点时(用户确认、Agent给出方案),生成决策快照
- 兜底机制:超过15轮未生成摘要强制触发
这个改动让摘要的“信息密度”提升很明显。之前一个摘要里可能混杂了闲聊、咨询、投诉三个话题,现在每个摘要都是单一主题的完整记录。
嗯...这个比较复杂,因为话题检测本身也不准。我的embedding阈值调了好久,0.65这个数是我在自己的数据集上试出来的,换别的场景可能需要重新调。
一个意外的发现
做了半年后我发现一个反直觉的事:Agent遗忘的内容本身也值得记录。
我们在日志里加了个“遗忘日志”,记录Agent主动丢弃了哪些信息以及丢弃原因。本来只是想方便debug,结果这个日志比任何监控都好用:
- “为什么Agent没记住用户的过敏信息?”→ 查遗忘日志,发现importance_score被误判为2
- “为什么Agent重复询问手机号?”→ 遗忘日志显示该信息在话题切换时被误清,清理原因写的是 `reason: "topic_change"`
现在我们的遗忘日志已经成了调优importance评分模型的核心数据源。这算是踩坑踩出来的feature。我们内部管这叫“元遗忘”,听着挺唬人,其实就是日志。
落地建议
1. 先上监控,再上优化:在改记忆机制之前,先把Agent的“遗忘率”“重复询问率”“信息冲突率”监控起来。不然你根本不知道改完是变好了还是变差了。我们用的是Prometheus + Grafana,三个指标挂了半年,现在回头看那些数据曲线,能清晰看到每次改动的效果。
2. 结构化摘要格式让业务方确认:我第一次设计的摘要JSON有20个字段,觉得自己想得特周全。结果业务方看了说“我们只需要知道用户想干嘛”。砍到5个核心字段后,Agent表现反而更好了。信息字段越多,LLM填充的质量越不稳定。
3. 别迷信向量检索:很多团队一说长记忆就上向量数据库,pinecone、weaviate、milvus全招呼上。但实际场景里,结构化查询(“用户上次提到的订单号是多少”)远比语义搜索频繁。我现在是结构化索引为主,向量检索为辅。据我了解,不少做客服系统的人都得出过类似结论。
4. 给遗忘加个“后悔药”:Agent可以主动标记“这个信息我可能需要,但暂时不确定”,这类信息不会立即遗忘,而是进入“待定区”,保留更长时间。这个机制救了无数次场,尤其是处理那种“用户提了一嘴但当时看起来不重要”的信息。
一个还在纠结的问题
现在我在尝试用Agent自我反思来动态调整记忆策略。思路是让Agent在会话结束后回顾自己的决策链,标记哪些记忆点对决策起了关键作用,哪些是噪音。然后用这些标注数据来训练importance评分模型。
但问题有两个:一是反思本身也消耗token,大概多30%的用量;二是Agent有时候会“过度反思”——把一些无关紧要的细节也标记为重要。
还在调参。prompt改到第7版了,有进展再来更新。
你们在长序列决策里踩过什么坑?有没有遇到过Agent“选择性失忆”的诡异情况?特别是做过游戏AI或者客服系统的兄弟,你们的遗忘机制怎么设计的?评论区聊聊,我现在最想知道的是怎么优雅地处理“用户说反话”这种情况——我们的Agent经常把反话当真,importance_score还给得贼高。
#AI Agent #长序列决策 #状态摘要 #遗忘机制 #记忆管理 #工程实践
读者评论 4