下面给你一个完整的、可直接运行的 Retriever Demo,涵盖了 4 种最常用的 Retriever 类型,并配有详细的注释和输出示例。你可以把它当作面试时的“武器库”来使用。


📦 环境准备

pip install langchain langchain-community chromadb rank_bm25 sentence-transformers openai

🚀 完整 Demo 代码

import os
from langchain_core.documents import Document
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.retrievers import BM25Retriever, MultiQueryRetriever, EnsembleRetriever
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain.retrievers.ensemble import EnsembleRetriever
from langchain_core.retrievers import BaseRetriever
from langchain_openai import ChatOpenAI

# ============================================================
# 1. 准备测试数据(10 篇关于宠物的文档)
# ============================================================
docs = [
    Document(page_content="猫是独立而优雅的宠物,喜欢清洁和安静的环境。", metadata={"source": "cat_article"}),
    Document(page_content="狗是人类最忠实的朋友,需要大量的运动和社交。", metadata={"source": "dog_article"}),
    Document(page_content="金鱼是常见的观赏鱼,适合在室内鱼缸中饲养。", metadata={"source": "fish_article"}),
    Document(page_content="仓鼠是小型啮齿动物,夜间活跃,适合城市家庭。", metadata={"source": "hamster_article"}),
    Document(page_content="鹦鹉是非常聪明的鸟类,能模仿人类语言。", metadata={"source": "parrot_article"}),
    Document(page_content="兔子是温顺的食草动物,需要宽敞的笼子和新鲜蔬菜。", metadata={"source": "rabbit_article"}),
    Document(page_content="乌龟是长寿的爬行动物,适应力强,但需要特定的温度和光照。", metadata={"source": "turtle_article"}),
    Document(page_content="猫和狗可以和平共处,但需要适当的引导和训练。", metadata={"source": "pet_guide"}),
    Document(page_content="饲养宠物需要考虑空间、时间和经济成本。", metadata={"source": "pet_care"}),
    Document(page_content="宠物可以缓解压力,提高生活质量。", metadata={"source": "pet_benefits"}),
]

# ============================================================
# 2. Retriever 类型 1:基于向量库的检索(最常用)
# ============================================================
print("=" * 50)
print("1. VectorStoreRetriever(向量检索)")
print("=" * 50)

# 使用轻量级中文嵌入模型
embeddings = HuggingFaceEmbeddings(
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
)

# 创建向量库
vectorstore = Chroma.from_documents(
    documents=docs,
    embedding=embeddings,
    collection_name="pet_demo"
)

# 转换成 Retriever
vector_retriever = vectorstore.as_retriever(
    search_kwargs={"k": 3}  # 返回 Top-3
)

query = "适合家庭养的宠物有哪些?"
result = vector_retriever.invoke(query)
print(f"查询:{query}")
for i, doc in enumerate(result):
    print(f"  {i+1}. {doc.page_content} (来源: {doc.metadata['source']})")

# ============================================================
# 3. Retriever 类型 2:BM25 关键词检索
# ============================================================
print("\n" + "=" * 50)
print("2. BM25Retriever(关键词检索)")
print("=" * 50)

bm25_retriever = BM25Retriever.from_documents(docs, k=3)
result = bm25_retriever.invoke("猫 狗")
print(f"查询:{'猫 狗'}")
for i, doc in enumerate(result):
    print(f"  {i+1}. {doc.page_content}")

# ============================================================
# 4. Retriever 类型 3:MultiQueryRetriever(多查询增强)
# ============================================================
print("\n" + "=" * 50)
print("3. MultiQueryRetriever(多角度生成查询)")
print("=" * 50)

# 需要 LLM 来生成多个不同表述的查询
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)  # 可换成通义千问等
multi_retriever = MultiQueryRetriever.from_llm(
    retriever=vectorstore.as_retriever(k=3),
    llm=llm,
    include_original=True  # 包含原始查询
)

result = multi_retriever.invoke("哪种宠物好养又可爱?")
print(f"查询:哪种宠物好养又可爱?")
# MultiQueryRetriever 不保证结果数量固定,这里打印所有结果
for i, doc in enumerate(result[:5]):
    print(f"  {i+1}. {doc.page_content}")

# ============================================================
# 5. Retriever 类型 4:EnsembleRetriever(混合检索)
# ============================================================
print("\n" + "=" * 50)
print("4. EnsembleRetriever(混合检索:BM25 + 向量)")
print("=" * 50)

ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.5, 0.5]  # 两者权重相等
)

