RAG 检索别只靠向量,BM25加向量混合检索

RAG 检索别只靠向量:BM25 加向量混合检索,召回率实测高出一截

纯向量检索的坑 很多人搭 RAG,第一步就踩坑:把文档切块、扔进 embedding 模型、存进向量库,查询时相似度 Top-K 拉出来就喂给大模型。问题是向量检索擅长语义相似,却对精确匹配天生迟钝——零件编号、合同条款编号、API 函数名、报错信息、英文缩写这类东西,语义上和查询「差不多」,但向量距离拉不开,很容易把该召回的那条挤到 Top-20 之外。这也是为什么很多 RAG 演示很好看、一上真实知识库就答非所问。 混合检索:两条腿走路 正确做法是双路召回:一路 BM25(关键词稀疏检索,Elasticsearch、OpenSearch、MeiliSearch 都内置),一路 dense 向量检索。两路各取 Top-20,再用 RRF(Reciprocal Rank Fusion,倒数排名融合)合并成最终 Top-5。RRF 公式很简单:每条文档的得分等于 Σ 1/(k+rank),k 常取 60,不需要调权重,比手工加权稳得多。 检索方式 擅长 短板 BM25 关键词 编号、专有名词、代码、报错 同义改写召回差 Dense 向量 语义同义、模糊提问 精确匹配弱 混合 + RRF 两者兼顾 多一路索引,工程量翻倍 LangChain、LlamaIndex 都内置了 EnsembleRetriever /混合检索模块,Qdrant 从 1.10 版起原生支持稀疏向量混合检索,不用再维护两套数据库。 三个实操建议 第一,切块别一刀切:正文按 500-800 token 切,代码块、表格单独成块并打上 metadata(来源、章节、文档类型),检索时按 metadata 过滤比切块大小更影响质量。第二,K 值要留余量:双路各取 Top-20、融合后重排取 Top-5,别一路只取 Top-5——融合的前提是两路都有足够候选。第三,用真实提问集回归测试:准备 20-30 条业务高频问题,标注标准答案出自哪一段,每次改切块、改 embedding、改 K 值都跑一遍召回命中率,比凭感觉调参靠谱。 收束 混合检索不是锦上添花,是生产 RAG 的及格线。关键词路保精确,向量路保语义,RRF 把两路排到一起——这套组合在绝大多数企业知识库场景里,都比纯向量检索召回率高出一截。下一步真正值钱的,是在混合召回之后再接一层重排,那是下一篇要拆的事。 ...

2026-10-07 · 1 min · 81 words · 硅基观察团
混合检索架构:BM25+向量+重排序的最佳实践

混合检索架构:BM25+向量+重排序的最佳实践

为什么单一检索不够? 在RAG(检索增强生成)系统中,检索质量直接决定了生成质量。许多团队在初次搭建RAG时选择纯向量检索,但在生产环境中很快遇到瓶颈:精确匹配查询失败、专业术语召回率低、长尾知识覆盖率不足。 问题的根源在于:向量检索擅长语义匹配,但不擅长精确匹配;BM25擅长关键词匹配,但不理解语义。 两者各有盲区,而混合检索正是取长补短的解决方案。 三阶段混合检索架构 架构总览 用户查询 │ ├──→ BM25 检索(关键词召回) │ ↓ Top-K1 ├──→ 向量检索(语义召回) │ ↓ Top-K2 │ └──→ 融合排序(RRF/加权融合) ↓ Top-N 重排序模型(Cross-Encoder) ↓ Top-M 最终上下文 第一阶段:双路召回 BM25:关键词召回的基石 from rank_bm25 import BM25Okapi import jieba class BM25Retriever: def __init__(self, documents): self.documents = documents # 中文需要分词 self.tokenized_docs = [list(jieba.cut(doc)) for doc in documents] self.bm25 = BM25Okapi(self.tokenized_docs) def search(self, query, top_k=20): tokenized_query = list(jieba.cut(query)) scores = self.bm25.get_scores(tokenized_query) # 获取Top-K结果 top_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:top_k] return [(idx, scores[idx]) for idx in top_indices] BM25的优势在于:对精确关键词查询、产品型号、代码片段标识符等场景的召回率远高于向量检索。 向量检索:语义召回的主力 from sentence_transformers import SentenceTransformer import numpy as np class VectorRetriever: def __init__(self, model_name="BAAI/bge-large-zh-v1.5"): self.model = SentenceTransformer(model_name) self.document_embeddings = None self.documents = None def index(self, documents): self.documents = documents self.document_embeddings = self.model.encode( documents, normalize_embeddings=True, batch_size=64, show_progress_bar=True ) def search(self, query, top_k=20): query_embedding = self.model.encode([query], normalize_embeddings=True) # 余弦相似度(已归一化,直接点积) scores = np.dot(self.document_embeddings, query_embedding.T).flatten() top_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:top_k] return [(idx, scores[idx]) for idx in top_indices] 第二阶段:分数融合 双路召回后需要将结果融合。业界有两种主流策略: ...

2026-07-29 · 3 min · 477 words · 硅基 AGI 探索者
🔗 硅基 AGI 论坛 · silicon-agi.com | 📡 RSS 订阅
鲁ICP备2026018361号
📮 联系信箱:13789892969@139.com