1. 为什么"单路召回"不够用了,以及混合架构到底在混什么
先说一个我上周刚经历的场景:线上有个知识库问答系统,用户问了一句"上次说的那个能自动生成报表的工具,后来怎么部署的?"——这句话里没有产品名、没有版本号、也没有标准术语,全是口语化指代。团队里负责维护的同学当时就愣住了,因为这玩意儿用纯关键词压根召回不到,用向量召回呢,语义是够近了,但"自动生成报表"和"部署"这两个概念被稠密向量揉成了一个语义点,召回结果里排前面的全是"报表工具怎么选""报表生成原理",真正想找的那篇部署文档被挤到了二十名开外。
这就是典型的单路召回瓶颈:稀疏检索懂精确匹配但不懂语义,稠密向量懂语义但容易把关键限定词"揉碎",而图关系能捕捉实体之间的连接,但单独使用又对长尾表达无能为力。混合架构这个词在工程圈喊了三四年,真正落地的难点从来不是"把三路结果合并到一起",而是"合并之后怎么保证延迟还在毫秒级,精度还不掉"。
这篇文章基于我在一个真实检索系统上的改造过程,梳理一套可以抄作业的工程层方案。核心指标先说清楚:改造前单路稠密向量召回,TP99延迟约180ms,召回率(Recall@20)在87%左右;改造后采用稠密向量+稀疏检索+图关系三路混合,TP99压到98ms,Recall@20做到96%,线上A/B实验里用户点击率提升了约9个百分点。
这套架构没有用上亿参数的重排序模型,也没有上GPU推理集群,纯粹靠工程层的设计把三路召回组织起来。适合的场景是:已有一套基础向量检索系统,想在不动模型、不动数据底座的条件下,通过工程架构调整把召回质量再往上顶一截的团队。下文所有方案、参数、踩坑记录都可以直接复现,不涉及商业闭源组件。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 架构全景:三路召回不是"都要有",而是"各干各的活"
2.1 三路各自的定位,以及为什么不能互相替代
先建立一个基本共识:稠密向量、稀疏检索、图关系,这三者在信息检索里对应的能力维度完全正交。
- 稠密向量(Dense Retrieval):解决"语义相近但字面不同"的问题。比如"怎么让电脑自己写周报"和"自动化生成工作汇报",字面没有公共词,但语义上是一回事。模型把文本映射到高维向量空间,空间距离近就代表语义近。工程上常见的有BERT系做embedding,也有更轻量的双塔模型。
- 稀疏检索(Sparse Retrieval):解决"精确匹配和关键词限定"的问题。它没有语义泛化能力,但擅长处理专有名词、型号、代码片段、人名地名这些"必须一个字都不能错"的信息。传统的BM25是代表,工程上也有可学习的稀疏表示,比如SPLADE,但落地最多的还是BM25家族的变体。
- 图关系(Graph Relation):解决"实体之间的关联路径"问题。比如"A文档里提到的方案在B文档里有完整实现"——这种跨文档的引用关系、共现关系、类别归属关系,是向量和关键词都很难直接表达的。图关系依赖一个预先构建的知识图谱或文档关系图,检索时从种子实体出发做多跳扩展。
这三路各有不可替代的能力边界。混合架构的核心思路不是什么"三个都要所以更牛",而是让每一路负责自己最擅长的召回子集,最后在合并阶段互相补漏。
2.2 整体链路:召回、粗排、精排,谁负责什么
工程层的混合架构,一定不是"三路各自召回一堆结果,然后全部堆在一起让下游排序"。那样在数据量大的时候,合并列表动辄几百条,排序成本和下游过滤压力根本扛不住。我最终落地的链路分四段:
code复制查询解析(Query Understanding)
→ 三路并行召回(Dense / Sparse / Graph)
→ 合并与粗排(Merge & Coarse Ranking)
→ 精排与过滤(Fine Ranking & Filter)
每一段都有明确职责。查询解析负责判断"这个query适合走哪几路",不是所有query都值得三路全跑——比如纯关键词查询"MySQL 索引失效",稀疏检索基本够用;而口语化长句就必须稠密向量主抓。召回阶段三路各自返回Top N;合并阶段做去重、加权、混合排序,产出候选集(一般是50~100条);精排阶段用更精确的打分模型或规则对候选集做最终排序,输出Top 20给业务方。
这套链路的关键在"查询路由+加权合并"这两个环节,后面核心实现部分会重点展开。
3. 工程层核心实现:三路召回的具体落地方式
3.1 稠密向量路:模型选型与索引参数
稠密向量路在这套架构里是基础盘,承担语义召回的基本盘。我选用的方案是 bge-large-zh-v1.5(中文场景),embedding维度是1024,如果是英文场景可以用E5或GTR系列。选它的原因不光是效果,更多是它的向量分布相对均匀,不需要做额外的归一化预处理,对后续多路分数融合友好。
索引层面用的是 FAISS的IVF-PQ配置,不是HNSW。原因后面延迟优化部分会细说。核心参数:
nlist = 4096:聚类中心数量,这个值大约是文档量(约600万条)的1/1500左右。nprobe = 32:查询时探测的聚类数,这个值直接影响召回质量和延迟的平衡。dim = 1024:向量维度。- 存储格式是PQ128,压缩后单条向量约128字节,600万条向量总索引约768MB,全部常驻内存,无压力。
查询时只做内积相似度计算,取Top 50作为稠密路召回结果。这里的Top 50不是随便拍的——后面合并阶段需要给其他两路留出融合空间,如果单路只取Top 20,合并后可能某些路贡献的长尾结果根本进不了最终列表。
3.2 稀疏检索路:BM25的工程化改造
稀疏检索使用基于 Elasticsearch 的BM25实现,但做了两个改造:
改造一:字段加权。 原始BM25对所有字段一视同仁,但这在文档检索里明显不合理——标题里出现关键词和正文里出现关键词,重要性差一
