news 2026/6/10 23:19:58

# AI Agent从0到1开发学习:【什么是多路召回?具体怎么做?】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
# AI Agent从0到1开发学习:【什么是多路召回?具体怎么做?】

本文是【AI Agent从0到1开发学习】专栏系列文章,更多内容持续更新中,文章内容基于作者本人理解与实践,如有纰漏与错误等问题,烦请告知,欢迎关注交流!

先用一句话回答:

多路召回,就是针对同一个用户查询,同时使用多种不同原理的检索策略去"撒网",然后把各路结果融合到一起,从而兼顾语义理解和关键词匹配,大幅提升召回的覆盖率和准确性。它解决的核心问题很直接——单一检索方式有盲区,多路并行才能补齐短板。


一、什么是多路召回?

在做 RAG(检索增强生成)或者搜索系统的时候,"召回"指的是从海量文档中,把和用户查询相关的候选项先捞出来。这是整个链路的第一步,后续的精排、Rerank、乃至最终的大模型生成,都依赖召回的质量。

多路召回(Multi-Channel Recall),顾名思义,就是不再只依赖一种检索方式,而是同时走多条路,每条路有自己的检索逻辑和匹配视角,最终把各路命中的结果汇总融合。

举个生活中的例子:你要找一本讲"分布式系统设计"的书。如果你只按书名搜,可能错过那些标题里没有"分布式"、但内容高度相关的书;如果你只按内容语义搜,可能漏掉那些关键词精准匹配的经典教材。两种方式各有盲区,同时用两种甚至更多方式去搜,才能最大程度把好书都找出来。

在 RAG 场景下,多路召回的典型结构是:用户 Query 一进来,同时触发向量检索、关键词检索、Query 扩展检索等多条路径,各路独立返回 Top-K 结果,最后通过 RRF(Reciprocal Rank Fusion)等融合算法合并排序,输出最终候选集。


二、为什么单路召回不够用?

很多人刚做 RAG 的时候,直接上一个向量检索(Dense Retrieval)就完事了。确实,向量检索能捕捉语义相似性,“深度学习"能匹配到"神经网络训练”,这比关键词搜索聪明多了。但实际跑起来你会发现,单路召回经常翻车,原因主要有三个:

2.1 向量检索的"语义偏差"

向量检索的原理是把 Query 和文档都映射到同一个向量空间,按余弦相似度取最近邻。问题在于,Embedding 模型不是万能的。当你问"Python GIL 是什么"的时候,向量检索可能给你召回一堆讲"Python 多线程"的文档,语义上确实相关,但真正解释 GIL 机制的那篇可能被排到了十名开外。原因很简单——Embedding 模型在训练时,对这种"精准术语 + 概念定义"的语义刻画不够细,导致向量空间中的距离存在偏差。

更极端的情况:用户问的是一个缩写、行话、或者领域特有的黑话,Embedding 模型压根没见过,那向量检索基本就是瞎猜。这种时候,关键词检索反而能精准命中。

2.2 关键词检索的"词汇鸿沟"

BM25 等关键词检索走的是另一条路:分词、建倒排索引、按 TF-IDF 打分。它的优势是"所见即所得"——用户输入的词,文档里必须有,才能匹配上。这意味着精准匹配场景下 BM25 非常可靠。

但关键词检索的死穴是词汇鸿沟:用户说的词和文档里用的词不一致,就匹配不上。用户问"如何提升模型性能",文档里写的是"模型推理加速优化方案",意思完全一样,但 BM25 匹配不上,因为"提升"和"加速"不是同一个词。这在中文场景下尤为严重,同义表述太多了。

2.3 任何单路都有系统性盲区

检索方式擅长场景盲区
向量检索语义相似、泛化匹配精准术语、缩写黑话、长尾概念
关键词检索精准匹配、专有名词同义改写、语义理解、跨语言
知识图谱检索实体关系、结构化推理开放域问答、非结构化文本

每种检索方式都有自己的"能力边界",单路召回的召回率天花板是显而易见的。实际业务数据告诉我们,单路召回的 Recall@10 通常在 60%-75% 之间,而多路组合后能稳定推到 85% 以上。这个差距在 RAG 场景下意味着:用户问 10 个问题,单路可能有 3-4 个因为没召回到关键文档而回答错误或者胡编。


三、多路召回的具体实现(分路策略)

下面进入实操部分。一个典型的多路召回方案至少包含三路检索,每路各司其职:


多路召回架构:Query 预处理后并行触发三路检索,各路独立召回后经 RRF 融合输出

第一路:向量检索(Dense Retrieval)

向量检索是多路召回里最核心的一路,也是语义理解的"主力军"。

