向量检索不准怎么办:混合检索与 Rerank 重排序召回优化实战

简介: 向量检索看不懂报错码和型号?一文讲透 BM25 关键词检索、RRF 混合检索与 Rerank 重排序,附 Python 代码,拉满 RAG 召回率与答案准确率

大家好,我是程序员天天困。

上一篇聊向量数据库原理,咱们把「文档切块 → Embedding 向量化 → HNSW/IVF 近邻检索」这条链路走通了。但向量库解决的是「存得下、搜得快」,真把 RAG 机器人搬到公司内部知识库上跑,「搜得准」的问题才刚露头,翻车来得比想象快。这篇就讲向量检索翻车怎么救——混合检索与召回优化,建议先收藏,写代码时对着抄。

一、纯向量检索的三道坎:编号、年份和内部黑话

先说结论:纯向量检索翻车,多半不是 Embedding 模型不行,而是关键词这类信息在向量化那一刻就被丢掉了。

拿一个最典型的场景来说:公司内部的 IT/HR 知识库问答机器人上线后,最先翻车的是这三类问题。

第一类,精确编号。用户问「OA 报销单提交报错 E-503 怎么处理」,向量检索回来的是《OA 系统常见问题》《报销审批操作指南》《财务流程制度说明》——篇篇都和 OA 报销相关,语义挑不出毛病,唯独那篇《E-503 报错处理手册》沉在下面,甚至没进 Top-5。

第二类,年份和数字。问「2026 年端午怎么调休」,回来的是 2024、2025、2026 三年的调休通知。「端午调休」这四个字的语义每年都一模一样,向量分不出新旧。

第三类,内部黑话。问「OC 校准会是什么流程」,OC 是公司内部对某类会议的缩写,Embedding 模型训练时压根没怎么见过这个词,只能猜个「开会」的语义,回来一堆会议管理制度。

你说它不会吧,相关文档它全给你捞回来了;你说它会吧,最关键的那篇就是找不到。

根子在于 Embedding 是一次有损压缩:一段话压成几百维向量,语义和意图被保留,字面 token 被「磨平」了。向量空间里「报错码 E-503 怎么处理」和「OA 登不上怎么办」离得很近——这正是它的优点;但「E-503」这串字符到底重不重要,向量表达不出来。

于是检索分成了两派,各管一摊:

维度 向量检索(稠密) 关键词检索(稀疏)
擅长 语义理解、同义改写、口语化提问 精确词项、编号型号、缩写黑话
底层手段 Embedding 向量 + ANN 索引 分词 + 倒排索引 + BM25 打分
典型 query 「OA 登不上咋办」 「报错码 E-503」
短板 编号、数字、低频黑话会丢 同义词、改写完全匹配不上

向量检索管「意思到了」,关键词检索管「字对上了」,生产级 RAG 两个都要。 这也是检索策略这些年的演进路线:

检索策略四代演进:从纯关键词 BM25 到向量检索、混合检索再加重排序

这篇文章的路线图就是这张图:先补关键词这一路(BM25),再把两路捏合(混合检索 + RRF),最后解决排序精度(重排序)。

二、BM25:关键词检索这一路怎么打分

BM25(Best Matching 25):信息检索领域经典的词项相关性打分函数,基于倒排索引统计词频给文档排序,Elasticsearch 等搜索引擎默认用它;你可以理解为「数关键词出现了几次,但越稀有的词越值钱」。

它不理解语义,只看三件事。

第一,词频,但有饱和上限。 一个词在文档里出现得越多,文档越相关——「E-503」出现 3 次的手册比出现 1 次的相关。但出现 100 次不等于相关 100 倍,可能只是文档啰嗦。BM25 对词频做了饱和处理:从 1 次到 3 次分数涨得快,再往上基本不涨了,防止长文档靠堆词刷分。

第二,稀有度,越稀有越值钱。 「OA」在知识库一半的文档里都出现,命中了也不稀罕;「E-503」只在一篇手册里出现,一旦命中,这篇极可能就是答案。BM25 用逆文档频率(IDF)量化这件事:一个词出现在越少的文档里,权重越高。

第三,文档长度,长文档要打折。 5000 字的《OA 操作大全》里出现「OA」的概率天然比 500 字的手册高,但这不代表它更相关。BM25 按文档长度做归一化,长文档扣分、短文档加分,大家站同一条起跑线。

拿「OA 报错 E-503」这条 query 走一遍:《E-503 处理手册》篇幅短,「E-503」词频适中且这个词全库只此一家,IDF 拉满,分数一骑绝尘;《OA 操作大全》虽然「OA」刷了十几次,但没出现「E-503」这个高权重词,长度还吃亏,分数直接垫底。这就是关键词检索的脾气——字对不上,它一分都不给

