最近在搭一个客服场景的AI Agent,用RAG做知识库检索。遇到一个头疼的问题:多轮对话里,用户会问“那它价格呢?”或者“具体怎么用啊”,这种指代性很强的问题。我现在是把整个历史对话拼到query里重新检索,但结果经常跑偏——模型会去匹配历史里的“老问题”,而不是当前真正的意图。试过只截取最后两轮,但有些上下文又不够。想请教一下各位大佬,你们在Agent+RAG里是怎么处理多轮历史信息的?是直接丢给LLM去改写,还是有什么更好的策略?求具体方案或踩坑经验。
RAG+Agent做多轮对话时,历史记录怎么塞才不会让检索变“智障”?
全部回复
共 11 条同感,这个问题我最近也卡了很久。我试过把历史对话用LLM压缩成“当前用户核心诉求”再拼接,效果比直接堆原始记录好一些,但偶尔会丢失关键指代信息。比如用户说“那第三个方案呢”,压缩后的结果可能把“第三个”对应的具体选项给漏了。
你提到只截取最后两轮不够,我这边有个折中方案:按token数动态截取,比如保留最近800 token的历史记录,但用滑动窗口+关键词权重调整。比如检测到“它”、“这个”、“刚才”这类代词时,把前几轮中对应的实体(比如商品名、价格)高亮保留,其他寒暄或无关信息直接丢弃。不过这个逻辑写起来挺麻烦的,而且依赖实体识别质量。
另外看到你说“拼到query里重新检索”,这里有个坑:历史对话里如果包含类似“我不需要这个”这样的否定句,直接拼接会让检索偏向否定内容。我最近在试把历史记录和当前query分开编码,用交叉注意力融合——但实现成本太高,还在搁置中。
想请教一下,你有没有考虑过用改写模型?比如单独调一个小的LLM,专门负责把“那它价格呢”补全成“请查询某某产品的价格”。这样检索词干净很多,就是需要额外维护一个改写模型,而且实时性要求高的场景可能扛不住。或者你们客服场景的对话轮次通常有多长?我这边平均5-6轮就开始飘了。
这个问题我也踩过坑,现在用的是两步方案:先让LLM基于当前query和历史对话生成一个推理意图(比如“用户想知道xx产品的价格”),然后拿这个推理意图去检索知识库,而不是直接拼原始历史。这样检索准确率高很多,缺点是多了一次LLM调用,但效果值得。另外可以试试给历史轮次加衰减权重,让近期对话在embedding里占比更高。
可以试试把历史对话用大模型压缩成精简的上下文摘要,再拼到当前query里。
试过让LLM把历史记录压缩成当前query的上下文摘要,效果比直接拼要好不少。
试试把历史对话先让LLM总结成当前意图,再拿去检索,效果比直接塞历史好很多。
试过用LLM把历史对话提炼成一句话的当前目标,检索准确了不少。
试试把历史对话让LLM压缩成当前相关的语义摘要,再拼到query里,效果比直接堆原文好不少。
我最近也在搞类似的东西,试过把历史对话用LLM压缩成一句当前意图再检索,效果比直接拼历史好很多。比如把“那它价格呢”补全成“XX产品价格多少”,检索精度直接上来了。不过要注意控制压缩长度,不然LLM自己会脑补太多。另外可以给每轮对话加个时间戳或意图标签,检索时优先匹配最近的意图,能减少历史干扰。
这题我太有感触了,之前也被历史拼接搞到崩溃。后来试了把最近两轮对话单独让LLM用类似“用户最新问题:xxx,结合历史:xxx”的模版重写成一个独立query再检索,效果比直接拼好很多。另外也可以考虑给历史轮次打标签,比如用户问价格后只保留含价格的关键轮,不然无关历史太容易把向量带偏。你目前用的embedding模型对长文本敏感吗?
我之前也踩过这个坑,直接拼历史太容易把检索带偏了。后来我是把最近两轮对话单独抽出来,先用LLM把指代消解成明确的实体或问题,比如“那它价格呢”变成“某某产品的价格”,再丢回去跟原始query一起检索,效果比直接拼好不少。你也可以试试按轮次加权,越新的对话权重越高,历史太久的直接剪掉,这样上下文够用又不会太乱。
我也遇到过这个问题,后来试了把历史对话先压缩成一句话的“当前状态摘要”再拼到query里,效果比直接塞整段历史好不少。可以参考LangChain那个ConversationSummaryMemory的思路,让LLM每轮结束后自动生成精简版背景。另外,检索时把历史对话里的高频实体词单独拎出来做权重加成,也能减少“跑偏”的概率,你可以试试看。