result = ensemble_retriever.invoke("宠物 陪伴 减压")
print(f"查询:宠物 陪伴 减压")
for i, doc in enumerate(result):
    print(f"  {i+1}. {doc.page_content}")

# ============================================================
# 6. 自定义 Retriever(演示扩展能力)
# ============================================================
print("\n" + "=" * 50)
print("5. 自定义 Retriever(按文档长度过滤)")
print("=" * 50)

class LengthFilterRetriever(BaseRetriever):
    """只返回长度小于 30 个字符的文档(演示自定义逻辑)"""
    retriever: BaseRetriever
    max_len: int = 30

    def _get_relevant_documents(self, query: str) -> list[Document]:
        docs = self.retriever.invoke(query)
        return [d for d in docs if len(d.page_content) <= self.max_len]

    async def _aget_relevant_documents(self, query: str) -> list[Document]:
        docs = await self.retriever.ainvoke(query)
        return [d for d in docs if len(d.page_content) <= self.max_len]

custom_retriever = LengthFilterRetriever(
    retriever=vector_retriever,
    max_len=30
)

result = custom_retriever.invoke("宠物")
print(f"查询:宠物(仅返回长度 <= 30 个字符的文档)")
for i, doc in enumerate(result):
    print(f"  {i+1}. {doc.page_content} (长度: {len(doc.page_content)})")

print("\n✅ Demo 执行完毕!")

📤 预期输出(部分节选)

==================================================
1. VectorStoreRetriever(向量检索)
==================================================
查询:适合家庭养的宠物有哪些?
  1. 仓鼠是小型啮齿动物,夜间活跃,适合城市家庭。 (来源: hamster_article)
  2. 兔子是温顺的食草动物,需要宽敞的笼子和新鲜蔬菜。 (来源: rabbit_article)
  3. 宠物可以缓解压力,提高生活质量。 (来源: pet_benefits)

==================================================
2. BM25Retriever(关键词检索)
==================================================
查询:猫 狗
  1. 猫是独立而优雅的宠物,喜欢清洁和安静的环境。
  2. 狗是人类最忠实的朋友,需要大量的运动和社交。
  3. 猫和狗可以和平共处,但需要适当的引导和训练。

...

🎯 面试时如何讲解这个 Demo

Retriever 类型 一句话讲清楚 适用场景
VectorStoreRetriever 用向量相似度找语义相关的文档 通用 RAG 标配
BM25Retriever 用关键词精确匹配 产品名、代码等精确检索
MultiQueryRetriever 让 LLM 从多角度生成查询,提高召回率 用户表述模糊时
EnsembleRetriever 混合关键词和向量检索,取长补短 生产环境推荐
自定义 Retriever 继承 BaseRetriever 实现业务逻辑 有特殊过滤需求的场景

面试加分句:“在生产环境中,我通常采用 EnsembleRetriever 组合 BM25 和向量检索,因为用户既可能输入口语化的描述,也可能输入精确的专有名词,混合检索能保证两种情况的召回率。同时我会配置一个 Reranker 对结果重排序,进一步优化相关性。”

你可以直接复制上述代码到本地运行(需要配置 OPENAI_API_KEY 或替换成通义千问),亲身体验每种 Retriever 的效果差异。如果遇到任何问题,欢迎继续提问!

📖 什么是 BM25?

在深入面试题之前,我们首先需要理解 BM25 是什么。BM25(Best Matching 25)是一种词袋检索模型,用于信息检索系统,评估文档与给定搜索查询的相关性。它基于词频(TF)和逆文档频率(IDF)等统计信息进行打分,是 TF-IDF 的进化版:它根据查询词项在文档中出现的频率进行打分,同时对那些在整个语料库中过于常见的词进行惩罚。

其核心思想是:如果一篇文档包含了用户查询中的关键词,且这些词在其他文档中不常见,那么这篇文档的相关性就越高


⚔️ BM25 与向量检索:差异与互补

这是面试中的核心问题,理解两者的差异是回答所有后续问题的基础。