基本流程:

  1. 将所有文档分 chunk 后,用 Embedding 模型(如 BGE、GTE、text-embedding-3 等)编码为向量
  2. 存入向量数据库(Milvus、Qdrant、FAISS 等)
  3. 用户 Query 同样编码为向量
  4. 在向量空间中按余弦相似度做 ANN 检索,取 Top-K

实现关键点:

  • Chunk 策略:按固定 token 数切分(如 512 token),重叠窗口(overlap)设 50-100 token,避免语义断裂。也可以按段落、章节切分,保留结构信息。
  • Embedding 模型选择:中文场景推荐 BGE-M3 或 GTE-Qwen2,英文场景推荐 text-embedding-3-large。模型选型对召回质量的影响非常大,值得单独做一次评测。
  • 向量索引:数据量小(<100万)直接用 FAISS flat 索引,精度最高;数据量大用 HNSW 或 IVF-PQ,牺牲一点精度换速度。
# 向量检索核心代码示例(伪代码)fromsentence_transformersimportSentenceTransformerimportfaiss model=SentenceTransformer('BAAI/bge-m3')# 文档入库doc_embeddings=model.encode(doc_chunks,normalize_embeddings=True)index=faiss.IndexFlatIP(doc_embeddings.shape[1])index.add(doc_embeddings)# 检索query_embedding=model.encode([user_query],normalize_embeddings=True)scores,indices=index.search(query_embedding,top_k=10)

第二路:BM25 关键词检索

BM25 是信息检索领域的经典算法,基于词频统计和文档长度归一化,关键词匹配的"守门员"。

基本流程:

  1. 对文档库建立倒排索引(Elasticsearch、Whoosh 等)
  2. 用户 Query 分词后,按 BM25 公式打分
  3. 返回 Top-K 文档

实现关键点:

  • 分词器选择:中文推荐 jieba 或 HanLP,英文用标准 tokenizer。分词质量直接决定 BM25 的效果。
  • 同义词扩展:对 Query 做同义词扩展能缓解词汇鸿沟问题,比如"性能优化"扩展出"提速、加速、调优"。
  • 与 Elasticsearch 集成:ES 内置 BM25,开箱即用,适合生产环境快速落地。
# BM25 检索核心代码示例(伪代码)fromrank_bm25importBM25Okapiimportjieba# 分词tokenized_corpus=[list(jieba.cut(doc))fordocindoc_chunks]bm25=BM25Okapi(tokenized_corpus)# 检索tokenized_query=list(jieba.cut(user_query))scores=bm25.get_scores(tokenized_query)top_k_indices=scores.argsort()[::-1][:10]

第三路:多 Query 扩展召回

这一路的思路是:用户原始 Query 可能表述不够精确,或者和文档的表述方式对不上,那就让大模型帮忙改写或扩展出多个角度的 Query,分别检索后合并。

基本流程:

  1. 用 LLM 对原始 Query 生成 2-4 个改写/扩展 Query
  2. 每个扩展 Query 分别走向量检索或 BM25 检索
  3. 合并所有检索结果,去重后作为第三路的输出

Prompt 示例:

你是一个搜索查询改写专家。请根据用户的原始问题,生成3个不同角度的搜索查询, 用于在知识库中检索相关文档。要求: 1. 保持核心意图不变 2. 使用不同的表述方式 3. 尝试从不同维度描述问题 原始问题:{user_query} 请输出3个改写后的查询,每行一个。

实现关键点:

  • 扩展数量控制:2-4 个为宜,太多会引入噪声,且增加延迟。
  • 去重策略:多 Query 检索的结果间可能有大量重复,需要按文档 ID 去重。
  • 延迟控制:LLM 改写 Query 有额外耗时(通常 200-500ms),对延迟敏感的场景可以用异步并行或者本地小模型替代。
# 多 Query 扩展召回核心代码示例(伪代码)fromopenaiimportOpenAI client=OpenAI()defexpand_query(original_query:str,num_expansions:int=3)->list:prompt=f"""根据以下原始问题,生成{num_expansions}个不同角度的搜索查询。 保持核心意图不变,使用不同表述方式。 原始问题:{original_query}每行输出一个改写查询:"""response=client.chat.completions.create(model="gpt-4o-mini",messages=[{"role":"user","content":prompt}],temperature=0.7)expanded=response.choices[0].message.content.strip().split('\n')#这里仅作示例因为#LLM可能返回带序号(如“1. xxx”)或空行,简单split可能导致解析错误。实际需更健壮的解析return[original_query]+[q.strip()forqinexpandedifq.strip()]# 每个扩展 Query 分别检索all_results=[]forqueryinexpand_query(user_query):results=vector_search(query,top_k=5)# 或 bm25_searchall_results.extend(results)# 去重seen=set()unique_results=[]forrinall_results:ifr.doc_idnotinseen:seen.add(r.doc_id)unique_results.append(r)

