引言

在前面的介绍中说明了RAG系统从原理到核心环节的全拆解:从文档加载、文本分块,到文本向量化、向量存储,已经搭建起了RAG系统的完整基础链路。但想要让RAG系统在生产环境中达到可用的效果,还需要解决检索精准度、结果多样性、上下文连贯性、多工具协同等一系列问题。

LangChain 检索器(Retrievers):从基础检索到高级策略

检索器(Retrievers)是LangChain中对检索能力的统一抽象,它的核心职责是:接收用户的自然语言查询,返回与查询语义最相关的文档块。

向量数据库的similarity_search是最基础的检索能力,而LangChain的检索器在其基础上,封装了大量高级检索策略,解决基础检索的常见痛点:比如用户查询表述模糊、检索结果冗余、上下文信息分散、检索结果与问题无关等。

检索器的核心定位与基础用法

所有检索器都实现了统一的BaseRetriever接口,核心方法是invoke(query: str),输入用户查询,返回相关的Document列表。

最基础的检索器,可直接通过向量数据库的as_retriever()方法生成,它封装了向量库的基础检索能力,同时支持配置检索参数:

from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings

# 加载已持久化的向量库
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
db = Chroma(
    persist_directory="./chroma_db",
    embedding_function=embeddings,
    collection_name="enterprise_knowledge_base",
)

# 从向量库生成基础检索器
retriever = db.as_retriever(
    search_type="similarity",  # 检索类型
    search_kwargs={"k": 4},  # 检索参数,返回top4相关文档
)

# 执行检索
query = "RAG系统的核心流程是什么?"
docs = retriever.invoke(query)

# 查看结果
print(f"检索到文档数量:{len(docs)}")
for doc in docs:
    print(doc.page_content)

as_retriever()支持4种基础检索类型,适配不同的基础场景:

检索类型

核心能力

核心参数

适用场景

similarity

基础语义相似度检索,默认类型

k:返回的文档数量

绝大多数通用场景

similarity_score_threshold

相似度阈值检索,仅返回超过阈值的文档

score_threshold:相似度阈值,0-1之间,越接近1越严格

避免返回低相关的无效文档,提升回答准确率

mmr

最大边际相关性检索,平衡结果的相关性与多样性

k:返回文档数量;lambda_mult:多样性权重,0=最大多样性,1=最小多样性

避免检索结果高度重复,覆盖更多相关维度

mmr_score_threshold

结合MMR与相似度阈值,同时控制相关性与多样性

上述两者参数结合

对结果多样性和精准度都有要求的场景

示例:相似度阈值检索,过滤低相关内容

# 仅返回相似度超过0.7的文档,避免无效内容进入Prompt
retriever = db.as_retriever(
    search_type="similarity_score_threshold",
    search_kwargs={"k": 4, "score_threshold": 0.7},
)
docs = retriever.invoke("今天天气怎么样?")  # 知识库中无相关内容,返回空列表

主流高级检索策略详解与实战

基础检索只能解决简单场景的问题,面对复杂的企业级场景,我们需要用到LangChain提供的高级检索器,这里详解生产环境中最常用的5种高级检索策略,解决RAG系统的核心痛点。

MultiQueryRetriever:解决用户查询表述歧义问题

基础检索的核心痛点之一:用户的查询表述可能模糊、口语化、有歧义,直接用原始查询检索,往往无法匹配到知识库中最相关的内容。

MultiQueryRetriever的核心思路是:调用LLM将用户的原始查询,改写为多个不同表述、不同维度的查询语句,用多个查询同时检索,再合并去重结果,大幅提升召回率

import os

from langchain_classic.retrievers import MultiQueryRetriever
from langchain_community.chat_models import ChatOpenAI
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import Chroma

# 加载向量库与LLM
embeddings = DashScopeEmbeddings(model="text-embedding-v4",
                        dashscope_api_key=os.getenv("DASHSCOPE_API_KEY")
                        )
db = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
llm =  ChatOpenAI(model="qwen-plus",
                        api_key=os.getenv("DASHSCOPE_API_KEY"),
                        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
                        )

# 初始化多查询检索器
retriever = MultiQueryRetriever.from_llm(
    retriever=db.as_retriever(search_kwargs={"k": 3}),  # 基础检索器
    llm=llm,  # 用于改写查询的LLM
    include_original=True,  # 是否保留原始查询
)

# 执行检索
query = "RAG的核心优势?"
docs = retriever.invoke(query)
# 查看结果
print(f"多查询检索到的文档数量:{len(docs)}")
for doc in docs:
    print(doc.page_content[:100])