好消息是不用自己造轮子。Milvus 从 2.5 版本开始内置全文检索:写库时声明一个 BM25 Function,原文进库后自动分词、自动生成稀疏向量,大多数场景不一定要再单独维护一套 Elasticsearch(本文代码基于 pymilvus 2.5+ 写法,升级前建议对照官方文档核对 API)。

三、混合检索:两路召回,RRF 按排名拍板

混合检索的关键不是「两路都查」,而是怎么把两路结果合成一份排名——直接加分数,是新手最容易踩的坑。

流程上很直白:一条 Query 进来,稠密路转向量查 ANN 索引,稀疏路直接传原文查倒排索引,各取 Top-N,最后融合成一份候选集:

混合检索流程图:向量检索与 BM25 关键词检索双路召回,RRF 按排名融合

1)为什么分数不能直接加

稠密路返回的是余弦相似度,文本检索里通常落在 0~1,比如 0.82;稀疏路返回的是 BM25 分,非负且没有固定上限,比如 11.6。这俩分数量纲都不一样,0.82 + 11.6 这种加法等于让 BM25 单方面碾压。

有人说那归一化到 0-1 再加权?实际也坑:假设稠密路 Top-10 的分数全挤在 0.80-0.85 之间,min-max 归一化会把这 0.05 的区间硬撑成 0~1,0.01 的噪声差就被放大成 0.2 的悬殊差距;反过来分数散的时候,真差异又被压平。分数分布每批 query 都在变,归一化参数根本调不住。

所以工程上最稳的做法是不看分数,只看排名

RRF(Reciprocal Rank Fusion,倒数排名融合):多路检索结果的融合算法,不给各路分数做任何换算,只按每路排名投票,最终分为各路 1/(k + 名次) 之和,k 通常取 60;像选秀节目的评委投票——不看评委各自打了多少分,只看每位评委把选手排第几。

举个例子。稠密路排名 [D2, D1, D5, D6, D3],稀疏路排名 [D1, D7, D2, D5, D4],k=60:

文档 稠密路名次 稀疏路名次 RRF 得分
D1 2 1 1/62 + 1/61 ≈ 0.0325
D2 1 3 1/61 + 1/63 ≈ 0.0323
D5 3 4 1/63 + 1/64 ≈ 0.0315
D7 未进榜 2 1/62 ≈ 0.0161
D6 4 未进榜 1/64 ≈ 0.0156

最终排名 D1、D2、D5 稳居前三——这三篇在两路里都靠前;D7 只在稀疏路出现过,但也没被丢掉,照样进候选。k 是个平滑常数:k 越小,头部名次的优势越夸张;k 越大,各路排名越被「和稀泥」。60 是 RRF 原论文在大量数据集上试出来的默认值,几十名以内的名次差距影响温和,掉到几十名开外的候选基本没什么贡献——所以每路召回给到 top 几十就够用。

2)代码:pymilvus 建库

先建 Collection,注意三个字段的分工:text 存原文并开启中文分词,dense 存 Embedding 向量,sparse 交给 BM25 Function 自动填充,插入时不用传。

from pymilvus import MilvusClient, DataType, Function, FunctionType

client = MilvusClient(uri="http://localhost:19530")

schema = MilvusClient.create_schema(auto_id=True, enable_dynamic_field=False)
schema.add_field("id", DataType.INT64, is_primary=True)
schema.add_field("text", DataType.VARCHAR, max_length=2048,
                 enable_analyzer=True,
                 analyzer_params={
   "type": "chinese"})   # 中文建议指定,否则按单字切
schema.add_field("dense", DataType.FLOAT_VECTOR, dim=1024)
schema.add_field("sparse", DataType.SPARSE_FLOAT_VECTOR)  # BM25 自动生成

schema.add_function(Function(
    name="bm25_fn",
    function_type=FunctionType.BM25,
    input_field_names=["text"],
    output_field_names=["sparse"]))

index_params = client.prepare_index_params()
index_params.add_index(field_name="dense",
                       index_type="AUTOINDEX", metric_type="COSINE")
index_params.add_index(field_name="sparse",
                       index_type="SPARSE_INVERTED_INDEX", metric_type="BM25")
client.create_collection("it_kb", schema=schema, index_params=index_params)

3)代码:三路检索对比