四、结果融合:RRF 算法

三路检索各自返回了 Top-K 结果,问题来了——怎么把这些结果合并成一个统一的排序列表?这就要用到RRF(Reciprocal Rank Fusion,互惠排序融合)算法。

4.1 RRF 核心思想

RRF 的思路非常简洁:不看分数,只看排名。每个文档在每一路中都有一个排名(rank),RRF 根据排名给分,排名越靠前分数越高,最后把各路的分数加起来就是最终得分。

公式:

RRF_Score(d)=∑r∈R1k+rankr(d)\text{RRF\_Score}(d) = \sum_{r \in R} \frac{1}{k + \text{rank}_r(d)}RRF_Score(d)=rRk+rankr(d)1

其中RRR是所有检索路的集合,rankr(d)\text{rank}_r(d)rankr(d)是文档ddd在第rrr路中的排名,kkk是平滑常数(通常取 60)。

三路检索各有各的排序,RRF 根据各路排名加权求和,输出融合排序

4.2 为什么 k=60?

kkk的作用是平滑。如果kkk太小(比如k=1k=1k=1),排名靠前的文档会获得过大的优势,排名稍后的文档几乎没机会;如果kkk太大,各路排名的差异被抹平,融合就失去意义了。k=60k=60k=60是原论文通过实验得出的推荐值,在大多数场景下表现稳定。也可以基于自己的验证集调优

4.3 手动计算示例

假设 Doc_A 在三路检索中的排名分别是:第1名、第2名、第2名。那么它的 RRF 得分为:

RRF_Score(Doc_A)=160+1+160+2+160+2=0.01639+0.01613+0.01613=0.04865\text{RRF\_Score}(\text{Doc\_A}) = \frac{1}{60+1} + \frac{1}{60+2} + \frac{1}{60+2} = 0.01639 + 0.01613 + 0.01613 = 0.04865RRF_Score(Doc_A)=60+11+60+21+60+21=0.01639+0.01613+0.01613=0.04865

而另一个 Doc_E 只在向量检索中排第3,其他路没进 Top-K:

RRF_Score(Doc_E)=160+3+0+0=0.01587\text{RRF\_Score}(\text{Doc\_E}) = \frac{1}{60+3} + 0 + 0 = 0.01587RRF_Score(Doc_E)=60+31+0+0=0.01587

可以看到,RRF 天然偏好那些"多路都靠前"的文档,而不只是某一路特别靠前的。这正是多路召回的核心优势——多方印证,交叉验证

# RRF 融合核心代码示例defrrf_fusion(rankings_list:list[list[str]],k:int=60)->list[tuple[str,float]]:""" rankings_list: 多路检索的排名列表,每个元素是一路检索的doc_id排序列表 k: RRF平滑常数 返回: 按RRF分数降序排列的(doc_id, score)列表 """rrf_scores={}forrankinginrankings_list:forrank,doc_idinenumerate(ranking,start=1):ifdoc_idnotinrrf_scores:rrf_scores[doc_id]=0.0rrf_scores[doc_id]+=1.0/(k+rank)# 按分数降序排列sorted_results=sorted(rrf_scores.items(),key=lambdax:x[1],reverse=True)returnsorted_results# 使用示例vector_ranking=['Doc_A','Doc_C','Doc_E','Doc_B']bm25_ranking=['Doc_B','Doc_A','Doc_D','Doc_F']multi_q_ranking=['Doc_D','Doc_A','Doc_C','Doc_G']final_results=rrf_fusion([vector_ranking,bm25_ranking,multi_q_ranking])fordoc_id,scoreinfinal_results:print(f"{doc_id}:{score:.5f}")

五、实战建议

理论和代码都有了,最后聊聊我在实际项目中踩过的坑和总结的经验。

5.1 先跑通单路,再加多路

不要一上来就搞三路召回。先用向量检索跑通整个 RAG 链路,确认数据质量、Embedding 模型、Prompt 工程这些基础环节没问题,再逐步加 BM25 和多 Query 扩展。多路召回是锦上添花,不是雪中送炭——基础没打好,多路只会放大噪声。

5.2 各路 Top-K 参数要差异化

三路检索不需要都取一样的 Top-K。向量检索通常取 Top-10~20,BM25 取 Top-10(关键词匹配的精度高,不需要太多),多 Query 扩展每路取 Top-5 再合并。总候选池控制在 50-80 个文档以内,太多的话 Reranker 压力大,延迟也上去了。

