从RAG到智能体:构建能检索、规划与执行的RAG Agent实战指南
如果你正在构建一个基于大语言模型LLM的智能应用是否遇到过这样的困境模型回答看似流畅但一旦涉及你公司内部的文档、代码库或专业知识它就变得“一问三不知”甚至开始胡编乱造你或许已经尝试了RAG检索增强生成技术搭建了向量知识库让模型能“查阅资料”后再回答。但很快新的问题又出现了当用户的问题复杂到需要多步推理、调用外部工具或执行具体操作时一个简单的“检索-生成”流水线就显得力不从心了。这正是RAG Agent要解决的核心问题。它不是一个新概念而是将RAG的“知识检索”能力与Agent的“自主规划与工具调用”能力深度融合的实践范式。很多人以为Agent只是聊天机器人或者RAG只是给模型加了个搜索引擎。实际上RAG Agent的关键价值在于它让大模型从一个被动的“答题者”转变为一个能主动利用知识库、规划步骤、使用工具来“解决问题”的智能体。本文将以一个实战项目为例手把手带你构建一个功能完整的RAG Agent。我们将超越简单的问答实现一个能根据知识库内容进行逻辑判断、并调用工具执行操作的智能体。你将了解到RAG Agent的核心架构它与普通RAG和普通Agent的本质区别在哪里。从零搭建的完整流程包括知识库构建、Agent框架选择、工具定义与集成。关键代码实现使用LangChain框架提供可复用的核心模块代码。高级技巧与避坑指南如何设计有效的工具、处理复杂查询、以及提升系统稳定性。无论你是想为内部团队打造一个智能助手还是开发面向客户的专业问答系统理解并实践RAG Agent都将是你技术栈中至关重要的一环。1. RAG Agent解决复杂场景的“大脑”与“手脚”在深入代码之前我们必须厘清几个关键概念以及为什么需要将它们结合起来。传统RAG的局限标准的RAG流程是“检索相关文档片段 - 拼接成提示词 - 生成答案”。它擅长回答事实性问题比如“公司年假政策是什么”。但对于“帮我对比一下项目A和项目B在上季度的KPI完成情况并写一份摘要报告”这类问题传统RAG就束手无策了。它缺乏拆解问题、分步执行、汇总信息的能力。传统Agent的局限一个Agent可以通过规划Planning和工具调用Tool Calling来完成复杂任务比如“查询天气 - 计算出行时间 - 预订航班”。但如果任务涉及大量非公开的、结构化的领域知识如公司制度、产品手册Agent缺乏一个高效、准确的“记忆库”来支撑其决策。RAG Agent 知识库记忆 推理规划大脑 工具调用手脚它的工作流程可以概括为理解与规划Agent接收用户查询理解其复杂意图。知识检索针对规划中需要事实支撑的步骤从向量知识库中检索相关信息。工具执行调用合适的工具如计算器、API、数据库查询执行具体操作。综合生成结合检索到的知识和工具执行的结果生成最终答案或执行下一步规划。这个循环可能迭代多次。例如用户问“根据我们的销售手册客户XXX属于哪一档如果是VIP档帮他计算一下本次订单的折扣价。” Agent需要1) 检索销售手册判断客户等级2) 若为VIP则调用折扣计算工具。2. 环境准备与核心工具选型本次实战我们将使用LangChain这一流行的LLM应用开发框架。它的优势在于提供了构建Agent和RAG所需的大量标准化组件并且与多种模型和向量数据库兼容。2.1 基础环境Python: 3.8 或更高版本。包管理: 使用pip或conda。LLM服务: 我们将使用 OpenAI 的 GPT 系列模型如 gpt-3.5-turbo作为Agent的“大脑”。你需要准备一个有效的 OpenAI API Key。当然你也可以替换为其他兼容的模型如通过 Ollama 本地部署的模型。向量数据库: 为了存储和检索知识我们选择Chroma因为它轻量、易用且与LangChain集成良好。生产环境也可考虑 Qdrant, Pinecone 等。2.2 安装依赖创建一个新的Python虚拟环境然后安装以下核心包# 核心框架 pip install langchain langchain-community langchain-openai # 用于文本分割和嵌入 pip install sentence-transformers # 向量数据库 pip install chromadb # 用于网页内容抓取示例工具 pip install beautifulsoup4 requests # 环境变量管理推荐 pip install python-dotenv2.3 项目结构规划一个清晰的项目结构有助于管理复杂度rag_agent_project/ ├── knowledge_base/ # 知识库相关 │ ├── raw_docs/ # 存放原始文档PDF, TXT, MD等 │ ├── load_and_split.py # 文档加载与分割脚本 │ └── create_vectorstore.py # 创建向量库脚本 ├── tools/ # 自定义工具 │ └── custom_tools.py ├── agents/ # Agent定义 │ └── rag_agent.py ├── app.py # 主应用入口 ├── .env # 存储API密钥等敏感信息 └── requirements.txt3. 第一步构建你的专属向量知识库知识库是RAG Agent的“长期记忆”。质量直接决定检索效果。3.1 准备与加载文档将你的领域文档如产品手册、API文档、公司规章的PDF或TXT文件放入knowledge_base/raw_docs/。我们创建一个load_and_split.py脚本来处理文档# knowledge_base/load_and_split.py from langchain_community.document_loaders import DirectoryLoader, TextLoader, PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter import os def load_and_split_documents(data_path./raw_docs, chunk_size500, chunk_overlap50): 加载指定目录下的所有文档并进行文本分割。 参数: data_path: 原始文档目录路径 chunk_size: 每个文本块的最大字符数 chunk_overlap: 块之间的重叠字符数保持上下文连贯 # 支持多种格式 loaders { .txt: TextLoader, .pdf: PyPDFLoader, # 可扩展更多如 .md: UnstructuredMarkdownLoader } all_docs [] for ext, loader_class in loaders.items(): file_pattern f**/*{ext} try: loader DirectoryLoader(data_path, globfile_pattern, loader_clsloader_class, show_progressTrue) docs loader.load() all_docs.extend(docs) print(fLoaded {len(docs)} documents with extension {ext}) except Exception as e: print(fWarning: Could not load files with pattern {file_pattern}: {e}) if not all_docs: raise ValueError(fNo documents found in {data_path} or failed to load.) # 文本分割这是关键步骤影响检索精度 text_splitter RecursiveCharacterTextSplitter( chunk_sizechunk_size, chunk_overlapchunk_overlap, length_functionlen, separators[\n\n, \n, 。, , , , , , ] ) splits text_splitter.split_documents(all_docs) print(fSplit {len(all_docs)} documents into {len(splits)} chunks.) return splits if __name__ __main__: # 示例处理当前目录下的raw_docs文件夹 documents load_and_split_documents() # 可以在这里预览前几个片段 for i, doc in enumerate(documents[:2]): print(f\n--- Chunk {i} ---) print(doc.page_content[:200])关键点chunk_size和chunk_overlap需要根据文档类型和模型上下文长度调整。一般经验是块大小在500-1000字符重叠50-150字符。分割策略至关重要。RecursiveCharacterTextSplitter会优先按段落、句子等自然分隔符切割比简单按字符切割效果好得多。3.2 生成嵌入并存入向量数据库接下来我们将分割后的文本块转换为向量嵌入并存储到ChromaDB中。# knowledge_base/create_vectorstore.py from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma import os from dotenv import load_dotenv from load_and_split import load_and_split_documents # 加载环境变量其中应包含 OPENAI_API_KEY load_dotenv() def create_and_persist_vectorstore(persist_directory./chroma_db): 创建向量存储并持久化到本地目录。 # 1. 加载并分割文档 print(Loading and splitting documents...) splits load_and_split_documents() # 2. 初始化嵌入模型 # 使用OpenAI的text-embedding-ada-002也可替换为其他如HuggingFace模型 embedding_model OpenAIEmbeddings(modeltext-embedding-ada-002) # 3. 创建向量存储 print(Creating vectorstore...) # 将分割后的文档、嵌入函数和持久化目录传入 vectorstore Chroma.from_documents( documentssplits, embeddingembedding_model, persist_directorypersist_directory ) # 4. 持久化到磁盘Chroma会自动执行这里显式调用确保完成 vectorstore.persist() print(fVectorstore created and persisted to {persist_directory}) print(fTotal {vectorstore._collection.count()} chunks indexed.) return vectorstore if __name__ __main__: create_and_persist_vectorstore()运行此脚本后会在项目根目录生成一个chroma_db文件夹里面存储了所有文本块的向量索引。以后应用启动时只需加载这个目录无需重新处理文档。4. 第二步为Agent打造“工具箱”Agent的强大之处在于能使用工具。工具可以是任何能执行特定功能的函数或API。这里我们定义几个示例工具。4.1 定义基础工具创建一个tools/custom_tools.py文件# tools/custom_tools.py from langchain.tools import tool from datetime import datetime import math import requests from bs4 import BeautifulSoup tool def get_current_time(): 获取当前的日期和时间。当用户询问时间、日期或需要时间戳时使用此工具。 now datetime.now() return now.strftime(%Y-%m-%d %H:%M:%S) tool def calculate(expression: str): 执行数学计算。支持加减乘除、乘方、开方等基本运算。 参数: expression: 数学表达式字符串例如 3 5 * 2, sqrt(16) # 注意使用eval有安全风险此处仅作演示。生产环境应使用更安全的解析库如ast.literal_eval或限制表达式。 try: # 为表达式添加安全的数学函数 safe_dict {__builtins__: None} safe_dict.update(math.__dict__) # 简化处理实际建议用更安全的方式 result eval(expression, {__builtins__: {}}, safe_dict) return f计算结果: {expression} {result} except Exception as e: return f计算错误: {e} tool def search_web(query: str): 根据查询词进行网页搜索模拟。在实际应用中你可以接入真正的搜索引擎API。 参数: query: 搜索关键词 # 这是一个模拟函数。真实场景下你可以调用Serper API、Google Custom Search等。 print(f[模拟] 正在搜索: {query}) # 模拟返回一些结果 mock_results [ {title: f关于 {query} 的百科介绍, snippet: f这里是一些关于{query}的模拟摘要信息...}, {title: f{query} 的最新新闻, snippet: 模拟新闻内容...}, ] return mock_results tool def fetch_webpage_content(url: str): 获取指定URL的网页正文内容简化版。 参数: url: 网页URL try: headers {User-Agent: Mozilla/5.0} response requests.get(url, headersheaders, timeout10) response.raise_for_status() soup BeautifulSoup(response.content, html.parser) # 简单提取正文实际应用可能需要更复杂的清洗 for tag in soup([script, style, header, footer, nav]): tag.decompose() text soup.get_text(separator , stripTrue) return text[:2000] # 限制返回长度 except Exception as e: return f获取网页内容失败: {e} # 工具列表方便导入 CUSTOM_TOOLS [get_current_time, calculate, search_web, fetch_webpage_content] if __name__ __main__: # 测试工具 print(get_current_time.invoke({})) print(calculate.invoke({expression: 3**2 4**2}))工具设计要点清晰的文档字符串Docstring这是最重要的LangChain Agent依赖工具的描述来决定在什么情况下调用哪个工具。描述务必准确、具体。明确的参数使用类型注解让Agent知道需要提供什么参数。单一职责一个工具只做一件事。不要设计一个“万能”工具。错误处理工具内部应处理好异常并返回友好的错误信息避免导致Agent崩溃。5. 第三步组装RAG检索器与创建智能体这是最核心的部分。我们将把向量知识库封装成一个特殊的“检索工具”并与其他工具一起交给Agent。5.1 创建RAG检索工具首先我们需要一个函数能够根据问题从知识库中查找相关信息。# agents/rag_agent.py (部分1) from langchain_openai import ChatOpenAI from langchain_community.vectorstores import Chroma from langchain_openai import OpenAIEmbeddings from langchain.tools.retriever import create_retriever_tool from langchain.agents import AgentExecutor, create_openai_tools_agent from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from dotenv import load_dotenv import os # 加载环境变量 load_dotenv() def setup_rag_retriever(persist_directory./chroma_db): 加载已持久化的向量数据库并创建检索器 embedding_model OpenAIEmbeddings(modeltext-embedding-ada-002) vectorstore Chroma( persist_directorypersist_directory, embedding_functionembedding_model ) # 将向量库转换为检索器可以配置搜索参数如返回结果数k retriever vectorstore.as_retriever(search_kwargs{k: 3}) # 返回最相关的3个片段 return retriever def create_rag_tool(retriever, namecompany_knowledge_base, descriptionNone): 将检索器包装成一个Agent可用的工具。 参数: retriever: 上面创建的检索器对象 name: 工具名称 description: 工具描述告诉Agent何时使用它。务必清晰 if description is None: description ( 专门用于检索公司内部知识库信息。 当用户的问题涉及公司产品、政策、流程、规章制度、历史数据等内部知识时优先使用此工具。 输入应为具体的问题或关键词。 ) rag_tool create_retriever_tool( retriever, namename, descriptiondescription ) return rag_tool5.2 构建Agent执行器现在我们将RAG工具、自定义工具和LLM组合起来创建一个能规划、检索、执行的智能体。# agents/rag_agent.py (部分2) from tools.custom_tools import CUSTOM_TOOLS def create_rag_agent_executor(): 创建并返回一个配置好的RAG Agent执行器。 # 1. 初始化LLMAgent的大脑 llm ChatOpenAI(modelgpt-3.5-turbo-1106, temperature0, streamingFalse) # 注意使用OpenAI的function calling/tool calling能力需要模型支持如gpt-3.5-turbo-1106及以上版本 # 2. 准备工具列表 # 2.1 创建RAG工具 retriever setup_rag_retriever() rag_tool create_rag_tool(retriever) # 2.2 组合所有工具 tools [rag_tool] CUSTOM_TOOLS # 3. 设计Agent的提示词模板 # 这是指导Agent行为的关键。清晰的系统提示能极大提升表现。 system_prompt 你是一个专业的助手拥有访问公司内部知识库的权限并且可以使用多种工具。 你的职责是准确、高效地回答用户的问题或完成用户的任务。 请遵循以下步骤思考 1. 首先理解用户问题的核心。 2. 如果问题明确涉及公司内部信息如产品、政策、员工、历史记录你必须使用“company_knowledge_base”工具来查找相关信息。 3. 如果需要计算、获取当前时间、搜索公开网络信息或获取网页内容请使用相应的专用工具。 4. 如果你已经从知识库或工具中获得了足够信息请综合这些信息给出清晰、完整的最终答案。 5. 如果信息不足或工具执行失败请诚实地告知用户你无法完成并说明原因。 请始终以专业、友好的态度进行交流。 prompt ChatPromptTemplate.from_messages([ (system, system_prompt), MessagesPlaceholder(variable_namechat_history), # 支持多轮对话历史 (human, {input}), MessagesPlaceholder(variable_nameagent_scratchpad), # Agent的思考过程 ]) # 4. 创建Agent agent create_openai_tools_agent(llmllm, toolstools, promptprompt) # 5. 创建执行器它负责运行Agent并处理工具调用循环 agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, # 设为True可以看到Agent的思考过程调试时非常有用 handle_parsing_errorsTrue, # 处理解析错误避免崩溃 max_iterations5, # 限制最大迭代次数防止死循环 early_stopping_methodgenerate, # 当Agent认为可以给出最终答案时停止 ) return agent_executor6. 第四步运行与测试你的RAG Agent让我们创建一个主程序来启动并测试这个智能体。# app.py from agents.rag_agent import create_rag_agent_executor import os from dotenv import load_dotenv load_dotenv() def main(): # 检查API Key if not os.getenv(OPENAI_API_KEY): print(错误: 请在 .env 文件中设置 OPENAI_API_KEY) return print(正在初始化RAG Agent...) agent_executor create_rag_agent_executor() print(初始化完成你可以开始提问了。输入 quit 或 exit 退出。\n) # 简单的聊天循环 while True: try: user_input input(\n你: ) if user_input.lower() in [quit, exit, q]: print(再见) break if not user_input.strip(): continue print(\n助手: , end, flushTrue) # 调用Agent执行器 response agent_executor.invoke({input: user_input, chat_history: []}) print(response[output]) except KeyboardInterrupt: print(\n\n程序被中断。) break except Exception as e: print(f\n发生错误: {e}) if __name__ __main__: main()6.1 运行与效果验证准备环境变量在项目根目录创建.env文件内容如下OPENAI_API_KEY你的OpenAI_API密钥构建知识库确保你的文档在knowledge_base/raw_docs/下然后运行python knowledge_base/create_vectorstore.py看到成功创建并持久化向量库的提示。启动Agentpython app.py测试查询纯知识库问题“我们公司的年假政策是怎样的”假设知识库中有员工手册混合型问题“今天是几号另外根据公司规定我今年还有多少天年假”需要调用get_current_time工具和RAG工具复杂推理问题“帮我计算一下如果项目预算文档里说硬件成本是5万软件成本是3万那么总成本是多少占总预算10万的百分比”需要RAG检索出成本数字再调用calculate工具预期效果当Agent遇到涉及内部知识的问题时你会从verbose日志中看到它调用了company_knowledge_base工具并接收到了检索到的文档片段。然后它会结合这些片段和工具计算结果生成最终答案。7. 常见问题与排查思路在开发RAG Agent过程中你可能会遇到以下典型问题问题现象可能原因排查方式解决方案Agent不调用知识库工具1. 工具描述不清晰。2. 用户问题表述太泛Agent认为无需检索。3. LLM温度temperature过高导致行为随机。1. 检查create_rag_tool中的description是否准确描述了使用场景。2. 开启verboseTrue观察Agent的思考链Reasoning Chain。3. 尝试更具体地提问。1. 重写工具描述包含明确的关键词和场景。2. 在系统提示词中强制要求涉及内部信息时使用该工具。3. 将LLM的temperature设为0。检索结果不相关1. 文本分割策略不佳块太大或太小。2. 嵌入模型不适合领域文本。3. 检索器返回结果数k不合适。1. 检查知识库中的文本块内容看是否完整保留了语义单元。2. 尝试不同的chunk_size和chunk_overlap。3. 测试不同的嵌入模型。1. 优化RecursiveCharacterTextSplitter的分隔符和大小。2. 针对中文或专业领域可尝试text2vec,bge等嵌入模型。3. 调整search_kwargs{k: n}尝试不同的n值。Agent陷入循环或迭代次数过多1. 工具执行结果未能满足Agent预期导致其反复尝试。2.max_iterations设置过高。观察verbose日志看Agent在哪一步卡住工具返回了什么。1. 优化工具设计确保其返回格式稳定、信息充足。2. 适当降低max_iterations如设为3-5。3. 在系统提示词中明确给出停止条件。处理速度慢1. 网络延迟调用OpenAI API。2. 检索的文档块太多或太大。3. Agent规划步骤过多。1. 使用本地模型如Ollama替代部分API调用。2. 分析耗时主要在哪个环节。1. 考虑对知识库进行摘要或索引优化。2. 限制检索返回的文档块数量和大小。3. 使用更轻量的LLM进行规划。多轮对话中遗忘历史Agent执行器默认不自动维护对话历史。检查agent_executor.invoke时是否传入了chat_history参数。在应用层维护一个对话历史列表并在每次调用时传入。需要设计历史消息的管理策略如长度限制。8. 进阶优化与最佳实践构建一个可用的RAG Agent只是第一步要使其健壮、高效还需要考虑以下方面8.1 知识库优化混合检索不要只依赖向量检索。对于精确匹配如产品代号、ID可以结合关键词检索如BM25。元数据过滤为文档块添加元数据如来源文件、章节、日期。检索时可以让用户指定“只在某类文档中搜索”。重排序Re-ranking向量检索返回Top K个结果后使用一个更精细的交叉编码器模型对结果进行重排序提升最相关结果的排名。定期更新建立知识库的更新机制确保信息时效性。8.2 Agent提示工程角色扮演在系统提示词中为Agent赋予更具体的角色如“资深技术支持专家”、“数据分析师”能引导其产生更专业的回答。分步指令明确要求Agent“先检索知识库再进行分析最后调用工具计算”比模糊的指令效果更好。输出格式约束要求Agent以特定格式如Markdown列表、JSON输出便于后续程序处理。8.3 工具设计进阶工具组合可以设计更高级的工具例如一个“数据分析”工具其内部先调用RAG获取数据再调用计算工具处理。工具验证在工具被调用前对输入参数进行验证和清洗避免无效调用。工具状态管理对于有状态的工具如登录会话需要设计好状态的管理和传递。8.4 生产环境考量错误处理与降级对LLM API调用、工具调用做好异常捕获。当核心工具失败时应有降级方案如返回缓存结果、提示用户简化问题。日志与监控详细记录Agent的思考过程、工具调用和结果用于分析和优化。成本控制监控Token使用量特别是当知识库文档块很大时检索到的内容会占用大量提示词空间。可以考虑对检索结果进行摘要后再喂给LLM。安全与权限RAG Agent能访问内部知识库和外部工具必须做好权限控制。例如区分不同用户可访问的知识范围对工具调用进行鉴权。通过以上步骤你不仅搭建了一个RAG Agent的demo更掌握了一套应对复杂、知识密集型任务的AI应用架构方法。这个模式可以扩展到客服系统、内部知识问答、智能数据分析等多种场景。记住核心在于让大模型在“知识”和“行动”之间形成闭环从而真正解决实际问题。