用户说的话和该搜的词往往不是同一回事
开篇引言
上一篇把记忆系统的最后一块拼图补上了——摘要压缩。滑动窗口保留最近 8 轮原文,更早的对话浓缩成一段摘要,水位线保证不重复压缩,异步执行不阻塞主流程。到这里,ctx.history 已经装好了完整的对话记忆,Pipeline 的阶段 1 结束。
阶段 2 要做什么? 来看一个场景。
假设你在一家电商公司做智能客服助手,接入了 3C 数码、家电、服装等多个品类的商品知识库。有个用户第一轮问了 iPhone 16 Pro 的退货政策是什么,系统从 3C 数码知识库检索到退货政策文档,回答得很好。第二轮,用户追问了一句:
那它的保修期呢?
记忆系统让模型知道 它 是 iPhone 16 Pro。但检索引擎呢?检索引擎拿到的查询就是这句原话——那它的保修期呢。拿 它 去向量数据库里搜,能搜到什么?大概率搜到的是各种产品的保修通用条款,可能不是 iPhone 16 Pro 的保修政策。
模型有记忆,但检索没有。这个问题在基础系列的 Query 改写篇已经讲过了——解法是在检索之前把原始问题改写成对检索友好的形式。基础系列讲了五种改写策略的原理,本篇不重复那些概念,聚焦 Ragent 项目里的工程实现。
Ragent 的查询改写有一个特别的设计:一次 LLM 调用同时完成两件事——改写和拆分。 改写解决检索精度问题(指代消解、去噪),拆分解决另一个问题——复合问题的意图覆盖。
什么意思?再看一个场景。用户问了一句:
iPhone 16 Pro 的退货政策是什么?AirPods Pro 的保修期呢?
这一句话里包含两个完全不同方向的问题。如果只改写不拆分,改写后变成 iPhone 16 Pro 退货政策和 AirPods Pro 保修期,拿这一整句话去做意图分类,LLM 可能只匹配到退货政策方向,AirPods Pro 的保修期被忽略了。拆分成两个子问题后,每个子问题独立做意图分类,两个方向都不丢。
本篇就围绕这两件事展开:改写怎么做,拆分怎么做,以及两者怎么在一次调用里同时搞定。