混合检索用 AnnSearchRequest 分别描述两路请求,再交给 hybrid_searchRRFRanker 融合。稀疏路有个反直觉的点:data 里直接传 Query 原文,不传向量——Milvus 内部会走 BM25 Function 实时算稀疏向量。

from pymilvus import AnnSearchRequest, RRFRanker

dense_req = AnnSearchRequest(
    data=[query_vec],                 # 稠密路传 Embedding 向量
    anns_field="dense",
    param={
   "metric_type": "COSINE", "params": {
   "nprobe": 16}},
    limit=30)
sparse_req = AnnSearchRequest(
    data=[query_text],                # 稀疏路传原文!
    anns_field="sparse",
    param={
   "metric_type": "BM25",
           "params": {
   "drop_ratio_search": 0.2}},  # 过滤底部 20% 低分结果
    limit=30)

results = client.hybrid_search(
    collection_name="it_kb",
    reqs=[dense_req, sparse_req],
    ranker=RRFRanker(k=60),
    limit=5,
    output_fields=["text"])

想单独跑纯 BM25 路对比效果,用普通 search 传原文即可:client.search(..., anns_field="sparse", data=[query_text])

你可以做个最小实验:往库里塞 6 条 IT/HR 文档,用「OA 报销单提交报错 E-503 怎么处理」跑三路对比,典型结果长这样(玩具数据,数字为示意,建议亲手跑一遍):

排名 纯向量检索 纯 BM25 混合检索(RRF)
Top-1 OA 系统常见问题 E-503 处理手册 E-503 处理手册
Top-2 报销审批操作指南 (无结果返回) OA 系统常见问题
Top-3 E-503 处理手册 报销审批操作指南

三个现象值得记住:BM25 高精度但不做语义兜底,上面代码里稀疏路开了 drop_ratio_search=0.2,词项不重叠的低分文档被直接过滤,整条路只返回 1 条命中;向量检索高召回,语义沾边的全捞回来,但《E-503 手册》只排到第三;混合检索里《E-503 手册》稀疏路排第 1、稠密路进了前三,RRF 得分约 0.032,而第二名只在稠密路有成绩、约 0.016,被甩开近一倍——两路都认可的结果,融合后优势会被放大,精确命中和语义兜底都拿到了。数据量上万以后,这个差距只会更明显。

四、重排序:召回管「别漏」,精排管「排对」

混合检索解决的是「找得全」,但大模型只看上下文里的前 3~5 条——顺序错了,照样答非所问。

打个招聘的比方:召回阶段像 HR 初筛,按岗位关键词把沾边的简历全捞进池子,宁可多捞一百份,不可漏掉一个合适的;重排序像用人部门主管逐份面试,把真正匹配的三五份排到最前面发 offer。

这里涉及两种编码器结构:

Bi-Encoder(双编码器):把 Query 和文档各自独立编码成一个向量,再算两个向量的相似度,Embedding 模型就是这种结构;像两个人各自填完一份问卷再比对答案。文档向量可以提前算好存进向量库,查询时只编码 Query 一次,所以快。

Cross-Encoder(交叉编码器):把 Query 和文档拼接成一段文本一起输入模型,让注意力机制在两者之间逐词交互,最后只输出一个相关性分数;像把两个人叫到一起当面聊一场,判断准,但每一对都得聊一次,速度慢得多。

主流的重排序模型(Reranker),比如 bge-reranker 和 Qwen3-Reranker,基本都是 Cross-Encoder 结构。两者的差别看这张对比图:

Bi-Encoder 与 Cross-Encoder 结构对比:双编码器各自算向量,交叉编码器拼接后交互打分

1)为什么不直接拿 Cross-Encoder 全库扫

既然它准,为什么还要两阶段?算笔账:知识库 100 万个 chunk,一次提问就要把 100 万个配对逐个过模型,就算 GPU 批量并行也得几十分钟,没人等得起。所以工程上一定是两阶段——粗排快但糙,全库扫出几十个候选;精排慢但准,只给这几十个候选重新打分:

两阶段检索漏斗:百万 chunk 经混合检索粗排到约 40 条,Cross-Encoder 精排到 5 条送入 LLM

可能有人会问:那重排序直接让大模型(LLM)来打分不行吗?

技术上可行,部分框架也支持,但贵:40 个候选就是 40 次大模型推理,每次都要带几千 token 的文档,延迟奔着秒级去,批量场景成本肉疼。专用 Reranker 是几亿参数的编码器,就干「判断相关性」这一件事,几百毫秒搞定。追求极致且预算无上限可以上 LLM 重排,绝大多数团队用专用模型就够。

2)Reranker 怎么选

截至 2026 年 8 月,常用的就这么几款:

