跳到主要内容

你的 RAG 系统缺少的查询改写层

· 阅读需 11 分钟
Tian Pan
Software Engineer

大多数团队在调优 RAG 系统时关注两个杠杆:分块策略和嵌入模型选择。当检索质量下降时,他们重新分块;当召回率数据不好看时,他们升级嵌入模型。这两步都合理——但它们在优化流水线的中间环节,却让最高杠杆点一直没有触及。

用户的查询几乎从来不是向量检索的理想形式。它简短、口语化、模糊,或者假设了索引中并不存在的上下文。无论你的嵌入有多好,如果你用一个表述糟糕的查询来搜索,检索结果就会很差。解决方法不在下游——而是在查询命中向量索引之前对其进行变换。

这就是查询改写层的作用。它是一个额外的步骤(有时是额外的 LLM 调用),将原始用户输入转换为一个或多个表述更好的检索查询。实施了这一层的团队持续看到检索召回率的提升,远超他们从分块实验中获得的收益。工程上的权衡——额外 LLM 调用带来的延迟和成本——是真实存在的,但它通常不是跳过这一步的正当理由。

为何原始用户查询在向量搜索中失败

向量搜索通过找到嵌入与查询嵌入在语义空间中接近的文档来工作。其隐含假设是用户查询和相关文档共享相似的语义领域。实践中,它们往往并不如此。

用户以对话的方式提问。他们引用会话中的先前上下文。他们使用与文档中术语不同的领域词汇。他们在需要具体答案时提问宽泛,或在相关知识以更高抽象层次表述时提出具体问题。

嵌入模型无法修正这一点。它忠实地编码你给它的任何查询。如果你的查询是"我怎么修上周的那个认证问题",嵌入会捕获这个意思——但它不会匹配你的文档"解决会话中间件中的 JWT 过期错误"。

查询改写从源头解决了词汇不匹配问题。

HyDE:用答案搜索,而非用问题搜索

HyDE(假设文档嵌入)是查询改写技术中最反直觉的一种。它不是将用户问题嵌入后搜索相似文档,而是提示 LLM 生成一个假设性的文档来回答这个问题——然后将该假设文档嵌入进行检索。

其洞察在于:问题和答案占据语义空间的不同区域。一篇解释 JWT 过期错误的文档,看起来远比用户关于认证问题的提问更像其他 JWT 过期相关文档。通过生成一个合理(不必然准确)的答案,你在与目标文档相同的语义区域中进行搜索。

假设文档的事实准确性不是必须的。HyDE 之所以有效,是因为它捕获了答案的形式词汇,而不是因为它的内容是正确的。一个有幻觉但听起来合理的文档仍然占据语义上有用的领域。

性能提升是显著的。在 BEIR 基准上,HyDE 比标准密集检索平均召回率提升约 6 个百分点。更近期的变体更高——HyPE(2025 年)报告在特定数据集上比基线检索精度提升了高达 42 个百分点。

权衡是延迟。在 1-4B 参数量级的较小 LLM 上,HyDE 会增加 43-60% 的查询时延迟。在更大的模型上,如果你能在专用硬件上运行推理,延迟成本是真实的但更可控。实践含义:HyDE 在查询和文档之间词汇差距显著时表现突出,当检索失败有高度下游影响时(答案错误,而不仅仅是不完整),值得付出延迟成本。它最适合有直接答案的事实性问题。对于个人化、模糊或高度依赖上下文的查询,假设文档生成可能偏离目标并损害精度。

一个生产安全的方法:使用相似度置信度阈值。如果原始查询与检索文档之间的嵌入相似度高于阈值,则跳过 HyDE。只有在基线检索看起来薄弱时才调用它。

退后提示:检索背景,而非仅检索答案

退后提示采取与 HyDE 相反的方法。它不是生成假设答案,而是生成一个更抽象的问题版本——在抽象层次上退后一步——并检索框定答案的背景知识。

如果用户问"为什么认证中间件在周二部署后开始返回 503 错误?",退后提示可能生成:"部署期间认证服务故障的常见原因是什么?"检索随后浮现关于部署相关认证故障的基础知识——运行手册、架构文档、已知故障模式——然后再尝试具体答案。

这在需要跨多个知识来源综合的查询、或用户的具体问题只能在建立上下文后才能回答时最为有用。复杂诊断问题、架构决策、以及嵌入未陈述假设的问题都是好的候选。

风险是过度抽象。退后在抽象问题真正涵盖相关领域时有效。如果抽象丢失了重要的具体细节——特定的中间件、具体的错误代码、时间戳——它会检索到正确但不相关的背景,生成步骤就必须更努力地得出有用答案。

子查询分解:正确进行多跳检索

复杂问题通常将多个独立的信息需求打包在一个查询中。"在 RAG 流水线中启用查询改写的延迟影响是什么,与用更好的嵌入模型改善检索相比成本如何?"实际上是两个问题:一个关于延迟,一个关于比较成本。将其视为单一检索查询意味着向量索引必须找到同时解决这两个问题的文档——这比分别为每个问题找到文档要难得多。

