LangChain 检索增强生成(RAG)
检索增强生成(Retrieval-Augmented Generation, RAG) 是一种将信息检索与语言模型生成相结合的技术范式。其核心思想是:在语言模型生成答案之前,先从外部知识库中检索出与问题相关的文档片段,将这些片段作为上下文注入模型,从而让模型能够利用最新、最专业或私有数据生成更准确、更可信的回答。
一、RAG 简介
检索增强生成(Retrieval-Augmented Generation, RAG) 是一种将信息检索与语言模型生成相结合的技术范式。其核心思想是:在语言模型生成答案之前,先从外部知识库中检索出与问题相关的文档片段,将这些片段作为上下文注入模型,从而让模型能够利用最新、最专业或私有数据生成更准确、更可信的回答。RAG 解决了纯语言模型的以下痛点:
- 解决知识截止问题:模型可以访问最新或私有数据。
- 减少幻觉:基于检索到的真实信息生成答案,更可靠。
- 可解释性:可以追溯答案的来源文档。
- 成本可控:无需频繁微调模型,只需更新知识库。
二、RAG 标准工作流
LangChain 1.0 的 RAG 标准工作流可以概括为:
- 索引:加载文档 → 分割 → 嵌入 → 存储
- 查询:检索 → 增强 → 生成
加载文档 → 切分文档 → 生成 Embedding → 存入向量库
→ 用户提问 → 检索相似文档 → 构建提示词 → LLM 生成答案
- 加载文档:使用 Document Loader 从各种源加载文档。LangChain 支持数十种格式和来源。
- 切分文档:长文档必须切分成小块,便于检索和放入上下文窗口。
- 生成 Embedding:使用嵌入模型将每个文档块转换为向量。
- 存入向量库:将转换的向量存入向量数据库。
- 创建检索器:从向量库获取检索器,并设置检索参数(如返回最相似的 k 个文档块)。
- 构建生成链:LangChain 1.0 推荐使用 LCEL 和预构建链来组装检索和生成,把检索到的内容 + 用户问题拼给模型生成最终答案。
三、RAG 核心组件
3.1 文档加载器(Document Loaders)
-
作用:读取各种文件 → 转成 LangChain Document
-
常用类型:
- PyPDFLoader
- Docx2txtLoader
- TextLoader
- WebBaseLoader
- UnstructuredFileLoader
- CSVLoader、ExcelLoader
from langchain_community.document_loaders import PyPDFLoader loader = PyPDFLoader("xxx.pdf") docs = loader.load()
3.2 文档切分(Text Splitters)
-
作用:把长文档切成小块,提高检索精度
-
常用类型:
- RecursiveCharacterTextSplitter(最常用)
- CharacterTextSplitter
-
关键参数:
- chunk_size:块大小
- chunk_overlap:重叠长度
from langchain_text_splitters import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50) splits = splitter.split_documents(docs)
3.3 嵌入模型(Embeddings)
- 作用:文本 → 向量
- 常用类型:
- OpenAIEmbeddings
- HuggingFaceEmbeddings(BGE 中文最强)
- OllamaEmbeddings
- ChatTongyiEmbeddings
- ERNIEEmbeddings
- 核心方法:
- embed_query():用户问题
- embed_documents():文档块
3.4 向量库(VectorStore)
-
作用:存向量 + 做相似度检索
-
常用:
- FAISS(本地轻量)
- Chroma(简单)
- Qdrant(高性能)
- Milvus / Zilliz(企业级)
- PGVector(PostgreSQL + 向量)
from langchain_community.vectorstores import FAISS vectorstore = FAISS.from_documents(splits, embedding)
3.5 检索器(Retriever)
- RAG 的核心入口,向量存储的轻量级封装,实现 Runnable 接口,可直接用于 LCEL。
retriever = vectorstore.as_retriever( search_kwargs={"k": 3} # 取最相似3条 )
3.6 提示词模板(Prompt Template)
-
把检索到的内容 + 用户问题拼给模型
from langchain_core.prompts import ChatPromptTemplate prompt = ChatPromptTemplate.from_template(""" 根据以下上下文回答问题: <context> {context} </context> 问题:{question} """)
3.7 LLM / ChatModel
-
生成最终答案
from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o", temperature=0)
四、使用 LCEL 构建基础 RAG 链
以下是一个完整的 RAG 问答链示例:
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
# 1. 加载文档
loader = TextLoader("knowledge.txt")
docs = loader.load()
# 2. 分割文档
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)
# 3. 创建向量存储
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(chunks, embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# 4. 定义提示模板
prompt = ChatPromptTemplate.from_template("""
基于以下上下文回答问题。如果上下文中没有相关信息,请说明不知道。
上下文:{context}
问题:{question}
""")
# 5. 定义模型
model = ChatOpenAI(model="gpt-4o")
# 6. 构建 LCEL 链
def format_docs(docs):
return "\n\n".join([doc.page_content for doc in docs])
rag_chain = (
RunnableParallel(context=retriever | format_docs, question=RunnablePassthrough())
| prompt
| model
)
# 7. 调用
result = rag_chain.invoke("什么是 RAG?")
print(result.content)
链的分解:
- RunnableParallel 并行执行:左侧 context 分支执行检索并格式化,右侧 question 分支原样传递用户问题。
- | prompt 将字典传入提示模板,生成完整的提示消息。
- | model 调用模型生成回答。
五、高级 RAG 技术
5.1 多查询检索(Multi-Query Retrieval)
将用户问题扩展为多个相似问题,分别检索后合并结果,提高召回率。
from langchain.retrievers.multi_query import MultiQueryRetriever
retriever = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(),
llm=ChatOpenAI(model="gpt-3.5-turbo")
)
5.2 重排(Reranking)
对检索结果使用交叉编码器重新排序,将最相关文档排在前面。
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
reranker = CrossEncoderReranker(
model=HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-base")
)
compression_retriever = ContextualCompressionRetriever(
base_compressor=reranker,
base_retriever=vectorstore.as_retriever(search_kwargs={"k": 10})
)
5.3 混合搜索(Hybrid Search)
结合关键词检索(如 BM25)和向量检索,兼顾精确匹配和语义相似性。
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 3
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.3, 0.7]
)
5.4 上下文压缩(Contextual Compression)
在检索后对文档进行压缩,只保留与问题最相关的内容,减少 token 消耗。
from langchain.retrievers.document_compressors import LLMChainExtractor
compressor = LLMChainExtractor.from_llm(ChatOpenAI(model="gpt-3.5-turbo"))
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vectorstore.as_retriever()
)
5.5 自查询检索(Self-Querying Retriever)
让模型从问题中提取查询条件(如元数据过滤),实现结构化检索。
from langchain.retrievers.self_query import SelfQueryRetriever
from langchain.chains.query_constructor.base import AttributeInfo
metadata_field_info = [
AttributeInfo(name="source", description="文档来源", type="string"),
AttributeInfo(name="date", description="发布日期", type="date"),
]
document_content_description = "技术文档"
retriever = SelfQueryRetriever.from_llm(
llm=ChatOpenAI(model="gpt-4"),
vectorstore=vectorstore,
document_content_description=document_content_description,
metadata_field_info=metadata_field_info
)
六、RAG 与智能体的结合
在 LangChain 1.0 中,RAG 可以作为智能体的一个工具,让智能体自主决定是否需要检索。
6.1 将检索器封装为工具
from langchain.tools import tool
@tool
def search_knowledge_base(query: str) -> str:
"""从知识库中检索与问题相关的信息"""
docs = vectorstore.similarity_search(query, k=3)
return "\n\n".join([doc.page_content for doc in docs])
6.2 创建智能体并使用检索工具
智能体会根据问题自行判断是否调用 search_knowledge_base 工具,获取上下文后再生成答案。
from langchain.agents import create_agent
from langgraph.checkpoint.memory import MemorySaver
agent = create_agent(
model="openai:gpt-4o",
tools=[search_knowledge_base],
system_prompt="你是一个知识助手,必要时会检索知识库。",
checkpointer=MemorySaver()
)
result = agent.invoke({
"messages": [("human", "LangChain 1.0 的 RAG 有什么新特性?")]
})
七、高级优化
RAG(检索增强生成)效果不佳是实际落地中常见的问题,可能表现为回答不准确、遗漏关键信息、产生幻觉、逻辑混乱等。优化需要系统性地审视整个流程,从数据、检索、生成到评估逐一排查和改进。
7.1 数据层优化
-
文档清洗
- 去除噪声:移除无关元素(如网页导航、广告、页眉页脚),只保留正文。
- 统一格式:将 PDF、扫描件转换为可解析的文本,修复乱码。
- 标准化:统一日期、单位、术语,便于模型理解。
-
智能分块
- 块大小:根据模型上下文窗口(如 512/1024 tokens)和语义单元确定,避免过大导致信息稀释,过小导致上下文割裂。
- 重叠:设置 10-20% 的重叠,避免切分打断关键实体或句子。
- 语义分块:使用段落、章节标题或递归字符分割,保持语义完整性。LangChain 的 RecursiveCharacterTextSplitter 是常用工具。
-
添加元数据
- 为每个块附加来源、标题、页码、时间戳、摘要、实体标签等。
- 元数据可用于过滤(如只检索最新文档)、排序(优先权威来源)和生成时引用。
-
数据增强
- 对每个文档生成 QA 对(使用 LLM 辅助),构建“问题-原文块”索引,提高检索命中率。
- 生成文档摘要作为额外检索单元(MultiVectorRetriever 可同时索引原文、摘要、假设问题)。
7.2 检索层优化
-
嵌入模型选择与微调
- 通用 vs 领域:通用模型(如 text-embedding-3-small)适合泛化场景;专业领域(法律、医疗)可考虑微调,如使用 bge-large-zh 或 text2vec-large-chinese 并在领域数据上微调。
- 混合检索:结合稀疏检索(BM25)和密集检索(向量)的互补优势。LangChain 的 EnsembleRetriever 可加权融合。
-
索引优化
- 选择合适的向量索引:FAISS(精确或近似)、HNSW(高召回)、IVF(节省内存)。
- 调整参数:efConstruction、M(HNSW)影响召回率与速度的平衡。
-
检索参数调优
- Top-K:通常设为 3-10,过少易遗漏,过多引入噪声。
- 相似度阈值:过滤低于阈值的块,避免无关内容干扰生成。
-
查询改写与扩展
- 对原始问题进行改写(如同义词替换、分解复杂问题),提升与文档块的匹配度。
- HyDE(假设文档嵌入):先让模型根据问题生成一个假设答案,再用该答案去检索,提高语义对齐。
7.3 重排序与后处理
-
重排序模型
- 初次检索(如 top=20)后,使用 cross-encoder 模型(如 bge-reranker-large)对候选块与问题计算相关性得分,重新排序取 top-K。
- 重排序能显著提升相关性,但需额外计算成本,适合对质量要求高的场景。
-
上下文压缩与选择
- 使用 ContextualCompressionRetriever 提取每个块中最相关的片段,减少无关 token。
- 动态选择:根据生成过程需要,迭代检索更多信息(如 Self-QueryRetriever 或 Agent 模式)。
-
去重与冲突解决
- 对语义重复的块去重。
- 如果多个块信息矛盾,可根据元数据(如来源权威性、时间戳)选择优先采用。
7.4 生成层优化
-
提示工程
- 明确角色:如“你是一位严谨的客服,只能依据以下资料回答,若资料不足请说明。”
- 提供格式:要求模型按照“答案[来源]”格式输出,强制引用。
- 注入未知指令:明确“如果资料中没有相关信息,请回答‘资料中未提及’。”
- Few-shot 示例:给出几个正确回答的范例,引导模型行为。
-
生成参数调整
- 降低温度(如 0.1~0.3)减少随机性,适合事实问答。
- 限制 Top-p(如 0.9)控制采样范围。
- 禁用流式有时可改善长文本逻辑(但流式仍是推荐体验)。
-
微调生成模型
- 在领域数据上微调基础模型,使其更擅长遵循指令和引用资料。
- 可结合 RAG 场景特定微调:如训练模型在收到检索块后直接生成答案,减少幻觉。
-
迭代生成
- 对于复杂问题,采用 多轮对话:先回答部分,再根据后续检索补充。
- Self-Ask 或 ReAct 智能体可主动调用检索工具逐步完善答案。
八、完整 RAG 应用构建
场景:构建一个公司内部知识库问答机器人,支持多轮对话和文档来源展示。
步骤概览:
- 加载与处理文档(PDF、Word、网页)
- 创建向量存储
- 构建带记忆的 RAG 链
- 集成流式输出与来源显示
import os
from langchain_community.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain.memory import ConversationBufferMemory
from langchain.chains import create_history_aware_retriever
from langchain.chains import create_retrieval_chain
# 加载文档
loader = DirectoryLoader("./docs/", glob="**/*.pdf")
docs = loader.load()
# 分割
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)
# 向量存储
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(chunks, embeddings)
retriever = vectorstore.as_retriever()
# 创建历史感知检索器(支持多轮对话)
prompt = ChatPromptTemplate.from_messages([
("system", "根据对话历史和上下文回答问题。"),
("placeholder", "{chat_history}"),
("human", "{input}")
])
history_aware_retriever = create_history_aware_retriever(
llm=ChatOpenAI(),
retriever=retriever,
prompt=prompt
)
# 构建问答链
qa_chain = create_retrieval_chain(
retriever=history_aware_retriever,
combine_docs_chain=create_stuff_documents_chain(
llm=ChatOpenAI(),
prompt=prompt
)
)
# 带记忆的链
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
# 执行
result = qa_chain.invoke({"input": "什么是 RAG?", "chat_history": memory.chat_history.messages})
memory.save_context({"input": "什么是 RAG?"}, {"output": result["answer"]})
print(result["answer"])
print("来源:", [doc.metadata for doc in result["context"]])
更多推荐


所有评论(0)