登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

向量检索与关键词检索怎样做混合召回

来源:17golang原创

时间:2026-10-08 12:32:43 293浏览 收藏

向量检索与关键词检索做混合召回,最稳妥的起点是:两路使用同一查询并行取候选,先各自保留排名,再用 RRF(Reciprocal Rank Fusion,倒数排名融合)合成统一顺序。不要直接把 BM25 分数和向量相似度相加,因为两者的范围、分布和含义不同,换模型或换语料后还会漂移。

核心方案
  • 关键词路负责产品编号、错误码、专有名词、日期和精确短语。
  • 向量路负责同义改写、自然语言问题和概念相近但字面不同的内容。
  • 两路候选按稳定文档 ID 去重,用 RRF 合并名次;权限过滤必须在两路保持一致。
  • 如果还需要更高精度,可在融合后的较小候选集上增加交叉编码器或语义重排。

混合召回解决的不是二选一

关键词检索依赖倒排索引,常见排序基础是 BM25。它擅长精确词面:查询“ERR_CONNECTION_RESET”、SKU“AX-1042”或法规编号时,字面命中通常比语义近似更可靠。它的盲区是同义表达,例如文档写“撤销订单”,用户问“怎么取消购买”,如果没有同义词扩展,召回可能偏低。

向量检索把查询和文档映射到同一个嵌入空间,再按余弦、点积或欧氏距离寻找近邻。它能找出语义接近的改写,但对极短编号、罕见人名、版本号和精确短语未必稳定。混合召回的价值,就是让一个通道补另一个通道的盲区,而不是简单选一个“更先进”的通道。

关键词 BM25 与向量相似度两路候选经过权限过滤和 RRF 合并的静态结构图
图1:关键词与向量双路召回的静态结构图,两路候选先独立排序再融合。

支持范围:先统一文档粒度、标识和过滤

两路结果能不能正确合并,关键不在模型,而在索引契约。关键词索引与向量索引必须指向同一个稳定 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 实验决定。

BM25 排名与向量排名通过倒数名次贡献形成 RRF 融合排名的静态说明图
图2:RRF 名次融合静态说明图,双路靠前的文档会累加更多贡献。

兼容处理:什么时候不用纯 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 答案质量,可以在小候选集上增加二阶段重排。

过滤应该在融合前还是融合后?

权限和租户过滤必须尽量在两路检索时执行,并在融合前确保一致;普通业务筛选可按引擎能力选择前置或后置,但要评估它对候选数量和召回率的影响。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>