查询改写与多轮检索 Query Rewriting & Multi-Turn Retrieval
所属:01-fundamentals / 03-rag
面试权重:★★
来源:通用领域知识(B站「RAG 面试」合集高频);未做一手考证
一句话#
用户说的话不是好的检索查询 —— 它口语、省略、依赖上下文、还可能一次问三件事;Query Rewriting 就是在检索前用一次 LLM 调用把它变成「适合检索的样子」(补全指代、拆解子问题、补同义术语、或生成假想文档),代价是多一次延迟和改写跑偏的风险。
为什么重要(面试角度)#
RAG 的检索质量有一半取决于 query,而不是索引。这个考点在面试里通常是追加题:
「你说的都对,那用户问『它的价格是多少』这种多轮指代的问题怎么办?」
答不上来,说明你只在单轮 demo 上验证过。面试官想听的是:
- 知道 coreference resolution(指代消解)必须发生在检索之前,否则「它」这个字根本检索不出任何东西
- 能区分改写(rewrite)/ 扩展(expansion)/ 拆解(decomposition)/ 假设文档(HyDE) 是四种不同动作,解决不同问题
- 清楚 改写是有风险的(可能偏离原意、幻觉出错误的术语),需要可回退
- 有什么时候不该改写的判断(简单精确查询、延迟敏感),而不是无脑加一层
核心内容#
为什么原查询经常不能用#
| 查询形态 | 例子 | 为什么检索失败 |
|---|---|---|
| 多轮指代(coreference) | 「那它的价格呢?」 | 检索器看不到「它」指什么,向量/词面都对不上 |
| 口语化(colloquial) | 「机器烫得厉害咋整」 | 与文档用词(「过热保护机制」)无重合 |
| 过度省略(underspecified) | 「政策」 | 缺语境,语义太宽,什么都召回一点 |
| 多意图(multi-part) | 「A 和 B 的区别,以及分别多少钱」 | 一个向量平均了三件事,哪件都匹配不准 |
| 术语错位(vocabulary mismatch) | 用户说「笔记本」文档写「便携式计算机」 | 词面不重合;稠密向量可能也救不全 |
四类动作,别混为一谈#
① 改写 / 标准化(rewrite / normalization) —— 同义、口语 → 术语、补全指代。
- 多轮指代消解:把「它的价格呢」+ 对话历史 → 「iPhone 16 Pro 的价格」。这是多轮 RAG 的必做项,常见做法是让 LLM 基于历史把当前轮改写成独立可检索的完整问句(standalone question)。
- 属于「一对一的语义等价改写」,目标是不改变意图地把 query 变成文档的语言。
② 扩展(expansion) —— 往查询里加词。
- 用 LLM 生成同义词、上下位词、领域术语。
- 风险:加了模型幻觉出的错词会污染检索(原本能召回的反而被噪声挤掉)。所以扩展要保守,且最好用「多路查询 + 融合」而不是「把词硬拼进一个 query」。
③ 拆解(decomposition) —— 把多意图问题拆成多个子查询。
- 「A 和 B 的区别,以及分别多少钱」→ 三个子查询:A 是什么 / B 是什么 / A 的价格 / B 的价格。各自检索、分别作答、再合成。
- 这是 multi-hop / 复合问题的标准解法,也是 agentic RAG 的基础动作。
④ HyDE(Hypothetical Document Embeddings) —— 不检索 query,检索假想的答案文档。
- 做法:让 LLM 先"编"一段可能回答该问题的文档,再用这段假想文档去 embedding 检索。
- 直觉:真实的文档是长文本 + 陈述句,而用户 query 是短问句 —— 两者在 embedding 空间里的"体裁"(长度、句式、用词分布)不同,直接比会有 gap。把 query 先"翻译"成一篇文章的体裁,向量就更容易和真实文档对齐。
- 注意:假想文档内容可能是错的,但这没关系 —— 它只用来"指路",不用来作答。这个"用错误内容做正确检索"的思路是本考点最反直觉、也最值得说出的一点。
- 代价:每次查询多一次生成;且效果依赖问题类型(概念性/描述性问题收益明显,事实型短查询可能无感甚至有害),必须实测。
⑤ Step-back(后退提问) —— 先问一个更抽象的上位问题,检索到背景知识后再回答具体问题。适合那些"必须先懂原理才能答细节"的问题。也属于依赖问题类型的经验技巧,非普适。
多轮对话上下文怎么处理#
多轮 RAG 有两类做法:
- 每轮改写为独立 query(rewrite-then-retrieve):LLM 读全部历史 + 本轮,输出一个 standalone question,再走单轮检索流程。简单、成熟、调试容易;代价是每轮多一次 LLM 调用,且历史很长时要控成本。
- 对话感知检索(conversational retrieval):把对话历史和 query 一起编码。需要专门训练的模型,工程上更重,主流开源方案里改写式更常见。
要点:历史越长,改写越容易跑偏,所以通常只保留最近 N 轮 + 一个滚动摘要(summary)作为改写输入。
代价与风险#
| 收益 | 代价 / 风险 |
|---|---|
| 解决指代、口语、术语错位 | 额外一次 LLM 调用的延迟与成本(在检索之前,直接叠加首字延迟) |
| 拆解提升复合问题召回 | 子查询数量放大 → 检索与融合变复杂,且合成答案要额外一步 |
| HyDE 弥合 query-doc 体裁 gap | 假想文档本身可能幻觉;对某些问题类型无效 |
| 扩展提升长尾召回 | 幻觉术语污染检索;多路查询增加延迟 |
什么时候不该改写:单轮、精确、已含关键词的查询(如直接搜型号/错误码)—— 改写只会引入漂移。所以工程上常做成条件触发(is_multi_turn / is_underspecified 才改写),而不是每轮必跑。
怎么知道改写有没有用#
- 离线:准备带 gold chunk 的评测集,对照「原始 query vs 改写后 query」的 Recall@k —— 这是唯一能证明改写有效的指标。若 Recall 不升,改写就是白付延迟。
- 多轮:专门构造含指代的对话测试集(「它」「那个」「刚才说的」),否则这类 bug 在单轮 demo 里永远测不出来。
- 线上:监控改写后的 query 字符串(人工抽查漂移)、P95 首字延迟增量。
与其他优化手段的优先级#
query rewriting 属于低成本、先做的一类,通常排在这些之前:
- hybrid 检索(救精确匹配)
- query rewriting / 指代消解(救多轮与口语)
- reranker(救排序)
- 微调 embedding(最贵,最后做)
理由:1、2、3 都不需要重跑索引或训练模型,可以在天级别落地并立刻用评测验证。
常见面试问法#
Q:多轮对话里用户说「它的价格呢」,你怎么检索? A:不能直接拿这句话去检索 —— 检索器不知道「它」指什么。标准做法是 rewrite-then-retrieve:用 LLM 结合对话历史把当前轮改写成独立可检索的完整问句(如「iPhone 16 Pro 的价格是多少」),再用它走单轮检索。为控成本通常只保留最近 N 轮 + 滚动摘要作为改写输入。
Q:HyDE 是什么?为什么用「可能错的假想文档」去检索反而有效? A:HyDE 让 LLM 先生成一段假想的答案文档,再用它去 embedding 检索。关键在于用户 query 是短问句、真实文档是长陈述句,两者在 embedding 空间有"体裁"差异;假想文档把 query 翻译成了文档的体裁,向量更容易对齐。假想内容即使有错也不影响 —— 它只用于"指路",最终答案仍来自真实检索到的文档。
Q:查询扩展会不会引入噪声? A:会有风险。LLM 扩展时若加入幻觉出的错误术语,或把语义拉偏,反而会污染检索、把原本能召回的挤下去。所以扩展要保守,而且更推荐用多路查询 + RRF 融合而不是把扩展词硬拼进一个 query —— 融合能让"原查询"这一路的准确结果仍有机会进入结果集。
Q:什么情况下不做改写? A:单轮、精确、已含关键词的查询(型号、错误码、专有名词)—— 改写只会带来漂移和额外延迟。所以工程上一般做成条件触发(多轮 / 明显省略 / 多意图时才改写),并用离线 Recall@k 验证净收益,而不是每轮无条件加一层 LLM 调用。
Q:改写之后效果怎么评估? A:分两条。①离线:拿带 gold chunk 标注的评测集,对比改写前后 Recall@k(以及端到端 Faithfulness),Recall 不升就说明白付延迟;②多轮专项:单独构造含指代/省略的对话测试集,因为这类问题在单轮 demo 里测不出来。线上再监控改写后 query 的漂移比例与 P95 首字延迟增量。
易错点#
- ❌ 把改写当成「必开的一层」,不评估它对具体问题类型的净收益。
- ❌ 忽略多轮指代消解这一步 —— 这是多轮 RAG 最高频的失败点。
- ❌ 认为改写一定让检索更好;实际可能引入漂移与幻觉术语,必须用 Recall@k 验证。
- ❌ 把改写/扩展/拆解/HyDE 混为一谈 —— 它们解决的问题不同(语义等价 / 加词 / 一拆多 / 换体裁)。
- ❌ 把 HyDE 的假想文档当"答案来源"—— 它只用来指路,不能用来作答。
- ❌ 忽略改写带来的首字延迟增量(它发生在生成之前,直接叠加在用户等待上)。
自测#
1. 为什么多轮 RAG 里指代消解必须发生在检索"之前"?
答案
因为检索器(BM25 或向量模型)只能看到当前 query 的文本,看不到对话历史。「它的价格是多少」里的「它」在检索时是一个无信息量的代词,词面匹配不到任何文档,向量也会落到一个泛化的、什么都沾一点的区域。必须先借助历史把它还原成完整实体(rewrite 为 standalone question),检索器才「知道」要找什么。顺序不能颠倒。
2. HyDE 的直觉是什么?它为什么不需要保证假想文档的内容正确?
答案
直觉是弥合查询与文档之间的体裁差异:用户 query 是短问句,真实文档是长陈述句,两者在 embedding 空间的分布有系统性偏移。HyDE 让 LLM 先写一段"看起来像答案"的文档,把 query 转成文档的体裁再检索,向量更容易和真实文档对齐。假想内容可以有错,因为它只承担检索指引的作用,不进入最终答案;最终回答仍然基于真实检索到的文档。这也是它"用可能错的内容做可能对的检索"这个反直觉点。
3. 用户问「A 和 B 的区别,以及它们分别多少钱」,一次检索能不能答好?为什么?
答案
通常不能。这是一个多意图问题,包含三类信息需求(A 的定义、B 的定义、A 的价格、B 的价格)。把整句话编码成一个向量会得到"四件事的平均",语义被稀释,每一件都匹配不准;BM25 上也会被"区别""多少钱"这类高频词主导。标准解法是拆解(decomposition):用 LLM 拆成多个子查询,各自检索、各自作答,最后合成。这也是 agentic RAG 的基础动作。
4. 查询扩展和「多路查询 + RRF 融合」相比,各自的取舍是什么?
答案
查询扩展是往一个 query 里加同义词/术语,实现简单,但一旦加入幻觉出的错词就会污染整条 query 的检索结果,且丢掉了原查询的信息,属于"把鸡蛋放一个篮子"。多路查询则保留原查询、另生成若干变体,各自检索后用 RRF 融合 —— 原查询的准确结果仍有机会进入结果集,鲁棒性更好,代价是检索次数与延迟增加。实践中后者更稳,但路由与融合逻辑更复杂。
5. 什么信号会让你决定"这个查询不需要改写"?
答案
①单轮、无对话历史(没有指代可消解);②查询本身已含精确关键词或标识符(型号、错误码、专有名词、URL)—— 改写只可能带来漂移;③延迟极度敏感、且离线评测显示该查询类型上改写没有提升 Recall@k。工程上做法是加一个轻量判据(是否多轮 / 是否过短或省略 / 是否含疑问代词指代)来决定是否触发改写,并用离线评测守住"净收益为正"这个底线。