12 混合检索(BM25 + 向量)
2026/9/14 20:15:59 网站建设 项目流程

混合检索(BM25 + 向量)

为什么纯向量检索不够

纯向量检索(语义检索),它有一个弱点:

查询:"Python 3.11 有什么新特性" 向量检索可能返回:"编程语言的演化历史"(语义相关但没提 3.11) BM25 检索会返回:包含 "Python 3.11" 关键词的文档(精确匹配)

检索方式对比

检索方式优势劣势
向量检索擅长语义相近但用词不同的内容精确关键词匹配弱
BM25(关键词)擅长精确词匹配不理解语义,同义词失效
混合检索两者兼得需要分数融合

混合检索原理

查询 ├── 向量检索 → [doc_A: 0.92, doc_C: 0.85, doc_E: 0.81] └── BM25检索 → [doc_B: 15.3, doc_A: 12.1, doc_D: 9.8] ↓ RRF 融合(Reciprocal Rank Fusion) ↓ 最终排序: [doc_A, doc_B, doc_C, ...]

RRF 公式

RRF 公式(记住思路就行,不用背):

score(doc) = Σ 1 / (k + rank_i)
  • rank_i:该文档在第 i 个检索结果里的名次(第 1 名 rank=1,第 2 名 rank=2…)
  • k:常取 60,用来压低高名次的优势、抬高低名次的贡献,避免「只要某一路排第一就碾压一切」
  • Σ:对每个检索器分别算一项,再相加

例如:向量:A > C > E;BM25:B > A > D;融合时,通常不看原始分数,只看排名。

文档向量项BM25 项总分(约)
doc_A1/(60+1) ≈ 0.01641/(60+2) ≈ 0.0161≈ 0.0325
doc_B1/(60+1) ≈ 0.0164≈ 0.0164
doc_C1/(60+2) ≈ 0.0161≈ 0.0161
doc_D1/(60+3) ≈ 0.0159≈ 0.0159
doc_E1/(60+3) ≈ 0.0159≈ 0.0159

最终排序:doc_A > doc_B > doc_C > doc_D ≈ doc_E

前置概念

rank_bm25 是什么(了解即可)

pipinstallrank_bm25

BM25 是一个关键词打分算法,rank_bm25 是这个算法的 Python 实现包,BM25Retriever 在底层调用了它,故使用BM25Retriever要安装rank_bm25

jieba 是什么

BM25 的工作方式是把句子拆成词再匹配:

英文: "machine learning" → ["machine", "learning"] ← 空格天然分词 中文: "机器学习" → ??? ← 没有空格,不能直接拆字

jieba 是中文分词库,专门解决这个问题:

importjieba jieba.lcut("机器学习是人工智能的子领域")# → ['机器', '学习', '是', '人工智能', '的', '子', '领域']

不加 jieba,中文会被逐字拆开(“机”、“器”、“学”、“习”),匹配效果很差。

LangChain 的实现方式

LangChain 提供了 EnsembleRetriever,直接组合多个检索器:

importjiebafromlangchain.retrieversimportEnsembleRetrieverfromlangchain_community.retrieversimportBM25Retrieverfromlangchain_qdrantimportQdrantVectorStore# BM25 检索器(基于原始文档)bm25_retriever=BM25Retriever.from_documents(chunks,k=3,preprocess_func=jieba.lcut)# bm25_retriever.k = 3# 向量检索器## 方式一:从文档直接创建(内部会建 collection 并写入)vector_store=QdrantVectorStore.from_documents(documents=chunks,embedding=embeddings,location=":memory:",# 或 url="http://localhost:6333"collection_name="my_docs",)# 方式二:连接已有 Qdrant 服务# client = QdrantClient(url="http://localhost:6333")# vector_store = QdrantVectorStore(# client=client,# collection_name="my_docs",# embedding=embeddings,# )# vector_store.add_documents(chunks) # ← 在这里:embed + upsert# 或# vector_store.add_texts(["文本1", "文本2"]) # 只传纯文本也行vector_retriever=vector_store.as_retriever(search_kwargs={"k":3})# 混合ensemble=EnsembleRetriever(retrievers=[bm25_retriever,vector_retriever],weights=[0.5,0.5],# BM25 和向量各占 50%)results=ensemble.invoke("你的查询")

混合检索输出:list[Document]

ensemble_retriever().invoke(query) 返回的是 LangChain 的 Document 对象列表。
每个 Document 的结构:

Document(page_content="文档正文片段...",metadata={"source":"xxx.pdf",# 来源文件"chunk_id":"chunk_001",# 分块 ID})

LangChain 的 Document(page_content + metadata)会被存成 Qdrant 的 point:

对应向量库中的每一条point的结构:

{"id":"uuid-xxx",# 唯一 ID"vector":[0.12,-0.34,...],# embedding 向量(scroll 时 with_vectors=False 不返回)"payload":{# 业务数据,任意 JSON"page_content":"...","metadata":{"source":"xxx...sample.md",# 固定,metadata其他属性可手动添加"doc_type":"pdf","chunk_id":"sample.pdf_p0_c0","page":0}}}

QdrantClient 与 QdrantVectorStore

二者区别

QdrantClientQdrantVectorStore
来源qdrant_client(Qdrant 官方)langchain_qdrant(LangChain 生态)
层级数据库 API 层RAG 业务抽象层
职责连接 Qdrant,直接操作 collection / point把「文本 + Embedding + 检索」串成 LangChain 接口
是否管 Embedding否,只存/取向量是,内置 embedding 模型

QdrantClient

QdrantClient 是连接 Qdrant 向量数据库的原生客户端,相当于 SQL 里的 database driver。

它负责基础设施级操作,例如:

  • 检查:collection_exists()
  • 建表:create_collection(),指定向量维度、距离度量(COSINE)
  • 原始数据读取:scroll() 分页拉取 point 的 payload

不走向量检索,只导出文本做 BM25 索引

QdrantVectorStore

QdrantVectorStore 是 LangChain 的 VectorStore 实现,内部持有 QdrantClient,并额外绑定 Embedding 模型:

它提供面向 RAG 的高层 API:

方法作用
add_documents(docs)自动 embed 文本 → 写入 Qdrant
similarity_search(query)自动 embed query → 向量检索
as_retriever()转成 LangChain Retriever,供 RAG 链路使用

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询