06-检索增强复盘:Query Rewrite、Hybrid Search 和多路召回
上一篇把一次在线 RAG 请求从用户问题进入系统,到最终答案返回完整串了一遍。放在整条链路里看,检索层承担的是一个很朴素但很关键的职责:让正确证据进入候选池。
06-检索增强复盘:Query Rewrite、Hybrid Search 和多路召回
上一篇把一次在线 RAG 请求从用户问题进入系统,到最终答案返回完整串了一遍。放在整条链路里看,检索层承担的是一个很朴素但很关键的职责:让正确证据进入候选池。
这个职责看起来简单,真正做起来并不轻松。用户问题通常很短、很口语、带着上下文和业务省略;知识库里的文档则往往是标题化、规范化、版本化的表达。两者之间存在天然距离。单次向量检索可以覆盖一部分语义相似问题,但它很难稳定处理所有查询形态,尤其是编号、错误码、接口名、配置项、专有名词、时间版本和强精确匹配场景。
所以,检索增强的核心不在于堆更多检索技巧,而在于围绕“正确证据能否进候选池”这件事,把 query 改写、关键词检索、向量检索、metadata 过滤、多路召回和结果融合组织成一套可解释、可评估、可排查的流程。
这一篇的核心判断可以先放在前面:
检索增强解决的是证据入口问题。Query Rewrite 让问题更适合被检索,Hybrid Search 让语义相似和词面匹配互相补位,多路召回让不同查询路径覆盖不同证据形态,融合和去重则把这些候选重新整理成可排序的证据池。
单路向量检索的局限
向量检索是 RAG 最常见的入口。它把用户 query 和文档 chunk 映射到同一个语义空间,再根据向量相似度找出接近内容。这个机制很适合处理表达不同但意思相近的问题。用户问“账号找不回来了怎么办”,文档写“忘记登录凭证后的账户恢复流程”,向量检索有机会把它们连接起来。
但向量相似并不能直接等同于答案相关。一个 chunk 可能和问题主题接近,却没有回答关键条件。另一个 chunk 可能包含准确答案,但因为用户 query 很短、术语不完整、语义表达过于抽象,没有被排到前面。
精确符号和专有表达是向量检索经常不稳定的地方。错误码 E1024、接口名 createKnowledgeBase、配置项 retrieval.top_k、合同条款“第 12.3 条”、产品型号“X-Plus 2026”,这些内容的意义不一定来自语义相似,而来自文本本身的精确出现。向量模型可能知道它们是某类技术名词,却未必能把它们和正确文档稳定对齐。
短 query 也会带来问题。用户输入“超时”“怎么开启”“限制是多少”,这些文本本身缺少足够语义信息。向量化后得到的是一个很模糊的方向,召回结果容易被高频主题吸走。多轮对话里,用户还会大量使用“它”“这个功能”“上面那个配置”,如果没有上下文补全,检索系统很难知道真正的目标对象。
另外,向量检索通常对时间、权限、版本、租户、产品线这类业务约束没有天然理解。它可以找到语义接近内容,却不能自动判断这份文档是否仍然有效、当前用户是否有权限、是否适用于指定版本。这些约束需要 metadata filter 和业务逻辑参与。
因此,向量检索适合作为召回的重要路径,但很少适合作为唯一入口。生产级 RAG 更常见的做法,是让多个检索路径共同工作。
| 单路向量检索容易失稳的场景 | 原因 |
|---|---|
| 错误码、接口名、配置项、合同条款编号 | 更依赖精确词面匹配 |
| 短 query、指代、省略表达 | 语义信号不足,容易召回泛化内容 |
| 时间、权限、版本、租户、产品线 | 需要 metadata 和业务规则约束 |
Query Rewrite 先把问题改成可检索表达
检索增强通常从 query 开始。用户写出来的是自然语言问题,搜索系统需要的是可命中文档的检索表达。Query Rewrite 的作用,就是把用户问题改写成更适合召回证据的形式。
这里的“改写”并不追求文采,也不追求像最终回答。它更像是在做检索前的规范化:补齐省略信息,消解指代,提取关键词,展开缩写,加入业务对象,把口语表达靠近文档表达。
例如,用户在上一轮问“知识库权限继承怎么配置”,下一轮接着问“它会影响子空间吗”。直接检索“它会影响子空间吗”很容易失败。改写后可以变成“知识库权限继承 是否影响子空间 权限配置规则”。这个版本保留了用户的真实意图,也把会话上下文带入了搜索表达。
再比如,用户问“新接口的限流是多少”。如果系统知道当前页面或会话里讨论的是“文档解析任务提交接口”,并且用户选择的是 v2.1 版本,改写 query 时就应该补入接口名和版本范围。检索阶段还可以同时注入 metadata filter,只检索 v2.1 相关文档。
Query Rewrite 做得好,后面的向量检索和关键词检索都会受益。向量检索能获得更完整的语义,关键词检索能命中更准确的术语,metadata 过滤也能更早发挥作用。
但改写也有风险。模型如果在改写时加入了用户没有表达的条件,检索方向会被带偏。把“导入失败”改写成“CSV 导入失败”,在用户确实讨论 CSV 时有帮助;如果用户实际上传的是 Excel,这个改写就会缩小到错误范围。稳妥的做法,是保留原始 query,把改写 query 作为一路召回来源,同时记录改写内容和召回结果,便于后续排查。
Query Expansion 提高表达覆盖
Query Rewrite 通常强调把当前问题改得更清楚,Query Expansion 更强调生成多个表达方式,扩大召回覆盖。
同一个问题在文档里可能有不同说法。用户说“账号找回”,文档可能写“账户恢复”“登录凭证重置”“身份校验流程”。如果只用一个 query 检索,可能只命中其中一种表达。Query Expansion 会围绕原始意图生成多个等价或相近查询,让系统从不同角度进入知识库。
在 RAG 里,Expansion 的价值主要体现在召回阶段。它可以把一个模糊 query 扩成多个子 query,也可以把同义词、缩写和领域术语补充进去。对于业务文档,这种方式经常能弥补用户表达和文档表达之间的差异。
不过,扩展越多,噪声也越多。一个 query 如果被扩成十几个方向,候选池会迅速膨胀,Rerank 压力和延迟都会上升。扩展内容过宽时,系统还可能召回很多主题相关但答案无关的材料。
所以 Query Expansion 需要控制边界。更合理的方式是生成少量高质量改写,保留原始 query 作为基准,并通过评测集观察 correct chunk 是否更容易进入 topK。扩展策略的目标应该落在正确证据覆盖上,召回数量变大只是中间现象。
HyDE 适合处理抽象和表达稀疏的问题
HyDE 可以理解为一种特殊的检索改写方法。它先让模型根据用户问题生成一段“假想答案”或“假想文档”,再用这段生成文本去做向量检索。
这种方法的直觉是:用户 query 太短时,语义信息不足;如果先生成一段可能回答该问题的文本,里面会包含更多领域词和上下文线索,向量检索就更容易靠近真实文档。
比如用户问“权限继承会有什么坑”。原始 query 很口语,也比较宽泛。HyDE 可能生成一段关于“父空间权限变更会影响子空间、显式授权可能覆盖继承规则、历史权限需要重新计算”的假想说明。拿这段文本检索,可能更容易命中真正讨论权限继承边界的文档。
HyDE 的风险也很清楚。生成内容本身可能带有模型猜测,里面出现的概念未必来自知识库。如果系统过度相信这段假想文本,检索会被模型幻觉带偏。因此,HyDE 更适合作为多路召回中的一路,不适合替代原始 query。它负责提供额外语义入口,最终证据仍然要来自真实文档。
在实际使用时,HyDE 适合抽象问题、概念解释、用户表达很短但需要语义扩展的场景。对于错误码、接口名、精确编号这类问题,关键词检索和结构化过滤通常更可靠。
Step-back 和 Self-Query 各有适用位置
除了 Rewrite、Expansion 和 HyDE,还有两类经常出现在 RAG 检索增强里的方法:Step-back 和 Self-Query。
Step-back 的思路是先把具体问题上升到更抽象的问题,再用抽象问题帮助检索。用户问“为什么这个审批节点会跳过”,系统可以先退一步理解为“流程审批节点跳过的触发条件”,再去检索流程规则、条件分支、权限角色和自动跳转配置。它适合用户问题很具体,但背后需要查找上层规则或机制的场景。
Self-Query 更偏向结构化查询理解。它让模型从自然语言问题里解析出检索条件,例如时间范围、版本号、文档类型、产品线、作者、租户或权限范围。用户问“上个月发布的 v2.1 接口变更有哪些”,系统需要检索的是 version = v2.1、doc_type = release_note、publish_time 在指定区间内的文档,单纯语义相似度匹配很难处理这些条件。
这两类方法的共同点,是让 query 不再只是一段文本。它们会把用户问题转成更适合检索系统执行的表示:有时是更抽象的语义问题,有时是文本 query 加结构化过滤条件。
| 方法 | 主要作用 | 更适合 |
|---|---|---|
| Query Rewrite | 补全和规范化当前问题 | 指代、省略、口语化问题 |
| Query Expansion | 生成多个等价表达 | 文档表达多样、同义词较多 |
| HyDE | 用假想答案补充语义信息 | 抽象问题、短 query、概念解释 |
| Step-back | 从具体问题退到上层规则 | 需要查机制、规则、原因的问题 |
| Self-Query | 解析结构化过滤条件 | 时间、版本、类型、权限等条件明确的问题 |
在生产系统里,Self-Query 的效果高度依赖 metadata 质量。如果文档入库时没有稳定的版本、时间、类型、权限等字段,模型即使解析出了条件,也没有可靠字段可以过滤。这也说明离线侧的数据治理会直接影响在线侧的检索增强上限。
Hybrid Search 让语义召回和关键词召回互补
Hybrid Search 通常指混合检索,最常见的是向量检索和关键词检索结合。向量检索负责语义相似,关键词检索负责词面匹配,两者覆盖的问题形态不同。
BM25 是关键词检索里很常见的一类算法。它会根据词项在文档中的出现情况、词频、逆文档频率和文档长度等因素计算相关性。对于错误码、配置项、接口名、标题关键词、专有名词,BM25 经常很有效。
向量检索的优势在于语义泛化。用户说“怎么撤销账号”,文档写“注销账户流程”,二者词面并不完全一致,但语义接近。BM25 可能因为关键词重合不足而排得不高,向量检索更有机会召回。
把两者结合起来,系统就能同时覆盖语义相似和精确匹配。一个问题里既可能有自然语言意图,也可能有关键实体。比如“E1024 上传失败怎么处理”,向量检索可以找到“上传失败处理流程”,BM25 可以直接命中 E1024,metadata filter 可以限制在当前产品线和版本。三者一起工作,候选池会更稳。
| 检索方式 | 更擅长覆盖 |
|---|---|
| 向量检索 | 表达不同但语义接近的问题 |
| BM25 / 关键词检索 | 错误码、接口名、标题、专有名词、编号 |
| metadata filter | 权限、版本、产品线、租户、时间范围 |
Hybrid Search 的难点在于分数融合。向量相似度、BM25 分数和结构化检索得分往往不在同一尺度上,直接相加容易让某一路结果异常放大。常见处理方式包括分数归一化、按来源设置权重,或者使用 RRF 这类基于排序名次的融合方法。
RRF 的直觉比较简单:如果一个候选在多路召回里都排得比较靠前,它就应该获得更高优先级;如果它只在某一路里出现,也仍然保留机会。它避免了直接比较不同检索器原始分数的问题,适合多路结果融合。
多路召回把不同检索策略并行起来
当系统同时跑多个 query、多个检索器、多个过滤条件时,就进入了多路召回。它的目标是扩大正确证据进入候选池的机会。
一个较完整的召回链路可以这样理解:
原始 query
-> 上下文补全 query
-> 关键词 query
-> 语义改写 query
-> HyDE 生成文本
-> metadata filter
-> 向量检索、BM25、结构化检索并行召回
-> 融合、去重、聚合
-> 候选证据池
这里的每一路都承担不同角色。原始 query 保留用户最初表达,避免改写带偏。上下文补全 query 解决多轮对话里的指代和省略。关键词 query 强化实体、术语、编号和错误码。语义改写 query 把口语靠近文档表达。HyDE 提供更丰富的语义上下文。metadata filter 保证候选范围符合权限、版本和业务边界。
多路召回的收益,来自路径差异。多个相似策略堆在一起,效果提升有限,还会增加延迟。如果三路召回都只是把同一个 query 做向量检索,更多时候只是重复命中同一批 chunk。更有价值的组合,是让不同策略覆盖不同失败模式。
多路召回也要控制成本。每增加一路检索,都会增加查询延迟、资源消耗、融合复杂度和调试难度。在线系统通常要在召回覆盖和响应时间之间取舍。高频简单问题可以走轻量策略,复杂问题再触发更强的改写、多路召回或二阶段检索。
融合、去重和聚合决定候选池质量
多路召回之后,系统拿到的是一批来源复杂、分数尺度不同、重复度较高的候选,还需要继续清洗成更干净的证据池。
融合阶段首先要解决排序合并。不同召回源的分数不能简单比较,需要归一化、加权或排序融合。对于业务场景,还可以加入来源质量权重。例如正式文档优先于历史问答,最新版本优先于旧版本,人工审核文档优先于自动抓取内容。
| 处理环节 | 解决的问题 |
|---|---|
| 融合 | 把不同召回源的结果合并成统一候选池 |
| 去重 | 避免相同或相邻 chunk 重复占用后续窗口 |
| 聚合 | 补充标题、父章节、相邻片段和来源 metadata |
| 来源权重 | 让正式文档、新版本、审核内容获得更高优先级 |
去重也很重要。同一段内容可能被向量检索和 BM25 同时召回,也可能以相邻 chunk 的形式重复出现。如果不处理,后续 Rerank 和上下文窗口会被重复证据占据。去重可以按 chunk ID、文档 ID、文本相似度、标题层级或哈希指纹处理。
聚合则关注文档结构。一个答案可能分散在相邻 chunk 里。只保留单个高分 chunk,有时会丢掉前置条件和后续限制。更稳的做法,是在候选进入 Rerank 或上下文工程前,适当补充父标题、相邻段落、表格说明和来源 metadata,让证据不至于变成孤立碎片。
融合、去重和聚合做得粗糙,候选池会显得很热闹,但真正可用证据不一定多。好的候选池应该覆盖正确答案,同时保持足够干净,让 Rerank 和上下文工程有空间继续筛选。
检索增强要用评测来收敛
检索增强很容易越做越复杂。Rewrite、Expansion、HyDE、Hybrid Search、多路召回、RRF、metadata filter 都能带来收益,也都可能引入噪声、延迟和成本。
判断这些策略是否有效,不能只看最终答案主观感觉。更直接的方式,是构造一批真实问题和标准证据,观察正确 chunk 是否进入候选池,以及进入的位置是否足够靠前。
检索层常看的指标包括 Hit@K、Recall@K、MRR、NDCG 等。Hit@K 关心正确证据有没有出现在前 K 个结果中,Recall@K 关心标准相关证据的覆盖率,MRR 关注第一个正确结果的位置,NDCG 则能处理多个相关结果的排序质量。这些指标不需要等到大模型生成阶段,就可以直接评估召回和排序效果。
| 指标 | 关注点 |
|---|---|
| Hit@K | 正确证据有没有进入前 K 个结果 |
| Recall@K | 标准相关证据覆盖了多少 |
| MRR | 第一个正确结果排在多靠前 |
| NDCG | 多个相关结果的排序质量 |
除了离线指标,还要看在线链路里的成本和稳定性。一个策略如果把 Hit@20 提升了一点,却让延迟翻倍、候选噪声明显增多、Rerank 成本大幅上升,就需要结合业务场景判断是否值得。面向内部知识库的深度问答,可以接受更高延迟;面向实时客服的高并发问答,可能更需要轻量稳定。
日志是评测之外的另一条线。系统应该记录原始 query、改写 query、扩展 query、各路召回结果、融合结果、Rerank 前后的变化、最终进入上下文的证据。没有这些日志,很难判断某个策略是在提升召回,还是只是让链路变复杂。
常见失败可以按召回入口排查
检索增强的排查也可以分层看。
如果用户问题表达很短、指代明显、依赖上下文,优先看 Query Rewrite 和上下文补全。原始 query 过于稀疏时,向量检索和 BM25 都可能缺少有效信号。
如果问题包含错误码、接口名、配置项、编号和产品型号,优先看关键词检索、分词、大小写归一化、符号保留和字段索引。很多精确匹配失败的原因常常在索引层,例如关键符号被切坏了,或者没有建立合适字段。
如果问题带有版本、时间、权限、租户和产品线,优先看 metadata 解析和过滤条件。正确文档存在但被过滤掉,是生产 RAG 里很常见的失败。
如果各路召回都找到了正确证据,但最终候选排序靠后,就要检查融合策略、来源权重、去重策略和 RRF 参数。正确证据进入候选池只是第一步,后面还要保证它有机会进入 Rerank 和上下文。
如果召回数量很多但答案仍然不稳,说明候选池可能噪声过大。此时继续扩大 topK 未必有帮助,反而可能让 Rerank 和上下文工程承受更多干扰。更好的方向,是让召回路径更有针对性,或者在融合阶段提升候选质量。
| 失败现象 | 优先排查 |
|---|---|
| 问题短、指代多、依赖上下文 | Query Rewrite、上下文补全 |
| 错误码、接口名、配置项召回差 | 关键词检索、分词、符号保留、字段索引 |
| 正确文档存在但被过滤掉 | metadata 解析和过滤条件 |
| 正确证据进入候选但排序靠后 | 融合策略、来源权重、去重策略、RRF 参数 |
| 召回很多但答案不稳 | 候选池噪声、topK、融合质量 |
小结
检索增强围绕一个目标展开:让正确证据稳定进入候选池,并尽量把噪声控制在后续阶段可以处理的范围内。
Query Rewrite 解决用户表达和文档表达之间的距离,Query Expansion 扩大等价表达覆盖,HyDE 为抽象问题提供更丰富的语义入口,Step-back 帮助系统从具体问题回到上层规则,Self-Query 把自然语言中的结构化条件解析出来。Hybrid Search 把向量检索和关键词检索结合起来,多路召回让不同策略并行覆盖不同失败模式,融合、去重和聚合则把分散候选整理成统一证据池。
这些方法的价值,不在于名字本身,而在于它们各自补上了单路向量检索的某个短板。真正稳定的 RAG 检索层,应该能解释每一路召回为什么存在、覆盖什么问题、带来多少收益、增加多少成本,以及在失败时如何排查。
下一篇进入 Rerank 与上下文工程。检索增强把候选证据拉进来以后,系统还要继续回答两个问题:哪些证据最该给模型看,以及这些证据应该怎样组织,才能真正支撑一次可靠生成。