LangChain 多文件 RAG 应用
概述
本项目演示如何使用 LangChain 构建一个基于多文档的智能问答系统(Retrieval-Augmented Generation,RAG),能够从文档集合中检索相关信息并生成高质量的回答。
核心功能:从 docs 目录加载多种格式文档,构建向量索引,实现基于上下文的智能问答
技术架构
| 技术组件 | 功能描述 | 在本例中的作用 |
|---|---|---|
| DirectoryLoader | 目录文档加载器 | 从指定目录加载文档文件 |
| RecursiveCharacterTextSplitter | 递归文本分割器 | 将长文档分割为适合模型处理的块 |
| DashScopeEmbeddings | 文本嵌入模型 | 将文本转换为向量表示 |
| FAISS | 向量检索引擎 | 高效存储和检索向量数据 |
| ChatTongyi | 大语言模型 | 基于上下文生成最终回答 |
代码分析
导入模块
import os
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_community.chat_models import ChatTongyi
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
导入所需的 LangChain 组件:
DirectoryLoader, TextLoader: 用于从目录加载文档RecursiveCharacterTextSplitter: 递归文本分割器DashScopeEmbeddings: DashScope 文本嵌入模型FAISS: 向量检索引擎ChatTongyi: 通义千问语言模型ChatPromptTemplate: 聊天气泡提示模板StrOutputParser: 字符串输出解析器
API 配置
# 获取 API Key
DASHSCOPE_API_KEY = os.getenv('DASHSCOPE_API_KEY')
if not DASHSCOPE_API_KEY:
raise ValueError("请设置环境变量 DASHSCOPE_API_KEY")
从环境变量获取 API 密钥并验证其有效性,这是安全的最佳实践,避免硬编码敏感信息。
文档加载与索引构建
def load_documents_and_create_index(file_dir: str = './docs', persist_dir: str = './langchain_storage'):
# 创建嵌入模型 - 将文本转换为向量表示
embeddings = DashScopeEmbeddings(
model="text-embedding-v1", # 阿里云文本嵌入模型
dashscope_api_key=DASHSCOPE_API_KEY, # API访问凭证
)
# 检查索引是否已存在 - 实现缓存机制,提升性能
if os.path.exists(persist_dir):
try:
# 从存储中加载已训练好的索引
vector_store = FAISS.load_local(
persist_dir,
embeddings,
allow_dangerous_deserialization=True # 允许反序列化(注意安全风险)
)
print("从存储加载索引成功")
return vector_store
except Exception as e:
print(f"加载索引失败: {e},将重新创建索引")
# 如果索引不存在或加载失败,创建新索引
if not os.path.exists(file_dir):
print(f"文档目录 {file_dir} 不存在")
return None
# 加载目录下的所有txt文件 - 目前仅支持txt格式
loader = DirectoryLoader(
file_dir, # 要扫描的目录
glob="**/*.txt", # 文件匹配模式(递归匹配所有txt文件)
loader_cls=TextLoader, # 使用TextLoader加载器
loader_kwargs={"encoding": "utf-8"} # 指定文件编码,保证中文正常处理
)
documents = loader.load() # 执行文档加载
print(f"加载了 {len(documents)} 个文档")
if not documents:
print("没有找到任何文档") # 提示用户可能的配置问题
return None
# 文本分块处理 - 将长文档分割为适合模型处理的小块
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每个文本块的最大字符数
chunk_overlap=200, # 相邻块之间的重叠字符数(保持上下文连续性)
length_function=len, # 计算文本长度的方法
)
chunks = text_splitter.split_documents(documents) # 执行分块
print(f"文本被分割成 {len(chunks)} 个块")
# 创建向量索引 - 将文本块转换为向量并建立索引
vector_store = FAISS.from_documents(chunks, embeddings)
# 保存索引到本地 - 实现持久化,下次可直接加载
os.makedirs(persist_dir, exist_ok=True) # 确保目录存在
vector_store.save_local(persist_dir) # 保存向量索引
print(f"索引已保存到 {persist_dir}")
return vector_store
这个函数实现了文档加载、文本分块、向量索引构建和持久化存储的完整流程。它具有缓存机制,避免重复计算,提升性能。
问答链构建
def create_qa_chain(llm):
# QA Prompt模板 - 定义AI助手的行为和上下文处理方式
qa_prompt = ChatPromptTemplate.from_messages([
("system", """你是一个乐于助人的AI助手。
根据以下上下文内容回答用户的问题。如果上下文中没有相关信息,请如实说明。
你总是用中文回复用户。
上下文内容:
{context}"""), # 系统角色设定,指导AI如何使用上下文信息
("human", "{question}") # 用户问题占位符
])
# 创建问答链(LCEL管道语法)
# 执行流程:prompt -> llm -> output_parser
# 1. 用qa_prompt模板格式化输入
# 2. 传递给大语言模型处理
# 3. 使用字符串解析器处理输出
qa_chain = qa_prompt | llm | StrOutputParser()
return qa_chain
使用 LCEL (LangChain Expression Language) 构建问答链,将提示模板、语言模型和输出解析器组合成一个处理管道。
主执行流程
初始化LLM
→
加载索引
→
创建问答链
→
执行查询
→
生成回答
def main():
# 配置大语言模型 - 使用DeepSeek-V3模型
llm = ChatTongyi(
model_name="deepseek-v3", # 指定使用的模型名称
dashscope_api_key=DASHSCOPE_API_KEY # API访问凭证
)
# 加载文档并创建索引 - 如果索引已存在则直接加载,否则重新创建
vector_store = load_documents_and_create_index()
if vector_store is None:
print("无法创建索引,程序退出") # 提供明确的错误提示
return
# 创建问答链 - 将prompt模板、LLM和输出解析器组合成完整处理链条
qa_chain = create_qa_chain(llm)
# 定义查询问题 - 这是用户想要咨询的内容
query = "介绍下雇主责任险"
print(f"\n用户查询: {query}\n")
# 相似度搜索 - 从向量数据库中找到与查询最相关的文档片段
# k=5表示返回最相似的5个文档片段
docs = vector_store.similarity_search(query, k=5)
# 显示召回的文档内容 - 让用户了解AI参考了哪些信息
print("===== 召回的文档内容 =====")
if docs:
for i, doc in enumerate(docs):
print(f"\n文档片段 {i+1}:")
print(f"内容: {doc.page_content[:200]}...") # 仅显示前200字符作为预览
print(f"来源: {doc.metadata.get('source', '未知')}") # 显示文档来源
else:
print("没有召回任何文档内容") # 检索失败的提示
print("===========================\n")
# 格式化上下文 - 将检索到的文档片段合并为AI可理解的上下文
context = "\n\n".join(doc.page_content for doc in docs)
# 执行问答链 - 使用组装好的处理链条生成最终答案
print("===== AI 回复 =====")
response = qa_chain.invoke({"context": context, "question": query}) # 同步调用
print(response)
print("===================\n")
主函数执行完整的RAG问答流程:初始化模型、加载文档索引、构建问答链、执行查询并展示结果。
流程图
graph TD
A[程序启动] --> B[检查环境变量 DASHSCOPE_API_KEY]
B --> C{API Key 是否有效?}
C -->|否| D[抛出 ValueError 异常]
C -->|是| E[初始化 ChatTongyi LLM]
E --> F[尝试从 ./langchain_storage 加载向量索引]
F --> G{索引是否存在?}
G -->|是| H[加载现有索引]
G -->|否| I[使用 DirectoryLoader 加载 ./docs 目录下的 txt 文件]
I --> J[使用 RecursiveCharacterTextSplitter 分割文档]
J --> K[使用 DashScopeEmbeddings 创建向量嵌入]
K --> L[创建 FAISS 向量存储]
L --> M[保存索引到本地 ./langchain_storage]
H --> N[创建 QA 问答链]
M --> N
N --> O[设置查询问题: 介绍下雇主责任险]
O --> P[执行相似度搜索 retrieval]
P --> Q[获取前5个相关文档片段]
Q --> R[格式化上下文内容]
R --> S[调用 LLM 生成最终回答]
S --> T[输出 AI 回复结果]
style A fill:#8BC34A,stroke:#4CAF50
style D fill:#FFCDD2,stroke:#F44336
style T fill:#C8E6C9,stroke:#4CAF50
style N fill:#FFF9C4,stroke:#FFC107
核心概念
1. RAG(检索增强生成)
RAG是一种结合信息检索和语言生成的技术,通过从外部知识源检索相关信息来增强语言模型的生成能力,特别是在处理特定领域或私有数据时非常有效。
2. 向量索引
将文档转换为向量表示,并建立索引以支持快速相似性搜索。FAISS是Facebook开发的高效相似性搜索库,能够快速找到与查询向量最相似的文档片段。
3. 文本分块
将长文档分割为较小的块,确保每个块都能适应语言模型的上下文窗口限制,同时保持内容的连贯性。
应用场景
这种多文件RAG系统适用于:
- 企业知识库问答
- 学术文献检索与摘要
- 法律文档查询
- 技术支持文档问答
- 医疗知识库查询
扩展思路
可以进一步扩展此示例:
- 支持更多文档格式(PDF、Word、HTML等)
- 实现更高级的文本分块策略
- 添加查询改写功能
- 实现多轮对话记忆
- 添加结果评估机制