LangChain 检索器完全指南:从向量检索到生产级 RAG 架构
系列前言:本文是 LangChain Expression Language (LCEL) 系列文章的开篇,后续将深入探讨 LCEL 的链式组合与高级用法。系列文章将遵循统一的 Markdown 格式规范,标题层级清晰,代码示例可复现。
目录
一、检索器是什么?
在 RAG(Retrieval-Augmented Generation)架构中,检索器(Retriever)是连接外部知识库与大语言模型的桥梁。它接受一个字符串查询,返回一组相关的 Document 对象。
LangChain 中检索器的核心接口极其简洁:
python
class BaseRetriever(ABC):
@abstractmethod
def _get_relevant_documents(self, query: str) -> List[Document]:
"""根据查询返回相关文档"""
所有检索器都遵循这一契约,这意味着它们可以无缝替换和组合。
二、为什么需要检索器抽象?
2.1 解耦存储与检索
向量存储(VectorStore)负责存储和相似度计算 ,检索器负责检索策略 。通过 as_retriever() 方法,LangChain 将向量库"包装"成标准检索器:
python
from langchain_community.vectorstores import Chroma, FAISS, Qdrant
# 三种不同的向量库,接口完全一致
chroma = Chroma.from_documents(docs, embeddings)
faiss = FAISS.from_documents(docs, embeddings)
# 都通过 as_retriever() 得到统一接口
retriever_chroma = chroma.as_retriever()
retriever_faiss = faiss.as_retriever()
# 上层代码完全不关心底层实现
def build_rag_chain(retriever):
return {"context": retriever, "question": RunnablePassthrough()} | prompt | llm
2.2 装饰器模式的威力
检索器的设计遵循装饰器模式 (Decorator Pattern)------每一层都在底层检索器的基础上增强功能,但对外保持统一的 invoke(query) -> List[Document] 接口:
基础能力:invoke(query) -> docs
+ EnsembleRetriever -> 多路召回
+ MultiQueryRetriever -> 查询扩展
+ ContextualCompression -> 结果压缩
每一层增强功能,接口不变
三、核心检索器类型详解
3.1 向量存储检索器(VectorStoreRetriever)
最基础的语义检索,基于向量相似度:
python
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
vectorstore = Chroma.from_documents(docs, OpenAIEmbeddings())
# 三种检索策略
retriever = vectorstore.as_retriever(
search_type="similarity", # 标准相似度检索
search_kwargs={"k": 4}
)
retriever_mmr = vectorstore.as_retriever(
search_type="mmr", # 最大边际相关性,兼顾多样性
search_kwargs={"k": 4, "lambda_mult": 0.5}
)
retriever_threshold = vectorstore.as_retriever(
search_type="similarity_score_threshold", # 阈值过滤
search_kwargs={"k": 4, "score_threshold": 0.8}
)
3.2 多查询检索器(MultiQueryRetriever)
解决用户查询表述不准确的问题。LLM 生成多个查询变体,分别检索后合并去重:
python
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI
retriever = MultiQueryRetriever.from_llm(
retriever=base_retriever,
llm=ChatOpenAI(temperature=0)
)
# 用户输入:"LangChain 咋用"
# 内部自动扩展为:
# "LangChain 入门教程"
# "LangChain 快速开始指南"
# "LangChain 使用方法"
3.3 上下文压缩检索器(ContextualCompressionRetriever)
解决检索结果冗余问题。先检索,再用 LLM 压缩每个文档,只保留与查询相关的片段:
python
from langchain.retrievers.contextual_compression import ContextualCompressionRetriever
from langchain_community.document_transformers import LLMChainExtractor
compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=base_retriever
)
# 原始文档 1000 字 -> 压缩为 50 字精华
# 只保留与查询相关的片段
3.4 父文档检索器(ParentDocumentRetriever)
解决分块导致上下文丢失的问题。检索子块(用于精准匹配),但返回父文档(保持完整上下文):
python
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
retriever = ParentDocumentRetriever(
vectorstore=child_vectorstore, # 存储小片段向量
docstore=InMemoryStore(), # 存储完整父文档
child_splitter=RecursiveCharacterTextSplitter(chunk_size=200),
parent_splitter=RecursiveCharacterTextSplitter(chunk_size=2000)
)
3.5 集成检索器(EnsembleRetriever)
多路召回 + RRF 重排序。结合多个检索器的结果(如 BM25 + 向量检索),使用 Reciprocal Rank Fusion 算法排序:
python
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
bm25_retriever = BM25Retriever.from_documents(docs)
bm25_retriever.k = 2
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.5, 0.5]
)
3.6 自查询检索器(SelfQueryRetriever)
结构化过滤 + 语义检索。让 LLM 从自然语言查询中提取结构化过滤条件:
python
from langchain.retrievers.self_query.base import SelfQueryRetriever
retriever = SelfQueryRetriever.from_llm(
llm=llm,
vectorstore=vectorstore,
document_content_description="电影简介",
metadata_field_info=[
AttributeInfo(name="genre", description="电影类型", type="string"),
AttributeInfo(name="year", description="上映年份", type="integer"),
AttributeInfo(name="rating", description="评分1-10", type="float"),
]
)
# 查询:"2020年后评分高于8分的科幻电影"
# 自动提取:{"year": {"$gt": 2020}, "rating": {"$gt": 8}, "genre": "科幻"}
四、检索器组合:从单层到多层架构
4.1 单层架构(原型阶段)
python
# 最简单,直接向量检索
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
4.2 双层架构(通用场景)
python
# 多查询扩展 + 向量检索
from langchain.retrievers.multi_query import MultiQueryRetriever
retriever = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(search_kwargs={"k": 6}),
llm=ChatOpenAI(temperature=0)
)
4.3 三层架构(生产推荐)
python
# 多路召回 + 查询扩展
from langchain.retrievers import EnsembleRetriever, MultiQueryRetriever
from langchain_community.retrievers import BM25Retriever
# 语义通道
semantic = vectorstore.as_retriever(search_kwargs={"k": 8})
# 关键词通道
keyword = BM25Retriever.from_documents(documents)
keyword.k = 4
# 融合 + 查询扩展
ensemble = EnsembleRetriever(retrievers=[semantic, keyword], weights=[0.7, 0.3])
retriever = MultiQueryRetriever.from_llm(retriever=ensemble, llm=llm)
4.4 四层架构(极致效果)
python
# 多路召回 + 查询扩展 + 结果压缩
from langchain.retrievers.contextual_compression import ContextualCompressionRetriever
from langchain_community.document_transformers import LLMChainExtractor
# 先构建三层
semantic = vectorstore.as_retriever(search_kwargs={"k": 8})
keyword = BM25Retriever.from_documents(documents)
keyword.k = 4
ensemble = EnsembleRetriever(retrievers=[semantic, keyword], weights=[0.7, 0.3])
multi_query = MultiQueryRetriever.from_llm(retriever=ensemble, llm=llm)
# 再加压缩层
compressor = LLMChainExtractor.from_llm(llm)
final_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=multi_query
)
五、生产级检索器构建实践
5.1 完整可复现代码
python
from langchain.retrievers import (
EnsembleRetriever,
MultiQueryRetriever,
ContextualCompressionRetriever,
ParentDocumentRetriever
)
from langchain_community.retrievers import BM25Retriever
from langchain_community.document_transformers import LLMChainExtractor
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.storage import InMemoryStore
from langchain.text_splitter import RecursiveCharacterTextSplitter
def build_production_retriever(documents, llm=None):
"""
构建生产级检索器: 四层架构
- 语义检索 + 关键词检索(多路召回)
- 查询扩展(MultiQuery)
- 结果压缩(Compression)
"""
if llm is None:
llm = ChatOpenAI(temperature=0)
# ========== 第1层: 语义通道 ==========
# 向量相似度, 找"意思相近"的
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(documents, embeddings)
semantic = vectorstore.as_retriever(search_kwargs={"k": 8})
# ========== 第2层: 关键词通道 ==========
# BM25 字面匹配, 找"词对上了"的
keyword = BM25Retriever.from_documents(documents)
keyword.k = 4
# ========== 第3层: 多路融合 ==========
# 两个通道结果合并, RRF 算法排序
ensemble = EnsembleRetriever(
retrievers=[semantic, keyword],
weights=[0.7, 0.3]
)
# ========== 第4层: 查询扩展 ==========
# 用户口语化 -> LLM 改写为多个专业查询
multi_query = MultiQueryRetriever.from_llm(
retriever=ensemble,
llm=llm
)
# ========== 第5层: 结果压缩 ==========
# 每篇文档让 LLM 压缩, 只保留相关片段
compressor = LLMChainExtractor.from_llm(llm)
final = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=multi_query
)
return final
# ========== 使用示例 ==========
from langchain_core.documents import Document
# 准备文档
documents = [
Document(page_content="LangChain Expression Language (LCEL) 允许用 | 符号连接组件...", metadata={"source": "docs1"}),
Document(page_content="LCEL 是 LangChain 的声明式语法, 用于组合链...", metadata={"source": "docs2"}),
# ... 更多文档
]
# 构建检索器
llm = ChatOpenAI(temperature=0)
retriever = build_production_retriever(documents, llm)
# 调用(自动走完所有层)
docs = retriever.invoke("LangChain 怎么链式调用?")
5.2 调用链内部流程
用户输入: "LangChain 怎么链式调用?"
|
v
[ContextualCompressionRetriever]
|-- 调用 base_retriever.invoke()
| |
| v
| [MultiQueryRetriever]
| |-- LLM 改写为 ["LCEL 用法", "链式调用教程", "pipe 操作符"]
| |
| |-- 调用 retriever.invoke("LCEL 用法")
| | |
| | v
| | [EnsembleRetriever]
| | |-- semantic.invoke("LCEL 用法") -> 向量库查
| | |-- keyword.invoke("LCEL 用法") -> BM25查
| | |-- RRF 融合返回
| |
| |-- 调用 retriever.invoke("链式调用教程")
| | |-- [EnsembleRetriever] 同样流程...
| |
| |-- 调用 retriever.invoke("pipe 操作符")
| | |-- [EnsembleRetriever] 同样流程...
| |
| |-- 合并 3 次结果, 去重
|
|-- 对每篇文档调用 LLM 压缩
| "这篇跟'链式调用'有关吗? 提取相关片段"
|
|-- 返回压缩后的精华文档
六、性能与成本的权衡
| 组合层级 | 召回率 | 精准度 | LLM 调用次数 | 适用场景 |
|---|---|---|---|---|
| 裸 VectorStore | 中 | 低 | 0 | 原型/测试 |
| + MultiQuery | 高 | 中 | 1次/查询 | 通用首选 |
| + Compression | 中 | 高 | 1次/文档 | 长文档问答 |
| + ParentDocument | 高 | 高 | 0 | 结构化长文档 |
| Ensemble + 多路 | 高 | 中 | 0 | 术语密集型 |
| 全套组合 | 高 | 高 | 2-3次/查询 | 高价值场景 |
6.1 成本优化策略
python
# 策略1: 去掉 Compression(最省成本)
retriever = MultiQueryRetriever.from_llm(
retriever=EnsembleRetriever(
retrievers=[semantic, keyword],
weights=[0.7, 0.3]
),
llm=llm
)
# 策略2: 减少 MultiQuery 生成数量
retriever = MultiQueryRetriever.from_llm(
retriever=base_retriever,
llm=llm,
parser_key="lines", # 只生成3个查询
)
# 策略3: 用 ParentDocument 替代 Compression(零 LLM 成本)
retriever = ParentDocumentRetriever(
vectorstore=child_vectorstore,
docstore=parent_docstore,
child_splitter=RecursiveCharacterTextSplitter(chunk_size=200),
parent_splitter=RecursiveCharacterTextSplitter(chunk_size=2000)
)
七、总结与选型指南
7.1 决策树
你的检索失败属于哪种情况?
|
|-- 查询表述太口语化/模糊?
| |-- 加 MultiQueryRetriever
|
|-- 检索结果太长、夹杂无关内容?
| |-- 加 ContextualCompressionRetriever
|
|-- 分块后语义断裂?(代码、表格跨块)
| |-- 用 ParentDocumentRetriever
|
|-- 需要精确匹配(ID、术语、人名)?
| |-- 加 BM25, 做 EnsembleRetriever
|
|-- 有结构化元数据(时间、类别、价格)?
| |-- 用 SelfQueryRetriever
|
|-- 知识时效性强?(新闻、政策)
| |-- 用 TimeWeightedVectorStoreRetriever
|
|-- 以上多种情况并存?
|-- 叠加组合, 从简单开始逐步增强
7.2 核心原则
- 先上监控: 没有评估指标(如 Ragas)谈优化是盲目的
- 从简单开始: 裸 VectorStore -> 加 MultiQuery -> 看数据加其他
- 组合不是越多越好: 每加一层就多一次延迟和成本, 只加能解决实际问题的层
- ParentDocument 和 Ensemble 是零成本增强(不额外调 LLM), 优先尝试
7.3 在 LCEL 中使用
python
from langchain_core.runnables import RunnablePassthrough
from langchain_core.prompts import ChatPromptTemplate
template = """基于以下上下文回答问题:
{context}
问题: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
# 构建 RAG 链
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| ChatOpenAI()
)
# 直接调用, 自动完成检索+生成
response = rag_chain.invoke("什么是 LCEL?")
结语
LangChain 的检索器设计遵循单一职责原则 和可组合性。理解每一层的能力边界和组合方式, 是构建生产级 RAG 系统的关键。没有银弹, 但有套路------从简单开始, 用数据驱动优化。