RAG(检索增强生成)是解决大模型 “知识过时” 与 “事实性错误” 的核心方案,本文将通过一个完整案例,带大家用 LangChain 实现基于 PDF 文档的卢浮宫知识问答,并对比不同chain_type的效果差异,帮你快速上手实用级 RAG 应用。
一、RAG 核心逻辑:让大模型 “有据可依”
传统大模型回答问题依赖训练数据,无法获取私有文档(如本地 PDF)或实时信息。RAG 通过 “检索 + 生成” 两步解决这一问题:
- 检索:从私有知识库(本文为卢浮宫 PDF)中,精准找到与问题相关的文本片段。
- 生成:大模型结合检索到的 “事实依据”,生成准确、有来源的回答,而非 “凭空想象”。
本文案例的技术链路为:PDF加载→文本分割→向量存储→检索→对话式生成,全程基于 LangChain 生态实现。
二、完整代码实战:从 0 搭建卢浮宫问答系统
1. 环境准备与依赖安装
首先安装核心库,cryptography用于解决部分环境下的加密依赖问题。
# 安装依赖
!pip install --upgrade cryptography langchain langchain_community langchain_openai langchain_text_splitters faiss-cpu pypdf dashscope
2. 核心步骤拆解
步骤 1:加载 PDF 文档
用PyPDFLoader读取本地的 “卢浮宫.pdf”,将 PDF 每页内容转换为 LangChain 可处理的Document对象。
from langchain_community.document_loaders import PyPDFLoader
# 加载PDF,docs为Document对象列表,每个对象对应PDF一页
loader = PyPDFLoader("./卢浮宫.pdf")
docs = loader.load()
步骤 2:文本分割(关键优化点)
PDF 内容通常是长文本,直接嵌入会丢失上下文关联。用RecursiveCharacterTextSplitter按中文语义分割,平衡 “上下文完整性” 与 “检索精准度”。
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个文本块500字符
chunk_overlap=40, # 块间重叠40字符,避免语义断裂
separators=["\n", "。", "!", "?", ",", "、", ""] # 按中文标点分割,更符合语义
)
texts = text_splitter.split_documents(docs) # 分割后得到文本块列表
步骤 3:构建向量数据库
将文本块转换为向量(嵌入),存储到 FAISS(轻量级向量库)中,方便后续快速检索相似文本。这里用阿里云 DashScope 的嵌入模型,也可替换为 OpenAI Embeddings。
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import DashScopeEmbeddings
# 初始化嵌入模型,需替换为自己的DashScope API Key
embeddings_model = DashScopeEmbeddings(
model="text-embedding-v2",
dashscope_api_key="你的API Key"
)
# 将文本块嵌入并存储到FAISS,db即为向量数据库
db = FAISS.from_documents(texts, embeddings_model)
步骤 4:配置检索器与大模型
- 检索器:从向量数据库中 “按相似度” 提取相关文本,是 RAG 的 “信息来源”。
- 大模型:这里用 DashScope 兼容 OpenAI 格式的 “qwen-plus” 模型,也可替换为 GPT-3.5/4。
from langchain_openai import ChatOpenAI
# 构建检索器,默认按相似度返回相关文本
retriever = db.as_retriever()
# 初始化大模型,API Base为DashScope兼容地址
model = ChatOpenAI(
model="qwen-plus",
openai_api_key="你的API Key", # 与DashScope API Key相同
openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
步骤 5:对话记忆与 RAG 链构建
用ConversationBufferMemory保存对话历史,让系统具备 “多轮对话理解” 能力;通过ConversationalRetrievalChain串联 “检索→记忆→生成” 全流程,核心差异在chain_type。
三、3 种 chain_type 对比:哪种更适合你的场景?
chain_type决定了 RAG 链 “如何利用检索到的多个文本块生成回答”,不同类型各有优劣,以下是实战对比。
1. map_reduce:“并行处理 + 汇总”,适合多片段融合
逻辑:先对每个检索到的文本块单独生成回答(map),再汇总所有回答生成最终结果(reduce)。代码:
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
# 初始化对话记忆
memory = ConversationBufferMemory(
return_messages=True,
memory_key='chat_history', # 与链的输入键对应
output_key='answer' # 存储生成的回答
)
# 构建map_reduce类型的RAG链
qa_map_reduce = ConversationalRetrievalChain.from_llm(
llm=model,
retriever=retriever,
memory=memory,
chain_type="map_reduce" # 核心参数
)
# 调用问答,问题:“卢浮宫这个名字怎么来的?”
result_map_reduce = qa_map_reduce.invoke({
"chat_history": memory,
"question": "卢浮宫这个名字怎么来的?"
})
print("map_reduce回答:", result_map_reduce["answer"])
优势:能融合多个文本块的信息,适合问题需要 “多来源佐证” 的场景。劣势:调用次数多(每个文本块 1 次 map 调用),成本较高,速度较慢。
2. refine:“迭代优化”,适合逐步补充细节
逻辑:先基于第一个文本块生成初始回答,再逐个结合后续文本块 “优化” 回答(如补充细节、修正错误)。代码:
# 重新初始化记忆(避免与其他链冲突)
memory_refine = ConversationBufferMemory(return_messages=True, memory_key='chat_history', output_key='answer')
# 构建refine类型的RAG链
qa_refine = ConversationalRetrievalChain.from_llm(
llm=model,
retriever=retriever,
memory=memory_refine,
chain_type="refine"
)
# 调用问答
result_refine = qa_refine.invoke({
"chat_history": memory_refine,
"question": "卢浮宫这个名字怎么来的?"
})
print("refine回答:", result_refine["answer"])
优势:回答会逐步完善,细节更丰富,适合需要 “深度解析” 的问题。劣势:依赖文本块顺序,若前序文本块信息不准确,后续优化可能受影响;速度同样较慢。
3. map_rerank:“评分筛选”,适合精准优先
逻辑:对每个文本块生成回答并打分(评分基于 “回答与问题的相关性”),最终返回评分最高的回答。代码:
# 重新初始化记忆
memory_rerank = ConversationBufferMemory(return_messages=True, memory_key='chat_history', output_key='answer')
# 构建map_rerank类型的RAG链
qa_rerank = ConversationalRetrievalChain.from_llm(
llm=model,
retriever=retriever,
memory=memory_rerank,
chain_type="map_rerank"
)
# 调用问答
result_rerank = qa_rerank.invoke({
"chat_history": memory_rerank,
"question": "卢浮宫这个名字怎么来的?"
})
print("map_rerank回答:", result_rerank["answer"])
优势:只返回最优回答,精准度高,调用次数少(仅 map 阶段,无 reduce),速度快、成本低。劣势:无法融合多个文本块信息,若最优文本块信息不全,回答会遗漏细节。
3 种 chain_type 核心差异表
| 类型 | 核心逻辑 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| map_reduce | 多块并行生成→汇总 | 多信息融合,全面性强 | 调用多、成本高、速度慢 | 需多来源佐证的复杂问题 |
| refine | 单块初始→多块迭代优化 | 细节丰富,逐步完善 | 依赖顺序、速度较慢 | 需深度解析的细节类问题 |
| map_rerank | 多块生成→评分选最优 | 精准度高、速度快、成本低 | 无法融合信息,可能遗漏细节 | 需快速获取精准答案的简单问题 |
四、实战优化建议
- 文本分割参数调优:
chunk_size并非固定值,若文档多为短句(如新闻),可设为 300-400;若为长段落(如论文),可设为 600-800,需结合实际文档测试。 - 检索器优化:默认检索器返回 5 个文本块,可通过
retriever = db.as_retriever(search_kwargs={"k":3})调整数量,k 值越小速度越快,k 值越大信息越全。 - API Key 安全:实际项目中不要硬编码 API Key,可通过环境变量(如
os.getenv("DASHSCOPE_API_KEY"))或配置文件加载。 - 记忆类型选择:
ConversationBufferMemory会保存全部对话历史,若对话轮次多,可换ConversationBufferWindowMemory(只保存最近 N 轮),减少 token 消耗。
五、总结
RAG 的核心价值是 “让大模型结合私有知识回答问题”,而 LangChain 已封装好从文档加载到对话生成的全流程工具,降低了开发门槛。选择chain_type时,需根据 “精准度需求”“成本预算”“速度要求” 综合判断:
- 追求全面性:选 map_reduce
- 追求细节丰富度:选 refine
- 追求速度与精准平衡:选 map_rerank
PDF文档内容:
罗浮宫(法语:MuséeduLouvre,英语 /ˈluː v(rə)/ ),正式名称为罗浮博物馆,位于法国巴 黎市中心的塞纳河边,原是建于12世纪末至13世纪初的王宫,现在是一所综合博物馆,亦 是世界上最大的艺术博物馆之一,以及参观人数最多的博物馆,是巴黎中心最知名的地标。 罗浮宫的建筑物始建于1190年左右,并在近代曾多次进行扩建,今天所见的模样则一个巨 大的翼楼和亭阁建筑群,主要组成部分的总面积则超过60,600平方公尺(652,000平方英尺), 馆内永久收藏则包括雕塑、绘画、美术工艺及古代东方、古代埃及和古希腊罗马等7个分类, 主要收藏1860年以前的艺术作品与考古文物,罗浮宫博物馆在1793年8月10日开幕起正 式对公众开放,平均每天有15,000名游客到此参观,其中65%是外国游客。 位置 罗浮宫与杜乐丽花园的卫星照片 罗浮宫博物馆位于巴黎市中心的卢浮宫内,位于塞纳河右岸,毗邻杜乐丽花园。最近的两个 地铁站是皇家宫-罗浮宫站和卢浮-里沃利站,前者有直达地下购物中心 CarrouselduLouvre 的地下通道。 在1980年代末和1990年代大改建之前,罗浮宫共有好几个街道入口,目前大部分入口已经 永久关闭。自1993年以来,博物馆的正门位置位于拿破仑广场金字塔底下的地下空间,游 客可以从金字塔本身、旋转阶梯处连接到博物馆的通道。 博物馆的参观时间随著时代的推移而变化。自18世纪开放以来,只有艺术家和来自外国的 观光游客享有特权参观,这项特权后在1850年代才消失。当博物馆从1793年首次开放时, 新历法法国共和历规定了“十天周”(法语:décades),其中前六天为艺术家和外国人访问, 后三天为将军访问,民众仅能在最后一天参观,后在在1800年代初期在恢复七天周后,民 众在每周只有4小时的时间能在罗浮宫参观,周六和周日则是缩减至下午2点至下午4点期 间参观。 从1824 年开始的一项新规定允许公众在星期日和节假日时参观,然而其他日子只对艺术家 和外国游客开放,这种情况到1855年才发生了变化,博物馆更改成除了周一外全天向公众 免费开放,直到1922年才开始收费。 当前自1946年开始,罗浮宫除了在周二公休和特殊假日外,通常向游客全面开放参观,内 部允许使用照相机和录像机,但禁止使用闪光灯。
2397

被折叠的 条评论
为什么被折叠?