通过大模型改写的原始提示词

DEFAULT_QUERY_PROMPT = PromptTemplate(
    input_variables=["question"],
    template="""You are an AI language model assistant. Your task is
    to generate 3 different versions of the given user
    question to retrieve relevant documents from a vector  database.
    By generating multiple perspectives on the user question,
    your goal is to help the user overcome some of the limitations
    of distance-based similarity search. Provide these alternative
    questions separated by newlines. Original question: {question}""",
)
ContextualCompressionRetriever:解决检索结果冗余问题

基础检索返回的文档块中,往往只有一小部分内容与用户的问题相关,大量无关内容会占用Token,同时干扰LLM的推理。

ContextualCompressionRetriever的核心思路是:先基础检索得到相关文档块,再调用LLM/压缩器,对每个文档块进行压缩、提取,仅保留与用户问题直接相关的内容,过滤无效信息

import os

from langchain_classic.retrievers import ContextualCompressionRetriever
from langchain_classic.retrievers.document_compressors import LLMChainExtractor
from langchain_community.chat_models import ChatOpenAI
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import Chroma

# 加载向量库与LLM
embeddings = DashScopeEmbeddings(model="text-embedding-v4",
                        dashscope_api_key=os.getenv("DASHSCOPE_API_KEY")
                        )
db = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
llm =  ChatOpenAI(model="qwen-plus",
                        api_key=os.getenv("DASHSCOPE_API_KEY"),
                        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
                        )

# 初始化文档压缩器
compressor = LLMChainExtractor.from_llm(llm)

# 初始化上下文压缩检索器
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=db.as_retriever(search_kwargs={"k": 4}),
)

# 执行检索
query = "RAG的核心优势?"
compressed_docs = compression_retriever.invoke(query)

# 查看压缩后的结果(仅保留与问题相关的内容)
print(f"压缩后的文档数量:{len(compressed_docs)}")
for doc in compressed_docs:
    print(f"压缩后的内容:{doc.page_content}")
ParentDocumentRetriever:解决语义完整性与检索精准度的矛盾

分块环节有一个天然的矛盾:块太小,会破坏语义完整性,LLM无法理解完整的上下文;块太大,会降低检索的精准度,无法匹配到细粒度的答案

ParentDocumentRetriever的核心思路是:父子文档分块策略。将文档拆分为大的父块(保证语义完整),再将每个父块拆分为小的子块(保证检索精准度)。子块向量化入库,检索时先匹配相关子块,再返回子块对应的完整父块给LLM,完美平衡精准度与语义完整性。

from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.document_loaders import TextLoader

# 初始化嵌入模型
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")

# 定义父子拆分器
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)  # 父块,大尺寸
child_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)  # 子块,小尺寸

# 初始化向量库(存储子块向量)与文档存储(存储完整父块)
vector_store = Chroma(collection_name="parent_child_docs", embedding_function=embeddings)
doc_store = InMemoryStore()  # 生产环境可替换为Redis、MongoDB等持久化存储

# 初始化父子文档检索器
retriever = ParentDocumentRetriever(
    vectorstore=vector_store,
    docstore=doc_store,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)

# 加载文档并入库
loader = TextLoader("./data/企业产品手册.txt", encoding="utf-8")
docs = loader.load()
retriever.add_documents(docs)

# 执行检索:先匹配子块,返回完整父块
query = "产品的退款政策是什么?"
parent_docs = retriever.invoke(query)
print(f"返回的父文档数量:{len(parent_docs)}")
for doc in parent_docs:
    print(f"完整父文档内容:{doc.page_content}")
TimeWeightedVectorStoreRetriever:解决知识时效性问题

很多业务场景中,文档的时效性非常重要(如新闻资讯、产品更新日志、政策文件),越新的内容权重应该越高,基础检索无法区分内容的新旧。

TimeWeightedVectorStoreRetriever的核心思路是:结合语义相似度与时间权重,对检索结果进行重新排序,越新的文档得分越高,越旧的文档得分越低,保证优先返回最新的相关内容。

EnsembleRetriever:融合多路检索结果,提升召回率

单一的检索方式往往有局限性,比如语义检索擅长匹配语义相关的内容,关键词检索擅长匹配精准的专业术语、产品名称。

EnsembleRetriever的核心思路是:集成多个不同的检索器(如语义检索+关键词检索),对多路检索结果进行融合重排序(RRF算法),兼顾语义匹配与关键词匹配,大幅提升召回率

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