向量检索与关键词检索怎样做混合召回
来源:17golang原创
时间:2026-10-08 12:32:43 293浏览 收藏
向量检索与关键词检索做混合召回,最稳妥的起点是:两路使用同一查询并行取候选,先各自保留排名,再用 RRF(Reciprocal Rank Fusion,倒数排名融合)合成统一顺序。不要直接把 BM25 分数和向量相似度相加,因为两者的范围、分布和含义不同,换模型或换语料后还会漂移。
- 关键词路负责产品编号、错误码、专有名词、日期和精确短语。
- 向量路负责同义改写、自然语言问题和概念相近但字面不同的内容。
- 两路候选按稳定文档 ID 去重,用 RRF 合并名次;权限过滤必须在两路保持一致。
- 如果还需要更高精度,可在融合后的较小候选集上增加交叉编码器或语义重排。
混合召回解决的不是二选一
关键词检索依赖倒排索引,常见排序基础是 BM25。它擅长精确词面:查询“ERR_CONNECTION_RESET”、SKU“AX-1042”或法规编号时,字面命中通常比语义近似更可靠。它的盲区是同义表达,例如文档写“撤销订单”,用户问“怎么取消购买”,如果没有同义词扩展,召回可能偏低。
向量检索把查询和文档映射到同一个嵌入空间,再按余弦、点积或欧氏距离寻找近邻。它能找出语义接近的改写,但对极短编号、罕见人名、版本号和精确短语未必稳定。混合召回的价值,就是让一个通道补另一个通道的盲区,而不是简单选一个“更先进”的通道。

支持范围:先统一文档粒度、标识和过滤
两路结果能不能正确合并,关键不在模型,而在索引契约。关键词索引与向量索引必须指向同一个稳定 doc_id,并尽量使用相同的分块粒度。如果关键词路返回整篇文章、向量路返回段落,融合时就会出现一篇文档挤占多个位置或证据定位不一致。
权限、租户、语言、时间范围和内容状态也必须保持一致。多租户知识库中,不能让向量路先召回全库内容,再依赖最后一步隐藏无权文档;正确做法是让两路都带上可执行的权限过滤,或在进入融合前完成同等强度的过滤。
| 查询特征 | 更有优势的通道 | 混合时的处理 |
|---|---|---|
| 编号、错误码、专有名词 | 关键词 | 保留精确匹配,必要时提高关键词路候选量 |
| 口语问题、同义改写 | 向量 | 用向量路补充字面不一致的内容 |
| 既有术语又有描述 | 两者 | 并行召回后用 RRF 合并 |
| 强权限或租户隔离 | 两者都必须过滤 | 统一过滤条件,不能只在最终展示层处理 |
最小实现:用 RRF 合并两组名次
RRF 不关心两路原始分数,只关心每个文档在各自结果中的名次。文档在某一路的贡献是 1 / (k + rank);如果它同时出现在多路结果中,就把贡献相加。k 用来减弱第一名与后续名次的陡峭差异,实践中常从 60 开始,再用评估集决定是否调整。
from collections import defaultdict
def rrf_fuse(rankings: list[list[str]], k: int = 60) -> list[tuple[str, float]]:
"""把多路文档排名合并为统一的 RRF 排名。"""
scores: dict[str, float] = defaultdict(float)
for ranking in rankings:
# rank 从 1 开始;某文档未出现在该路时不增加贡献。
for rank, doc_id in enumerate(ranking, start=1):
scores[doc_id] += 1.0 / (k + rank)
# 分数降序,doc_id 作为稳定次级排序,避免同分时随机抖动。
return sorted(scores.items(), key=lambda item: (-item[1], item[0]))
keyword_topk = ["doc-a", "doc-b", "doc-c"]
vector_topk = ["doc-b", "doc-d", "doc-a"]
# 两路都靠前的 doc-b 会累加两份倒数名次贡献。
merged = rrf_fuse([keyword_topk, vector_topk], k=60)
print(merged[:3])
实际系统中,关键词检索与向量检索应并行执行,各自取比最终返回数更大的候选。例如最终需要 10 条,可以先让每路取 30~100 条,再融合、去重、过滤并截取 TopN。具体候选量不能照抄固定值,应由语料规模、查询类型、延迟预算和 Recall@K 实验决定。

兼容处理:什么时候不用纯 RRF
RRF 是很好的默认方案,因为它不要求分数同量纲,也不需要先训练校准器。但它只利用名次,不利用“第一名比第二名究竟高多少”的置信差距。如果你已经有稳定标注集,并能把 BM25 与向量分数校准到可比较范围,就可以尝试线性加权;否则直接写 0.5 × BM25 + 0.5 × cosine 往往只是看起来公平。
另一种常见结构是“混合召回 + 二阶段重排”。先用 RRF 得到几十条候选,再让交叉编码器或语义排序器结合查询和正文重排。它通常能提升前几位精度,但会增加推理延迟与成本,因此适合只处理小候选集,而不是对整个语料逐条打分。
如果业务查询有明显类型,也可以做轻量路由:纯编号查询提高关键词候选配额,自然语言问题提高向量候选配额,但仍保留另一条通道作为兜底。路由规则必须通过真实查询日志和标注集验证,不能只凭长度或是否含空格做武断判断。
性能与安全:并行、超时、去重和权限
两路串行会把延迟相加,因此应并行发起并设置独立超时。若一个通道超时,可在明确降级策略下返回另一路结果,同时记录降级率;如果搜索用于高风险决策或强一致场景,则应选择失败而不是静默降级。
融合前先用稳定 ID 去重,融合后再执行内容级去重,避免同一文章相邻分块占满结果。向量生成模型、分块策略或 BM25 字段权重发生变化时,应视为检索版本变更,重新跑离线评估,而不是只观察点击率。
安全边界比排序更优先:关键词与向量索引都要带租户、ACL 和内容状态;缓存键必须包含租户与权限摘要;调试日志可以记录文档 ID、通道名和排名,但不要记录敏感正文、用户私有查询或向量原值。
用评估集决定参数,而不是凭感觉调权重
准备一组来自真实业务的查询,并为每个查询标注相关文档。至少对比三条基线:关键词单路、向量单路、混合召回。召回阶段关注 Recall@K,排序质量可看 MRR 或 nDCG@K,同时记录 P50/P95 延迟、超时率和每次查询成本。
调参时一次只改一个变量:每路 TopK、RRF 的 k、向量权重、过滤位置或重排候选数。特别检查“精确术语”“同义改写”“多语言”“短查询”“长问题”和“无答案查询”几个切片。只有混合方案在核心切片上稳定优于单路基线,且延迟与成本可接受,才算完成验收。
相关问题
RRF 中的 k 和向量检索 TopK 是同一个参数吗?
不是。RRF 的 k 是平滑名次贡献的常量;向量检索的 TopK 是候选数量。两者应分别调节。
关键词和向量结果一定要各占一半吗?
不需要。RRF 默认按名次贡献,也可以为不同通道加入权重;但权重应来自标注集,而不是为了形式上的五五开。
混合召回后还需要重排吗?
取决于目标。如果只要求较高召回和低延迟,RRF 可能已经足够;如果前几位精度直接影响 RAG 答案质量,可以在小候选集上增加二阶段重排。
过滤应该在融合前还是融合后?
权限和租户过滤必须尽量在两路检索时执行,并在融合前确保一致;普通业务筛选可按引擎能力选择前置或后置,但要评估它对候选数量和召回率的影响。
-
225 收藏
-
449 收藏
-
101 收藏
-
191 收藏
-
468 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习