最近在尝试将 LangChain 的 RAG 能力与 Agent 框架结合构建一个能主动利用知识库进行问答的智能体时发现网上资料要么过于零散要么只停留在概念层面。本文将分享一套完整的实战方案从零开始构建一个具备知识库查询能力的 RAG Agent。通过本文你将掌握如何将 LangChain 的文档加载、向量化、检索链与 Agent 的决策、工具调用能力无缝集成最终打造一个能理解用户意图、自主检索知识库并给出精准回答的智能系统。无论你是想入门 LangChain 的开发者还是希望将 RAG 能力融入现有 Agent 项目的工程师都能从中获得可直接复用的代码和清晰的架构思路。1. 背景与核心概念在深入代码之前我们有必要厘清几个核心概念理解它们如何协同工作。RAG (Retrieval-Augmented Generation检索增强生成)是一种将外部知识库与大型语言模型LLM结合的技术范式。其核心流程分为三步1)检索根据用户问题从海量文档知识库中找出最相关的片段2)增强将这些相关片段作为上下文与原始问题一起提供给 LLM3)生成LLM 基于增强后的上下文生成最终答案。RAG 有效解决了 LLM 的“幻觉”问题即编造事实和知识更新不及时的痛点。Agent智能体在 LangChain 语境下指的是一个能够感知环境、进行决策并执行动作以完成目标的系统。一个典型的 Agent 由几个部分组成一个作为“大脑”的 LLM、一套可供调用的Tools工具、一个记录交互历史的Memory记忆以及决定下一步行动的AgentExecutor执行器。Agent 的核心魅力在于其能够根据 LLM 的推理自主决定何时、调用何种工具。RAG Agent则是上述两者的结合体。它本质上是一个将“知识库查询”作为核心工具之一的 Agent。当用户提出一个需要事实性知识回答的问题时Agent 会决策调用 RAG 工具该工具内部执行文档检索与上下文增强然后将结果返回给 Agent由 Agent 控制的 LLM 最终组织语言生成答案。这种架构比单纯的 RAG 链更灵活因为 Agent 可以判断问题是否需要查询知识库也可以结合其他工具如计算器、搜索引擎来综合解决问题。简单来说单纯 RAG所有问题都走检索-生成流程。RAG Agent由 LLM 判断“这个问题我需要查知识库吗”如果需要则调用 RAG 工具如果不需要可能直接回答或调用其他工具。2. 环境准备与版本说明本实战基于 Python 环境需要安装 LangChain 及相关依赖。为了专注于架构和流程我们使用轻量级的本地向量数据库ChromaDB和 OpenAI 的 Embedding 模型需 API Key。你也可以替换为其他向量库如 FAISS, Pinecone和 Embedding 模型如 HuggingFace 模型。操作系统: Windows / macOS / Linux 均可。Python 版本: 建议 3.8 及以上。核心库版本以下版本为撰写本文时的稳定版本实际操作时安装最新兼容版本即可。# 创建虚拟环境可选但推荐 python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # 安装核心依赖 pip install langchain langchain-community langchain-openai # 安装向量数据库和文档加载器 pip install chromadb pip install pypdf # 用于加载PDF文档 pip install tiktoken # 用于OpenAI模型的Tokenizer # 安装用于Agent的额外库 pip install langchain-agents版本说明与关键选择langchain: 核心框架。本文示例基于langchain0.1.0的较新版本其模块化程度更高如langchain-community包含社区集成。langchain-openai: 官方维护的 OpenAI 集成。chromadb: 一个轻量、易用的开源向量数据库适合本地开发和测试。本文使用gpt-3.5-turbo作为 LLMtext-embedding-ada-002作为 Embedding 模型。你需要准备有效的OPENAI_API_KEY。项目结构预览rag_agent_project/ ├── data/ # 存放知识库原始文档如PDF、TXT │ └── example.pdf ├── vector_store/ # ChromaDB 持久化存储目录自动生成 ├── main.py # 主程序入口 ├── rag_agent.py # RAG Agent 核心构建模块 └── requirements.txt # 项目依赖3. 核心组件与原理拆解构建一个 RAG Agent 主要涉及四大核心组件理解它们的关系是成功的关键。3.1 文档加载与处理 (Document Loading Splitting)知识库的源头是各种格式的文档。LangChain 提供了大量的DocumentLoader如PyPDFLoader、TextLoader、UnstructuredFileLoader等用于将文件转换为统一的Document对象包含页面内容和元数据。原始文档通常很长直接嵌入效果差。因此需要TextSplitter进行切分。RecursiveCharacterTextSplitter是最常用的分割器它尝试按字符如换行、句号、空格递归地分割文本并尽量保持语义段落完整。关键参数包括chunk_size块大小和chunk_overlap块间重叠重叠部分可以避免上下文断裂。3.2 向量化与存储 (Embedding Vector Store)切分后的文本块需要转换为计算机可理解的数值形式即向量Embedding。我们使用 Embedding 模型如 OpenAI 的text-embedding-ada-002来完成这项工作。每个文本块被映射为一个高维向量语义相近的文本其向量在空间中的距离也更近。VectorStore向量数据库负责存储这些向量及其对应的原始文本。当进行检索时将用户问题也转化为向量然后在向量空间中查找距离最近最相似的文本块。ChromaDB是一个实现了该接口的轻量级数据库。3.3 检索链 (Retrieval Chain)这是 RAG 的核心逻辑。RetrievalQA链封装了检索问答的流程。它内部包含一个Retriever检索器从 VectorStore 中获取相关文档和一个LLM。其工作流程是输入问题 - Retriever 获取相关文档 - 将问题和文档组合成 Prompt - LLM 生成答案。在 Agent 框架中我们将这个RetrievalQA链包装成一个Tool供 Agent 调用。3.4 智能体与工具 (Agent Tools)Agent是协调者。我们通常使用create_react_agent来构建一个基于 ReAct 框架的 Agent。ReAct 鼓励 LLM 以“思考 - 行动 - 观察”的循环来解决问题非常适合工具调用。Tool是 Agent 可以使用的函数或链。每个 Tool 需要有明确的name和description。Description 至关重要因为 LLM 仅根据描述来决定是否以及如何调用该工具。我们将 RAG 链包装成 Tool并为其编写清晰的描述例如“一个用于查询公司内部知识库的工具当问题涉及公司政策、产品文档或历史资料时使用。”最后AgentExecutor是运行 Agent 的引擎它处理 LLM 的输出、调用工具、管理交互历史并控制循环的结束。4. 完整实战构建 RAG Agent接下来我们一步步实现一个完整的 RAG Agent 系统。4.1 创建项目与初始化环境首先创建项目目录和文件。mkdir rag_agent_project cd rag_agent_project mkdir data touch main.py rag_agent.py requirements.txt将你的知识库文档如company_handbook.pdf放入data/目录。在requirements.txt中写入依赖langchain0.1.0 langchain-community0.0.10 langchain-openai0.0.5 chromadb0.4.18 pypdf3.17.0 tiktoken python-dotenv # 用于管理环境变量安装依赖pip install -r requirements.txt。创建一个.env文件来安全存储你的 API Key确保该文件在.gitignore中OPENAI_API_KEYyour_openai_api_key_here4.2 构建知识库向量化存储我们在rag_agent.py中编写构建知识库的函数。# rag_agent.py import os from dotenv import load_dotenv from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma # 加载环境变量 load_dotenv() def create_knowledge_base(pdf_directory./data, persist_directory./vector_store): 从指定目录的PDF文件创建并持久化向量知识库。 Args: pdf_directory: 存放PDF文件的目录。 persist_directory: 向量数据库持久化存储路径。 Returns: retriever: 一个配置好的检索器可直接用于检索。 # 1. 加载文档 documents [] for filename in os.listdir(pdf_directory): if filename.endswith(.pdf): file_path os.path.join(pdf_directory, filename) print(f正在加载文档: {filename}) loader PyPDFLoader(file_path) documents.extend(loader.load()) print(f共加载 {len(documents)} 页文档。) # 2. 分割文本 text_splitter RecursiveCharacterTextSplitter( chunk_size1000, # 每个块约1000字符 chunk_overlap200, # 块间重叠200字符保持上下文 separators[\n\n, \n, 。, , , , , , ] ) splits text_splitter.split_documents(documents) print(f文档被分割成 {len(splits)} 个文本块。) # 3. 创建向量存储 embeddings OpenAIEmbeddings(modeltext-embedding-ada-002) # 如果已有持久化存储则加载否则创建新的 if os.path.exists(persist_directory) and os.listdir(persist_directory): print(f从 {persist_directory} 加载已有向量库...) vectorstore Chroma( persist_directorypersist_directory, embedding_functionembeddings ) else: print(创建新的向量库并持久化...) vectorstore Chroma.from_documents( documentssplits, embeddingembeddings, persist_directorypersist_directory ) vectorstore.persist() # 确保保存到磁盘 print(f向量库已保存至 {persist_directory}) # 4. 创建检索器 # search_kwargs 可以控制返回结果的数量和相似度阈值 retriever vectorstore.as_retriever( search_typesimilarity, search_kwargs{k: 4} # 返回最相关的4个块 ) return retriever if __name__ __main__: # 单独运行此文件可以初始化或更新知识库 retriever create_knowledge_base() print(知识库构建/加载完成。)4.3 创建 RAG 工具 (Tool)接下来我们创建一个 RAG 链并将其封装成 LangChain Tool。# rag_agent.py (续) from langchain_openai import ChatOpenAI from langchain.chains import RetrievalQA from langchain.agents import Tool def create_rag_tool(retriever, tool_nameknowledge_base, tool_descriptionNone): 创建一个基于检索器的RAG问答链并将其包装成Agent可用的Tool。 Args: retriever: 上一步创建的检索器。 tool_name: 工具的名称。 tool_description: 工具的详细描述用于指导Agent何时调用它。 Returns: tool: 一个配置好的Tool对象。 # 初始化LLM llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) # 创建检索问答链 qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # “stuff”将检索到的所有文档塞入上下文适合中等长度文档 retrieverretriever, return_source_documentsFalse, # 为简化不返回源文档 verboseFalse # 设为True可看到链的详细执行过程 ) # 定义工具描述这是Agent决策的关键 if tool_description is None: tool_description 一个专门查询公司内部知识库的工具。当你需要回答关于公司政策、员工手册、 产品规格、技术文档、历史项目资料或任何有明确书面记录的事实性问题时应该使用此工具。 对于需要计算、推理、总结个人意见或查询实时信息如天气、股价的问题请不要使用此工具。 # 将链包装成Tool tool Tool( nametool_name, funcqa_chain.run, # Tool执行时调用qa_chain.run descriptiontool_description, # 注意这里直接使用.run对于复杂交互可能需要包装函数来处理输入格式 ) return tool4.4 构建智能体 (Agent)现在我们创建 Agent并将 RAG 工具和其他可能用到的工具一起赋予它。# rag_agent.py (续) from langchain.agents import create_react_agent, AgentExecutor from langchain import hub # 用于拉取预定义的Prompt def create_rag_agent(retriever, additional_toolsNone): 创建一个具备知识库查询能力的智能体。 Args: retriever: 知识库检索器。 additional_tools: 除RAG工具外Agent可用的其他工具列表。 Returns: agent_executor: 配置好的Agent执行器。 # 1. 初始化LLMAgent的“大脑” llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) # 2. 准备工具列表 rag_tool create_rag_tool(retriever) tools [rag_tool] # 添加其他工具例如一个简单的计算器 if additional_tools: tools.extend(additional_tools) # 3. 拉取一个适合ReAct框架的Prompt # LangChain Hub 上有许多社区共享的Prompt我们使用一个标准的ReAct Prompt prompt hub.pull(hwchase17/react) # 4. 创建Agent agent create_react_agent(llm, tools, prompt) # 5. 创建执行器 agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, # 设为True可以看到Agent的思考过程Thought/Action/Observation handle_parsing_errorsTrue, # 优雅地处理解析错误 max_iterations5, # 限制最大迭代次数防止死循环 early_stopping_methodgenerate # 停止条件 ) return agent_executor4.5 主程序与交互测试最后我们在main.py中整合所有部分并创建一个简单的交互循环。# main.py from rag_agent import create_knowledge_base, create_rag_agent import sys def main(): print( RAG Agent 知识库问答系统 ) print(正在初始化知识库和智能体请稍候...) # 步骤1创建或加载知识库 try: retriever create_knowledge_base() except Exception as e: print(f初始化知识库失败: {e}) print(请检查1. data/目录下是否有PDF文件 2. .env文件中的OPENAI_API_KEY是否正确) sys.exit(1) # 步骤2创建智能体 try: agent_executor create_rag_agent(retriever) print(智能体初始化成功) except Exception as e: print(f创建智能体失败: {e}) sys.exit(1) # 步骤3交互循环 print(\n你可以开始提问了。输入 quit 或 exit 退出程序。) print(- * 50) while True: user_input input(\n你的问题: ).strip() if user_input.lower() in [quit, exit, q]: print(再见) break if not user_input: continue try: # 执行Agent response agent_executor.invoke({input: user_input}) print(f\n助手: {response[output]}) except Exception as e: print(f\n处理问题时出现错误: {e}) print(这可能是因为Agent的解析出错或者知识库中暂无相关信息。请尝试换一种问法。) if __name__ __main__: main()4.6 运行与验证确保你的data/文件夹中有 PDF 文件例如company_handbook.pdf。在项目根目录下运行python main.py。首次运行会花费一些时间加载文档、切分文本、调用 OpenAI API 生成 Embedding 并存入 ChromaDB。控制台会显示进度。初始化成功后进入交互界面。测试用例问题1“我们公司的年假政策是怎样的”假设知识库中有员工手册预期行为Agent 的verbose输出会显示Thought: 用户问的是公司政策我应该使用知识库工具。然后调用 RAG 工具最终给出基于文档的答案。问题2“请计算一下 125 的平方根是多少”预期行为Agent 可能思考后表示无法计算因为它目前只有 RAG 工具。这时我们可以为它添加一个计算器工具见下文扩展。问题3“今天天气怎么样”预期行为Agent 应判断这不是知识库能回答的问题并可能直接回应无法处理。通过观察verboseTrue时的Thought/Action/Observation日志你可以清晰地看到 Agent 的决策过程。5. 功能扩展与进阶优化基础的 RAG Agent 已经能工作但一个健壮的系统还需要更多功能。5.1 为 Agent 添加更多工具一个强大的 Agent 应该能处理多种任务。我们来添加一个计算器工具和一个用于处理通用对话的“兜底”工具。首先安装计算器工具依赖pip install numexpr。然后修改rag_agent.py中的create_rag_agent函数。# rag_agent.py (续在文件顶部导入) from langchain.agents import Tool from langchain.tools import BaseTool from langchain.pydantic_v1 import BaseModel, Field from typing import Optional, Type import numexpr # 1. 定义一个计算器工具使用Pydantic模型定义输入 class CalculatorInput(BaseModel): query: str Field(description一个需要计算的数学表达式例如 3 * 7 5) class CalculatorTool(BaseTool): name calculator description 用于执行数学计算。输入应该是一个明确的数学表达式。 args_schema: Type[BaseModel] CalculatorInput def _run(self, query: str) - str: 执行计算 try: # 使用numexpr安全地评估表达式 result numexpr.evaluate(query).item() return f计算结果为: {result} except Exception as e: return f计算错误: {e} async def _arun(self, query: str) - str: raise NotImplementedError(此工具不支持异步) # 2. 定义一个通用对话工具当问题与任何工具都不匹配时使用 def general_chat(input: str) - str: 处理与知识库或计算无关的通用对话。 # 这里可以连接一个通用的聊天LLM或者返回一个固定回复。 # 为简单起见我们返回一个提示。 return 我主要擅长回答基于公司知识库的事实性问题或者进行数学计算。您的问题超出了我当前的能力范围。 general_tool Tool( namegeneral_chat, funcgeneral_chat, description用于处理问候、闲聊或与知识库、计算无关的通用对话。 ) # 修改 create_rag_agent 函数 def create_rag_agent(retriever, additional_toolsNone): llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) rag_tool create_rag_tool(retriever) calculator_tool CalculatorTool() # 工具列表的顺序有时会影响Agent的偏好将RAG工具放在前面 tools [rag_tool, calculator_tool, general_tool] if additional_tools: tools.extend(additional_tools) prompt hub.pull(hwchase17/react) agent create_react_agent(llm, tools, prompt) agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, handle_parsing_errorsTrue, max_iterations5 ) return agent_executor现在Agent 拥有了三个工具知识库查询、计算器和通用聊天。当被问及“125的平方根是多少”时它应该能正确调用计算器工具。5.2 优化检索质量检索是 RAG 的基石其质量直接决定最终答案的准确性。调整检索参数在create_knowledge_base函数中可以调整search_kwargs{k: 4}。k值越大返回的上下文越多但可能引入噪声。对于事实性强的问答k3或4通常足够。还可以尝试search_typemmr(最大边际相关性)在保证相关性的同时增加结果的多样性。优化文本分割chunk_size和chunk_overlap需要根据文档类型调整。对于技术文档chunk_size800可能更合适对于连贯性强的文章chunk_size1500且overlap300可能更好。添加元数据过滤在加载文档时可以为不同文档或章节添加元数据如source,chapter。在检索时可以让 Retriever 根据元数据过滤实现更精准的查询。# 示例创建带元数据的检索器 retriever vectorstore.as_retriever( search_kwargs{k: 4, filter: {source: employee_handbook.pdf}} )5.3 使用更强大的 Agent 类型create_react_agent是基础。LangChain 还提供了其他类型的 Agent如OpenAI Tools Agent如果使用gpt-3.5-turbo-1106或gpt-4-turbo及以上版本它们原生支持函数调用。这种 Agent 的格式解析更稳定。# 可选使用OpenAI Tools Agent (需要支持函数调用的模型) from langchain.agents import create_openai_tools_agent def create_openai_tools_rag_agent(retriever): llm ChatOpenAI(modelgpt-3.5-turbo-1106, temperature0) # 注意模型版本 rag_tool create_rag_tool(retriever) calculator_tool CalculatorTool() tools [rag_tool, calculator_tool] # 需要为OpenAI Tools Agent准备特定的Prompt from langchain.agents import AgentExecutor from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder prompt ChatPromptTemplate.from_messages([ (system, 你是一个有帮助的助手可以访问知识库和计算器。), (user, {input}), MessagesPlaceholder(variable_nameagent_scratchpad), ]) agent create_openai_tools_agent(llm, tools, prompt) agent_executor AgentExecutor(agentagent, toolstools, verboseTrue) return agent_executor6. 常见问题与排查思路在开发和运行过程中你可能会遇到以下问题问题现象常见原因解决思路运行main.py时报错No module named langchain_community依赖未正确安装或版本冲突。1. 检查requirements.txt是否正确。2. 尝试pip install --upgrade langchain-community。3. 确认虚拟环境已激活。知识库初始化时卡住或报 OpenAI 认证错误OPENAI_API_KEY未设置或无效。1. 检查.env文件是否存在且密钥格式正确。2. 在代码中临时print(os.getenv(OPENAI_API_KEY))验证是否加载。3. 确认 API Key 有余额且未过期。Agent 不调用 RAG 工具总是直接回答或说不知道1. Tool 的description描述不清晰。2. LLM 温度 (temperature) 过高导致输出不稳定。3. Prompt 不适合。1. 精炼 Tool 的description明确使用场景和限制。2. 将temperature设为 0 以获得更确定性的输出。3. 尝试不同的 Prompt如hwchase17/react-chat用于对话。4. 开启verboseTrue观察 Agent 的思考过程。检索到的内容不相关导致答案错误1. 文本分割不合理。2. Embedding 模型不适合。3. 检索数量k不合适。1. 调整chunk_size和chunk_overlap。2. 尝试不同的 Embedding 模型如text-embedding-3-small。3. 减小k值或使用MMR搜索类型。4. 检查源文档质量确保是清晰可读的文本。Agent 陷入循环不断重复同一个动作max_iterations设置过高或停止条件不明确。1. 将max_iterations设为较小值如 5。2. 在 Prompt 中明确指示“在得到最终答案后你必须以 ‘Final Answer:’ 开头输出”。处理中文文档时效果差默认的文本分割器对中文支持不佳。1. 使用专门的中文文本分割器如ChineseTextSplitter需自定义或从社区找。2. 在RecursiveCharacterTextSplitter的separators参数中加入中文标点如[“\n\n”, “\n”, “。”, “”, “”, “”, “”, “ ”, “”]。程序运行慢1. 每次提问都重新初始化向量库。2. Embedding 调用有网络延迟。1. 确保向量库持久化只需初始化一次。2. 对于大量查询可以考虑缓存 Embedding 结果或使用本地 Embedding 模型如BGE、text2vec。7. 最佳实践与工程建议将 RAG Agent 投入生产环境或严肃项目时需要考虑以下几点1. 知识库管理与更新版本控制将原始文档和生成向量库的脚本纳入 Git 管理。当文档更新时应有流程重新生成向量库。增量更新对于频繁更新的知识库研究 ChromaDB 或其他向量库的增量添加功能避免全量重建。元数据管理为每个文档块添加丰富的元数据如来源、更新时间、章节、权限等级便于检索时过滤和追溯答案来源。2. 系统健壮性错误处理在agent_executor.invoke外围添加全面的 try-catch处理网络超时、API 限额、工具调用失败等异常给用户友好的反馈。超时控制为 LLM 调用和工具执行设置超时防止单个请求阻塞整个系统。验证与测试构建一个测试集包含各类问题事实性、计算性、闲聊、边界情况定期运行以评估系统整体表现。3. 性能优化缓存对频繁出现的相同或相似查询的结果进行缓存可以显著减少对 LLM 和 Embedding 模型的调用降低成本并提高响应速度。异步处理如果处理流程长考虑使用异步框架如asyncio来并行执行某些任务。本地化部署对于数据敏感或要求低延迟的场景考虑使用本地部署的 LLM如 Llama 2、ChatGLM和 Embedding 模型以及本地向量数据库。4. 可观察性与评估日志记录详细记录每个用户查询、Agent 的思考过程、调用的工具、检索到的文档片段以及最终答案。这对于调试和优化至关重要。答案溯源在最终答案中可以附上引用来源如“根据《XX手册》第Y章……”。这需要让 RAG 链返回source_documents。人工反馈循环设计机制收集用户对答案质量的反馈如“有帮助/无帮助”用于持续改进系统。5. 安全与权限输入净化对用户输入进行基本的清理和检查防止注入攻击或滥用。工具权限不同的 Agent 实例或用户可能拥有不同的工具集。例如普通员工 Agent 可能只能查询公共知识库而管理员 Agent 可以访问所有工具。输出审查对于高风险应用可以考虑对 LLM 生成的内容进行二次审查或过滤。构建 RAG Agent 是一个迭代过程。从最简单的原型开始逐步添加工具、优化检索、完善提示词并围绕实际业务需求进行打磨。本文提供的代码框架是一个坚实的起点你可以在此基础上探索更复杂的 Agent 架构如使用 LangGraph 编排多 Agent 工作流、集成更多样的工具数据库、API、自定义函数以及应用更先进的 RAG 技术如重排序、HyDE 等从而打造出真正强大和实用的智能应用。