模型 特点 适用场景
bge-reranker-v2-m3 轻量、中文效果稳、可本地部署 预算敏感、想私有化
Qwen3-Reranker(0.6B/4B/8B) 2025 年 6 月发布,中文榜单第一梯队,支持指令定制 中文问答、复杂意图排序
Cohere / Jina 商业 API 开箱即用,多语言成熟 海外业务、不想运维模型

硅基流动等平台提供了开箱即用的 rerank 接口(格式与 Cohere rerank 一致),不用自己部署。代码就一段 HTTP 调用:

import requests

def rerank(query, documents, top_k=5,
           model="BAAI/bge-reranker-v2-m3"):
    resp = requests.post(
        "https://api.siliconflow.cn/v1/rerank",  # 以你用的平台文档为准
        headers={
   "Authorization": "Bearer <你的 API Key>"},
        json={
   "model": model, "query": query,
              "documents": documents,
              "top_n": top_k,
              "return_documents": True},
        timeout=30)
    resp.raise_for_status()
    return resp.json()["results"]   # 每项含 index 和 relevance_score

返回结果已按 relevance_score 降序排好,取前 5 个 index 回原候选列表取文档就行。用 Qwen3-Reranker 时,部分平台还支持额外传 instruction 参数,比如「优先排序包含具体报错码和操作步骤的文档」,业务定制很顺手(具体字段以平台文档为准)。

再看个例子,Query「Mac 电脑怎么申请重装系统」(候选集是混合检索召回的几十条,这里只列重排前后各自的 Top-3):

阶段 Top-1 Top-2 Top-3
混合检索重排前 办公电脑维修与报废流程 Mac 重装系统申请指南 新员工设备申领须知
Rerank 重排后 Mac 重装系统申请指南 办公电脑维修与报废流程 macOS 自助重装 U 盘借用说明

重排把最该进上下文的那条提到了第一,原本排在候选集中后部、带「自助重装」关键词的那条也升到了第三。注意重排只调整候选集内部的顺序,不会变出召回阶段没有的文档——这也是后面强调「先保召回」的原因。重排序的价值从来不是多找几篇,而是让前五名名副其实。

五、串成一条链路:参数怎么调、效果怎么评

把整条链路画成时序图,谁先谁后、每段多少数据、耗时多少,一目了然:

RAG 完整检索链路时序图:Embedding 转向量、Milvus 双路混合检索、Reranker 精排、LLM 生成答案

1)参数经验值

不知道怎么配的时候,从这组值起步,再按日志调:

参数 起步值 调整方向
稠密路 topK 20~30 漏召回就加大,延迟敏感就减小
稀疏路 topK 20 编号、型号类 query 多就加到 40
BM25 drop_ratio_search 0.2 低分噪声多就调大,想多留候选就调小
RRF 的 k 60 一般别动,两路结果波动大也保持 60
Rerank 候选数 30~50 精度不够先加到 50,同时盯延迟
最终返回 K 3~5 跟着 LLM 上下文窗口和塞 prompt 的成本走

2)调优顺序:先保召回,再调排序

这是我最想强调的一条:召回阶段漏掉的文档,Reranker 再强也变不出来。调检索的第一原则,是先保召回、再调排序。

我见过不少团队一上来就换 Reranker、换大模型,折腾两周没效果,最后发现根因是相关文档压根没进 Top-50——精排阶段连这篇文档的面都见不到,怎么可能把它排上来?钱花了,水里都不响。

正确的顺序分三步:

  1. 先看召回:从真实对话日志里挑 50~100 条 query,人工标注每条「最该命中的文档」,算 Recall@30——相关文档出现在两路融合后候选集里的比例。没到 90% 就先别动排序,去查切块策略、topK、中文分词,必要时上查询改写。
  2. 再看排序:Recall 达标后,盯 MRR(每个问题取第一个相关结果排名的倒数——排第 1 得 1 分、排第 5 得 0.2 分,再对所有问题求平均)和 nDCG@10(排名越靠前权重越高的排序得分)。这俩上不去,才是 Reranker 该解决的事。
  3. 最后看业务:答非所问率、用户追问率、人工兜底率,这是最终裁判。

3)选型决策

场景 推荐策略
数据量小、query 全是自然语言口语 纯向量检索,简单够用
query 常带编号、型号、内部黑话 混合检索,补上关键词这一路
答案准确率敏感、能接受多一百毫秒 混合检索 + Rerank,效果上限最高
成本敏感、不想多调一个模型 混合检索不加重排,RRF 本身已经很能打

