主题
第 4 章 · 检索优化技术:从"能用"到"好用"
🎯 本章目标:掌握把 RAG 召回从 60% 拉到 90%+ 的核心工程技术,理解每种技术的"为什么"和"什么时候用"。
💡 核心思路:基础 RAG = 单一向量检索。生产级 RAG = 查询优化 + 混合检索 + Rerank + 元数据过滤 的组合拳。
目录
- 为什么需要检索优化
- 查询改写(Query Rewriting)
- 混合检索(Hybrid Search)
- Rerank 重排(精排)
- HyDE:先脑补答案再检索
- 多查询生成(Multi-Query)
- 元数据过滤与混合检索
- Context 压缩与去重
- 综合优化路径
- 思考题
1. 为什么需要检索优化
1.1 基础 RAG 的常见困境
困境 1:查询和文档"词不达意"
用户问:「怎么解决程序闪退?」
文档里写:「应用程序异常退出(崩溃)的排查方法...」
→ 用户用的是"闪退",文档用的是"异常退出"
→ 纯向量检索可能漏掉这条困境 2:短查询语义不明
用户问:「退货」
→ 是想问退货流程?还是退货政策?还是退货状态查询?
→ 系统迷茫,乱召回困境 3:复杂多意图问题
用户问:「比较一下产品 A 和产品 B 的价格、库存和发货时间」
→ 需要同时找 6 个事实
→ 单次向量检索只能找一个角度困境 4:相似但不相关
用户问:「Python 的装饰器怎么用?」
召回:「Java 的注解(Annotation)」 ← 概念相似但语言不对1.2 检索优化的四大武器
2. 查询改写 Query Rewriting
2.1 什么是查询改写
用 LLM 把用户原始查询改写成更利于检索的版本。
2.2 改写的几种类型
类型 A:拼写纠错和规范化
原查询:「我家猫米发烧了怎么办」
改写后:「我家猫咪发烧了怎么办」类型 B:同义词扩展
原查询:「程序闪退」
改写后:「程序闪退 / 应用崩溃 / 异常退出 / Crash」类型 C:补全省略信息
对话历史:
用户:「我家有只布偶猫」
AI:「好的」
用户:「它生病了怎么办?」 ← "它" 指代不明
改写后:「我家布偶猫生病了怎么办?」类型 D:多轮意图融合
对话历史:
用户:「介绍下你们的产品 A」
AI:「产品 A 是......」
用户:「价格呢?」 ← 缺主语
改写后:「产品 A 的价格是多少?」类型 E:分解复杂问题
原查询:「比较产品 A 和产品 B 的价格和库存」
分解为:
- "产品 A 的价格"
- "产品 A 的库存"
- "产品 B 的价格"
- "产品 B 的库存"
(分别检索后再综合)2.3 改写的 Prompt 模板
你是一个查询改写助手。
请根据【对话历史】和【当前查询】,生成一个独立、完整、清晰的查询,
便于在知识库中检索相关信息。
要求:
1. 消解指代("它"、"那个"等替换为具体实体)
2. 补全省略的主语/宾语
3. 修正明显的拼写错误
4. 保持查询的原意,不要扩展额外含义
【对话历史】
{history}
【当前查询】
{current_query}
【改写后的查询】2.4 改写的代价
| 优点 | 缺点 |
|---|---|
| ✅ 大幅提升召回率 | ❌ 多一次 LLM 调用,延迟增加 100-500ms |
| ✅ 解决短查询 / 多轮指代问题 | ❌ 改写错误可能误导检索 |
| ✅ 提升用户体验 | ❌ 增加 Token 成本 |
💡 建议:用小而快的模型(如 GPT-4o-mini、Qwen-Turbo)做改写,平衡成本和效果。
3. 混合检索 Hybrid Search
3.1 单一向量检索的局限
向量检索擅长 语义匹配,但有两个软肋:
| 软肋 | 例子 |
|---|---|
| 对专有名词、术语不敏感 | 用户搜 "API-X-2024 错误码",向量可能匹到无关的 API 概念 |
| 对精确关键词不重视 | 用户搜 "iPhone 15 Pro Max",可能返回 iPhone 12 相关 |
3.2 关键词检索(BM25)回归
BM25 是传统搜索引擎(Elasticsearch、Lucene)的核心算法,本质是 "关键词频率 + 文档长度归一化"。
优势
- 精确匹配术语、产品名、错误码
- 速度极快(倒排索引)
- 不需要 GPU
劣势
- 不理解同义词("汽车" 搜不到 "车辆")
- 不理解上下文
3.3 混合检索:1 + 1 > 2
混合检索 = 向量检索 + BM25 检索,结果融合排序
工作流程
融合算法:RRF(Reciprocal Rank Fusion)
最常用、最简单有效的融合方法。
公式(不需要记,理解思路即可):
RRF_score(doc) = sum(1 / (k + rank_i(doc)))
其中:
rank_i = 该文档在第 i 路检索中的排名
k = 常数,通常取 60直观例子
用户查询:「iPhone 15 Pro 防水等级」
向量检索 Top-3:
Rank 1: "iPhone 15 Pro 拥有 IP68 防水防尘等级..."
Rank 2: "iPhone 14 Pro 的防水性能..."
Rank 3: "智能手机防水等级标准 IP67 / IP68..."
BM25 检索 Top-3:
Rank 1: "iPhone 15 Pro 拥有 IP68 防水防尘等级..." ← 关键词完全匹配
Rank 2: "iPhone 15 Pro Max 规格清单..."
Rank 3: "苹果官方 iPhone 15 系列发布会..."
RRF 融合后:
→ "iPhone 15 Pro 拥有 IP68 防水防尘等级..." 在两路都排第一,最终也是第一
→ 其他文档按 RRF 公式重新排序适用场景
| 是否推荐 | 场景 |
|---|---|
| ✅ 强烈推荐 | 包含大量术语、产品名、错误码的文档 |
| ✅ 强烈推荐 | 电商、企业知识库、技术文档 |
| ✅ 推荐 | 几乎所有生产 RAG |
| ⭕ 可选 | 纯口语化文本 |
💡 业界共识:单一向量检索 → 混合检索,召回率通常提升 10%-30%。
3.4 工程实现选择
| 方案 | 实现 |
|---|---|
| Elasticsearch + 向量字段 | ES 8.x 原生支持 |
| Weaviate | 内置 hybrid search API |
| Qdrant | 多查询 + 客户端融合 |
| OpenSearch | k-NN + BM25 |
| 自己拼装 | 向量库召回 + ES 召回 + Python 写 RRF |
4. Rerank 重排(精排)
4.1 为什么 Rerank 是性价比之王?
检索(粗排)找到的 Top-50,可能只有 5 个真正相关。Rerank 帮你把这 5 个挑出来。
数据说话
业界经验数据:
| 优化方法 | 召回质量提升 | 实现复杂度 |
|---|---|---|
| 加 Rerank | +15%-30% | ⭐⭐(接 API) |
| 加混合检索 | +10%-20% | ⭐⭐⭐ |
| 切分调优 | +5%-15% | ⭐⭐⭐⭐ |
| 换更好的 Embedding | +5%-10% | ⭐⭐ |
Rerank 是性价比最高的优化。
4.2 Rerank vs Embedding 的本质区别
工作机制不同
| Embedding(Bi-Encoder) | Rerank(Cross-Encoder) | |
|---|---|---|
| 输入 | 查询、文档独立编码 | 查询和文档拼在一起编码 |
| 优势 | 快(向量预计算可缓存) | 准(能捕捉查询-文档细粒度交互) |
| 劣势 | 准确度有上限 | 慢(每对都要算) |
| 典型耗时 | 1-10ms/查询 | 50-200ms/Top-50 |
| 用法 | 粗排(亿级数据) | 精排(候选 50-100) |
💡 类比:
- Embedding 像看相亲简介(快速筛选,但只能看个大概)
- Rerank 像真人见面(深度交流,了解契合度)
4.3 主流 Rerank 方案
| 方案 | 类型 | 特点 |
|---|---|---|
| Cohere Rerank v3 | API | 易用,多语支持,效果好 |
| bge-reranker-v2-m3 | 开源 | 中英文,免费,效果接近 SOTA |
| bge-reranker-large | 开源 | 中文优秀 |
| Jina Reranker | API | 多语 |
| Voyage Rerank | API | 垂直领域强 |
| LLM-as-Reranker | 用 GPT-4 直接打分 | 灵活但慢且贵 |
4.4 Rerank 接入示意
4.5 Rerank 实战参数
| 参数 | 建议 |
|---|---|
| 粗排 Top-N | 50-100(太少召回不全,太多 Rerank 慢) |
| 精排 Top-K | 3-10(喂给 LLM 的最终数量) |
| 相关性阈值 | < 0.3 直接丢弃(防止低质量片段进入 Context) |
5. HyDE:先脑补答案再检索
5.1 HyDE 是什么
HyDE = Hypothetical Document Embeddings
核心思路:用户的查询往往很短、信息量小,直接 Embedding 检索效果差。 先用 LLM 根据查询生成一段假想答案,用假想答案的 Embedding 去检索。
5.2 为什么 HyDE 有效?
直观理解
用户查询:「光合作用」 (太短,语义弱)
↓ LLM 脑补一个假想答案
假想答案:「光合作用是植物利用阳光、水和二氧化碳合成葡萄糖和氧气的过程。
叶绿体是光合作用的主要场所。光合作用分为光反应和暗反应两个阶段...」
↓ 用假想答案做 Embedding,再去检索
召回:真实文档中关于光合作用的详细章节💡 本质:把"短查询"转成"伪文档",让"查询的向量"和"目标文档的向量"分布更接近。
5.3 工作流程
5.4 HyDE 的 Prompt 模板
请根据以下问题,写一段详细的、像维基百科条目的回答。
注意:你的回答不必完全正确,目的是用于检索相关文档。
问题:{user_query}
回答:5.5 HyDE 适用场景
| 适用 | 不适用 |
|---|---|
| ✅ 短查询("光合作用") | ❌ 长查询本身已经很完整 |
| ✅ 学术、知识查询 | ❌ 实时事件查询(LLM 不知道) |
| ✅ 跨语言查询 | ❌ 严格事实查询(LLM 可能脑补错) |
5.6 HyDE 的副作用
⚠️ HyDE 不是银弹!
| 问题 | 解释 |
|---|---|
| 延迟增加 | 多一次 LLM 调用(500ms-2s) |
| 成本增加 | 多消耗 Token |
| 幻觉传染 | LLM 脑补的内容可能完全错,导致检索方向跑偏 |
| 不总有效 | 对长查询提升不明显 |
6. 多查询生成 Multi-Query
6.1 思路
一个问题从多个角度同时检索,结果合并。
用户查询:「Python 装饰器如何实现?」
↓ LLM 生成多个变体
变体 1:「Python decorator 的工作原理是什么?」
变体 2:「Python 装饰器的语法和用法示例」
变体 3:「如何定义一个 Python 装饰器函数?」
↓ 每个变体分别检索
↓ 结果去重 + 融合
最终 Top-K6.2 为什么有效?
不同表述能命中不同的文档片段:
变体 1 命中:"decorator 原理章节"
变体 2 命中:"语法示例代码"
变体 3 命中:"函数定义教程"合并后召回更全面。
6.3 工作流程
6.4 Multi-Query 的进阶版:RAG-Fusion
RAG-Fusion = Multi-Query + RRF 融合排序
1. LLM 生成 N 个变体查询
2. 每个变体独立检索 Top-K
3. 用 RRF 算法融合所有结果
4. 输出全局 Top-K💡 在多个公开评测中,RAG-Fusion 比单一查询召回率提升 15%-25%。
6.5 Multi-Query 的取舍
| 优点 | 缺点 |
|---|---|
| ✅ 召回更全 | ❌ 调用次数 N 倍 → 成本 N 倍 |
| ✅ 对模糊查询友好 | ❌ 延迟增加 |
| ✅ 适合开放性问题 | ❌ 对精确查询可能引入噪声 |
7. 元数据过滤与混合检索
7.1 为什么需要元数据过滤
真实场景
公司知识库有 100 万文档,包括:
- 2020-2024 年的所有文档
- 涵盖产品 A、B、C、D 四个产品线
- 分中文版、英文版
用户问:「产品 A 在 2024 年新功能有哪些?」
如果不过滤,可能召回到:
❌ 产品 B 的 2024 年新功能(产品错)
❌ 产品 A 的 2022 年新功能(时间错)
❌ 产品 A 的 2024 年英文版新功能(语言可能错)7.2 元数据过滤
入库时给每个 Chunk 打标签,检索时按标签过滤。
入库时
{
vector: [...],
metadata: {
product: "A",
year: 2024,
language: "zh",
department: "engineering",
update_time: "2024-09-15"
}
}检索时
vector_db.search(
query_vector=q,
filter={
"product": "A",
"year": 2024,
"language": "zh"
},
top_k=5
)7.3 自然语言 → 结构化过滤(Self-Query)
让 LLM 自动从用户查询中提取过滤条件。
用户查询:「2024 年产品 A 的中文版新功能」
LLM 解析:
{
semantic_query: "新功能",
filters: {
product: "A",
year: 2024,
language: "zh"
}
}
→ 检索时既做语义匹配,又做精确过滤💡 LangChain 里叫
SelfQueryRetriever,LlamaIndex 里叫AutoRetriever。
7.4 元数据设计的最佳实践
| 字段 | 类型 | 用途 |
|---|---|---|
source | 字符串 | 来源文件名(引用追溯) |
page | 数字 | 页码 |
section | 字符串 | 章节标题 |
category | 枚举 | 分类("产品" / "运营" / "法务") |
tags | 列表 | 标签(["发烧", "猫咪", "急救"]) |
update_time | 时间戳 | 更新时间(时效性过滤) |
access_level | 枚举 | 权限("public" / "internal" / "secret") |
language | 枚举 | 语言("zh" / "en") |
author | 字符串 | 作者 |
⚠️ 关键经验:元数据入库时就要设计好,后期补加要全量重新入库。
8. Context 压缩与去重
8.1 问题:Context 冗余
Top-5 召回到:
Chunk 1: "猫咪发烧的处理方法包括降温、补水、送医..."
Chunk 2: "猫咪发烧时,可以采取降温、补水的方法..." ← 和 1 大量重复
Chunk 3: "宠物发烧时家长常用的处理方式有..." ← 也重复
Chunk 4: "猫咪疫苗副作用..." ← 不相关
Chunk 5: "猫咪体温正常范围是 38-39 度"
→ 喂给 LLM 的 Context 浪费严重8.2 解决方案
方案 A:去重
- 基于 Chunk 之间的相似度,相似度 > 0.9 的视为重复,只保留一个
- 或者基于文本字面去重
方案 B:Context 压缩(Contextual Compression)
用 LLM 把召回的每个 Chunk 抽取出和查询真正相关的部分,丢弃无关内容。
原 Chunk: "猫咪发烧的处理方法包括降温、补水、送医。
此外,猫咪还需要定期接种疫苗,注意饮食卫生。
注意环境清洁,避免接触流浪动物。"
LLM 压缩(针对查询"猫咪发烧怎么办"):
"猫咪发烧的处理方法包括降温、补水、送医。"方案 C:相关性过滤
- 用 Rerank 分数过滤掉低相关性(如 < 0.3)的 Chunk
- 避免无关片段进入 Context 干扰 LLM
8.3 LangChain 的 Compression 工具
| 工具 | 作用 |
|---|---|
LLMChainExtractor | 用 LLM 抽取相关句子 |
LLMChainFilter | 用 LLM 判断是否保留整个 Chunk |
EmbeddingsFilter | 按相似度阈值过滤 |
DocumentCompressorPipeline | 组合多种压缩器 |
9. 综合优化路径
9.1 从 0 到生产级的演进
9.2 优化优先级建议
| 优先级 | 优化项 | 投入 | 收益 |
|---|---|---|---|
| 🔥 P0 | 加 Rerank | 1 天 | +15-30% |
| 🔥 P0 | 切分调优 | 1-3 天 | +5-15% |
| 🌟 P1 | 混合检索 | 2-5 天 | +10-20% |
| 🌟 P1 | 元数据过滤 | 1-2 天 | +10%(特定场景 +30%) |
| ⭐ P2 | 查询改写 | 1-2 天 | +5-10% |
| ⭐ P2 | Context 压缩 | 1-2 天 | 降低成本 30%+ |
| 💎 P3 | HyDE / Multi-Query | 1 天 | +5-15%(场景相关) |
9.3 优化前后对比示例
| 指标 | 基础 RAG | 优化后 |
|---|---|---|
| Recall@5 | 60% | 92% |
| 答案准确率 | 65% | 88% |
| 延迟 | 800ms | 1.5s |
| 单次成本 | $0.002 | $0.005 |
💡 典型权衡:质量 ↑ → 延迟 ↑ + 成本 ↑。需要根据业务需求平衡。
9.4 一个生产级 RAG Pipeline 完整图
10. 思考题
Rerank 这么有效,为什么不直接用 Rerank 检索整个知识库,省去向量检索?
参考答案
- Rerank(Cross-Encoder)必须把"查询 + 每个候选文档"拼起来过模型,复杂度 O(N×D),N 个候选要算 N 次。
- 知识库百万级文档,Rerank 每个都算一遍要几分钟,根本不可行。
- Bi-Encoder(Embedding + 向量检索)通过向量预计算 + ANN,复杂度近似 O(log N),可以毫秒级检索。
- 结论:向量检索负责 "快速从亿级中选 Top-50",Rerank 负责 "从 50 中精选 Top-5"。两者互补。
混合检索的两路结果(向量 + BM25),如果一个文档只在向量检索里出现没在 BM25 里,怎么处理?
参考答案
- 用 RRF 公式时,没出现的那一路视为排名无穷大(贡献 0 分)。
- 因此 RRF 自动处理 "只在一路出现" 的情况,不需要特殊逻辑。
- 优点:单路漏召的文档,另一路能补回来。
HyDE 在哪种场景下会变得有害?
参考答案
- 新事实查询:LLM 不知道的最新信息,HyDE 脑补的答案完全错,导致检索跑偏。
- 严格事实查询:例如 "2024 年 10 月 1 日股价",LLM 脑补会瞎写数字,检索方向受污染。
- 本身已经很完整的长查询:HyDE 收益小,反而增加延迟。
- 小众领域 / 私有知识:LLM 没学过的内容,脑补质量低。
如果我的 RAG 召回经常返回"似是而非"的文档(看起来相关其实不是),怎么诊断和优化?
参考答案
诊断步骤:
- 检查切分:是不是 Chunk 太大,多个主题混在一起?
- 检查 Embedding:是不是模型对你的领域不敏感?跑个垂直领域评测看 Recall@K。
- 检查相似度分布:召回的 Top-5 相似度都很低(< 0.5)?说明知识库里根本没相关内容,需要补数据。
优化方案:
- 加 Rerank:用 Cross-Encoder 把"看起来相关"和"真正相关"区分开。
- 加元数据过滤:缩小检索范围,减少干扰。
- 加阈值:相似度 < X 直接返回 "不知道",避免硬答。
- 换 Embedding:试 BGE-M3 或 Cohere v3。
📌 本章小结
| 你应该已经知道 | ✅ |
|---|---|
| 查询改写解决"短查询"和"多轮指代"问题 | □ |
| 混合检索(向量 + BM25)是生产标配 | □ |
| RRF 是融合多路检索结果的事实标准 | □ |
| Rerank 是性价比最高的优化 | □ |
| Bi-Encoder 和 Cross-Encoder 的本质区别 | □ |
| HyDE 适用场景和坑 | □ |
| Multi-Query 和 RAG-Fusion 的关系 | □ |
| 元数据设计对检索精度的影响 | □ |
| 一个生产级 RAG Pipeline 包含哪些组件 | □ |
🚀 下一步
工程优化已经到位,下一章看 学术前沿 的高级 RAG 架构,让你的简历多几个亮点: