RAG (Retrieval Augmented Generation) 是一种结合了检索(Retrieval)和生成(Generation)两大能力的技术。它允许大型语言模型(LLM)在生成回答之前,先从外部知识库中检索相关信息,从而提高回答的准确性、时效性,并减少“幻觉”现象。本文将详细介绍如何构建一个简单的 RAG 系统,并以 DeepSeek 大模型为例,展示如何将其集成到流程中。
RAG 的核心思想
RAG 的工作流程通常如下:
- 用户提问 (Query):用户提出一个问题。信息检索 (Retrieval):系统将用户的问题在知识库中进行搜索,找出最相关的文档片段。上下文增强 (Context Augmentation):将检索到的相关信息与用户的原始问题一起,构建成一个更丰富的提示(Prompt)。答案生成 (Generation):将增强后的提示输入到大语言模型(如 DeepSeek)中,由模型生成最终的回答。
技术选型
为了实现一个简单的 RAG 系统,我们将使用以下工具:
- Python: 主要编程语言。LangChain: 一个强大的框架,用于简化 LLM 应用的开发,提供了文档加载、文本分割、向量存储、LLM 接口等模块。Sentence Transformers: 用于生成文本嵌入(Embeddings),将文本转换为向量表示。FAISS (Facebook AI Similarity Search): 一个高效的相似性搜索库,用于构建和查询向量数据库。DeepSeek API: DeepSeek 提供的 LLM 服务接口。
实现步骤
1. 环境准备
首先,确保你安装了必要的 Python 库。
pip install langchain sentence-transformers faiss-cpu deepseek-llm openai python-dotenv
langchain
: RAG 框架。sentence-transformers
: 文本嵌入模型。faiss-cpu
: 向量数据库 (CPU 版本,如果需要 GPU 加速,可以安装 faiss-gpu
)。deepseek-llm
: DeepSeek 官方 Python SDK。openai
: LangChain 内部某些与 OpenAI API 兼容的接口(即使使用 DeepSeek,有时也会间接依赖)。python-dotenv
: 用于管理环境变量(如 API 密钥)。接下来,你需要获取 DeepSeek API Key。访问 DeepSeek 开放平台 注册并获取你的 API Key。
建议将 API Key 存储在项目根目录下的 .env
文件中,以避免硬编码:
# .env 文件内容DEEPSEEK_API_KEY="your_actual_api_key"
2. 准备知识库
创建一个名为 knowledge_base
的文件夹,并在其中放入一些文本文件作为你的知识库。例如:
knowledge_base/deepseek_info.txt
:
DeepSeek是一家致力于研究通用人工智能(AGI)的公司。DeepSeek的使命是“用AI改变世界”。DeepSeek发布了多款强大的语言模型,包括DeepSeek Coder和DeepSeek LLM。DeepSeek LLM 67B在多个基准测试中表现出色。
knowledge_base/rag_intro.txt
:
RAG代表检索增强生成。它结合了信息检索系统的能力和大型语言模型的生成能力。RAG可以帮助减少LLM的幻觉,并提供基于特定文档的答案。
3. 代码实现
下面是完整的 Python 代码实现:
import osfrom dotenv import load_dotenvfrom langchain_community.document_loaders import DirectoryLoader, TextLoaderfrom langchain.text_splitter import RecursiveCharacterTextSplitterfrom langchain_community.embeddings import HuggingFaceEmbeddingsfrom langchain_community.vectorstores import FAISSfrom langchain_deepseek import ChatDeepseek # langchain_community.chat_models for older versionsfrom langchain.chains import RetrievalQAfrom langchain.prompts import PromptTemplate# 1. 加载环境变量 (DeepSeek API Key)load_dotenv()api_key = os.getenv("DEEPSEEK_API_KEY")if not api_key: raise ValueError("DEEPSEEK_API_KEY not found in .env file or environment variables.")# --- 知识库处理 ---# 2. 加载知识库文档# 使用 TextLoader 加载单个文件,或 DirectoryLoader 加载整个目录print("加载知识库文档...")loader = DirectoryLoader('./knowledge_base/', glob="**/*.txt", loader_cls=TextLoader, loader_kwargs={'encoding': 'utf-8'})documents = loader.load()if not documents: print("未能加载任何文档,请检查 'knowledge_base' 文件夹和文件路径。") exit()print(f"成功加载 {len(documents)} 个文档。")# 3. 文本分割# 将加载的文档分割成更小的块,以便嵌入和检索print("分割文档...")text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)texts = text_splitter.split_documents(documents)if not texts: print("未能分割文档。") exit()print(f"文档被分割成 {len(texts)} 个文本块。")# 4. 文本嵌入# 使用 HuggingFace 上的预训练模型将文本块转换为向量# 'all-MiniLM-L6-v2' 是一个轻量级且效果不错的模型print("生成文本嵌入...")embeddings_model_name = "sentence-transformers/all-MiniLM-L6-v2"embeddings = HuggingFaceEmbeddings(model_name=embeddings_model_name)# 5. 构建向量数据库# 使用 FAISS 将嵌入向量化并存储,以便进行快速相似性搜索print("构建向量数据库...")# FAISS.from_documents 会自动处理嵌入和索引vector_store = FAISS.from_documents(texts, embeddings)print("向量数据库构建完成。")# --- 与 DeepSeek 大模型集成 ---# 6. 初始化 DeepSeek LLM# 使用 DeepSeek 的聊天模型# 注意:model_name 可能需要根据 DeepSeek 官方文档更新# 常见的模型如 'deepseek-chat' 或 'deepseek-coder' (如果你用coder模型)print("初始化 DeepSeek LLM...")llm = ChatDeepseek( model="deepseek-chat", # 或者其他 DeepSeek 模型,如 "deepseek-coder" api_key=api_key, temperature=0.1 # 控制生成文本的随机性,较低的值使输出更确定)print("DeepSeek LLM 初始化完成。")# 7. 创建 RAG 链 (RetrievalQA)# RetrievalQA 链封装了检索、构建提示和调用 LLM 的整个过程# 定义一个Prompt模板 (可选,但推荐用于更好地控制输出)prompt_template = """请根据以下提供的上下文信息来回答问题。如果你在上下文中找不到答案,请说你不知道,不要试图编造答案。保持答案简洁。上下文:{context}问题: {question}答案:"""PROMPT = PromptTemplate( template=prompt_template, input_variables=["context", "question"])chain_type_kwargs = {"prompt": PROMPT}print("创建 RAG 链...")qa_chain = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", # "stuff" 是最简单的方法,将所有检索到的文本块直接塞入上下文 retriever=vector_store.as_retriever(search_kwargs={"k": 3}), # k=3 表示检索最相关的3个文档块 chain_type_kwargs=chain_type_kwargs, return_source_documents=True # 同时返回源文档,方便溯源)print("RAG 链创建完成。")# 8. 进行提问print("\n--- 开始提问 ---")while True: user_query = input("\n请输入你的问题 (输入 '退出' 来结束程序): ") if user_query.lower() == '退出': break if not user_query.strip(): print("问题不能为空,请重新输入。") continue print(f"\n正在处理问题: {user_query}") try: result = qa_chain.invoke({"query": user_query}) # LangChain 0.1.0+ 使用 invoke print("\n模型回答:") print(result["result"]) print("\n引用的源文档片段:") for i, source_doc in enumerate(result["source_documents"]): print(f"--- 片段 {i+1} (来自: {source_doc.metadata.get('source', '未知来源')}) ---") print(source_doc.page_content) print("-" * 20) except Exception as e: print(f"处理问题时发生错误: {e}")print("\n程序已退出。")
详细解释
加载环境变量 (load_dotenv
, os.getenv
):
- 从
.env
文件安全地加载 DEEPSEEK_API_KEY
。这是保护敏感信息的良好实践。加载知识库文档 (DirectoryLoader
, TextLoader
):
DirectoryLoader
用于从指定目录加载所有匹配 glob
模式(这里是 *.txt
)的文件。loader_cls=TextLoader
指定用 TextLoader
来处理每个找到的文件。loader_kwargs={'encoding': 'utf-8'}
确保以 UTF-8 编码读取文件,避免中文乱码。文本分割 (RecursiveCharacterTextSplitter
):
- LLM 的上下文窗口长度有限,且对较短、集中的文本块进行嵌入效果更好。
RecursiveCharacterTextSplitter
会尝试按特定字符(如 \n\n
, \n
,
)递归地分割文本。chunk_size=500
: 每个文本块的最大字符数。chunk_overlap=50
: 块之间的重叠字符数,有助于保持语义的连续性,避免重要信息在分割点被切断。文本嵌入 (HuggingFaceEmbeddings
):
- 嵌入是将文本转换为高维向量的过程,使得语义相似的文本在向量空间中也相近。
HuggingFaceEmbeddings
使用 sentence-transformers
库从 Hugging Face Hub 下载并运行指定的嵌入模型(这里是 sentence-transformers/all-MiniLM-L6-v2
,一个流行且高效的模型)。这些嵌入是在本地计算的。备选方案:DeepSeek 可能也提供自己的嵌入 API。如果使用其 API,则需要替换此步骤,调用 DeepSeek 的嵌入接口,这可能带来与 DeepSeek LLM 更一致的语义理解。构建向量数据库 (FAISS
):
- FAISS 是一个用于高效相似性搜索和稠密向量聚类的库。
FAISS.from_documents(texts, embeddings)
这个便捷方法会:- 对所有分割后的
texts
(文档块)使用提供的 embeddings
模型生成向量。将这些向量和原始文本块一起存储在 FAISS 索引中。初始化 DeepSeek LLM (ChatDeepseek
):
ChatDeepseek
是 LangChain 中与 DeepSeek 聊天模型交互的类。model="deepseek-chat"
: 指定要使用的 DeepSeek 模型。请查阅 DeepSeek 官方文档获取最新的可用模型名称 (如 deepseek-chat
, deepseek-coder
等)。api_key=api_key
: 传入之前加载的 API Key。temperature=0.1
: 控制模型输出的创造性/随机性。较低的温度(如0.1-0.3)使输出更具确定性和事实性,适合 RAG 场景。较高的温度(如0.7-1.0)则更具创造性。创建 RAG 链 (RetrievalQA
):
RetrievalQA
是 LangChain 提供的一个标准链,它将检索器(Retriever)和 LLM 组合在一起。llm=llm
: 指定我们初始化的 DeepSeek LLM。chain_type="stuff"
: 这是最直接的链类型。它获取所有检索到的文档,将它们的内容“塞入”(stuff)到提示中,然后将这个组合的提示传递给 LLM。其他链类型如 map_reduce
, refine
, map_rerank
用于处理大量文档或需要更复杂处理的场景。retriever=vector_store.as_retriever(search_kwargs={"k": 3})
:vector_store.as_retriever()
: 将我们的 FAISS 向量数据库转换为一个 LangChain Retriever
对象。search_kwargs={"k": 3}
: 配置检索器在每次查询时返回最相关的 k=3
个文档块。可以根据需求调整 k
的值。prompt_template
和 PROMPT
: 定义了一个自定义的提示模板。这非常重要,因为它指导 LLM 如何利用提供的上下文来回答问题,并指示其在信息不足时如何回应(例如,“如果你在上下文中找不到答案,请说你不知道”)。{context}
和 {question}
是占位符,RetrievalQA
链会自动填充它们。chain_type_kwargs={"prompt": PROMPT}
: 将自定义提示应用到 stuff
链。return_source_documents=True
: 使链在返回 LLM 生成的答案(result
)的同时,也返回检索到的源文档片段(source_documents
)。这对于调试和验证答案来源非常有用。进行提问 (qa_chain.invoke
):
- 在一个循环中接收用户输入。
qa_chain.invoke({"query": user_query})
(在 LangChain 0.1.0+ 版本中,旧版为 qa_chain({"query": user_query})
或 qa_chain.run(user_query)
):- 用户的
user_query
首先被传递给 retriever
。retriever
使用嵌入模型将 user_query
转换为向量,并在 FAISS 向量数据库中执行相似性搜索,找到 k=3
个最相关的文档块。这些文档块的内容(作为 context
)和原始 user_query
(作为 question
)被填充到 PROMPT
模板中。最终形成的完整提示被发送给 DeepSeek LLM (llm
)。LLM 根据提供的上下文和问题生成答案。result["result"]
) 和引用的源文档 (result["source_documents"]
)。如何运行
- 将上述 Python 代码保存为
simple_rag.py
。确保你的 .env
文件和 knowledge_base
文件夹与 simple_rag.py
在同一目录下。在终端中运行脚本: python simple_rag.py
程序会加载文档、构建索引,然后提示你输入问题。进阶与优化方向
这个简单的 RAG 系统是一个很好的起点,但还有许多可以优化和扩展的地方:
- 更优的文本分割策略: 探索不同的
chunk_size
和 chunk_overlap
,或使用基于语义的分割器。更强的嵌入模型: all-MiniLM-L6-v2
是轻量级的,对于复杂任务,可以考虑更大更强的嵌入模型,或者 DeepSeek 官方提供的嵌入服务(如果可用)。混合搜索 (Hybrid Search): 结合关键词搜索(如 BM25)和向量搜索,可能会提高检索效果。重排 (Re-ranking): 在初步检索后,使用一个更复杂的模型(如 Cross-Encoder)对检索到的文档进行重排序,以提高最顶部结果的质量。提示工程 (Prompt Engineering): 进一步优化提示模板,引导 LLM 更好地利用上下文。上下文管理: 对于需要多轮对话的场景,需要管理对话历史和上下文。处理找不到答案的情况: 更优雅地处理知识库中没有相关信息的情况。异步处理与流式输出: 对于生产环境,异步处理和流式输出可以改善用户体验。评估: 建立评估 RAG 系统性能的指标和流程(如 RAGAS 框架)。用户界面: 使用 Streamlit 或 Gradio 为 RAG 系统创建一个简单的 Web UI。总结
通过 LangChain、Sentence Transformers、FAISS 和 DeepSeek API,我们成功构建了一个基础的 RAG 系统。这个系统能够从本地知识库中检索信息,并利用 DeepSeek 大模型的强大能力生成基于上下文的回答。RAG 是增强 LLM 应用能力的关键技术,希望本文能为你探索更高级的 AI 应用打下坚实的基础。