子查询分解将原始查询分解为原子子查询,对每个子查询执行检索,然后在生成之前合成结果。有两种模式:

并行分解同时运行所有子查询并合并结果。速度更快(总延迟 ≈ 单个子查询延迟,而非总和),在子查询相互独立时有效——当回答一个子查询不依赖于知道另一个的答案时。

顺序分解使用一个子查询的答案来指导下一个。这处理依赖推理链:"我们使用的是哪个版本的库,该版本有哪些已知漏洞?"你需要先知道第一个问题的答案才能回答第二个。权衡是与链深度成比例的延迟——N 个顺序检索意味着 N 倍延迟。

子查询分解是多跳问题和具有多个不同方面查询的正确默认选择。主要失败模式是过度分解:将简单问题分解为不必要的子查询会增加延迟而没有召回收益。一个轻量级的预分类器来路由查询——简单 vs. 复杂,单意图 vs. 多方面——可以防止这种情况并保持快速路径的速度。

多查询扩展:以吞吐量换召回率

多查询扩展(有时称为 RAG-Fusion)是暴力选项。生成 N 个查询的改写版本,对所有 N 个版本运行检索,并使用倒数排名融合(RRF)合并结果。RRF 以一种能浮现出现在多个结果集中的文档的方式合并排名列表——一种粗糙的相关性共识信号。

召回率提升是显著的。对于任何单一查询表述都会错过 top-k 截止点的文档,往往会在合并列表中浮现。但成本与 N 呈线性关系:三个查询变体意味着三次嵌入调用、三次向量搜索和 N 倍的基础设施成本。对于处理大量查询量的服务,多查询扩展需要接受成本或选择性应用。

选择性应用模式:将复杂度超过阈值的查询路由到多查询,让简单查询走快速路径。一个轻量级的意图分类器——甚至是微调的 BERT 级模型——可以廉价地做到这一点。

延迟数学

工程师对查询改写提出的担忧始终是延迟。在检索之前额外的 LLM 调用意味着在 p50 增加额外的 100-500ms,具体取决于模型大小以及你是否在调用 API 或本地推理。

ElevenLabs 分享了他们生产 RAG 重构的具体数字:通过单个外部托管 LLM 进行查询改写占其总 RAG 延迟的 80% 以上。他们的解决方案是切换到在自托管的 Qwen 3-4B 和 3-30B 实例上的并行推理,并设置一秒超时回退。中位延迟从 326ms 降至 155ms——改写步骤通过更小更快来降低成本,而非被移除。

这是正确的框架。问题不是"我们是否应该付出查询改写的成本?"——对于大多数存在真实检索质量问题的系统,答案是肯定的。问题是"我们如何让改写步骤足够快?"答案是:

  • 用比生成器更小的模型进行查询改写。7B 级模型以前沿模型一小部分的延迟很好地处理改写。
  • 缓存改写后的查询。用户会问类似的问题。在高流量系统中,对改写形式的 TTL 缓存能快速回本。
  • 在可能的情况下,将改写与其他流水线步骤并行运行——在生成改写形式的同时嵌入原始查询。
  • 选择性应用改写。只有当基线检索置信度低于阈值,或查询复杂度信号表明需要时,才路由通过改写层。

当检索失败的成本高于延迟预算时,额外的 LLM 调用就能回本。对于检索失败意味着向付费客户提供错误答案的客户支持 RAG 系统,数学计算几乎总是倾向于改写。

查询选择的决策框架

并非每个查询都需要每种技术。以下是如何将变换方式与查询类型匹配:

简单事实型查询("API 的速率限制是多少?")——首先尝试 HyDE。问题和答案之间的语义空间差距是核心问题,HyDE 直接解决它。

多方面或多跳查询("比较 X 和 Y 之间的延迟和成本,然后为我们的用例推荐一种方法")——子查询分解,如果子查询相互独立则并行进行。

需要背景上下文的查询(复杂诊断或架构问题)——退后提示,在尝试具体答案之前检索基础知识。

模糊或表述不足的查询("我怎么修认证问题?")——多查询扩展以探索语义邻域,结合意图分类来缩小范围。

混合查询类型的高流量系统——实现一个路由器,对查询复杂度进行分类并应用适当的变换,为简单查询保持快速路径(无改写)。

这对分块和嵌入意味着什么

改善分块和嵌入模型仍然重要。但这些优化作用于固定输入——它们让你的索引更好地匹配你发送给它的任何查询。查询改写改变了查询本身,这改变了你要匹配的分布。两者是互补的,而非竞争的。

实施查询改写的团队持续报告,它浮现了分块实验无法解释的检索改进。这是因为检索质量的上限不是由索引设定的——而是由查询质量设定的。先投资于此。

工程工作是适中的:一个额外的提示,一次 LLM 调用(使用比生成器更小的模型),以及一个选择性应用它的路由层。回报——以检索召回率、答案质量和面向用户的错误率衡量——通常是你对已投入生产的 RAG 系统能做出的最高 ROI 改变。

References:Let's stay in touch and Follow me for more thoughts and updates