5.3 Reranker 是多路召回的好搭档

RRF 融合后的排序只是"粗排",精度有限。实际生产中,几乎都会在 RRF 之后再加一个 Cross-Encoder Reranker(如 bge-reranker-v2-m3),对 Top-30 左右的候选做精排。粗排保召回率,精排保准确率,这是标准操作。

5.4 留意延迟和成本

多路召回意味着并行多次检索 + 一次 LLM 调用(Query 扩展),整体延迟会上升。几个优化方向:

  • 向量检索和 BM25 可以并发调用而非串行
  • Query 扩展用小模型或本地模型,避免线上调用大模型的延迟
  • 设置合理的超时时间,某路超时就用其余路的结果

5.5 评估指标盯 Recall

多路召回的优化目标很明确——提升召回率(Recall)。用 Recall@5、Recall@10、Recall@20 这些指标来衡量,不要盯着 MRR 或 NDCG 看,那是 Reranker 的活。建议搭一个评估集,标注 50-100 个 Query 和对应的相关文档,定期跑评估看趋势。


写在最后

多路召回不是什么高深的技术,本质就是"别把鸡蛋放在一个篮子里"。向量检索有语义理解的能力但缺精准度,BM25 有精准匹配的能力但缺语义泛化,多 Query 扩展能弥补表述差异但引入噪声——三路各有所长,融合后互相补位,这才是工程上靠谱的做法。

如果你正在做 RAG 或者搜索相关的项目,强烈建议从两路(向量 + BM25)开始尝试,跑一下对比实验,用数据说话。效果提升大概率会让你觉得值得。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 23:17:12

效率直接起飞!盘点2026年人气爆表的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。以下是2026年最炸裂、实测能大幅提速的AI论文写作工具神器&#xff0c;覆盖全流程生成、文献处理、降重润色、格式排版四大核心场景&#xff0c;帮你高效搞定毕业论文。 一、全流程王者&#xff1a;一站式搞定论文全链路&#xff08…

作者头像 李华
网站建设 2026/6/10 23:15:17

告别Token烧钱焦虑!「秒云Tokens管家」智能预警,筑牢AI成本防线

近期&#xff0c;米哈游一夜间烧掉价值200万元Token、Uber提前耗尽年度AI预算、Meta单月Token消耗成本超亿美元等事件频发引起行业热议&#xff0c;企业盲目追求“Tokenmaxxing"&#xff08;最大化Token使用量&#xff09;却难以将高额消耗转化为实际价值&#xff0c;AI算…

作者头像 李华
网站建设 2026/6/10 23:15:10

新用户第1天 vs 第30天:政策推荐系统的“学习曲线”

一组真实数据。 某政策信息平台&#xff08;政策快报平台&#xff09;的内部统计显示&#xff1a; 新用户注册第1天&#xff1a;推荐政策的点击率约为12% 使用30天后&#xff1a;推荐政策的点击率上升到34% 使用90天后&#xff1a;推荐政策的点击率稳定在38%-42% 点击率从…

作者头像 李华
网站建设 2026/6/10 23:11:44

PLGA包载紫杉醇PTX,PTX@PLGA微球 粒径100nm-200nm

PTXPLGA 微球&#xff08;PLGA 包载紫杉醇微球&#xff09; 以PLGA为可降解载体、紫杉醇 (PTX)为疏水抗肿瘤药物的缓释微球递送体系&#xff0c;多用于肿瘤局部化疗、长效缓释、降低毒副作用&#xff0c;是抗肿瘤微球制剂经典体系。 一、原料基础特性 PLGA 生物相容、体内可…

作者头像 李华
网站建设 2026/6/10 23:10:59

Java常用类

一、Object 类java.lang.Object 是所有类的根父类&#xff0c;所有类默认直接或间接继承它&#xff0c;其中方法所有类均可使用。1. getClass()&#xff1a;返回对象实际类型&#xff0c;可判断两个对象类型是否一致。2. hashCode()&#xff1a;返回对象哈希码&#xff0c;不同…

作者头像 李华
网站建设 2026/6/10 23:10:58

胡彦斌45天手搓APP,程序员慌了:国产Vibe Coding真要抢饭碗?

最近程序员圈子里最炸的消息&#xff0c;不是哪个大模型又更新了&#xff0c;而是一张照片——歌手胡彦斌坐在电脑前&#xff0c;配文“vibe coding的都懂这个姿势&#xff0c;修bug在路上”。他花了一个多月&#xff0c;从零学起&#xff0c;用自然语言跟AI对话&#xff0c;硬…

作者头像 李华