查询重写:HyDE、多查询与分解


文档摘要

查询重写:HyDE、多查询与分解 本节摘要:用户敲下的查询,不是你的检索器想要的查询。改写在检索之前架桥,让索引看到更接近答案样貌的东西。本节实现三种重写器:HyDE(Hypothetical Document Embeddings)——让 LLM 写一个假答案文档,嵌入它去检索;多查询展开(Multi-Query)——把一个查询改写成 N 个释义,各检索一次再用 RRF 合并;查询分解(Decomposition)——把复杂问题拆成子问题,各检索后合并。三者在同一固定语料上对比:HyDE 赢在措辞错配,多查询赢在释义方差,分解赢在多主题。配套一个确定性 mock LLM,让重写回路离线运行。读完本节,你能根据查询长度、连词、术语密度为每条查询选对策略。

查询重写:HyDE、多查询与分解

本节摘要:用户敲下的查询,不是你的检索器想要的查询。改写在检索之前架桥,让索引看到更接近答案样貌的东西。本节实现三种重写器:HyDE(Hypothetical Document Embeddings)——让 LLM 写一个假答案文档,嵌入它去检索;多查询展开(Multi-Query)——把一个查询改写成 N 个释义,各检索一次再用 RRF 合并;查询分解(Decomposition)——把复杂问题拆成子问题,各检索后合并。三者在同一固定语料上对比:HyDE 赢在措辞错配,多查询赢在释义方差,分解赢在多主题。配套一个确定性 mock LLM,让重写回路离线运行。读完本节,你能根据查询长度、连词、术语密度为每条查询选对策略。

对应原课程:Phase 19 · Lesson 67 · query-rewriting-hyde(原英文 phases/19-capstone-projects/67-query-rewriting-hyde/docs/en.md)。本节属第 20 章「毕业项目」的进阶 RAG 赛道。

学习目标

阅读完本节,你应当能够:

  1. 实现 HyDE:生成假答案,嵌入它,用该向量而非查询向量检索。
  2. 实现多查询展开:把一个查询改写成 N 个释义,各检索一次,用倒数排名融合(RRF)合并并集。
  3. 实现查询分解:把复杂问题拆成子问题,按子问题检索,合并。
  4. 在固定语料上三者并跑,解释每种策略何时胜出。
  5. 接一个确定性 mock LLM,让重写回路离线运行。

一、问题与直觉

用户输入「我们团队在上传失败且预算耗尽时怎么办?」。语料里有一篇文档说「AbortMultipartOnFail 在上传失败时中止进行中的 S3 分片上传,并扣减每桶重试预算」。查询与文档不共享任何名词短语:BM25 漏掉;双塔把该文档排第三或第四,因为查询向量落在偏爱「取消任务」文档而非「中止上传」文档的嵌入区域;第 66 节的两段式重排若答案进了 top-N 还能救,若连 top-N 都没进,重排器永远看不到它。

解法是在查询碰到检索器之前先改写。2023 年 Gao 等人的论文《Precise Zero-Shot Dense Retrieval without Relevance Labels》提出 HyDE:让 LLM 写一篇本会回答该查询的文档,嵌入这篇假想文档,用其嵌入作为检索向量。假想文档落在正确的嵌入区域,因为它用的是语料的口吻;查询向量不会。

两种近亲技术与 HyDE 搭配。多查询展开(微软 GraphRAG 用过的术语)生成 N 个查询释义,各检索一次再合并;分解(2024 年斯坦福 DSPy 的「子查询分解」)把「上传失败且预算耗尽时怎么办」拆成「上传失败时怎么办」与「重试预算耗尽时怎么办」,两次检索、一份合并结果,答案的两半都可达。本节实现这三种并跑在同一固定语料上。

HyDE 详解

HyDE 用 LLM 写的假想文档向量替换用户的查询向量。提示很短:

你是领域专家。写一段回答下列问题的文字。用本领域文档会用的词汇与措辞。 不要拒绝,不要说不知道。 问题:{user_query} 段落:

LLM 的答案作为事实答案是错的,因为它不知道你的语料——没关系,检索器不在乎事实正确性,只在乎 token 分布。假想段落含「abort」「multipart」「bucket」「budget」这些词,因为这就是该主题的文档会用的词。嵌入它,向量落在真实段落附近。生产里把假想文档限制在两三句,太长收集噪声,太短丢失 HyDE 需要的词法信号。

多查询展开详解

生成用户查询的 N 个释义,最简提示:

用 {N} 种不同方式改写下列问题。每个改写必须保留原意。编号 1 到 {N}。不要加解释。

每个释义取 top-k,把 N 个排名列表用 RRF(第 65 节同算法)合并。便宜、可并行、确定性。多查询在用户措辞只是多种等价问法之一、且任一改写会问得更好时胜出;在所有改写因原查询同样地坏而同样地坏时失败。

分解详解

单次检索无法满足多面问题。分解让 LLM 把问题拆成子问题,系统按子问题检索。提示:

下列问题可能需要多个不同主题的信息。把它分解成子问题列表。每个子问题必须能独立回答。 若问题已是原子问题,原样返回。 问题:{user_query}

按子问题检索,合并。分解适合含连词、多子句对比、两个无关主题的问题;不适合原子问题——那时分解器的职责是返回原问题,别发明假子问题。

为什么三者并存

三者互补:HyDE 桥接查询-语料的 token 鸿沟,多查询覆盖释义方差,分解覆盖多主题查询。生产系统三者都跑,按查询选策略(第 69 节端到端系统展示选择器)。

二、Mock LLM

