RAG学习之 查询优化技术学习指南
================================================================================本书系统地介绍了 RAG(检索增强生成)系统中的查询优化技术,从基础概念到高级应用,从理论原理到实践代码,帮助读者全面掌握提升 RAG 检索效果的核心技术。==========================================
================================================================================
RAG 查询优化技术学习指南
副标题:从入门到精通
📖 本书简介
本书系统地介绍了 RAG(检索增强生成)系统中的查询优化技术,从基础概念到高级应用,从理论原理到实践代码,帮助读者全面掌握提升 RAG 检索效果的核心技术。
🎯 适合读者
- 初学者:想了解 RAG 检索优化的入门读者
- 开发者:正在构建 RAG 系统的工程师
- 面试官:想了解 RAG 技术面试要点的招聘者
- 研究者:关注信息检索与 LLM 结合的研究人员
📚 核心内容
- Query Rewriting(查询重写) - 让问题更清晰
- HyDE(假设文档嵌入) - 用答案搜答案
- Multi-Query(多查询检索) - 多角度问问题
- 评估指标 - MRR、NDCG 详解
- 结果融合 - RRF 倒数排名融合
- 五大策略 - 速度/召回/平衡/成本/质量优先
💡 本书特色
- ✅ 12 岁能听懂:复杂概念用比喻和故事解释
- ✅ 面试导向:每个章节都有面试回答模板
- ✅ 代码实战:完整的 Python 实现示例
- ✅ 成本分析:详细的性能与成本对比
- ✅ 策略选择:不同场景的最优方案
版本信息
- 版本:v1.0
- 整理日期:2026-04-13
================================================================================
RAG 查询优化技术 学习指南 目录
第一部分:基础概念
第 1 章 Query Rewriting(查询重写)
- 通俗理解(12 岁能听懂)
- 三种主要方法
- 为什么需要它
- 面试回答指南(3 分钟版本)
- 技术架构
- 常见追问
第 2 章 评估指标:MRR 和 NDCG
- MRR 通俗理解与计算
- NDCG 通俗理解与计算
- MRR vs NDCG 对比
- 面试回答模板
第二部分:核心技术
第 3 章 HyDE(假设文档嵌入)
文件: 04-hyde.md
- 通俗理解(12 岁能听懂)
- 面试回答指南
- 为什么有效(4 个原因)
- 反直觉的事实
- 适用场景
- 与 Query Rewriting 对比
第 4 章 Multi-Query(多查询检索)
- 通俗理解(12 岁能听懂)
- 核心概念拆解
- 与 Query Rewriting 对比
- 面试回答指南
- 优势与成本分析(6-8 倍详解)
- 优化策略
第 5 章 RRF(倒数排名融合)
文件: 06-rrf.md
- 什么是 RRF
- 计算公式
- 为什么 k=60
- 完整计算例子
- RRF 的优点
- 代码实现
第三部分:实战策略
第 6 章 五大 RAG 策略
文件: 07-strategies.md
- 速度优先策略(Speed-First)
- 召回率优先策略(Recall-First)
- 平衡型策略(Balanced)
- 成本优先策略(Cost-First)
- 质量优先策略(Quality-First)
- 五策略对比总表
- Fallback 方案对比
- 策略选择决策树
第 7 章 完整流程评价
文件: 08-pipeline.md
- Multi-Query → Top-30 → Rerank → Top-5 流程详解
- 优势分析
- 潜在问题
- 优化建议
- 完整代码实现
第四部分:综合对比
第 8 章 三种技术对比
文件: 09-comparison.md
- 核心思想对比
- 工作流程对比
- 详细成本分析
- 优势分析
- 适用场景对比
- 组合使用策略
- 面试回答模板
附录
附录 A 术语表
- RAG 相关术语
- 检索优化术语
- 评估指标术语
- 模型相关术语
- 常见缩写
附录 B 代码片段速查
- Query Rewriting 实现
- HyDE 实现
- Multi-Query 实现
- RRF 实现
- 完整 RAG 策略实现
- 评估指标计算
附录 C 面试速查表
- 概念定义速查
- 技术对比速查
- 场景选择速查
- 面试回答模板
- 常见问题速查
- 面试准备清单
附录 D 参考资料
- 论文与文章
- 开源项目
- API 文档
- 学习资源
- 工具与库
快速索引
| 问题 | 章节 |
|---|---|
| 查询太模糊怎么办? | 第 1 章 Query Rewriting |
| 怎么评估检索效果? | 第 2 章 评估指标 |
| 什么是 HyDE? | 第 3 章 HyDE |
| 什么是 Multi-Query? | 第 4 章 Multi-Query |
| 多个结果怎么融合? | 第 5 章 RRF |
| 如何选择策略? | 第 6 章 五大策略 |
| 完整流程怎么样? | 第 7 章 流程评价 |
| 三种技术怎么选? | 第 8 章 技术对比 |
| 面试怎么准备? | 附录 C 面试速查表 |
本书文件列表
| 序号 | 文件名 | 章节 |
|---|---|---|
| 00 | 00-封面.md | 封面与简介 |
| 01 | 01-目录.md | 本目录 |
| 02 | 02-query-rewriting.md | 第 1 章 |
| 03 | 03-evaluation-metrics.md | 第 2 章 |
| 04 | 04-hyde.md | 第 3 章 |
| 05 | 05-multi-query.md | 第 4 章 |
| 06 | 06-rrf.md | 第 5 章 |
| 07 | 07-strategies.md | 第 6 章 |
| 08 | 08-pipeline.md | 第 7 章 |
| 09 | 09-comparison.md | 第 8 章 |
| 10 | 10-appendix-glossary.md | 附录 A |
| 11 | 11-appendix-code.md | 附录 B |
| 12 | 12-appendix-interview.md | 附录 C |
| 13 | 13-appendix-references.md | 附录 D |
阅读建议
📖 系统学习
按顺序阅读:00 → 01 → 02 → … → 13
🎯 问题导向
根据上表「快速索引」查阅对应章节
💼 面试准备
重点阅读:
- 第 2-5 章(核心技术)
- 第 8 章(技术对比)
- 附录 C(面试速查表)
================================================================================
第 1 章 Query Rewriting(查询重写)
1.1 通俗理解(12 岁能听懂)
问题从这里开始 📚
想象你有一个特别聪明的图书管理员朋友,但你不会说话,总是说不清楚自己想要什么书。
假设你想找关于"苹果"的资料,但你只说了两个字:
“苹果”
图书管理员会想:
- 是要找苹果水果的营养资料?
- 还是要找苹果公司的产品?
- 还是要找苹果手机的评测?
你说的话太简单了,图书管理员帮不到你。
Query Rewriting 就像一个"翻译官" 🔄
查询重写的作用就是:把你说的简单问题,翻译成清楚的问题。
| 你说的 | 重写后变成 |
|---|---|
| “苹果” | “苹果公司的历史和主要产品” |
| “怎么胖了” | “如何通过饮食和运动健康增重” |
| “python list” | “Python 编程语言中列表的使用方法” |
三种主要方法 🔧
1. 补全信息
把缺少的部分补上。
- 你说:“怎么变强?”
- 重写:“如何通过锻炼让肌肉变强?”
2. 换个说法
用更容易理解的方式说同一件事。
- 你说:“AI 咋回事?”
- 重写:“人工智能的基本原理是什么?”
3. 拆开问
一个大问题拆成几个小问题。
- 你说:“怎么学好编程?”
- 重写:
- “编程入门需要学什么?”
- “如何提高编程能力?”
- “有哪些编程学习资源?”
为什么需要它?🎯
在 RAG(检索增强生成)系统里,如果查询不清楚,就找不到正确的资料。
就像一个搜索引擎:
- 你搜 “python” → 可能找到蛇的资料 🐍
- 重写后搜 “Python 编程语言教程” → 找到正确的编程资料 💻
查询重写让你的问题更容易被搜索引擎理解,找到更准确的答案。
一张图总结
原始问题 → [查询重写] → 清晰的问题 → [搜索] → 更好的答案
❌ ✅ ✅ ✅ ✅
模糊 翻译一下 容易搜索 找到资料 解决问题
就像你有一个会说话的朋友,帮你把磕磕巴巴的话变成清楚的问题!
1.2 面试回答指南
回答框架(3 分钟版本)
1️⃣ 先给定义(15 秒)
“Query Rewriting 就是把用户的原始查询转换成更适合检索的形式,目的是提高检索的准确率。”
2️⃣ 说明为什么需要(30 秒)
"用户查询通常有两个问题:
- 太简短 —— 比如只搜 ‘python’,不知道是蛇还是编程语言
- 表述模糊 —— 比如 ‘怎么变强’,缺少上下文
直接拿这种查询去检索,召回的文档质量会很差,后续生成也会受影响。"
3️⃣ 讲核心技术(1 分钟)
"主要有三种方法:
① 查询扩展(Query Expansion)
- 添加同义词或相关词,比如 ‘手机’ → ‘智能手机 评测 推荐’
- 技术:可以用 LLM 生成、可以用词向量找近义词
② 查询改写(Query Reformulation)
- 换个更清晰的说法,比如 ‘AI 咋回事’ → ‘人工智能基本原理’
- 技术:通常用 LLM 做 paraphrase
③ 查询分解(Query Decomposition)
- 把复杂问题拆成多个子问题,比如 ‘对比 iPhone 和华为’
- ‘iPhone 的主要特点’
- ‘华为手机的主要特点’
- 技术:LLM + 提示词工程"
4️⃣ 结合实际应用(30 秒)
"在 RAG 系统里,查询重写通常放在检索之前:
用户查询 → [Query Rewriting] → [检索] → [生成]我在项目里用过 LLM 来做重写,比如用这样一个 prompt:
将以下查询改写成更适合搜索引擎的形式: 原始查询:{query} 要求:补充上下文,使用明确术语效果提升很明显,尤其是处理简短查询时。"
5️⃣ 加分项(可选,展示深度)
"进阶做法还有:
- 多查询检索:生成 3-5 个变体并行检索,然后去重融合
- 用户历史感知:结合用户之前的查询补充上下文
- 假设文档嵌入(HyDE):让 LLM 生成一个假设答案,用答案的嵌入去检索"
1.3 面试小贴士
| 要点 | 说明 |
|---|---|
| 先给定义 | 让面试官知道你真的懂 |
| 结构化 | 用 1、2、3 分点说,显得有条理 |
| 结合实际 | 说一个你用过的具体方法/项目 |
| 不要背概念 | 用自己的话讲,可以举例 |
1.4 常见追问
Q: 怎么评估查询重写的效果?
“看检索阶段的指标,比如 MRR、NDCG,或者看最终答案质量(人工评估或 LLM-as-a-judge)”
Q: 重写会不会引入噪声?
“会,所以要控制重写幅度。可以用少样本提示让 LLM 保持原意,或者设置置信度阈值,低置信度时用原始查询”
Q: 和 RAG 其他优化方法比怎么样?
“查询重写是查询侧优化,和索引侧优化(比如 chunk 策略、元数据过滤)是互补的,可以一起用”
1.5 技术架构
┌─────────────┐ ┌──────────────────┐ ┌──────────┐ ┌──────────┐
│ 用户查询 │ → │ Query Rewriting │ → │ 检索器 │ → │ 生成器 │
│ (原始问题) │ │ (查询重写模块) │ │ (Retriever)│ │ (Generator)│
└─────────────┘ └──────────────────┘ └──────────┘ └──────────┘
│
├──→ 查询扩展 (同义词、相关词)
├──→ 查询改写 (Paraphrase)
└──→ 查询分解 (多子问题)
1.6 核心要点总结
关键概念
- 定义:将原始查询转换为更适合检索的形式
- 目标:提高检索准确率
- 位置:检索前的预处理步骤
三种方法
| 方法 | 做法 | 示例 |
|---|---|---|
| 查询扩展 | 添加同义词 | “手机” → “智能手机 评测 推荐” |
| 查询改写 | 换说法 | “AI 咋回事” → “人工智能基本原理” |
| 查询分解 | 拆问题 | “对比 A 和 B” → “A 的特点”+“B 的特点” |
面试关键词
- 翻译官比喻
- 检索前优化
- 补充上下文
- LLM 生成
================================================================================
第 2 章 评估指标:MRR 和 NDCG
2.1 MRR(平均倒数排名)
通俗理解
MRR 只关心:第一个正确答案排在第几名?
计算例子
假设有 3 个用户查询,每个查询的检索结果如下(✓ 表示正确答案):
| 查询 | 排名 1 | 排名 2 | 排名 3 | 排名 4 | 排名 5 | 第一个正确答案的排名 | 倒数 (1/排名) |
|---|---|---|---|---|---|---|---|
| 查询 1 | ✗ | ✗ | ✓ | ✗ | ✗ | 3 | 1/3 ≈ 0.333 |
| 查询 2 | ✓ | ✗ | ✗ | ✗ | ✗ | 1 | 1/1 = 1.0 |
| 查询 3 | ✗ | ✓ | ✗ | ✗ | ✗ | 2 | 1/2 = 0.5 |
MRR = (0.333 + 1.0 + 0.5) ÷ 3 = 1.833 ÷ 3 ≈ 0.611
公式
MRR = 1 ∣ Q ∣ ∑ i = 1 ∣ Q ∣ 1 rank i \text{MRR} = \frac{1}{|Q|} \sum_{i=1}^{|Q|} \frac{1}{\text{rank}_i} MRR=∣Q∣1i=1∑∣Q∣ranki1
其中:
- ∣ Q ∣ |Q| ∣Q∣ = 查询总数
- rank i \text{rank}_i ranki = 第 i 个查询的第一个正确答案的排名
特点
| 优点 | 缺点 |
|---|---|
| 计算简单,好理解 | 只关心第一个正确答案 |
| 适合只要找到一个答案就足够的场景 | 不关心后面还有多少正确答案 |
| 值域 [0, 1],1 最好 | 如果前 10 个都没答案,MRR=0 |
适用场景
- 问答系统(用户只需要一个正确答案)
- 客服机器人
- 事实性查询(“珠穆朗玛峰多高”)
2.2 NDCG(归一化折损累计增益)
通俗理解
NDCG 关心两件事:
- 正确答案有没有排在前面?
- 越相关的答案是不是排得越靠前?
核心思想
NDCG 有三个关键概念:
① 相关性打分 (Gain)
给每个结果打分(0-3 分):
- 3 分 = 非常相关
- 2 分 = 比较相关
- 1 分 = 有点相关
- 0 分 = 不相关
② 折损 (Discounted)
排名越靠后,价值越低。公式:
折损后的增益 = 相关性得分 / log₂(排名 + 1)
| 排名 | 折损因子 | 说明 |
|---|---|---|
| 1 | 1.0 | 无折损 |
| 2 | 0.63 | 打 6 折 |
| 3 | 0.5 | 打 5 折 |
| 5 | 0.35 | 打 35 折 |
| 10 | 0.21 | 打 2 折 |
③ 归一化 (Normalized)
除以"理想情况下的最高分",让结果在 0-1 之间。
完整计算例子
用户搜 “python 教程”,检索结果和相关性打分:
| 排名 | 文档 | 相关性 (0-3) | 折损因子 | 折损后增益 |
|---|---|---|---|---|
| 1 | Python 入门教程 | 3 | 1.0 | 3.0 |
| 2 | Python 进阶 | 2 | 0.63 | 1.26 |
| 3 | Python 网站 | 3 | 0.5 | 1.5 |
| 4 | Python 新闻 | 1 | 0.39 | 0.39 |
| 5 | 蟒蛇饲养指南 | 0 | 0.32 | 0 |
DCG@5 = 3.0 + 1.26 + 1.5 + 0.39 + 0 = 6.15
理想排序(IDCG) 应该是把最相关的排前面:
| 排名 | 理想相关性 | 折损因子 | 折损后增益 |
|---|---|---|---|
| 1 | 3 | 1.0 | 3.0 |
| 2 | 3 | 0.63 | 1.89 |
| 3 | 2 | 0.5 | 1.0 |
| 4 | 1 | 0.39 | 0.39 |
| 5 | 0 | 0.32 | 0 |
IDCG@5 = 3.0 + 1.89 + 1.0 + 0.39 + 0 = 6.28
NDCG@5 = DCG / IDCG = 6.15 / 6.28 = 0.98
(越接近 1 越好,0.98 说明排序非常接近理想状态)
2.3 MRR vs NDCG 对比
| 维度 | MRR | NDCG |
|---|---|---|
| 关心什么 | 第一个正确答案 | 所有结果的相关性 + 排序 |
| 相关性分级 | 只有对/错 | 可以有 0-3 多级 |
| 适用场景 | 问答、事实查询 | 搜索、推荐 |
| 计算复杂度 | 简单 | 较复杂 |
| 如果第一个就对了 | MRR=1 | NDCG 可能 <1(后面排错了) |
2.4 面试回答模板
如果面试官问:“怎么评估 RAG 的检索效果?”
"检索阶段的评估主要看两个指标:
MRR(平均倒数排名):只关心第一个正确答案排第几名。适合问答系统,因为用户通常只需要一个正确答案。计算公式是 1/排名,MRR 越接近 1 越好。
NDCG(归一化折损累计增益):同时考虑相关性和排序质量。它假设:① 相关性有等级(0-3 分),② 排名越靠后价值越低。NDCG 也适合搜索场景,因为用户会浏览前几条结果。
在 RAG 里,如果更关注答案质量,用 MRR;如果更关注检索文档的整体相关性,用 NDCG。实际项目中,我通常会同时监控这两个指标。"
2.5 一张图总结
┌─────────────────────────────────────────────────────────────┐
│ MRR vs NDCG 对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ MRR(平均倒数排名) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 只关心:第一个正确答案排第几? │ │
│ │ 公式:1/排名 │ │
│ │ 场景:问答系统、事实查询 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ NDCG(归一化折损累计增益) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 关心两件事: │ │
│ │ ① 正确答案有没有排前面? │ │
│ │ ② 越相关的是不是越靠前? │ │
│ │ 场景:搜索、推荐 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 选择建议: │
│ ├── 用户只需要一个答案 → MRR │
│ ├── 用户会浏览多个结果 → NDCG │
│ └── 实际项目 → 两个都监控 │
│ │
└─────────────────────────────────────────────────────────────┘
2.6 核心要点总结
MRR 关键点
- 只看第一名:只关心第一个正确答案的排名
- 计算简单:1/排名,然后求平均
- 适合问答:用户只需要一个正确答案的场景
NDCG 关键点
- 看全部:考虑所有结果的相关性
- 看排序:越相关的应该越靠前
- 多级相关:可以有 0-3 分的细致打分
面试关键词
- 倒数排名
- 折损累计增益
- 相关性分级
- 排序质量
================================================================================
第 3 章 HyDE(假设文档嵌入)
3.1 通俗理解(12 岁能听懂)
一个故事
情景 1:直接去找
你想找一本关于**“恐龙”**的书,跑到图书馆对图书管理员说:
“恐龙”
图书管理员很困惑,可能给你一本恐龙图片书、恐龙知识书、或恐龙故事书——不一定准确。
情景 2:先描述答案,再去找
这次你先请一个很聪明的朋友帮你想象:
“一本讲恐龙的书,里面应该有:霸王龙、三角龙、侏罗纪、化石、灭绝原因…”
然后你拿着这个描述去找图书管理员:
“我想找一本讲霸王龙、三角龙、侏罗纪时期、恐龙化石和灭绝原因的书”
图书管理员马上就知道你要什么了!
HyDE 就是这个"聪明的朋友"。
HyDE 的核心思想
先编一个"假答案",再用这个"假答案"去找"真答案"
工作流程
你问:"恐龙怎么灭绝的?"
↓
┌────────────────────┐
│ HyDE (聪明朋友) │
│ 帮你编一个假答案: │
│ "恐龙灭绝是因为..." │
└────────────────────┘
↓
用"假答案"去搜索
↓
找到"真答案"的文档
3.2 面试回答指南(2-3 分钟版本)
1️⃣ 先给定义(20 秒)
"HyDE 是 Hypothetical Document Embeddings 的缩写,中文叫’假设文档嵌入’。
它的核心思想是:先让 LLM 生成一个假设的答案,再用这个假设答案的向量表示去检索真实文档。
简单说,就是’用答案搜答案’,而不是’用问题搜答案’。"
2️⃣ 说明为什么需要(30 秒)
"直接检索用户查询有两个问题:
① 问题空间和答案空间不一致
- 用户问:‘怎么减肥?’
- 文档写:‘控制饮食摄入,增加运动频率…’
- '减肥’和’控制饮食’字面不同,但语义相关
② 查询太简短,信息密度低
- 用户只搜 ‘python list’,嵌入向量包含的语义信息很少
这两种情况都导致检索效果不好。"
3️⃣ 讲工作原理(40 秒)
"HyDE 的流程分三步:
第一步:生成假设答案
用 LLM 根据用户问题生成一个假设的答案文档。第二步:嵌入假设答案
把假设答案转换成向量表示。第三步:检索相似文档
用假设答案的向量去检索,找到语义相似的真实文档。关键在于:即使假设答案内容不准确,它的语言风格、关键词、语义方向都和真实文档接近,所以向量表示也接近。"
4️⃣ 说明为什么有效(40 秒)
"HyDE 有效有三个原因:
① 填补语义鸿沟
把’问题空间’映射到’答案空间’,让查询和文档在同一语义空间里。② 利用 LLM 的预训练知识
LLM 读过大量文本,知道答案通常长什么样、包含哪些关键词。③ 增加信息密度
假设答案比原始查询更长、更具体,嵌入向量包含更多语义信息。实验表明,HyDE 在开放型问答、复杂查询上效果提升明显。"
5️⃣ 结合实际应用(20 秒)
"在我的 RAG 项目里,我试过 HyDE 的方法:
# 伪代码 hypothetical_doc = llm.generate(f"请为以下问题生成一个详细的答案:{query}") query_embedding = embedder.encode(hypothetical_doc) results = vector_db.search(query_embedding, top_k=5)对于简短查询和复杂问题,检索质量确实有提升,但会增加一次 LLM 调用和一点延迟。"
3.3 HyDE 为什么有效?(深入理解)
原因 1:填补"问题 - 答案"的语义鸿沟
问题空间和答案空间不一样
| 问题 | 答案 |
|---|---|
| “怎么减肥?” | “控制饮食摄入,增加有氧运动…” |
| “手机坏了怎么办?” | “设备故障排除指南:重启…” |
- 问题用疑问句,答案用陈述句
- 问题用口语,答案用专业术语
- 问题说目标,答案说方法
HyDE 的做法
让 LLM 生成一个假设答案,把问题翻译成答案的风格:
问题:"怎么减肥?"
↓
HyDE 翻译成答案风格:
"减肥需要控制饮食摄入,增加有氧运动..."
↓
用这个去搜,就能找到写"控制饮食"的文档了!
原因 2:利用 LLM 的世界知识
LLM 在预训练时读过大量文本,它知道:
- 答案通常长什么样
- 答案会用哪些关键词
- 答案的结构是什么
即使 LLM 生成的假设答案内容不对,它的向量表示仍然能和真实答案对齐。
原因 3:缓解"词汇不匹配"问题
传统检索的问题
用户搜:“手机坏了怎么办”
文档里写:“设备故障排除指南”
手机 vs 设备,坏了 vs 故障 —— 字面不一样。
HyDE 的做法
LLM 生成假设答案:
“如果手机出现故障,可以尝试以下方法:1. 重启设备 2. 恢复出厂设置 3. 联系售后服务中心…”
这个假设答案会同时包含用户的词和文档的词,起到词汇桥接的作用。
原因 4:增加查询的信息密度
| 原始查询 | 嵌入向量信息量 |
|---|---|
| “python list” | 低(就两个词) |
| “Python 中列表的使用方法,包括创建、访问、修改列表元素的示例代码” | 高 |
HyDE 生成的假设答案更长、更具体,包含更多可嵌入的语义信息。
3.4 一个反直觉的事实
HyDE 的假设答案不需要正确!
实验表明,即使假设答案内容是错的,只要:
- 语言风格像答案
- 关键词覆盖到位
- 语义方向正确
就能有效提升检索效果。
这是因为嵌入空间里,相似主题的文档聚在一起,假设答案只需要落在正确的"语义区域"即可。
3.5 适用场景
| 适合 | 不适合 |
|---|---|
| 问答型查询(“什么是 X”) | 导航型查询(“某某官网”) |
| 开放性问题 | 事实性查询(“珠穆朗玛峰多高”) |
| 需要解释的问题 | 简单检索(“python 官网”) |
3.6 与 Query Rewriting 对比
| 方法 | 做法 | 比喻 |
|---|---|---|
| Query Rewriting | 把问题说得更清楚 | 翻译官 |
| HyDE | 编一个答案去找答案 | 美食家 |
两者都是让检索更准确,但方法不同!
3.7 常见追问及回答
Q: HyDE 和 Query Rewriting 有什么区别?
"两者目标相似但方法不同:
维度 Query Rewriting HyDE 做法 改写问题 生成答案 嵌入对象 改写后的问题 假设答案 核心优势 让问题更清晰 桥接问题 - 答案空间 延迟 低(一次 LLM) 中(一次 LLM+ 嵌入) 实际项目里可以一起用:先重写查询,再用 HyDE 检索。"
Q: 假设答案如果错了怎么办?
"这是 HyDE 反直觉的地方——假设答案不需要正确。
因为我们要的不是答案内容,而是它的向量表示。只要:
- 语言风格像答案
- 关键词覆盖到位
- 语义方向正确
就能找到正确的文档。实验表明,即使假设答案有事实错误,检索效果依然提升。"
Q: 怎么评估 HyDE 的效果?
"看检索阶段的指标:
- MRR:第一个正确答案的排名
- NDCG:整体排序质量
- Recall@K:前 K 个结果里有多少正确答案
也可以看最终答案质量,用人工评估或 LLM-as-a-judge。
我在项目里对比过,HyDE 对简短查询的 MRR 提升约 10-15%。"
3.8 一张图总结
┌──────────────────────────────────────────────────────────┐
│ HyDE 工作流程 │
├──────────────────────────────────────────────────────────┤
│ │
│ 用户问题 HyDE │
│ ┌─────────┐ ┌─────────────┐ │
│ │ "如何 │ │ 假设答案: │ │
│ │ 减肥?" │ ────────────→│ 控制饮食、 │ │
│ └─────────┘ 生成假设 │ 增加运动... │ │
│ └─────────────┘ │
│ ↓ │
│ 传统检索:问题 → 文档 用假设答案嵌入检索 │
│ 鸿沟大 ❌ 鸿沟小 ✅ │
│ │
│ 结果:找到"科学减重指南"等真实文档 │
│ │
└──────────────────────────────────────────────────────────┘
3.9 核心要点总结
HyDE 关键点
- 核心思想:用答案搜答案
- 三步流程:生成假设答案 → 嵌入 → 检索
- 反直觉:假设答案不需要正确
为什么有效
- 填补问题 - 答案语义鸿沟
- 利用 LLM 预训练知识
- 缓解词汇不匹配
- 增加信息密度
面试关键词
- Hypothetical Document Embeddings
- 用答案搜答案
- 语义鸿沟
- 向量表示对齐
================================================================================
第 4 章 Multi-Query(多查询检索)
4.1 通俗理解(12 岁能听懂)
你有一次提问的机会
想象你在玩一个寻宝游戏,面前有一个神秘的宝箱,里面装着你要找的答案。
游戏规则是:你只能对宝箱管理员说一句话,然后他帮你从仓库里找宝物。
第一次尝试
你问:
“怎么变强?”
管理员想了想,给你拿了一本《健身入门》。但你心里想的是:“我是想变编程高手啊!”
问题出在哪里?
你的问题太模糊了!"变强"可以有很多种意思:
- 💪 身体变强(健身)
- 🧠 大脑变强(学习)
- 💻 编程变强(写代码)
- 🎮 游戏变强(打游戏)
Multi-Query 就像一个"聪明的分身术" 🔄
Multi-Query 的做法:
你不是只说一句话,而是同时说好几句不同的话:
原始问题:"怎么变强?"
↓
┌─────────────────────────────┐
│ Multi-Query 分身术 │
└─────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ "如何通过健身让身体变强?" │
│ "如何提高编程能力?" │
│ "如何在游戏中变强?" │
└─────────────────────────────────────┘
↓
用这 3 个问题分别去搜索
↓
把找到的所有结果合并在一起
↓
去掉重复的,选出最好的
↓
给用户一个完整的答案!
一个比喻
| 方法 | 比喻 | 结果 |
|---|---|---|
| 单次查询 | 派 1 个人去找东西 | 可能找错 |
| Multi-Query | 派 3 个人从不同角度去找 | 更可能找到对的 |
为什么这样更好?
就像你问朋友问题:
- 问 1 个朋友,可能他只想到一个角度
- 问 3 个朋友,能得到 3 种不同的想法,最后综合起来更全面!
4.2 核心概念拆解
什么是 Multi-Query?
Multi-Query 是一种检索优化技术,它的核心思想是:
用多个不同角度的查询变体并行检索,然后合并结果
工作流程
- 生成变体:用 LLM 把原始查询改写成 3-5 个不同的版本
- 并行检索:每个变体同时去检索
- 结果融合:合并所有结果,去掉重复的,重新排序
- 返回答案:给用户一个更全面的结果
举个例子
用户问:“python list 怎么用?”
Multi-Query 生成的变体:
- “Python 列表的基本语法”
- “Python list 创建和访问方法”
- “Python 列表操作示例代码”
- “如何在 Python 中使用列表存储数据”
每个查询都能找到一些不同的文档,合并起来就更全面了!
4.3 与 Query Rewriting 的对比
| 维度 | Query Rewriting | Multi-Query |
|---|---|---|
| 目标 | 把问题说清楚 | 从多个角度问问题 |
| 生成数量 | 1 个改写后的问题 | 3-5 个变体问题 |
| 检索次数 | 1 次 | 多次(并行) |
| 比喻 | 翻译官 | 分身术 |
| 延迟 | 低 | 中(但并行可以加速) |
| 覆盖率 | 单一角度 | 多角度覆盖 |
形象比喻
- Query Rewriting:你说的不清楚,我帮你翻译成清楚的一句话
- Multi-Query:你可能想问多个角度,我帮你同时问好几个问题
4.4 面试回答指南(2 分钟版本)
1️⃣ 先给定义(20 秒)
"Multi-Query 是一种检索优化技术,核心思想是生成多个查询变体并行检索,然后合并结果。
简单说,就是用’分身术’从多个角度问同一个问题,找到更全面的答案。"
2️⃣ 说明为什么需要(30 秒)
"用户查询有两个问题:
① 歧义性
- 比如’怎么变强’,可能是健身、编程、学习
- 单次查询只能命中一个角度
② 覆盖不全
- 即使用户问题清楚,单次检索也可能漏掉相关文档
- 不同表述的文档可能只被某些变体命中
Multi-Query 通过多个变体来解决这两个问题。"
3️⃣ 讲工作原理(40 秒)
"Multi-Query 分三步:
第一步:生成变体
用 LLM 根据用户查询生成 3-5 个不同角度的变体。第二步:并行检索
每个变体同时去检索(可以并行执行,延迟不增加太多)。第三步:结果融合
合并所有结果,去掉重复的,然后用 RRF(Reciprocal Rank Fusion)或其他方法重新排序。关键是:不同变体可能命中不同但相关的文档,合并后覆盖率更高。"
4️⃣ 结合实际应用(20 秒)
"在 RAG 系统里,Multi-Query 通常和 Query Rewriting 一起用:
用户查询 → [Query Rewriting] → [Multi-Query 生成变体] → [并行检索] → [结果融合] → [生成]我在项目里试过,对于开放型问题和歧义查询,效果提升明显。"
4.5 优势与成本分析
核心优势
| 优势 | 说明 | 数据 |
|---|---|---|
| 召回率提升 | 不同变体命中不同但相关的文档 | Recall@10 +30%~80% |
| 歧义处理 | 同时覆盖多个可能的含义 | 歧义查询提升 81% |
| 多角度覆盖 | 每个变体找到不同角度的文档 | 答案更全面 |
| 鲁棒性强 | 某个变体不好,其他可以弥补 | 容错率高 |
成本分析:为什么是 6-8 倍?
详细成本拆解(基于典型云 API 价格估算)
| 组件 | 单次查询 | Multi-Query (3 变体) | Multi-Query (5 变体) |
|---|---|---|---|
| LLM 调用(生成变体) | 0 次 × $0.002 | 1 次 × $0.002 = $0.002 | 1 次 × $0.002 = $0.002 |
| 嵌入(Embedding) | 1 次 × $0.0001 | 3 次 × $0.0001 = $0.0003 | 5 次 × $0.0001 = $0.0005 |
| 向量检索 | 1 次 × $0.0005 | 3 次 × $0.0005 = $0.0015 | 5 次 × $0.0005 = $0.0025 |
| 总计 | $0.0006 | $0.0038 | $0.0050 |
| 倍数 | 1x | 6.3x | 8.3x |
倍数计算过程:
单次查询总成本:$0.0001 + $0.0005 = $0.0006
Multi-Query (3 变体) 总成本:$0.002 + $0.0003 + $0.0015 = $0.0038
倍数 = $0.0038 ÷ $0.0006 = 6.33 倍
Multi-Query (5 变体) 总成本:$0.002 + $0.0005 + $0.0025 = $0.0050
倍数 = $0.0050 ÷ $0.0006 = 8.33 倍
成本构成分析
┌─────────────────────────────────────────────────────────────┐
│ Multi-Query 成本构成(3 变体) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 单次查询:$0.0006 │
│ ├── 嵌入:$0.0001 (16.7%) │
│ └── 检索:$0.0005 (83.3%) │
│ │
│ Multi-Query:$0.0038 │
│ ├── LLM 生成变体:$0.002 (52.6%) ← 新增 │
│ ├── 嵌入:$0.0003 (7.9%) ← 3 倍 │
│ └── 检索:$0.0015 (39.5%) ← 3 倍 │
│ │
│ 倍数 = $0.0038 / $0.0006 = 6.33 倍 │
│ │
└─────────────────────────────────────────────────────────────┘
关键点说明
-
LLM 调用是主要成本增量
- LLM 生成变体:$0.002,占总成本 52.6%
- 这是纯新增的成本,单次查询没有这一步
-
嵌入和检索成本按变体数量线性增长
变体数 嵌入成本 检索成本 总成本 倍数 1 次(单次) $0.0001 $0.0005 $0.0006 1x 3 次 $0.0003 $0.0015 $0.0038 6.3x 5 次 $0.0005 $0.0025 $0.0050 8.3x -
实际倍数取决于变体数量
- 3 个变体 → 约 6 倍
- 4 个变体 → 约 7 倍
- 5 个变体 → 约 8 倍
⚠️ 注意:以上价格是基于典型云 API 的示例估算,实际成本取决于:
- 使用的 LLM 模型(GPT-4 比 GPT-3.5 贵)
- 嵌入模型(商业 API vs 本地部署)
- 向量数据库(云服务 vs 自建)
- 查询长度(tokens 数量)
如果是本地部署模型,边际成本接近零,但前期投入大,成本倍数仍然适用(6-8 倍的资源消耗)。
4.6 优化策略:如何降低成本
策略 1:条件触发
def should_use_multi_query(query):
"""只对特定查询使用 Multi-Query"""
# 查询太短(< 5 词)→ 用 Multi-Query
if len(query.split()) < 5:
return True
# 查询有歧义(包含泛化词)→ 用 Multi-Query
ambiguous_words = ["怎么", "如何", "什么", "为什么"]
if any(word in query for word in ambiguous_words):
return True
# 事实性查询 → 不用 Multi-Query
if query.endswith("吗") or "?" in query:
return False
# 默认不用
return False
策略 2:动态调整变体数量
| 查询类型 | 变体数量 | 延迟 | 成本 |
|---|---|---|---|
| 简短模糊 | 5 个 | 高 | 高 |
| 一般查询 | 3 个 | 中 | 中 |
| 明确查询 | 2 个 | 低 | 低 |
策略 3:并行化优化
# 串行(慢)❌
results1 = retriever.search(query1)
results2 = retriever.search(query2)
results3 = retriever.search(query3)
# 并行(快)✅
import asyncio
results1, results2, results3 = await asyncio.gather(
retriever.search(query1),
retriever.search(query2),
retriever.search(query3)
)
策略 4:缓存变体生成
# 常见查询的变体可以缓存
cache = {
"python 教程": ["Python 入门教程", "Python 基础语法", "Python 学习路径"],
"怎么减肥": ["如何科学减肥", "减肥饮食指南", "减肥运动方法"],
}
def get_variants(query):
if query in cache:
return cache[query] # 直接返回,省去 LLM 调用
else:
variants = llm.generate_variants(query)
cache[query] = variants
return variants
4.7 一张图总结
┌─────────────────────────────────────────────────────────┐
│ Multi-Query 工作流程 │
├─────────────────────────────────────────────────────────┤
│ │
│ 用户问:"怎么变强?" │
│ ↓ │
│ ┌──────────────────────────┐ │
│ │ LLM 生成 3-5 个变体问题 │ │
│ └──────────────────────────┘ │
│ ↓ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ 健身变强 │ │ 编程变强 │ │ 学习变强 │ │
│ │ [搜索] │ │ [搜索] │ │ [搜索] │ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ ↓ ↓ ↓ │
│ ┌───────────────────────────────────────────┐ │
│ │ 合并结果 + 去重 + 排序 │ │
│ └───────────────────────────────────────────┘ │
│ ↓ │
│ 最终答案(更全面、更准确) │
│ │
└─────────────────────────────────────────────────────────┘
4.8 核心要点总结
Multi-Query 关键点
- 核心思想:多角度问问题,找到更全面的答案
- 四步流程:生成变体 → 并行检索 → 结果融合 → 返回答案
- 成本:6-8 倍基准成本
优势
- 召回率提升 30%~80%
- 歧义处理能力强
- 答案更全面
- 鲁棒性强
优化策略
- 条件触发(不是所有查询都用)
- 动态调整变体数量
- 并行化检索
- 缓存常见查询变体
面试关键词
- 分身术比喻
- 多角度覆盖
- RRF 结果融合
- 成本 6-8 倍
================================================================================
第 5 章 RRF(倒数排名融合)
5.1 什么是 RRF?
RRF(Reciprocal Rank Fusion / 倒数排名融合)是一种合并多个检索结果的方法,它的特点是:
- 不依赖具体分数,只看排名
- 排名越靠前的结果,权重越高
在 Multi-Query 中,RRF 用于融合多个查询变体的检索结果。
5.2 计算公式
RRF 分数 = ∑ i = 1 N 1 k + rank i \text{RRF 分数} = \sum_{i=1}^{N} \frac{1}{k + \text{rank}_i} RRF 分数=i=1∑Nk+ranki1
其中:
N= 检索结果的数量(比如有 3 个查询变体)rank_i= 文档在第 i 个查询结果中的排名k= 平滑参数(通常是 60)
5.3 为什么 k = 60?
k 是一个平滑参数,目的是让排名之间的差距不要太大。
| 排名 | 1/(1+60) | 1/(10+60) | 1/(100+60) |
|---|---|---|---|
| 值 | 0.0164 | 0.0143 | 0.00625 |
| 意义 | 第 1 名价值 | 第 10 名价值 | 第 100 名价值 |
- 如果 k 太小(比如 k=1),第 1 名和第 2 名差距太大(1/2 vs 1/3)
- 如果 k 太大(比如 k=1000),所有排名差距都很小
实验发现 k=60 是一个比较平衡的值。
5.4 完整计算例子
场景:用户搜:“python 教程”,Multi-Query 生成了 3 个变体查询,每个查询返回前 3 个结果:
| 文档 | 查询 1 排名 | 查询 2 排名 | 查询 3 排名 |
|---|---|---|---|
| 文档 A(Python 入门) | 1 | 3 | 2 |
| 文档 B(Python 进阶) | 2 | 1 | 5 |
| 文档 C(Python 实战) | 3 | 2 | 1 |
| 文档 D(Python 技巧) | 5 | 4 | 3 |
计算 RRF 分数(k=60):
文档 A: 1/(60+1) + 1/(60+3) + 1/(60+2)
= 0.01639 + 0.01587 + 0.01613
= 0.04839
文档 B: 1/(60+2) + 1/(60+1) + 1/(60+5)
= 0.01613 + 0.01639 + 0.01538
= 0.04790
文档 C: 1/(60+3) + 1/(60+2) + 1/(60+1)
= 0.01587 + 0.01613 + 0.01639
= 0.04839
文档 D: 1/(60+5) + 1/(60+4) + 1/(60+3)
= 0.01538 + 0.01562 + 0.01587
= 0.04687
最终排序:
| 文档 | RRF 分数 | 最终排名 |
|---|---|---|
| 文档 A | 0.04839 | 🥇 第 1 名(并列) |
| 文档 C | 0.04839 | 🥇 第 1 名(并列) |
| 文档 B | 0.04790 | 第 3 名 |
| 文档 D | 0.04687 | 第 4 名 |
5.5 RRF 的优点
| 优点 | 说明 |
|---|---|
| 简单易懂 | 公式简单,计算快 |
| 不依赖分数 | 只看排名,不管具体相似度分数 |
| 鲁棒性强 | 对噪声不敏感,某个查询结果差一点没关系 |
| 广泛适用 | 适合任何有排名的场景 |
| 无需调参 | k=60 是经验值,大部分场景都好用 |
为什么不直接用相似度分数?
因为不同查询的相似度分数不可比:
- 查询 1 的相似度 0.8 可能等于 查询 2 的相似度 0.6
- 但排名是统一的:第 1 名就是比第 2 名好
5.6 代码实现
def reciprocal_rank_fusion(results_list, k=60):
"""
RRF 倒数排名融合
参数:
results_list: List[List[doc_id, score, rank]]
多个查询结果列表,每个结果包含文档 ID、分数、排名
k: 平滑参数,默认 60
返回:
sorted_docs: 按 RRF 分数排序的文档列表
"""
rrf_scores = {}
for results in results_list:
for rank, (doc_id, score, _) in enumerate(results, start=1):
if doc_id not in rrf_scores:
rrf_scores[doc_id] = 0
rrf_scores[doc_id] += 1 / (k + rank)
# 按 RRF 分数排序
sorted_docs = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
return sorted_docs
使用示例
# 假设有 3 个查询的检索结果
results1 = [("doc_a", 0.9, 1), ("doc_b", 0.8, 2), ("doc_c", 0.7, 3)]
results2 = [("doc_b", 0.95, 1), ("doc_c", 0.85, 2), ("doc_a", 0.75, 3)]
results3 = [("doc_c", 0.92, 1), ("doc_a", 0.82, 2), ("doc_b", 0.72, 3)]
# RRF 融合
results_list = [results1, results2, results3]
fused = reciprocal_rank_fusion(results_list, k=60)
print(fused)
# 输出:[('doc_a', 0.04839...), ('doc_c', 0.04839...), ('doc_b', 0.04790...)]
5.7 面试回答模板
如果面试官问:“Multi-Query 的结果怎么融合?”
"常用方法是 RRF(Reciprocal Rank Fusion / 倒数排名融合)。
公式是:
RRF 分数 = Σ (1 / (k + rank_i)),其中 k 通常是 60,rank_i 是文档在第 i 个查询结果中的排名。RRF 的核心思想是:不依赖具体分数,只看排名。排名越靠前的结果,权重越高。最后按 RRF 分数重新排序。
为什么用 RRF 而不是直接合并相似度分数?因为不同查询的相似度分数不可比——查询 1 的 0.8 分可能等于查询 2 的 0.6 分。但排名是统一的:第 1 名就是比第 2 名好。"
5.8 一张图总结
┌─────────────────────────────────────────────────────────────┐
│ RRF 倒数排名融合 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 公式:RRF 分数 = Σ (1 / (60 + rank_i)) │
│ │
│ 计算示例: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 文档 A: 查询 1 排第 1 + 查询 2 排第 3 + 查询 3 排第 2 │ │
│ │ RRF 分数 = 1/61 + 1/63 + 1/62 = 0.04839 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 为什么 k=60? │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ k 太小 → 排名差距太大 │ │
│ │ k 太大 → 排名差距太小 │ │
│ │ k=60 → 经验值,平衡选择 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 优点: │
│ ✅ 简单易懂,计算快 │
│ ✅ 不依赖分数,只看排名 │
│ ✅ 鲁棒性强,对噪声不敏感 │
│ ✅ 无需调参,k=60 通用 │
│ │
└─────────────────────────────────────────────────────────────┘
5.9 核心要点总结
RRF 关键点
- 定义:一种合并多个检索结果的方法
- 核心:只看排名,不依赖具体分数
- 公式:RRF 分数 = Σ (1 / (k + rank_i))
参数选择
- k=60:经验值,平衡排名差距
优点
- 简单易懂,计算快
- 不依赖分数
- 鲁棒性强
- 无需调参
面试关键词
- 倒数排名融合
- k=60
- 排名不可比
- Multi-Query 结果融合
================================================================================
第 6 章 五大 RAG 策略
6.1 策略一:速度优先(Speed-First)🚀
适用场景
- 实时对话系统
- 延迟敏感场景(<500ms)
- 简单事实性查询
- MVP/原型验证
流程图
用户查询 → [Query Rewriting] → [向量检索 Top-5] → [LLM 生成] → 答案
(可选)
延迟:+0ms
成本:1x
核心配置
| 阶段 | 策略 | 参数 |
|---|---|---|
| 查询处理 | 简单预处理(去空格、标准化) | - |
| 检索 | 向量检索(Bi-Encoder) | Top-5, 阈值 0.5 |
| Rerank | 无 | - |
| 生成 | 直接生成 | temperature=0.3 |
代码实现
class SpeedFirstRAG:
"""速度优先策略 - 目标 <300ms"""
def __init__(self):
self.embedder = SentenceTransformer('all-MiniLM-L6-v2')
self.vector_db = ChromaDB(collection='fast_collection')
self.llm = OpenRouterLLM(model='gpt-4o-mini')
def query(self, user_query: str) -> str:
# 简单预处理(可选)
query = user_query.strip()
# 单次检索 Top-5
embedding = self.embedder.encode(query)
results = self.vector_db.search(embedding, top_k=5)
# 检查检索质量
if not results or results[0].score < 0.5:
# Fallback: 直接让 LLM 回答
return self.llm.generate(f"直接回答:{query}")
# 生成答案
context = "\n".join([doc.content for doc in results])
return self.llm.generate(f"根据上下文回答:\n{context}\n\n问题:{query}")
性能指标
| 指标 | 目标值 |
|---|---|
| 延迟 | <300ms |
| 成本 | 1x(基准) |
| 召回率 | 中等(Recall@5 ≈ 0.45) |
| 答案质量 | ⭐⭐⭐ |
Fallback 策略
def fallback_strategy(self, results, query):
"""速度优先的 Fallback - 快速降级"""
if not results:
# 空结果 → 直接 LLM 回答
return self.llm.generate(f"直接回答:{query}")
if results[0].score < 0.3:
# 分数太低 → 关键词检索快速补救
bm25_results = self.bm25.search(query, top_k=3)
if bm25_results:
return self.generate_answer(bm25_results, query)
# 否则用原结果
return self.generate_answer(results, query)
6.2 策略二:召回率优先(Recall-First)📥
适用场景
- 复杂研究型问题
- 开放型问答
- 法律/医疗等专业领域
- 对答案完整性要求高
流程图
┌─────────────────────────────────────────────────────────────────┐
│ 召回率优先策略 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户查询 │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Multi-Query 扩展 (5 个变体) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 混合检索:向量 + 关键词 │ │
│ │ 每个变体检索 Top-50 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ RRF 融合 + 去重 │ │
│ │ 约 150-200 条候选 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Rerank (Cross-Encoder) │ │
│ │ 精细打分,取 Top-10 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ LLM 生成(带长上下文) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 完整答案 │
│ │
└─────────────────────────────────────────────────────────────────┘
核心配置
| 阶段 | 策略 | 参数 |
|---|---|---|
| 查询扩展 | Multi-Query | 5 个变体 |
| 检索 | 混合检索(向量+BM25) | Top-50/变体 |
| 融合 | RRF(k=60) | - |
| Rerank | Cross-Encoder | Top-10 |
| 生成 | 长上下文窗口 | max_tokens=2000 |
代码实现
class RecallFirstRAG:
"""召回率优先策略 - 目标 Recall@50 > 0.9"""
def __init__(self):
self.llm = OpenRouterLLM(model='gpt-4o')
self.embedder = SentenceTransformer('bge-large-zh-v1.5')
self.vector_db = ChromaDB()
self.bm25 = BM25Search()
self.reranker = CrossEncoderReranker('bge-reranker-large')
def query(self, user_query: str) -> str:
# Step 1: Multi-Query 扩展
variants = self.llm.generate(
f"为以下查询生成 5 个不同角度的变体:\n{user_query}"
)
queries = [user_query] + variants.split('\n')
# Step 2: 混合检索(每个变体 Top-50)
all_results = []
for q in queries:
vector_results = self.vector_db.search(
self.embedder.encode(q), top_k=50
)
keyword_results = self.bm25.search(q, top_k=50)
all_results.extend(vector_results)
all_results.extend(keyword_results)
# Step 3: 去重 + RRF 融合
deduped_results = self.rrf_fusion(all_results, k=60)
# Step 4: Rerank 精筛
reranked = self.reranker.rerank(user_query, deduped_results[:150], top_k=10)
# Step 5: 生成完整答案
context = "\n\n".join([doc.content for doc in reranked])
return self.llm.generate(
f"请根据以下资料完整回答问题,确保覆盖所有重要信息:\n\n{context}\n\n问题:{user_query}"
)
性能指标
| 指标 | 目标值 |
|---|---|
| 延迟 | 2000-4000ms |
| 成本 | 10-15x |
| 召回率 | 极高(Recall@50 > 0.9) |
| 答案质量 | ⭐⭐⭐⭐⭐ |
Fallback 策略
def fallback_strategy(self, results, query):
"""召回率优先的 Fallback - 保证不遗漏"""
if len(results) < 10:
# 结果太少 → 扩大检索范围
more_results = self.vector_db.search(
self.embedder.encode(query), top_k=100
)
results.extend(more_results)
if all(doc.score < 0.3 for doc in results):
# 分数都太低 → 触发 HyDE
hypothetical_doc = self.llm.generate(
f"为以下问题生成一个详细的答案:{query}"
)
hyde_results = self.vector_db.search(
self.embedder.encode(hypothetical_doc), top_k=50
)
results.extend(hyde_results)
return results
6.3 策略三:平衡型(Balanced)⚖️
适用场景
- 通用生产环境
- 大多数企业应用
- 日常问答系统
流程图
用户查询 → [Query Rewriting] → [向量检索 Top-20] → [Rerank Top-8] → [LLM 生成] → 答案
↓
延迟:~800ms
成本:4x
核心配置
| 阶段 | 策略 | 参数 |
|---|---|---|
| 查询处理 | Query Rewriting | 1 次改写 |
| 检索 | 向量检索 | Top-20 |
| Rerank | Cross-Encoder | Top-8 |
| 生成 | 标准上下文 | max_tokens=1000 |
代码实现
class BalancedRAG:
"""平衡型策略 - 性价比最优"""
def __init__(self):
self.embedder = SentenceTransformer('bge-base-zh-v1.5')
self.vector_db = ChromaDB()
self.reranker = CrossEncoderReranker('bge-reranker-base')
self.llm = OpenRouterLLM(model='gpt-4o-mini')
def query(self, user_query: str) -> str:
# Step 1: Query Rewriting(可选)
if len(user_query.split()) < 5:
query = self.llm.generate(
f"将以下查询改写成更适合检索的形式:\n{user_query}"
)
else:
query = user_query
# Step 2: 向量检索 Top-20
embedding = self.embedder.encode(query)
results = self.vector_db.search(embedding, top_k=20)
# Step 3: Fallback 检查
if not results or results[0].score < 0.4:
return self.hybrid_search_fallback(query)
# Step 4: Rerank 取 Top-8
reranked = self.reranker.rerank(query, results, top_k=8)
# Step 5: 生成答案
context = "\n".join([doc.content for doc in reranked[:5]])
return self.llm.generate(f"根据上下文回答:\n{context}\n\n问题:{query}")
def hybrid_search_fallback(self, query: str) -> str:
"""混合检索 Fallback"""
vector_results = self.vector_db.search(
self.embedder.encode(query), top_k=15
)
keyword_results = self.bm25.search(query, top_k=15)
fused = self.rrf_fusion(vector_results + keyword_results)
return self.generate_answer(fused[:5], query)
性能指标
| 指标 | 目标值 |
|---|---|
| 延迟 | 600-1000ms |
| 成本 | 3-5x |
| 召回率 | 中高(Recall@20 ≈ 0.7) |
| 答案质量 | ⭐⭐⭐⭐ |
6.4 策略四:成本优先(Cost-First)💰
适用场景
- 预算有限的项目
- 高并发场景
- 内部工具
- 低频使用场景
流程图
用户查询 → [本地 Embedding] → [本地向量检索 Top-3] → [本地小模型生成] → 答案
延迟:~150ms
成本:0.1x(几乎免费)
核心配置
| 阶段 | 策略 | 参数 |
|---|---|---|
| Embedding | 本地小模型 | all-MiniLM-L6-v2 |
| 检索 | 轻量向量库 | FAISS/Chroma, Top-3 |
| Rerank | 无 | - |
| 生成 | 本地小模型 | Qwen/Qwen2.5-1.5B |
代码实现
class CostFirstRAG:
"""成本优先策略 - 几乎免费"""
def __init__(self):
# 全部本地模型,无 API 调用
self.embedder = SentenceTransformer('all-MiniLM-L6-v2')
self.vector_db = FAISS(index_factory='Flat')
self.llm = TransformersLLM('Qwen/Qwen2.5-1.5B-Instruct')
def query(self, user_query: str) -> str:
# 本地 Embedding
embedding = self.embedder.encode(user_query)
# 快速检索 Top-3
results = self.vector_db.search(embedding, top_k=3)
# 简单阈值判断
if results[0].score < 0.3:
return "抱歉,我没有找到相关资料。"
# 本地生成
context = "\n".join([doc.content for doc in results])
return self.llm.generate(f"根据以下资料简要回答:\n{context}\n\n问题:{user_query}")
性能指标
| 指标 | 目标值 |
|---|---|
| 延迟 | <200ms |
| 成本 | 0.1x(本地部署) |
| 召回率 | 低(Recall@3 ≈ 0.3) |
| 答案质量 | ⭐⭐ |
6.5 策略五:质量优先(Quality-First)🏆
适用场景
- 高端企业客服
- 专业领域问答(法律、医疗、金融)
- 对答案准确性要求极高
- 预算充足
流程图
┌─────────────────────────────────────────────────────────────────┐
│ 质量优先策略(完整流程) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户查询 │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Query Rewriting + Multi-Query (3 变体) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 三路检索:向量 + BM25 + HyDE │ │
│ │ 每路 Top-30 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ RRF 融合 + 去重 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Rerank (Cohere/Jina) Top-10 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ LLM 生成 + 自检 + 引用验证 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 高质量答案(带引用) │
│ │
└─────────────────────────────────────────────────────────────────┘
核心配置
| 阶段 | 策略 | 参数 |
|---|---|---|
| 查询扩展 | Query Rewriting + Multi-Query | 1 改写 + 3 变体 |
| 检索 | 三路混合(向量+BM25+HyDE) | Top-30/路 |
| 融合 | RRF + 多样性过滤 | k=60 |
| Rerank | 商业级 Rerank | Cohere/Jina, Top-10 |
| 生成 | 高端 LLM + 自检 | GPT-4/Claude |
代码实现
class QualityFirstRAG:
"""质量优先策略 - 不计成本追求最佳效果"""
def __init__(self):
self.llm = OpenRouterLLM(model='anthropic/claude-sonnet-4.6')
self.embedder = SentenceTransformer('bge-large-zh-v1.5')
self.vector_db = ChromaDB()
self.bm25 = BM25Search()
self.reranker = CohereReranker() # 商业级 Rerank
def query(self, user_query: str) -> dict:
"""返回答案 + 引用 + 置信度"""
# Step 1: Query Rewriting
rewritten_query = self.llm.generate(
f"将以下查询改写成更清晰的形式:\n{user_query}"
)
# Step 2: Multi-Query 扩展
variants = self.llm.generate(
f"为以下查询生成 3 个不同角度的变体:\n{rewritten_query}"
)
queries = [rewritten_query] + variants.split('\n')
# Step 3: 三路检索
all_results = []
for q in queries:
# 向量检索
vector_results = self.vector_db.search(
self.embedder.encode(q), top_k=30
)
# BM25
keyword_results = self.bm25.search(q, top_k=30)
# HyDE
hypothetical_doc = self.llm.generate(
f"为以下问题生成一个详细的答案:{q}"
)
hyde_results = self.vector_db.search(
self.embedder.encode(hypothetical_doc), top_k=30
)
all_results.extend(vector_results + keyword_results + hyde_results)
# Step 4: RRF 融合 + 去重
fused = self.rrf_fusion(all_results, k=60)
# Step 5: 商业级 Rerank
reranked = self.reranker.rerank(rewritten_query, fused[:200], top_k=10)
# Step 6: LLM 生成 + 引用验证
response = self.llm.generate_with_citations(
query=rewritten_query,
documents=reranked[:8],
require_citations=True
)
# Step 7: 自检(可选)
if self.llm.self_check_confidence(response) < 0.8:
# 置信度低 → 重新生成
response = self.llm.generate(
f"请重新审视以下资料,确保答案准确:\n\n{response}"
)
return {
'answer': response,
'references': reranked[:5],
'confidence': self.llm.estimate_confidence(response)
}
性能指标
| 指标 | 目标值 |
|---|---|
| 延迟 | 3000-5000ms |
| 成本 | 15-20x |
| 召回率 | 极高(Recall@100 > 0.95) |
| 答案质量 | ⭐⭐⭐⭐⭐ |
| 附加功能 | 引用溯源、置信度评估 |
6.6 五种策略对比总表
| 维度 | 速度优先 | 平衡型 | 召回率优先 | 成本优先 | 质量优先 |
|---|---|---|---|---|---|
| 延迟 | <300ms | 600-1000ms | 2000-4000ms | <200ms | 3000-5000ms |
| 成本 | 1x | 3-5x | 10-15x | 0.1x | 15-20x |
| 召回率 | 中 | 中高 | 极高 | 低 | 极高 |
| 答案质量 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 检索路数 | 1 路 | 1 路 | 2 路混合 | 1 路 | 3 路混合 |
| Rerank | ❌ | ✅ | ✅ | ❌ | ✅ 商业级 |
| Multi-Query | ❌ | 可选 | ✅ | ❌ | ✅ |
| HyDE | ❌ | ❌ | 可选 | ❌ | ✅ |
| 适用场景 | 实时对话 | 通用生产 | 专业研究 | 内部工具 | 高端客服 |
6.7 各策略的 Fallback 方案对比
| 策略 | Fallback 1 | Fallback 2 | Fallback 3(保底) |
|---|---|---|---|
| 速度优先 | 关键词检索 Top-3 | - | 直接 LLM 回答 |
| 平衡型 | 混合检索(向量+BM25) | Query 重写后检索 | 直接 LLM 回答 |
| 召回率优先 | 扩大检索到 Top-100 | 触发 HyDE | 多轮迭代检索 |
| 成本优先 | 降低阈值到 0.2 | - | 返回预设回复 |
| 质量优先 | 增加检索路数 | Rerank 重试(换模型) | 人工介入 + 事后回复 |
6.8 策略选择决策树
你的需求是什么?
│
┌────────────────┼────────────────┐
↓ ↓ ↓
延迟<500ms 质量最重要 预算有限
│ │ │
↓ ↓ ↓
┌────────┐ ┌────────────┐ ┌────────┐
│速度优先│ │质量优先 │ │成本优先│
└────────┘ │或召回率优先│ └────────┘
└────────────┘
│
┌─────────┴─────────┐
↓ ↓
专业领域问答 通用企业应用
│ │
↓ ↓
┌────────┐ ┌────────┐
│召回率优先│ │平衡型 │
└────────┘ └────────┘
6.9 核心要点总结
五大策略概览
| 策略 | 核心目标 | 关键特性 |
|---|---|---|
| 速度优先 | <300ms 延迟 | 单次检索,无 Rerank |
| 平衡型 | 性价比最优 | Query 重写+Rerank |
| 召回率优先 | Recall@50 > 0.9 | Multi-Query+ 混合检索 |
| 成本优先 | 几乎免费 | 全部本地模型 |
| 质量优先 | 最佳效果 | 三路检索 + 商业 Rerank |
选择建议
- 实时对话 → 速度优先
- 通用生产 → 平衡型
- 专业研究 → 召回率优先
- 内部工具 → 成本优先
- 高端客服 → 质量优先
面试关键词
- 策略选择
- Fallback 机制
- 成本效益权衡
- 场景化方案
================================================================================
第 7 章 完整流程评价
7.1 完整流程:Multi-Query → Top-30 → Rerank → Top-5
这是一个非常经典且高质量的 RAG 检索优化流程!
流程拆解
┌─────────────────────────────────────────────────────────────────┐
│ 完整 RAG 检索优化流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户查询 │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. Multi-Query 扩展 │ │
│ │ 生成 3-5 个查询变体 │ │
│ │ 成本:+1 次 LLM 调用 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 2. 检索 Top-30 │ │
│ │ 每个变体检索 30 条 → 共 90-150 条 → 去重融合 │ │
│ │ 目标:高召回率 (Recall) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 3. Rerank 重排序 │ │
│ │ 用 Cross-Encoder 对 90-150 条精细打分 │ │
│ │ 目标:高精度 (Precision) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 4. 返回 Top-5 │ │
│ │ 取重排序后前 5 条给 LLM 生成 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 最终答案 │
│ │
└─────────────────────────────────────────────────────────────────┘
7.2 优势分析
| 优势 | 说明 |
|---|---|
| 召回率极高 | Multi-Query 多角度覆盖 + Top-30 宽泛检索 |
| 精度有保障 | Rerank 用 Cross-Encoder 精细筛选 |
| 架构清晰 | 两阶段:粗筛(Bi-Encoder)+ 精筛(Cross-Encoder) |
| 可扩展 | 可加 Fallback、条件触发等优化 |
核心设计思想
┌─────────────────────────────────────────────────────────────┐
│ 两阶段检索架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 第一阶段:粗筛(召回优先) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Multi-Query + Top-30 │ │
│ │ - 用 Bi-Encoder 快速检索 │ │
│ │ - 宁可多不可少,保证召回率 │ │
│ │ - 90-150 条候选 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 第二阶段:精筛(精度优先) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Rerank + Top-5 │ │
│ │ - 用 Cross-Encoder 精细打分 │ │
│ │ - 逐对比较 (query, document) 相似度 │ │
│ │ - 取最相关的 5 条 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 为什么这样设计? │
│ - Bi-Encoder 快但粗糙 → 适合粗筛 │
│ - Cross-Encoder 慢但精准 → 适合精筛 │
│ - 组合使用,兼顾速度和精度 │
│ │
└─────────────────────────────────────────────────────────────┘
7.3 性能与成本估算
延迟分析
| 阶段 | 延迟 | 说明 |
|---|---|---|
| Multi-Query 生成 | ~500ms | LLM 生成 3-5 个变体 |
| 并行检索 Top-30 | ~300ms | 3-5 路检索并行执行 |
| Rerank Top-100 | ~800ms | Cross-Encoder 精细打分 |
| LLM 生成 | ~1000ms | 根据 Top-5 生成答案 |
| 总延迟 | ~2600ms | 约 2.6 秒 |
成本分析
| 组件 | 单次查询 | 本流程 | 倍数 |
|---|---|---|---|
| LLM 调用(生成变体) | 0 次 | 1 次 | +$0.002 |
| Embedding | 1 次 | 3-5 次 | +$0.0002~0.0004 |
| 向量检索 | 1 次 | 3-5 次 | +$0.001~0.002 |
| Rerank | 0 次 | 1 次(~100 条) | +$0.001 |
| 总成本 | $0.0006 | $0.0062~0.0074 | ~10-12x |
7.4 潜在问题
| 问题 | 说明 |
|---|---|
| 延迟较高 | 约 2000-3000ms,不适合实时场景 |
| 成本较高 | 约 10-12 倍基准成本 |
| Rerank 依赖 | 选错模型效果打折 |
| 过度检索 | 简单查询可能不需要这么复杂 |
7.5 优化建议
1. 动态调整检索数量
根据查询类型调整 Top-K:
def get_top_k(query):
"""根据查询类型动态调整检索数量"""
# 简短模糊查询 → 多检索一些
if len(query.split()) < 5:
return 50
# 事实性查询 → 少检索一些
if query.endswith("吗") or "?" in query:
return 10
# 默认
return 30
2. Rerank 前预过滤
在 Rerank 之前先过滤掉明显不相关的文档:
def pre_filter(results, threshold=0.3):
"""预过滤,去掉低分文档"""
return [r for r in results if r.score >= threshold]
3. 条件触发 Multi-Query
不是所有查询都需要 Multi-Query:
def should_use_multi_query(query):
"""只对特定查询使用 Multi-Query"""
# 查询太短 → 用
if len(query.split()) < 5:
return True
# 有歧义词 → 用
ambiguous_words = ["怎么", "如何", "什么"]
if any(word in query for word in ambiguous_words):
return True
# 事实性查询 → 不用
return False
4. 缓存优化
# 常见查询的变体和结果可以缓存
cache = {}
def get_cached_results(query):
if query in cache:
return cache[query] # 直接返回缓存结果
# 否则正常检索
results = retrieve(query)
cache[query] = results
return results
7.6 完整代码实现
class AdvancedRAG:
"""
完整流程实现:
Multi-Query → Top-30 → Rerank → Top-5
"""
def __init__(self):
self.llm = OpenRouterLLM(model='gpt-4o')
self.embedder = SentenceTransformer('bge-large-zh-v1.5')
self.vector_db = ChromaDB()
self.bm25 = BM25Search()
self.reranker = CrossEncoderReranker('bge-reranker-large')
def query(self, user_query: str) -> str:
# Step 1: 判断是否需要 Multi-Query
use_multi = self.should_use_multi_query(user_query)
if use_multi:
# Step 2a: Multi-Query 扩展
variants = self.llm.generate(
f"为以下查询生成 3 个不同角度的变体:\n{user_query}"
)
queries = [user_query] + variants.split('\n')
else:
queries = [user_query]
# Step 3: 检索(每个变体 Top-30)
all_results = []
for q in queries:
results = self.vector_db.search(
self.embedder.encode(q), top_k=30
)
all_results.extend(results)
# Step 4: 去重 + RRF 融合
if use_multi:
fused = self.rrf_fusion(all_results, k=60)
else:
fused = all_results
# Step 5: 预过滤
filtered = self.pre_filter(fused, threshold=0.3)
# Step 6: Rerank 精筛 Top-5
reranked = self.reranker.rerank(user_query, filtered, top_k=5)
# Step 7: 生成答案
context = "\n".join([doc.content for doc in reranked])
return self.llm.generate(
f"根据以下资料回答问题:\n\n{context}\n\n问题:{user_query}"
)
def should_use_multi_query(self, query):
"""条件触发 Multi-Query"""
if len(query.split()) < 5:
return True
ambiguous_words = ["怎么", "如何", "什么", "为什么"]
if any(word in query for word in ambiguous_words):
return True
return False
def pre_filter(self, results, threshold=0.3):
"""预过滤"""
return [r for r in results if r.score >= threshold]
def rrf_fusion(self, results_list, k=60):
"""RRF 融合"""
rrf_scores = {}
for results in results_list:
for rank, doc in enumerate(results, start=1):
if doc.id not in rrf_scores:
rrf_scores[doc.id] = 0
rrf_scores[doc.id] += 1 / (k + rank)
return sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
7.7 一张图总结
┌─────────────────────────────────────────────────────────────────┐
│ 完整流程评价:Multi-Query → Top-30 → Rerank → Top-5│
├─────────────────────────────────────────────────────────────────┤
│ │
│ 流程: │
│ 用户查询 → Multi-Query → 检索 Top-30 → Rerank → Top-5 → 答案 │
│ │
│ 优势: │
│ ✅ 召回率极高(Multi-Query 多角度覆盖) │
│ ✅ 精度有保障(Cross-Encoder 精细筛选) │
│ ✅ 架构清晰(粗筛 + 精筛两阶段) │
│ │
│ 代价: │
│ ❌ 延迟高(~2600ms) │
│ ❌ 成本高(~10-12x) │
│ │
│ 优化建议: │
│ ① 动态调整检索数量(根据查询类型) │
│ ② Rerank 前预过滤(去掉明显不相关的) │
│ ③ 条件触发 Multi-Query(不是所有查询都需要) │
│ ④ 缓存常见查询(减少重复计算) │
│ │
│ 适用场景: │
│ ✅ 专业领域问答(法律、医疗) │
│ ✅ 复杂研究型问题 │
│ ✅ 对答案质量要求高的场景 │
│ ❌ 实时对话(延迟太高) │
│ ❌ 简单事实查询(杀鸡用牛刀) │
│ │
└─────────────────────────────────────────────────────────────────┘
7.8 核心要点总结
流程关键点
- 四步流程:Multi-Query → Top-30 → Rerank → Top-5
- 两阶段设计:粗筛(召回)+ 精筛(精度)
- 延迟:约 2600ms
- 成本:约 10-12 倍
优势
- 召回率极高
- 精度有保障
- 架构清晰
- 可扩展性强
优化建议
- 动态调整检索数量
- Rerank 前预过滤
- 条件触发 Multi-Query
- 缓存常见查询
面试关键词
- 两阶段检索
- 粗筛 + 精筛
- Bi-Encoder + Cross-Encoder
- 召回率 vs 精度
================================================================================
第 8 章 三种技术对比:Query Rewriting vs HyDE vs Multi-Query
8.1 核心思想对比
| 技术 | 核心思想 | 比喻 | 嵌入对象 |
|---|---|---|---|
| Query Rewriting | 把问题说清楚 | 翻译官 | 改写后的问题 |
| HyDE | 用答案搜答案 | 美食家 | 假设答案 |
| Multi-Query | 多角度问问题 | 分身术 | 多个查询变体 |
形象理解
- Query Rewriting:你说的不清楚,我帮你翻译成清楚的一句话
- HyDE:先编一个假答案,再用假答案去找真答案
- Multi-Query:一个问题可能不够,我帮你同时问好几个问题
8.2 工作流程对比
┌─────────────────────────────────────────────────────────────────┐
│ 三种技术工作流程对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Query Rewriting: │
│ 用户查询 → [LLM 改写] → 清晰的问题 → [检索] → 答案 │
│ 成本:+1 次 LLM │
│ │
│ HyDE: │
│ 用户查询 → [LLM 生成假设答案] → 假设答案 → [检索] → 答案 │
│ 成本:+1 次 LLM + 1 次嵌入 │
│ │
│ Multi-Query: │
│ 用户查询 → [LLM 生成 3-5 变体] → 多个问题 → [并行检索] → 融合 → 答案 │
│ 成本:+1 次 LLM + 3-5 次嵌入 + 3-5 次检索 │
│ │
└─────────────────────────────────────────────────────────────────┘
8.3 详细成本分析
成本拆解表(基于典型云 API 价格)
| 成本项 | 基准(无优化) | Query Rewriting | HyDE | Multi-Query (3 变体) |
|---|---|---|---|---|
| LLM 调用 | 1 次(生成) | 2 次 | 2 次 | 2 次 |
| Embedding | 1 次 | 1 次 | 2 次 | 3 次 |
| 向量检索 | 1 次 | 1 次 | 1 次 | 3 次 |
| Rerank | 0 次 | 0 次 | 0 次 | 0 次 |
| 总成本 | $0.0026 | $0.0046 | $0.0047 | $0.0058 |
| 倍数 | 1x | 1.8x | 1.9x | 2.2x |
| 延迟 | ~160ms | ~660ms | ~760ms | ~1200ms |
注:这里的倍数是仅检索阶段的对比。如果包含 LLM 生成答案的成本,总倍数会被摊薄。
成本计算过程
单价假设:
- LLM 调用:$0.002/次
- Embedding: $0.0001/次
- 向量检索:$0.0005/次
基准成本:
= 1×LLM + 1×Embedding + 1×检索
= $0.002 + $0.0001 + $0.0005 = $0.0026
Query Rewriting 成本:
= 2×LLM + 1×Embedding + 1×检索
= $0.004 + $0.0001 + $0.0005 = $0.0046
HyDE 成本:
= 2×LLM + 2×Embedding + 1×检索
= $0.004 + $0.0002 + $0.0005 = $0.0047
Multi-Query (3 变体) 成本:
= 2×LLM + 3×Embedding + 3×检索
= $0.004 + $0.0003 + $0.0015 = $0.0058
增量成本对比(仅计算优化部分的额外成本)
| 技术 | 额外 LLM | 额外 Embedding | 额外检索 | 增量成本 | 总倍数 |
|---|---|---|---|---|---|
| Query Rewriting | +1 次 | 0 | 0 | +$0.002 | 1.8x |
| HyDE | +1 次 | +1 次 | 0 | +$0.0021 | 1.9x |
| Multi-Query (3 变体) | +1 次 | +2 次 | +2 次 | +$0.0052 | 3.2x |
注:Multi-Query 的倍数在不同计算口径下有差异。如果考虑完整的 RAG 流程(包含 LLM 生成答案),倍数约为 2-3x;如果只看检索阶段,倍数可达 6-8x。
8.4 优势分析
效果提升对比
| 指标 | 基准 | Query Rewriting | 提升 | HyDE | 提升 | Multi-Query | 提升 |
|---|---|---|---|---|---|---|---|
| Recall@10 | 0.45 | 0.55 | +22% | 0.58 | +29% | 0.62 | +38% |
| MRR | 0.40 | 0.48 | +20% | 0.52 | +30% | 0.55 | +38% |
| NDCG@5 | 0.50 | 0.58 | +16% | 0.62 | +24% | 0.65 | +30% |
| 答案质量 | ⭐⭐⭐ | ⭐⭐⭐⭐ | - | ⭐⭐⭐⭐ | - | ⭐⭐⭐⭐ | - |
各技术独特优势
Query Rewriting 优势:
| 优势 | 说明 |
|---|---|
| 成本最低 | 仅增加 1 次 LLM 调用 |
| 延迟最低 | 约 500ms 额外延迟 |
| 实现简单 | 只需一个 Prompt |
| 适合简短查询 | “python list” → “Python 列表使用方法” |
HyDE 优势:
| 优势 | 说明 |
|---|---|
| 桥接语义鸿沟 | 问题空间 → 答案空间 |
| 适合问答型查询 | "什么是 X"类问题效果最好 |
| 利用 LLM 知识 | LLM 知道答案通常长什么样 |
| 缓解词汇不匹配 | 同时包含用户词和文档词 |
Multi-Query 优势:
| 优势 | 说明 |
|---|---|
| 召回率最高 | 多角度覆盖,减少遗漏 |
| 处理歧义最强 | "苹果"同时找到水果和公司 |
| 鲁棒性强 | 某个变体不好,其他可以弥补 |
| 适合开放型问题 | "怎么学好编程"类问题 |
8.5 适用场景对比
推荐使用场景
| 技术 | 推荐使用场景 | 不推荐使用场景 |
|---|---|---|
| Query Rewriting | 简短查询、模糊查询 | 已经很清晰的长查询 |
| HyDE | 问答型、开放型问题 | 导航型查询、事实性查询 |
| Multi-Query | 歧义查询、复杂问题 | 简单事实查询、延迟敏感场景 |
查询类型匹配表
| 查询类型 | 示例 | 推荐技术 | 预期提升 |
|---|---|---|---|
| 简短模糊 | “python list” | Query Rewriting + Multi-Query | Recall@10 +50% |
| 问答型 | “什么是机器学习” | HyDE | MRR +30% |
| 歧义查询 | “苹果” | Multi-Query | Recall@10 +80% |
| 开放型 | “怎么学好编程” | HyDE + Multi-Query | NDCG +40% |
| 事实性 | “珠穆朗玛峰多高” | 无需优化 | - |
| 导航型 | “python 官网” | 无需优化 | - |
8.6 组合使用策略
常见组合对比
| 组合 | 成本 | 延迟 | 效果 | 适用场景 |
|---|---|---|---|---|
| Query Rewriting + HyDE | 2.5x | ~900ms | ⭐⭐⭐⭐ | 问答型 + 模糊查询 |
| Query Rewriting + Multi-Query | 3.5x | ~1300ms | ⭐⭐⭐⭐⭐ | 歧义 + 开放型问题 |
| HyDE + Multi-Query | 4x | ~1500ms | ⭐⭐⭐⭐⭐ | 复杂研究型问题 |
| 三者全用 | 5x+ | ~2000ms | ⭐⭐⭐⭐⭐+ | 高端质量优先场景 |
组合工作流程
┌─────────────────────────────────────────────────────────────────┐
│ 三者组合工作流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户查询:"怎么学好编程?" │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Step 1: Query Rewriting │ │
│ │ "如何通过系统学习路径提升编程能力?" │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Step 2: HyDE (可选) │ │
│ │ 生成假设答案:"编程学习需要...掌握基础语法..." │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Step 3: Multi-Query │ │
│ │ 变体 1: "编程入门学习路径" │ │
│ │ 变体 2: "编程练习资源推荐" │ │
│ │ 变体 3: "编程能力提升方法" │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Step 4: 并行检索 + RRF 融合 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Step 5: Rerank + LLM 生成 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
8.7 面试回答模板
如果面试官问:“Query Rewriting、HyDE、Multi-Query 有什么区别?怎么选?”
"这三种都是 RAG 检索优化技术,但方法和适用场景不同。
核心区别:
技术 做法 成本 优势 Query Rewriting 改写问题 低 (1.8x) 成本低,适合模糊查询 HyDE 生成假设答案 中 (1.9x) 桥接问题 - 答案语义鸿沟 Multi-Query 多角度查询 高 (3.2x) 召回率最高,处理歧义 怎么选:
- 简短模糊查询(如’python list’)→ Query Rewriting
- 问答型问题(如’什么是 X’)→ HyDE
- 歧义查询(如’苹果’)→ Multi-Query
- 复杂开放型问题→ 组合使用(Query Rewriting + Multi-Query)
实际项目里,我会根据延迟和预算权衡:
- 延迟敏感 → Query Rewriting
- 质量优先 → 三者组合
- 一般生产环境 → Query Rewriting + Multi-Query"
8.8 一张图总结
┌─────────────────────────────────────────────────────────────────┐
│ Query Rewriting vs HyDE vs Multi-Query │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 成本对比:(倍数 = 检索阶段相对成本) │
│ Query Rewriting ████████ 1.8x │
│ HyDE ██████████ 1.9x │
│ Multi-Query ████████████████ 3.2x │
│ │
│ 效果对比:(Recall@10 提升) │
│ Query Rewriting ████████ +22% │
│ HyDE ██████████ +29% │
│ Multi-Query ██████████████ +38% │
│ │
│ 延迟对比: │
│ Query Rewriting ████████ ~660ms │
│ HyDE ██████████ ~760ms │
│ Multi-Query ████████████████ ~1200ms │
│ │
│ 推荐选择: │
│ ├── 预算有限/延迟敏感 → Query Rewriting │
│ ├── 问答型问题 → HyDE │
│ ├── 歧义查询/复杂问题 → Multi-Query │
│ └── 质量优先 → 组合使用 │
│ │
└─────────────────────────────────────────────────────────────────┘
8.9 核心要点总结
三种技术对比
| 技术 | 核心思想 | 成本倍数 | Recall 提升 | 适用场景 |
|---|---|---|---|---|
| Query Rewriting | 把问题说清楚 | 1.8x | +22% | 简短模糊查询 |
| HyDE | 用答案搜答案 | 1.9x | +29% | 问答型问题 |
| Multi-Query | 多角度问问题 | 3.2x | +38% | 歧义查询 |
选择建议
- 预算有限/延迟敏感 → Query Rewriting
- 问答型问题 → HyDE
- 歧义查询/复杂问题 → Multi-Query
- 质量优先 → 组合使用
面试关键词
- 翻译官/美食家/分身术比喻
- 成本效益权衡
- 场景化选择
- 组合使用策略
================================================================================
附录 A 术语表
A.1 RAG 相关术语
| 术语 | 英文 | 解释 |
|---|---|---|
| RAG | Retrieval-Augmented Generation | 检索增强生成,一种结合检索和生成的 AI 技术 |
| 检索器 | Retriever | 负责从文档库中查找相关文档的模块 |
| 生成器 | Generator | 负责根据检索到的文档生成答案的模块(通常是 LLM) |
| 向量数据库 | Vector Database | 存储文档嵌入向量的数据库,支持相似度搜索 |
| 嵌入 | Embedding | 将文本转换成向量表示的过程 |
| 相似度搜索 | Similarity Search | 根据向量相似度查找相关文档 |
| 余弦相似度 | Cosine Similarity | 衡量两个向量相似程度的指标,范围 [-1, 1] |
A.2 检索优化术语
| 术语 | 英文 | 解释 |
|---|---|---|
| Query Rewriting | 查询重写 | 将用户原始查询转换成更适合检索的形式 |
| 查询扩展 | Query Expansion | 添加同义词或相关词来扩展查询 |
| 查询改写 | Query Reformulation | 用更清晰的方式重新表述查询 |
| 查询分解 | Query Decomposition | 将复杂查询拆分成多个子查询 |
| HyDE | Hypothetical Document Embeddings | 假设文档嵌入,用假设答案的向量去检索 |
| Multi-Query | 多查询检索 | 生成多个查询变体并行检索,然后合并结果 |
| RRF | Reciprocal Rank Fusion | 倒数排名融合,一种合并多个检索结果的方法 |
| Rerank | 重排序 | 对检索结果进行精细打分和重新排序 |
| Bi-Encoder | 双编码器 | 分别编码查询和文档的模型,速度快 |
| Cross-Encoder | 交叉编码器 | 同时编码 (查询,文档) 对的模型,精度高 |
| 混合检索 | Hybrid Search | 结合多种检索方法(如向量 + 关键词) |
| BM25 | BM25 | 一种经典的关键词检索算法 |
A.3 评估指标术语
| 术语 | 英文 | 解释 |
|---|---|---|
| MRR | Mean Reciprocal Rank | 平均倒数排名,只关心第一个正确答案的排名 |
| NDCG | Normalized Discounted Cumulative Gain | 归一化折损累计增益,考虑相关性和排序质量 |
| Recall@K | 召回率@K | 前 K 个结果中有多少正确答案 |
| Precision@K | 精确率@K | 前 K 个结果中有多少是正确答案 |
| DCG | Discounted Cumulative Gain | 折损累计增益 |
| IDCG | Ideal DCG | 理想情况下的 DCG |
A.4 模型相关术语
| 术语 | 示例 | 解释 |
|---|---|---|
| Embedding 模型 | all-MiniLM-L6-v2, bge-large-zh | 将文本转换成向量的模型 |
| Rerank 模型 | bge-reranker-large, Cohere Rerank | 对检索结果进行精细打分的模型 |
| LLM | GPT-4, Claude, Qwen | 大型语言模型,用于生成答案 |
A.5 快速查阅表
核心技术速查
| 技术 | 作用 | 成本 | 适用场景 |
|---|---|---|---|
| Query Rewriting | 让问题更清晰 | 低 | 简短模糊查询 |
| HyDE | 用答案搜答案 | 中 | 问答型问题 |
| Multi-Query | 多角度覆盖 | 高 | 歧义查询 |
| RRF | 融合多个结果 | 无额外成本 | Multi-Query 结果融合 |
| Rerank | 精细排序 | 中 | 需要高精度场景 |
评估指标速查
| 指标 | 关心什么 | 适用场景 |
|---|---|---|
| MRR | 第一个正确答案的排名 | 问答系统 |
| NDCG | 所有结果的相关性 + 排序 | 搜索、推荐 |
| Recall@K | 前 K 个中有多少正确答案 | 召回率优先场景 |
A.6 常见缩写
| 缩写 | 全称 | 中文 |
|---|---|---|
| RAG | Retrieval-Augmented Generation | 检索增强生成 |
| LLM | Large Language Model | 大型语言模型 |
| API | Application Programming Interface | 应用程序接口 |
| MRR | Mean Reciprocal Rank | 平均倒数排名 |
| NDCG | Normalized Discounted Cumulative Gain | 归一化折损累计增益 |
| RRF | Reciprocal Rank Fusion | 倒数排名融合 |
| HyDE | Hypothetical Document Embeddings | 假设文档嵌入 |
| BM25 | BM25 (Okapi BM25) | BM25 算法 |
| FAISS | Facebook AI Similarity Search | Facebook 向量搜索库 |
| ChromaDB | Chroma Database | Chroma 向量数据库 |
================================================================================
附录 B 代码片段速查
B.1 Query Rewriting 实现
class QueryRewriter:
"""查询重写实现"""
def __init__(self, llm):
self.llm = llm
def rewrite(self, query: str) -> str:
"""将查询改写成更适合检索的形式"""
prompt = f"""将以下查询改写成更适合搜索引擎的形式:
原始查询:{query}
要求:补充上下文,使用明确术语,保持原意"""
rewritten = self.llm.generate(prompt)
return rewritten.strip()
def expand(self, query: str, num_synonyms: int = 3) -> str:
"""查询扩展:添加同义词"""
prompt = f"""为以下查询生成{num_synonyms}个同义词或相关词:
查询:{query}
输出格式:每行一个词"""
synonyms = self.llm.generate(prompt).split('\n')
return query + ' ' + ' '.join(synonyms)
def decompose(self, query: str, num_subquestions: int = 3) -> list:
"""查询分解:拆分成多个子问题"""
prompt = f"""将以下查询拆分成{num_subquestions}个子问题:
查询:{query}
输出格式:每行一个子问题"""
subquestions = self.llm.generate(prompt).split('\n')
return [q.strip() for q in subquestions if q.strip()]
使用示例
# 初始化
rewriter = QueryRewriter(llm)
# 改写查询
query = "python list"
rewritten = rewriter.rewrite(query)
print(rewritten) # "Python 编程语言中列表的使用方法"
# 查询扩展
expanded = rewriter.expand(query)
print(expanded) # "python list array data structure methods"
# 查询分解
subquestions = rewriter.decompose("怎么学好编程?")
print(subquestions)
# ["编程入门需要学什么?", "如何提高编程能力?", "有哪些编程学习资源?"]
B.2 HyDE 实现
class HyDERetriever:
"""HyDE 检索器实现"""
def __init__(self, llm, embedder, vector_db):
self.llm = llm
self.embedder = embedder
self.vector_db = vector_db
def generate_hypothetical_doc(self, query: str) -> str:
"""生成假设文档"""
prompt = f"""请为以下问题生成一个详细的答案:
问题:{query}
要求:答案应该详细、专业,包含相关关键词"""
return self.llm.generate(prompt)
def retrieve(self, query: str, top_k: int = 5) -> list:
"""使用 HyDE 进行检索"""
# Step 1: 生成假设文档
hypothetical_doc = self.generate_hypothetical_doc(query)
# Step 2: 嵌入假设文档
embedding = self.embedder.encode(hypothetical_doc)
# Step 3: 检索相似文档
results = self.vector_db.search(embedding, top_k=top_k)
return results
使用示例
# 初始化
hyde = HyDERetriever(llm, embedder, vector_db)
# 检索
query = "恐龙怎么灭绝的?"
results = hyde.retrieve(query, top_k=5)
for doc in results:
print(f"分数:{doc.score}, 内容:{doc.content[:50]}...")
B.3 Multi-Query 实现
class MultiQueryRetriever:
"""Multi-Query 检索器实现"""
def __init__(self, llm, embedder, vector_db, k=60):
self.llm = llm
self.embedder = embedder
self.vector_db = vector_db
self.k = k # RRF 平滑参数
def generate_variants(self, query: str, num_variants: int = 3) -> list:
"""生成查询变体"""
prompt = f"""为以下查询生成{num_variants}个不同角度的变体:
原始查询:{query}
要求:每个变体从不同角度询问,输出格式:每行一个变体"""
variants = self.llm.generate(prompt).split('\n')
return [v.strip() for v in variants if v.strip()]
def rrf_fusion(self, results_list: list) -> list:
"""RRF 倒数排名融合"""
rrf_scores = {}
for results in results_list:
for rank, doc in enumerate(results, start=1):
if doc.id not in rrf_scores:
rrf_scores[doc.id] = 0
rrf_scores[doc.id] += 1 / (self.k + rank)
# 按 RRF 分数排序
sorted_docs = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
return sorted_docs
def retrieve(self, query: str, num_variants: int = 3, top_k: int = 30) -> list:
"""Multi-Query 检索"""
# Step 1: 生成变体
variants = self.generate_variants(query, num_variants)
queries = [query] + variants # 包含原始查询
# Step 2: 并行检索
all_results = []
for q in queries:
embedding = self.embedder.encode(q)
results = self.vector_db.search(embedding, top_k=top_k)
all_results.append(results)
# Step 3: RRF 融合
fused = self.rrf_fusion(all_results)
return fused
使用示例
# 初始化
multi_retriever = MultiQueryRetriever(llm, embedder, vector_db)
# 检索
query = "怎么变强?"
results = multi_retriever.retrieve(query, num_variants=3, top_k=30)
print(f"融合后结果数量:{len(results)}")
for doc_id, rrf_score in results[:10]:
print(f"文档 ID: {doc_id}, RRF 分数:{rrf_score:.4f}")
B.4 RRF 实现
def reciprocal_rank_fusion(results_list, k=60):
"""
RRF 倒数排名融合
参数:
results_list: List[List[doc_id, score, rank]]
多个查询结果列表,每个结果包含文档 ID、分数、排名
k: 平滑参数,默认 60
返回:
sorted_docs: 按 RRF 分数排序的文档列表
"""
rrf_scores = {}
for results in results_list:
for rank, (doc_id, score, _) in enumerate(results, start=1):
if doc_id not in rrf_scores:
rrf_scores[doc_id] = 0
rrf_scores[doc_id] += 1 / (k + rank)
# 按 RRF 分数排序
sorted_docs = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
return sorted_docs
# 简化版(如果结果已经是文档对象列表)
def rrf_simple(results_list, k=60):
"""
简化版 RRF 实现
参数:
results_list: List[List[Document]]
多个查询结果列表,每个结果是 Document 对象列表
k: 平滑参数,默认 60
返回:
fused_results: 融合后的文档列表
"""
rrf_scores = {}
doc_map = {} # 保存 doc_id 到 Document 对象的映射
for results in results_list:
for rank, doc in enumerate(results, start=1):
if doc.id not in rrf_scores:
rrf_scores[doc.id] = 0
doc_map[doc.id] = doc
rrf_scores[doc.id] += 1 / (k + rank)
# 按 RRF 分数排序,返回 Document 对象
sorted_ids = sorted(rrf_scores.keys(), key=lambda x: rrf_scores[x], reverse=True)
fused_results = [doc_map[doc_id] for doc_id in sorted_ids]
return fused_results
使用示例
# 假设有 3 个查询的检索结果
results1 = [
Document(id="doc_a", content="...", score=0.9),
Document(id="doc_b", content="...", score=0.8),
Document(id="doc_c", content="...", score=0.7),
]
results2 = [
Document(id="doc_b", content="...", score=0.95),
Document(id="doc_c", content="...", score=0.85),
Document(id="doc_a", content="...", score=0.75),
]
results3 = [
Document(id="doc_c", content="...", score=0.92),
Document(id="doc_a", content="...", score=0.82),
Document(id="doc_b", content="...", score=0.72),
]
# RRF 融合
results_list = [results1, results2, results3]
fused = rrf_simple(results_list, k=60)
print("融合后排序:")
for doc in fused:
print(f" {doc.id}: {doc.content[:30]}...")
B.5 完整 RAG 策略实现
速度优先策略
class SpeedFirstRAG:
"""速度优先策略 - 目标 <300ms"""
def __init__(self):
self.embedder = SentenceTransformer('all-MiniLM-L6-v2')
self.vector_db = ChromaDB(collection='fast_collection')
self.llm = OpenRouterLLM(model='gpt-4o-mini')
def query(self, user_query: str) -> str:
query = user_query.strip()
embedding = self.embedder.encode(query)
results = self.vector_db.search(embedding, top_k=5)
if not results or results[0].score < 0.5:
return self.llm.generate(f"直接回答:{query}")
context = "\n".join([doc.content for doc in results])
return self.llm.generate(f"根据上下文回答:\n{context}\n\n问题:{query}")
平衡型策略
class BalancedRAG:
"""平衡型策略 - 性价比最优"""
def __init__(self):
self.embedder = SentenceTransformer('bge-base-zh-v1.5')
self.vector_db = ChromaDB()
self.reranker = CrossEncoderReranker('bge-reranker-base')
self.llm = OpenRouterLLM(model='gpt-4o-mini')
def query(self, user_query: str) -> str:
# Query Rewriting(针对短查询)
if len(user_query.split()) < 5:
query = self.llm.generate(
f"将以下查询改写成更适合检索的形式:\n{user_query}"
)
else:
query = user_query
# 检索 Top-20
embedding = self.embedder.encode(query)
results = self.vector_db.search(embedding, top_k=20)
# Rerank Top-8
reranked = self.reranker.rerank(query, results, top_k=8)
# 生成答案
context = "\n".join([doc.content for doc in reranked[:5]])
return self.llm.generate(f"根据上下文回答:\n{context}\n\n问题:{query}")
质量优先策略
class QualityFirstRAG:
"""质量优先策略 - 不计成本追求最佳效果"""
def __init__(self):
self.llm = OpenRouterLLM(model='anthropic/claude-sonnet-4.6')
self.embedder = SentenceTransformer('bge-large-zh-v1.5')
self.vector_db = ChromaDB()
self.bm25 = BM25Search()
self.reranker = CohereReranker()
def query(self, user_query: str) -> dict:
# Query Rewriting
rewritten_query = self.llm.generate(
f"将以下查询改写成更清晰的形式:\n{user_query}"
)
# Multi-Query 扩展
variants = self.llm.generate(
f"为以下查询生成 3 个不同角度的变体:\n{rewritten_query}"
)
queries = [rewritten_query] + variants.split('\n')
# 三路检索(向量 + BM25 + HyDE)
all_results = []
for q in queries:
vector_results = self.vector_db.search(self.embedder.encode(q), top_k=30)
keyword_results = self.bm25.search(q, top_k=30)
hypothetical_doc = self.llm.generate(f"为以下问题生成一个详细的答案:{q}")
hyde_results = self.vector_db.search(self.embedder.encode(hypothetical_doc), top_k=30)
all_results.extend(vector_results + keyword_results + hyde_results)
# RRF 融合
fused = self.rrf_fusion(all_results, k=60)
# 商业级 Rerank
reranked = self.reranker.rerank(rewritten_query, fused[:200], top_k=10)
# LLM 生成 + 引用
response = self.llm.generate_with_citations(
query=rewritten_query,
documents=reranked[:8],
require_citations=True
)
return {
'answer': response,
'references': reranked[:5],
'confidence': self.llm.estimate_confidence(response)
}
B.6 评估指标计算
def calculate_mrr(queries_results):
"""
计算 MRR(平均倒数排名)
参数:
queries_results: List[List[bool]]
每个查询的结果列表,True 表示正确答案
返回:
mrr: 平均倒数排名
"""
reciprocal_ranks = []
for results in queries_results:
for rank, is_correct in enumerate(results, start=1):
if is_correct:
reciprocal_ranks.append(1 / rank)
break
else:
reciprocal_ranks.append(0) # 没有找到正确答案
return sum(reciprocal_ranks) / len(reciprocal_ranks)
def calculate_dcg(relevances):
"""
计算 DCG(折损累计增益)
参数:
relevances: List[float]
每个结果的相关性分数(0-3)
返回:
dcg: 折损累计增益
"""
dcg = 0
for i, rel in enumerate(relevances):
rank = i + 1
dcg += rel / np.log2(rank + 1)
return dcg
def calculate_ndcg(relevances):
"""
计算 NDCG(归一化折损累计增益)
参数:
relevances: List[float]
每个结果的相关性分数(0-3)
返回:
ndcg: 归一化折损累计增益
"""
# 计算 DCG
dcg = calculate_dcg(relevances)
# 计算 IDCG(理想排序下的 DCG)
ideal_relevances = sorted(relevances, reverse=True)
idcg = calculate_dcg(ideal_relevances)
if idcg == 0:
return 0
return dcg / idcg
# 使用示例
if __name__ == "__main__":
# MRR 示例
queries_results = [
[False, False, True, False, False], # 第一个正确答案在第 3 名
[True, False, False, False, False], # 第一个正确答案在第 1 名
[False, True, False, False, False], # 第一个正确答案在第 2 名
]
mrr = calculate_mrr(queries_results)
print(f"MRR: {mrr:.3f}") # 输出:MRR: 0.611
# NDCG 示例
relevances = [3, 2, 3, 1, 0] # 每个结果的相关性分数
ndcg = calculate_ndcg(relevances)
print(f"NDCG: {ndcg:.3f}")
B.7 快速参考表
| 技术 | 关键函数 | 核心参数 |
|---|---|---|
| Query Rewriting | llm.generate(prompt) |
prompt 模板 |
| HyDE | embedder.encode(hypothetical_doc) |
假设文档生成 prompt |
| Multi-Query | generate_variants(query) |
num_variants, top_k |
| RRF | rrf_fusion(results_list) |
k=60 |
| Rerank | reranker.rerank(query, results) |
top_k |
================================================================================
附录 C 面试速查表
C.1 概念定义速查
| 概念 | 一句话定义 | 关键词 |
|---|---|---|
| Query Rewriting | 把用户的原始查询转换成更适合检索的形式 | 翻译官、补充上下文 |
| HyDE | 先让 LLM 生成一个假设答案,再用这个假设答案的向量去检索 | 用答案搜答案、语义鸿沟 |
| Multi-Query | 生成多个查询变体并行检索,然后合并结果 | 分身术、多角度覆盖 |
| RRF | 一种合并多个检索结果的方法,只看排名不看分数 | 倒数排名、k=60 |
| Rerank | 对检索结果进行精细打分和重新排序 | Cross-Encoder、精筛 |
| MRR | 只关心第一个正确答案排第几名 | 问答系统、倒数排名 |
| NDCG | 同时考虑相关性和排序质量 | 多级相关、折损因子 |
C.2 技术对比速查
三种优化技术对比
| 维度 | Query Rewriting | HyDE | Multi-Query |
|---|---|---|---|
| 核心思想 | 改写问题 | 生成假设答案 | 多角度查询 |
| 比喻 | 翻译官 | 美食家 | 分身术 |
| 成本倍数 | 1.8x | 1.9x | 3.2x |
| 延迟 | ~660ms | ~760ms | ~1200ms |
| Recall 提升 | +22% | +29% | +38% |
| 适用场景 | 简短模糊查询 | 问答型问题 | 歧义查询 |
五种策略对比
| 策略 | 延迟 | 成本 | 召回率 | 适用场景 |
|---|---|---|---|---|
| 速度优先 | <300ms | 1x | 中 | 实时对话 |
| 平衡型 | 600-1000ms | 3-5x | 中高 | 通用生产 |
| 召回率优先 | 2000-4000ms | 10-15x | 极高 | 专业研究 |
| 成本优先 | <200ms | 0.1x | 低 | 内部工具 |
| 质量优先 | 3000-5000ms | 15-20x | 极高 | 高端客服 |
C.3 场景选择速查
根据需求选择技术
| 用户需求 | 推荐技术 | 理由 |
|---|---|---|
| “系统要很快” | Query Rewriting | 成本最低,延迟最低 |
| “用户问题很短” | Query Rewriting + Multi-Query | 补充信息 + 多角度 |
| “问题是’什么是 X’” | HyDE | 问答型,桥接语义鸿沟 |
| “问题有歧义” | Multi-Query | 同时覆盖多个含义 |
| “答案要全面” | Multi-Query + Rerank | 高召回 + 高精度 |
| “预算有限” | Query Rewriting | 只增加 1 次 LLM 调用 |
| “质量最重要” | 三者组合 | 最佳效果 |
根据查询类型选择
| 查询类型 | 示例 | 推荐技术 |
|---|---|---|
| 简短模糊 | “python list” | Query Rewriting |
| 问答型 | “什么是机器学习” | HyDE |
| 歧义查询 | “苹果” | Multi-Query |
| 开放型 | “怎么学好编程” | HyDE + Multi-Query |
| 事实性 | “珠穆朗玛峰多高” | 无需优化 |
| 导航型 | “python 官网” | 无需优化 |
C.4 面试回答模板
模板 1:介绍 Query Rewriting
"Query Rewriting 就是把用户的原始查询转换成更适合检索的形式。
用户查询通常有两个问题:太简短和表述模糊。比如只搜’python’,不知道是蛇还是编程语言。
主要有三种方法:查询扩展(添加同义词)、查询改写(换说法)、查询分解(拆问题)。
在 RAG 系统里,查询重写放在检索之前,可以用 LLM 来实现。我在项目里用过,效果提升很明显。"
模板 2:介绍 HyDE
"HyDE 是 Hypothetical Document Embeddings 的缩写,中文叫’假设文档嵌入’。
核心思想是:先让 LLM 生成一个假设的答案,再用这个假设答案的向量去检索真实文档。简单说,就是’用答案搜答案’。
它有效的原因有三个:填补语义鸿沟(问题空间→答案空间)、利用 LLM 的预训练知识、增加信息密度。
反直觉的是,假设答案不需要正确,只要语言风格像答案、关键词覆盖到位,就能提升检索效果。"
模板 3:介绍 Multi-Query
"Multi-Query 是一种检索优化技术,核心思想是生成多个查询变体并行检索,然后合并结果。
用户查询有歧义性和覆盖不全的问题。比如’怎么变强’,可能是健身、编程、学习,单次查询只能命中一个角度。
Multi-Query 分三步:生成变体、并行检索、结果融合(常用 RRF)。不同变体可能命中不同但相关的文档,合并后覆盖率更高。
成本会增加 6-8 倍,但对于开放型问题和歧义查询,效果提升明显。"
模板 4:介绍 RRF
"RRF 是 Reciprocal Rank Fusion 的缩写,中文叫’倒数排名融合’。
公式是:RRF 分数 = Σ (1 / (k + rank_i)),其中 k 通常是 60,rank_i 是文档在第 i 个查询结果中的排名。
RRF 的特点是:不依赖具体分数,只看排名。排名越靠前权重越高。
为什么用 RRF 而不是直接合并分数?因为不同查询的分数不可比——查询 1 的 0.8 分可能等于查询 2 的 0.6 分,但排名是统一的。"
模板 5:比较三种技术
"这三种都是 RAG 检索优化技术,但方法和适用场景不同。
Query Rewriting 是改写问题,成本最低(1.8x),适合简短模糊查询。
HyDE 是生成假设答案,成本中等(1.9x),适合问答型问题,能桥接语义鸿沟。
Multi-Query 是多角度查询,成本最高(3.2x),但召回率提升最大(+38%),适合歧义查询。
实际项目里,我会根据延迟和预算权衡:延迟敏感用 Query Rewriting,质量优先可以三者组合。"
模板 6:介绍评估指标
"检索阶段的评估主要看两个指标:MRR 和 NDCG。
MRR 只关心第一个正确答案排第几名,适合问答系统,因为用户通常只需要一个正确答案。
NDCG 同时考虑相关性和排序质量,它假设相关性有等级(0-3 分),排名越靠后价值越低。NDCG 适合搜索场景,因为用户会浏览前几条结果。
在 RAG 里,如果更关注答案质量用 MRR,如果更关注检索文档的整体相关性用 NDCG。"
C.5 常见问题速查
Q: 怎么评估查询重写的效果?
“看检索阶段的指标,比如 MRR、NDCG,或者看最终答案质量(人工评估或 LLM-as-a-judge)。”
Q: 重写会不会引入噪声?
“会,所以要控制重写幅度。可以用少样本提示让 LLM 保持原意,或者设置置信度阈值,低置信度时用原始查询。”
Q: HyDE 的假设答案如果错了怎么办?
“这是 HyDE 反直觉的地方——假设答案不需要正确。因为我们要的不是答案内容,而是它的向量表示。只要语言风格像答案、关键词覆盖到位、语义方向正确,就能找到正确的文档。”
Q: Multi-Query 的成本会不会很高?
“会增加 LLM 调用和检索次数,但:LLM 调用只有一次(生成变体),检索可以并行执行,延迟不增加太多。对于复杂查询和开放型问题,效果提升值得这点成本。可以用条件触发:只对简短查询或低置信度查询用 Multi-Query。”
Q: 结果融合怎么做?
“常用方法是 RRF(Reciprocal Rank Fusion)。公式是 RRF 分数 = Σ (1 / (k + rank_i)),k 通常是 60。它不依赖具体分数,只看排名。最后按 RRF 分数重新排序。”
C.6 一张图总结
┌─────────────────────────────────────────────────────────────────┐
│ RAG 查询优化技术 面试速查 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 三种技术: │
│ Query Rewriting → 翻译官 → 1.8x → 简短模糊查询 │
│ HyDE → 美食家 → 1.9x → 问答型问题 │
│ Multi-Query → 分身术 → 3.2x → 歧义查询 │
│ │
│ 五个策略: │
│ 速度优先 (<300ms) → 实时对话 │
│ 平衡型 (600-1000ms) → 通用生产 │
│ 召回率优先 (2000-4000ms) → 专业研究 │
│ 成本优先 (<200ms) → 内部工具 │
│ 质量优先 (3000-5000ms) → 高端客服 │
│ │
│ 两个指标: │
│ MRR → 只关心第一个正确答案 → 问答系统 │
│ NDCG → 考虑相关性 + 排序 → 搜索、推荐 │
│ │
│ 一个融合: │
│ RRF → 倒数排名融合 → k=60 → 只看排名不看分数 │
│ │
└─────────────────────────────────────────────────────────────────┘
C.7 面试准备清单
必须掌握的概念
- Query Rewriting 的定义和三种方法
- HyDE 的核心思想和为什么有效
- Multi-Query 的工作流程和优势
- RRF 的公式和 k=60 的原因
- MRR 和 NDCG 的区别
必须会说的比喻
- Query Rewriting = 翻译官
- HyDE = 美食家(先描述答案再找)
- Multi-Query = 分身术
必须记住的数字
- Query Rewriting 成本:1.8x
- HyDE 成本:1.9x
- Multi-Query 成本:3.2x(或检索阶段 6-8x)
- RRF 的 k=60
- Recall 提升:+22% / +29% / +38%
必须能写的代码
- RRF 融合函数
- MRR 计算函数
- NDCG 计算函数
================================================================================
附录 D 参考资料
D.1 论文与文章
Query Rewriting
-
Query Reformulation for Retrieval-Augmented Generation (2023)
- 探讨 LLM 在查询重写中的应用
- 链接:https://arxiv.org/abs/2305.03653
-
Query Expansion Techniques for Information Retrieval (Survey)
- 查询扩展技术综述
- 涵盖传统方法和 LLM 方法
HyDE
-
HyDE: Hypothetical Document Embeddings (Gao et al., 2022)
- 原始论文,提出 HyDE 概念
- 链接:https://arxiv.org/abs/2212.10496
- 代码:https://github.com/texttron/hyde
-
Precise Zero-Shot Dense Retrieval without Relevance Labels (2022)
- HyDE 的理论基础
Multi-Query
-
Multi-Query: From One to Many (arXiv:2305.03653)
- 多查询检索的开创性工作
- 链接:https://arxiv.org/abs/2305.03653
-
LangChain Multi-Query Retriever
- LangChain 官方实现
- 链接:https://python.langchain.com/docs/modules/data_connection/retrievers/MultiQueryRetriever
RRF
-
The Probabilistic Relevance Framework: BM25 and Beyond (Robertson & Zaragoza, 2009)
- 介绍 RRF 和相关检索框架
-
Score Distribution Analysis for Reciprocal Rank Fusion (2021)
- RRF 的理论分析
评估指标
-
Information Retrieval Evaluation Metrics (TREC)
- 信息检索评估指标标准
- 链接:https://trec.nist.gov/
-
MRR and NDCG Explained (Towards Data Science)
- 通俗易懂的解释
- 链接:https://towardsdatascience.com/
RAG 优化
-
Retrieval-Augmented Generation for Large Language Models: A Survey (2023)
- RAG 技术综述
- 链接:https://arxiv.org/abs/2312.10997
-
Advanced RAG: Techniques and Best Practices (2023)
- 高级 RAG 技术实践
D.2 开源项目
Embedding 模型
| 模型 | 链接 | 说明 |
|---|---|---|
| all-MiniLM-L6-v2 | https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2 | 轻量级,384 维 |
| bge-large-zh-v1.5 | https://huggingface.co/BAAI/bge-large-zh-v1.5 | 中文效果好 |
| bge-base-zh-v1.5 | https://huggingface.co/BAAI/bge-base-zh-v1.5 | 平衡性能和速度 |
Rerank 模型
| 模型 | 链接 | 说明 |
|---|---|---|
| bge-reranker-large | https://github.com/FlagOpen/FlagEmbedding | 开源,中文支持好 |
| bge-reranker-base | https://github.com/FlagOpen/FlagEmbedding | 轻量级 |
| Cohere Rerank | https://docs.cohere.com/docs/rerank | 商业 API,效果好 |
向量数据库
| 数据库 | 链接 | 说明 |
|---|---|---|
| ChromaDB | https://docs.trychroma.com/ | 轻量,易用 |
| FAISS | https://github.com/facebookresearch/faiss | Facebook 开源,高性能 |
| Pinecone | https://www.pinecone.io/ | 云服务,托管 |
| Weaviate | https://weaviate.io/ | 开源,支持混合检索 |
RAG 框架
| 框架 | 链接 | 说明 |
|---|---|---|
| LangChain | https://python.langchain.com/ | 最流行的 RAG 框架 |
| LlamaIndex | https://docs.llamaindex.ai/ | 专注数据索引 |
| Haystack | https://haystack.deepset.ai/ | 端到端 RAG pipeline |
D.3 API 文档
LLM API
| 服务 | 文档链接 | 说明 |
|---|---|---|
| OpenRouter | https://openrouter.ai/docs | 聚合多个 LLM 提供商 |
| OpenAI API | https://platform.openai.com/docs | GPT 系列模型 |
| Anthropic API | https://docs.anthropic.com/ | Claude 系列模型 |
Embedding API
| 服务 | 文档链接 | 说明 |
|---|---|---|
| OpenAI Embeddings | https://platform.openai.com/docs/guides/embeddings | text-embedding-ada-002 |
| Cohere Embeddings | https://docs.cohere.com/docs/embeddings | 多语言支持 |
Rerank API
| 服务 | 文档链接 | 说明 |
|---|---|---|
| Cohere Rerank | https://docs.cohere.com/docs/rerank | 商业级 Rerank |
| Jina Rerank | https://jina.ai/reranker | 免费额度 |
D.4 学习资源
教程与课程
-
RAG Course (DeepLearning.AI)
- 链接:https://www.deeplearning.ai/
- RAG 专题课程
-
LangChain for LLM Applications (DeepLearning.AI)
- 链接:https://www.deeplearning.ai/
- LangChain 实战课程
博客与文章
-
Lil’Log Blog (OpenAI)
- 链接:https://lilianweng.github.io/
- 深度学习博客,有 RAG 相关文章
-
Towards Data Science - RAG
- 链接:https://towardsdatascience.com/tagged/rag
- RAG 技术文章集合
视频教程
- RAG Explained (YouTube)
- 链接:https://www.youtube.com/
- RAG 概念讲解视频
D.5 工具与库
Python 库
| 库 | 安装命令 | 说明 |
|---|---|---|
| sentence-transformers | pip install sentence-transformers |
Embedding 模型 |
| chromadb | pip install chromadb |
向量数据库 |
| faiss-cpu | pip install faiss-cpu |
Facebook 向量搜索 |
| rank-bm25 | pip install rank-bm25 |
BM25 检索 |
| langchain | pip install langchain |
RAG 框架 |
评估工具
| 工具 | 链接 | 说明 |
|---|---|---|
| Ragas | https://github.com/explodinggradients/ragas | RAG 评估框架 |
| TruLens | https://github.com/truera/trulens | LLM 应用评估 |
| Arize Phoenix | https://github.com/Arize-ai/phoenix | 可观测性工具 |
D.6 推荐阅读顺序
入门级
- RAG 技术综述文章
- Query Rewriting 基础教程
- LangChain MultiQueryRetriever 文档
进阶级
- HyDE 原始论文
- Multi-Query 论文
- RRF 技术分析文章
高级级
- RAG 最新研究论文
- 评估指标深入研究
- 开源项目源码分析
D.7 快速链接
| 主题 | 核心链接 |
|---|---|
| Query Rewriting | https://arxiv.org/abs/2305.03653 |
| HyDE | https://arxiv.org/abs/2212.10496 |
| Multi-Query | https://python.langchain.com/docs/modules/data_connection/retrievers/MultiQueryRetriever |
| RRF | https://en.wikipedia.org/wiki/Reciprocal_rank_fusion |
| BGE Reranker | https://github.com/FlagOpen/FlagEmbedding |
| Cohere Rerank | https://docs.cohere.com/docs/rerank |
更多推荐
所有评论(0)