最近在搭一个客服类的Agent,用的是RAG+LLM的方案。现在遇到个问题:用户问“昨天那个订单怎么回事”,我检索到的向量片段里根本没有“昨天”这个概念,只能靠对话历史去猜。如果把多轮对话历史全部塞进prompt,token消耗太大,而且经常把检索出来的关键信息“冲淡”。目前是把历史压缩成摘要存在内存里,但Agent一旦多步调用工具,摘要就乱套了。想问问大家,生产环境里一般是用向量库单独存对话记忆,还是直接用短期记忆+重写的方案?有没有比较成熟的实践?
RAG+Agent架构下,多轮对话的上下文到底该存哪里?
全部回复
共 7 条我们这边试过短期记忆加query重写,效果比直接塞摘要稳不少,尤其是工具调用多的时候,摘要容易丢关键实体。但重写这块得针对业务调prompt,不然“昨天”这种相对时间词还是容易翻车。你那个向量库存对话记忆的方案我也看过,检索精度要求高,不然反而引入噪音,不如把历史里的实体和意图单独抽出来存。目前我们生产环境是两层,轻量短期记忆跑对话流,关键节点异步把结构化记忆刷到库里,成本可控也好排查。
试过短期记忆+query重写,比向量库存对话靠谱,摘要太容易丢上下文了。
我们生产环境是短期记忆+query重写,摘要存redis,工具调用时单独存关键状态,比全塞向量库省心。
之前做过类似的项目,踩过一样的坑。我们最后是短期记忆存Redis,带时间戳和会话id,关键轮次做一次语义压缩写入向量库,每次对话先做意图识别,判断是事实型问题还是上下文依赖型,后者才去查记忆。摘要乱套的问题,我们是用LLM对工具调用结果做结构化重写,把状态变化单独存成键值对,效果还行。
另外“昨天”这种相对时间,光靠摘要也不行,得在改写时强制把相对时间转换成绝对日期,不然换个模型或者隔天再问就废了。你可以试试看,不一定非得分两个库。
说实话你这个场景我太熟了,之前做电商客服也卡在这。我的做法是短期记忆用内存里的滑动窗口存最近两轮原文,再配合一个轻量级的“意图+实体”压缩层,只提取订单号、时间、状态这些关键槽位,而不是整段摘要。这样工具调用时至少不会把槽位搞丢。至于长期记忆,我试过单独扔向量库,但效果一般,因为用户问“昨天”这种相对时间,向量检索根本匹配不上,最后还是得靠规则把相对时间转成绝对时间戳再查。现在比较稳的方案是双通道:对话历史按轮次做摘要后存Redis,带TTL,同时保留一个极简的“当前任务状态”结构体,Agent每步更新它。检索的时候先拿当前问题去重写,重写时把摘要里的关键实体拼进去,再去做RAG。你提到的摘要乱套,多半是摘要粒度太粗,建议按子任务分段存,别一串到底。另外可以考虑用LLM做“记忆裁剪”,每次只保留跟当前query相关的历史片段,token能省不少。生产上我觉得没有银弹,核心是区分“会话内短期记忆”和“跨会话长期记忆”,前者用内存+规则,后者才值得上向量库。
这个问题我最近也刚好踩过类似的坑。你提到的“摘要存内存里一调工具就乱”太真实了,因为摘要本身是静态的,Agent每步改写状态后,旧摘要和新事实会互相打架。我个人现在倾向于把“对话记忆”和“事实记忆”拆开存,前者用短期窗口(比如最近3轮原文+压缩摘要),后者才进向量库——这样至少工具调用时不会污染核心上下文。另外你提到“昨天”这种相对时间词,其实可以加一层轻量的改写模块,在进RAG之前先把用户query里的相对时间基于当前日期绝对化,比如“昨天”直接替换成具体日期,检索命中率会高很多。至于要不要用向量库单独存记忆,我觉得得看你的业务量,如果单用户会话轮次超过20轮,纯内存摘要肯定扛不住,但为这个上向量库又有点重,可以考虑先用Redis加个TTL存结构化摘要,配合一个简单的“关键实体-时间戳”索引,比全量向量化更可控。还有个思路是干脆让Agent在每次工具调用后主动更新一份“当前事实清单”,而不是每次从头压缩历史,这样摘要不会乱,代价是要多写点逻辑。总之这个事没有银弹,建议先量化一下你单次会话的平均token和工具调用次数,再决定要不要上向量记忆。
你这问题我太有同感了,摘要一乱基本就是工具调用把状态搞脏了。我生产里是短期记忆存原始对话(只留最近两轮),更早的用LLM抽关键实体和意图重写成结构化query,再拿这个去检索向量库。这样“昨天”这种相对时间词就能通过改写映射到具体日期,token开销也可控。你可以试试给工具调用加个状态标记,每次调用后强制把摘要里跟工具结果冲突的部分重写一遍。