本页目录
Lesson 07 — Query 改写:让检索更准
本课目标:搞懂为什么不能直接拿用户原话去检索,以及怎么改造 query 让检索命中率飙升。
前 6 课优化的都是"文档侧"(embedding、chunking、混合检索、rerank)。这一课转向"问题侧"——用户的话往往不适合直接检索。
1. 原始 query 为什么不好检索?
用户实际问的问题,往往长这样:
| 问题类型 | 例子 | 为什么不好检索 |
|---|---|---|
| 太短/太泛 | "年假" |
信息量太少,向量不够具体,召回一堆沾边的 |
| 口语化 | "我想歇几天,咋整?" |
和文档里的正式表述("带薪年假申请")语义距离远 |
| 指代不明 | "那个表怎么填?" |
"那个"指什么?向量检索完全懵 |
| 多意图 | "年假和病假有什么区别,分别怎么申请?" |
一个 query 塞了两个问题,检索只能偏向一个 |
| 缩写/黑话 | "PTO 政策?" |
文档里写的是"带薪休假",字面不匹配 |
直接拿这些问题去检索,召回质量必然差。所以要先"改造"问题,再去检索。
🎯 核心认知:检索质量 =
文档侧质量×问题侧质量。前面优化了文档侧,现在轮到问题侧。
2. HyDE:用"假设答案"去检索
HyDE (Hypothetical Document Embeddings) 是个反直觉但很有效的技巧。
直觉
向量检索的原理是"问题和文档的向量要接近"。但有个矛盾: - 问题通常是疑问句、简短、口语化 - 文档通常是陈述句、完整、正式
它们的"语言形态"不同,即使语义相关,向量也可能不够近。
HyDE 的思路:既然文档是陈述句,那我就让模型先编一个"假设的答案文档",用这个假设文档去和真实文档匹配——陈述句匹配陈述句,更接近。
流程
用户问题:"我想歇几天,咋整?"
│
▼
① 让 LLM 生成"假设答案"(一段看起来像文档的文字)
"员工可申请带薪年假,入职满一年享有 5 天..."
│
▼
② 把"假设答案"向量化(而不是把原问题向量化)
│
▼
③ 用假设答案的向量去检索真实文档
│
▼
④ 召回准确率大幅提升(陈述句 vs 陈述句,向量更近)
为什么有效?
因为 embedding 模型是在"文档语料"上训练的,它更擅长编码陈述性文本的语义。把口语问题转成假设文档,等于"翻译"成了 embedding 模型更熟悉的语言。
代价
多一次 LLM 调用(生成假设答案),有延迟和成本。但检索质量的提升通常值得。
3. 多查询展开 (Multi-Query)
一个问题可以从多个角度问,每个角度检索一次,合并结果。
流程
用户问题:"年假和病假有什么区别,分别怎么申请?"
│
▼
① 让 LLM 把它拆成/改写成多个子问题:
- "年假的天数和申请流程是什么?"
- "病假需要什么材料,怎么申请?"
- "年假和病假的区别是什么?"
│
▼
② 每个子问题分别检索 top-K
│
▼
③ 合并去重(用 RRF 或投票)
│
▼
④ 覆盖更全面(一个复杂问题 → 多个简单问题,每个都好检索)
适用场景
- 多意图问题("A 和 B 的区别/流程")
- 需要从多个文档综合信息的问题
- 用户问题太模糊,需要从多角度覆盖
4. 查询路由 (Query Routing)
不同类型的问题适合不同的检索策略:
| 问题类型 | 路由到 |
|---|---|
| 含编号/代码 | BM25 为主(参考 Lesson 06) |
| 语义模糊/口语 | 向量检索为主 |
| 需要精确数字/事实 | metadata 过滤 + BM25 |
| 元问题("有哪些文档") | 不检索,直接列目录 |
路由可以用 LLM 判断(让它分类问题类型),也可以用规则(正则匹配编号等)。
💡 本课重点讲 HyDE 和多查询展开(最常用、效果最显著),查询路由了解概念即可。
5. 本课代码会做什么
code.py 会做对比实验,让你看到 query 改写的威力:
① 原始 query 直接检索(基线)
拿一个口语化的烂问题直接检索,看召回质量。
② HyDE 改写后检索
让 LLM 生成假设答案,用假设答案检索,对比召回质量。
③ 多查询展开后检索
让 LLM 把问题展开成多个子问题,分别检索合并,对比召回质量。
三种方式用同一个文档库,量化对比谁召回了正确文档。
6. 跑起来
python lessons/07_query_rewrite/code.py
终端会打印三种方式的检索结果对比。重点看:口语化烂问题在"直接检索"时召不准,改写后召回质量明显提升。
下一课 Lesson 08 — RAG 评估 会讲:怎么用数据客观衡量 RAG 好不好,不凭感觉。