特性 BM25 (稀疏检索) 向量检索 (密集检索)
检索方式 基于关键词匹配和词频统计。 基于语义相似度,将文本映射为向量。
擅长处理 专有名词、ID、代码函数、产品型号等精确词汇。 同义词、语义相近但词汇不同的查询。
优点 精确匹配能力强,对稀有词汇敏感,可解释性强。 泛化能力强,能理解用户意图,处理自然语言。
缺点 无法理解同义词,对“一词多义”场景无能为力。 对精确的专有名词不敏感,可能召回语义相关但非精确的内容。
典型场景 用户搜索“ERR_CONN_RESET_4XX”等具体错误码。 用户搜索“如何解决网络连接问题”等意图类问题。

面试金句:向量检索懂“语义”,BM25 懂“字词”。两者是互补关系,而非替代关系。


🤔 为什么真实 RAG 系统通常需要两者?

面试官常会问:“为什么有的项目用 BM25,有的用 Embedding,还有的非要上混合检索?”。

答案是:没有一个检索器是万能的

  • 只用向量检索:当用户查询一个只在文档中出现过一次的专有名词(如“重排序”)时,仅靠语义相似度可能无法召回该文档。
  • 只用 BM25:当用户查询“如何让应用响应更快”时,它无法匹配到内容为“性能优化”的文档,因为缺少共同的关键词。

混合检索(Hybrid Search)正是为了解决这个矛盾。它在生产环境中的效果往往优于仅使用密集检索的系统,通过结合 BM25 的关键词匹配能力和向量检索的语义理解能力,实现 1+1 > 2 的效果。


💻 BM25Retriever 实战:LangChain 中的用法

在 LangChain 中,BM25Retriever 是一个开箱即用的组件。

1. 安装

pip install rank_bm25

2. 基本使用

from langchain_community.retrievers import BM25Retriever
from langchain_core.documents import Document

# 从文档列表创建检索器
docs = [
    Document(page_content="猫是独立而优雅的宠物。"),
    Document(page_content="狗是人类最忠实的朋友。"),
]
retriever = BM25Retriever.from_documents(docs, k=2)

3. 检索

results = retriever.invoke("狗")
# 输出: [Document(page_content='狗是人类最忠实的朋友。'), ...]

4. 自定义分词

为了更好的效果,可以传入自定义的分词函数。

import nltk
from nltk.tokenize import word_tokenize

retriever = BM25Retriever.from_documents(
    docs,
    preprocess_func=word_tokenize  # 使用 NLTK 分词
)

5. 使用 BM25Plus 变体

对于短文本或分块文档,BM25Plus 变体能有效提升召回率。

retriever = BM25Retriever.from_documents(
    docs,
    bm25_variant="plus",  # 启用 BM25Plus
    bm25_params={"delta": 0.5}  # 可调参数
)

🎯 高频面试题与解答

1. 为什么在 RAG 系统中要引入 BM25?

参考答案

引入 BM25 是为了弥补向量检索在精确关键词匹配上的不足。向量检索擅长理解语义,但当用户查询的是精确的专有名词、产品型号或代码函数时,它可能会召回语义相关但不包含精确关键词的内容。BM25 通过词频统计,能精准地捕获这些关键词,两者结合能显著提升系统的召回率。

2. 混合检索的流程是什么?BM25 和向量检索的结果如何融合?

参考答案

典型的混合检索流程是:

  1. 多路召回:分别使用 BM25 检索器和向量检索器对同一查询进行检索,各自得到一组候选文档。
  2. 结果融合:使用 RRF(倒数排名融合) 算法将两路结果合并。RRF 通过公式 RRF(d) = Σ 1/(k + rank_i(d)) 计算文档的最终得分,能有效平衡不同检索器的排名。
  3. 重排序(可选):对融合后的 Top-N 结果,使用一个更强大的模型(如 Cohere Rerank)进行重排序,进一步提升顶部结果的相关性。
3. 如何确定混合检索中 BM25 和向量检索的权重?

参考答案

权重的设置没有固定公式,通常需要根据具体业务场景和数据特性进行实验调优。可以从 1:1 的比例(或使用 RRF) 开始作为基线。然后,通过评估指标(如 Recall@k)在验证集上进行迭代优化。此外,也可以设计一套动态权重策略,例如,当检测到查询中包含明显的专有名词时,自动提升 BM25 的权重。

4. BM25 有什么局限性?

参考答案

BM25 的主要局限在于它是词袋模型,完全基于词汇匹配,无法理解同义词或语义。当查询和文档使用不同词汇表达相同含义时,它会失效。此外,当知识库规模增大(例如超过 1000 个条目)时,其效率会显著下降。在超大规模检索场景下,近似最近邻(ANN)的密集检索在效率上更具优势。