说白了,检索质量是 RAG 的天花板,大模型只是在天花板下发挥。向量数据库帮你解决了「存得下、搜得快」,而「搜得准」靠的是混合检索拓宽召回、重排序兑现精度——这也是向量检索工程化真正的分水岭。


我是程序员天天困,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你们的 RAG 项目,是栽在召回漏掉文档上,还是栽在排序把错的放第一上?

相关文章
|
17天前
|
人工智能 缓存 前端开发
DeepSeek Harness 首发实测 + 入门教程,夯爆了!梁神我错了
DeepSeek Harness + DeepSeek V4 Pro 项目实战保姆级教程!手把手带你从零安装开源 AI 编程工具,开发架构图、知识讲解网站、3D 网页游戏、全栈 AI 应用 4 个项目,覆盖运行模式选择、插件安装与开发,看看能不能对标 Claude。
12763 75
DeepSeek Harness 首发实测 + 入门教程,夯爆了!梁神我错了
|
5天前
|
人工智能 自然语言处理 安全
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
本文聚焦阿里云2026年推出的三款自研AI办公产品,清晰拆解千问办公、Qoder Teams、Qoder CN的差异化定位与能力边界:千问办公主打职场全场景提效,支持自然语言指令一键完成PPT生成、数据分析等高频办公任务;Qoder Teams面向程序员团队,深度整合AI代码生成、团队协同与企业知识库能力;Qoder CN则专为金融、政务等强合规场景打造,实现数据不出境与VPC私有化部署。文章同步给出分场景选型指南与最新活动定价,帮助不同类型的企业按需组合产品,实现业务岗、研发岗与强合规场景的AI能力全覆盖。
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
|
10天前
|
Web App开发 人工智能 API
16 个超火的 DeepSeek Harness 插件,大肥鱼已经落后 N 个版本了。。。
DeepSeek Harness 精选插件推荐合集,从图片识别、浏览器操控、多 Agent 协作到手机远程控制,一口气带你看完 DSH 社区热门的十几个插件,覆盖技能扩展、UI 界面增强、整活玩法三大类,让你的鲸鱼变得更强。
1617 2
|
人工智能 JavaScript 开发工具
DeepSeek Harness 本地安装与使用指南
DeepSeek Harness(DSH)是DeepSeek AI开源的Agent运行框架,支持本地文件操作、命令执行与工具调用。基于Cordis插件架构,具备高扩展性与强可控性,适合开发者搭建可控Agent环境或开展模型基准测试。当前为开发者预览版,需Node.js环境,推荐先用`npx @deepseek-ai/dsh web`快速体验。
4988 0
|
11天前
|
人工智能 Java BI
【AI】DeepSeek Harness 安装、运行、管理插件
本文介绍了如何运行DeepSeek开源的Agent框架DeepSeek Harness(dsh)。主要内容包括:使用nvm安装适配的Node版本;通过代理加速克隆GitHub源码;使用pnpm安装依赖并启动项目;配置DeepSeek API Token;安装扩展功能的插件。该框架自带Web界面,支持模型适配、文件编辑等插件化功能
1740 1
|
13天前
|
人工智能 JavaScript 测试技术
保姆级教程:DeepSeek Harness从安装到跑通测试,30分钟上手
DeepSeek Harness是DeepSeek开源的AI Agent运行时,主打“一行命令安装、5分钟跑通”。它让模型真正动手干活——读代码、跑测试、分析失败、生成修复方案。本文手把手教你30分钟从零上手,覆盖安装、配置、实测及避坑指南,助你快速掌握下一代AI编程范式。
|
15天前
|
开发工具 Swift git
DeepSeek Harness 插件推荐:4 款开源神器让写代码直接起飞
DeepSeek Harness 插件推荐:ModLens 视觉、Web UI 全家桶、Mac 原生与 GenUI 渲染,4 款开源插件给纯文本模型补齐短板。
2022 6
DeepSeek Harness 插件推荐:4 款开源神器让写代码直接起飞
|
12天前
|
人工智能 JavaScript 测试技术
从 0 到 1,DeepSeek Harness 保姆级安装与使用教程!
DeepSeek Harness是DeepSeek推出的开源Agent运行框架,秉持“一切皆插件”理念,支持模型、工具、技能、工作流等全模块自由替换与扩展。其核心Cordis内核实现动态插件管理,赋能Agent自进化。已成GitHub史上增速最快开源项目(15w+ Star),标志着国内大模型从拼价格转向重架构与生态的新拐点。
1283 5
从 0 到 1,DeepSeek Harness 保姆级安装与使用教程!

热门文章

最新文章