RAG 查询改写笔记:不是资料找不到,而是问题没问对
这篇主要讲什么
这一份 PDF 对应 Part 5 ~ Part 9,集中讨论一件事:
用户原始问题,不一定是最适合检索的问题。
也就是说,很多 RAG 失败,并不是知识库里没有答案,而是检索时用的提问方式太单一、太窄、太贴表面。
这一组方法都可以归到一个大方向里:查询改写。
为什么要改写问题
因为向量检索虽然能理解一部分语义,但它也不是万能的。
用户一句话里可能会有这些问题:
- 说法太口语
- 问题太短
- 问题太复杂,里面混了好几个子问题
- 用了知识库里不常见的表达
这时如果只拿原问题去搜,很容易漏掉真正相关的内容。
我自己的感觉是,查询改写就像给搜索动作加了一个“思考前置步骤”。
不是急着搜,而是先想一想:这个问题换个说法,会不会更容易搜到东西?
1. Multi Query:同一个问题,多问几遍
这个方法的意思很简单:
让模型把原问题改写成多个不同版本,然后分别检索,最后把结果合并。
比如你问:
什么是 LLM agent 的任务分解?
模型可能会改写成:
- 任务分解在智能体里起什么作用
- LLM agent 如何把复杂任务拆成小步骤
- autonomous agent 的 planning 与 task decomposition 有什么关系
这样做的好处是:
一个说法没搜到,另一个说法可能能搜到。
我觉得这是非常实用的一招,因为用户提问经常是不标准的,而知识库里的表达方式未必和用户完全一致。
2. RAG-Fusion:不只多搜,还要重新排序
它和 Multi Query 很像,也是先生成多个搜索问题。
但不同点在于:它不只是把结果简单拼起来,而是做一次融合排序。
原文里用了 Reciprocal Rank Fusion。不用记名字,理解它的目的就行:
如果某一段内容,在多个查询结果里都排得比较靠前,那它更可能是真的重要。
我对这个方法的评价是:
它比简单去重更靠谱,因为它考虑了“多次出现而且排名还不错”这个信号。
通俗一点说,就是把多次被不同问法都命中的内容,优先抬上来。
3. Decomposition:先把大问题拆小
这个方法特别适合复杂问题。
比如一个问题里同时在问:
- 系统由哪些部分组成
- 每个部分怎么协作
- 为什么这样设计
如果一次性去搜,检索器很容易抓不住重点。
所以更好的做法是:
先让模型把大问题拆成几个子问题,再分别检索和回答,最后合并。
我个人很认同这种思路,因为复杂问题本来就不该“一把梭”。
这其实是在模仿人类处理难题的方式:先拆,再逐个解决,再汇总。
4. Step Back:往上退一步,先问更一般的问题
这个方法很有意思。
有时候用户的问题太具体,反而不好搜。于是先把问题“退一步”,改成一个更泛化、更抽象的问题,再拿它去找背景知识。
比如原问题很窄,但退一步后,能先找到更稳的基础概念。
我觉得这特别像学习时的一个动作:
先别急着抠细节,先问“这件事本质上属于哪一类问题”。
这样检索出来的资料,往往更适合补背景。
5. HyDE:先假设一篇答案,再拿这篇答案去搜
HyDE 的思路很“反直觉”,但很巧。
它不是直接拿用户问题去检索,而是先让模型根据问题写一段“假想答案”,再用这段假想答案去做检索。
为什么这样可能有效?
因为用户问题通常很短,但一段假想答案会自然带出更多相关术语和表达方式,更像目标文档的样子。
这样一来,检索更容易命中真正相关的内容。
我自己的看法是:
HyDE 很像“先脑补一版可能的标准答案,再拿这版答案去搜资料”。
这个方法挺聪明,但也有风险。如果模型先脑补错方向,后面的检索也可能被带偏。所以它适合做增强,不适合盲信。
这份内容的核心思想
我觉得这一整份最核心的想法是:
检索效果差,很多时候不是库不行,而是提问方式不行。
所以在“搜”之前,先对问题做加工,本身就是 RAG 的重要一环。
我自己的理解
我现在会把查询改写看成 RAG 里的“搜索策略层”。
基础 RAG 更像:
有问题就直接搜。
而这一组方法更像:
先判断怎么搜最合适,再去搜。
这说明 RAG 不只是“检索 + 生成”,中间其实还有一个很值得认真设计的环节:怎么理解用户的问题。
什么时候用哪一种
- 问题表达可能有很多种说法:优先用
Multi Query - 想把多次检索结果整合得更稳:用
RAG-Fusion - 问题本身很复杂:用
Decomposition - 问题过于具体,缺背景:用
Step Back - 原问题太短,检索命中太弱:可以试
HyDE
一句总结
这一篇让我最有感触的一点是:RAG 不只是“去找文档”,而是“先把问题变成更适合找文档的样子”。很多效果提升,不在模型后面,而在检索前面。
核心代码实现
方法 1:Multi Query
1 | def generate_multi_queries(question): |
方法 2:RAG-Fusion
1 | def reciprocal_rank_fusion(results, k=60): |
方法 3:Decomposition
1 | def decompose_question(question): |
方法 4:Step Back
1 | def step_back_question(question): |
方法 5:HyDE
1 | def hyde_retrieve(question): |