本节离线运行。Mock LLM 是一个以用户查询为键的小查找表,外加对未见查询的兜底。查找表含:每个固定查询的写好的假想段落、三个释义、一个分解;对未知查询,做一个确定性变换:取查询的内容词,经同义词表扩展后返回。

💡 要紧的是 mock 的形状,不是数据。生产里把 mock 换成真实模型调用,检索器不变。

三、从零实现

code/main.py 实现:

  • MockLLM —— 上文描述的确定性替身。
  • HyDERewriter —— 调 LLM 写假想文档,返回带假想文本与检索器应使用查询的 RewriteResult
  • MultiQueryRewriter —— 调 LLM 取 N 个释义,返回查询列表。
  • DecomposeRewriter —— 调 LLM 分解,返回子问题。
  • retrieve_with_rewriter —— 接一个重写器与一个检索器,跑改写并融合结果。
  • 一个 demo,在固定语料上跑三种重写器,打印哪种策略把黄金答案文档排第一。

检索器形状复用第 65 节(混合 BM25 + 稠密),融合用同一个 RRF,唯一新形状是重写器接口——很小。

重写器接口骨架

@dataclass class RewriteResult: hypothetical: str # HyDE 的假想文档;非 HyDE 时为空 queries: list[str] # 实际去检索的查询列表(>=1) def retrieve_with_rewriter(rewriter, retriever, query, k=5): rw = rewriter.rewrite(query) # 改写 rankings = [retriever.search(q, k=k) for q in rw.queries] if rw.hypothetical: rankings.append(retriever.search_by_vector(embed(rw.hypothetical), k)) fused = rrf(rankings, k=60) # 第 65 节的 RRF return fused[:k]

运行:

python3 code/main.py

输出是每策略的排名与一份最终小结:HyDE 赢在措辞错配查询,多查询赢在释义方差查询,分解赢在多主题查询,兜底(无重写器)在三者中至少一个上失败。

四、框架对比

重写器 用 LLM 干什么 何时赢 何时输
HyDE 写假答案文档 查询与语料 token 鸿沟大 假想里幻觉了语料特有标识符
多查询 改写 N 个释义 用户措辞是多种等价问法之一 改写都因原查询同样地坏
分解 拆成子问题 多主题、含连词 原子问题(过拆有害)
步退(Step-back) 问更一般的问题 问题太具体检索不到 (练习项)

业界对比:LlamaIndex 的 query_transformations、LangChain 的 MultiQueryRetriever、微软 GraphRAG 都实现这些范式。它们的共识与本节一致:三者互补,按查询选策略,可并行三路再用 RRF 合并(成本是三次 LLM 调用 + 一次融合,质量是三者覆盖的并集)。

五、demo 会藏住的失败模式

HyDE 幻觉语料特有标识符。 模型发明一个函数名,假想文档对该文档的 BM25 分崩塌,因为发明的名成了索引里没有的高权 token。给假想长度设上限,并在融合里降低 BM25 权重。

多查询改写全收敛。 弱模型产出三个近乎相同的释义,N 次检索返回同一 top-k,RRF 合并不比单次检索好。在改写提示里加显式多样性指令,按 Jaccard 检测重复。

分解过拆。 分解器把原子问题变成列表,各检索返回同一文档但排名降低,合并比原查询更差。在扇出前加一道「这些子问题足够不同吗」的检查。

延迟倍增。 HyDE 成本一次 LLM 调用;多查询一次 LLM 调用生成 N 个改写 + N 次检索;分解一次 LLM 调用分解 + M 次检索。检索并行,LLM 调用是下限。

六、可复用产物

  • RewriteResult + retrieve_with_rewriter:本节的重写阶段是第 69 节「端到端 RAG 系统」在检索器之前的一环。
  • 三策略选择启发式:按查询长度(原子短查询用多查询)、连词(复杂多子句用分解)、术语密度(术语重的用 HyDE)选策略。
  • 并行三路 + RRF 合并模板:作为生产默认——成本可控、覆盖最广。

生产模式:

  • 按查询长度做每查询策略选择。
  • 按查询哈希缓存重写器输出(很多查询会重复)。
  • 三者并行跑,用 RRF 把三份结果融合成一份。

七、练习

  1. 实现 RAG-Fusion:多查询的 2024 变体,改写器的释义故意多样,再用第 66 节重排选最终列表。
  2. 加第四策略:步退提示(让 LLM 问更一般的问题,在其上检索,再收窄),在固定语料上对比。
  3. 训分解器识别原子查询:加一个「问题是否原子」的头,测量过拆率的前后变化。
  4. 换真实 LLM:把 mock 换成真实模型调用,测量每策略在你技术栈上的延迟。
  5. 加每改写的置信度:丢弃低于阈值的改写,测量对召回的影响。

本节要点回顾

  1. 用户查询 ≠ 检索器想要的查询——改写在检索前架桥。
  2. HyDE:LLM 写假答案,嵌入它去检索,假想落在正确嵌入区域因为它用语料口吻。
  3. 多查询:N 个释义各检索一次,RRF 合并;赢在释义方差。
  4. 分解:多主题问题拆子问题,各检索合并;赢在多主题,原子问题勿过拆。
  5. 三者互补:token 鸿沟归 HyDE,释义方差归多查询,多主题归分解。
  6. 生产默认:三路并行 + RRF 合并,成本 3 次 LLM 调用,覆盖是并集。
  7. 四大失败:HyDE 幻觉标识符、改写收敛、分解过拆、延迟倍增(LLM 调用是下限)。

下一节,我们将进入「RAG 评估」——同时给检索与答案打分的六大指标(精度、召回、MRR、nDCG、忠实度、答案相关性),让你能定位错答出在哪一段。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U