💎 总结

BM25Retriever 是 LangChain 生态中实现关键词检索的核心组件,是构建高性能 RAG 系统不可或缺的基石。

面试时,你需要清晰地阐述其基于词频统计的原理,准确说明它与向量检索的优势互补关系,并能够结合 RRF 等算法说明混合检索的具体实现流程。展示你对检索质量如何影响生成质量的深刻理解,将是你从众多候选人中脱颖而出的关键。
“重排(Rerank)”是 RAG 系统中介于“初筛检索”和“大模型生成”之间的一个“精筛”环节。如果说向量检索是“海选”,那重排就是“决赛圈”。

为了让你在面试或实战中都能讲透,我从场景痛点、核心原理、实战价值三个维度来拆解。


1. 为什么需要重排?(海选的局限性)

向量检索(如 Chroma、Milvus)本质是近似最近邻(ANN)搜索。它为了追求“快”,牺牲了一定的“准”。

  • 海选(向量检索):用几百维的向量在亿级数据里快速捞出 100 个“可能相关”的文档。它追求的是召回率(Recall),但排序往往不准。
  • 痛点:最相关的那个文档可能在初筛里只排第 50 名,如果直接把 Top 3 送给 LLM,可能就漏掉了正确答案。

2. 重排(Rerank)的核心原理:精准打分

重排器(Reranker)是一个交叉编码器(Cross-Encoder),而不是双编码器(Bi-Encoder)。

对比维度 向量检索(双编码器) 重排器(交叉编码器)
处理方式 Query 和 Document 独立编码成向量,最后算余弦相似度。 把 Query 和 Document 拼接在一起,放进模型里做联合注意力计算。
信息交互 (只有最后的向量点积交互)。 (在 Transformer 底层就开始互相看对方的词)。
速度 极快(毫秒级,适合海量数据)。 极慢(秒级,只能处理几百条数据)。
精度 较低(语义丢失)。 极高(能理解复杂的逻辑关系和细微差别)。

打个比方

向量检索像 HR 根据关键词筛简历,只看学校和专业名称(独立判断),一秒能看几百份。
重排像部门主管亲自看简历,会结合项目经历和岗位需求做综合判断(联合交互),但看一份要 1 分钟,只能挑最像的 10 份来看。


3. 重排在 RAG 流水线中的位置(关键步骤)

用户提问
   ↓
1. 向量检索(海选)—— 从 100w 条数据中捞出 Top-50(快)
   ↓
2. 重排器(精筛)—— 对这 50 条进行交叉编码重新打分,排出 Top-5(准)
   ↓
3. 注入 Prompt + LLM 生成最终答案

最佳实践:海选时 Top-K 设大一点(如 50),保证召回;重排后只取前 3~5 个送给 LLM,既保证精度,又不浪费 Token。


4. 重排的实战价值(能带来什么好处?)

  • 显著提升命中率(Hit Rate):在很多公开数据集中,重排能将 Top-20 的命中率提升 10%~20%
  • 降低幻觉:送进 LLM 的上下文质量更高,模型“胡编”的概率自然下降。
  • 可解释性:重排器会输出具体的相关性分数,你可以用这个分数来设置阈值,过滤掉低于 0.5 的低质量片段。

5. 如何配置重排?(在 Dify 中的实操)

如果你在用 Dify:

  1. 知识库设置 → 检索模式选 “混合检索”
  2. 开启“重排序”开关 → 选择一个 Rerank 模型(如 BAAI/bge-reranker-v2-m3 或 Cohere 的 Rerank 模型)。
  3. 设置 Top-K:向量检索拉回 30 条,重排后只取 Top-5 送入 LLM。

注意:使用重排器是需要额外消耗 Token 或 API 费用的,且会带来几百毫秒的延迟。但对于企业级知识库问答,这点代价换来的精度提升是值得的。


6. 面试话术(如何回答显得你是老手)

“在 RAG 系统中,初筛(Retrieval)决定‘有没有’,重排(Rerank)决定‘好不好’。向量检索追求高召回,但排序噪音大;我会利用重排器的交叉编码能力,对初筛的 Top-50 结果进行精准的语义精排。这能有效解决‘答案在库里但没送到 LLM 面前’的问题,是提升 RAG 落地效果性价比最高的一环。在 Dify 等平台中,我也会结合 Rerank 模型来配置最终的生产链路。”

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