
简介面向AI开发者与机器学习爱好者的Agentic RAG解析源码包聚焦传统RAG在单一知识源、一次性检索上的局限系统梳理代理式检索增强生成的工作原理、单/多代理架构及与普通RAG的差异并交代其优势、局限与企业应用前景。包内共3个文件包含核心演示HTML页面、inscode运行配置与Git忽略规则文件压缩包仅8KB结构精简适合快速查看并运行示例。已有139人学习资源通过可运行代码配合解析说明直观展示基于函数调用语言模型和代理框架如DSPy、LangChain的两种实施路径帮助读者理解多代理协作与动态信息源选择机制。读者能借此掌握Agentic RAG的设计思路在智能信息检索、动态决策场景中评估其适用性同时认识开发维护成本、系统集成等技术门槛为后续项目选型与动手实践提供参考。 做 RAG 项目做到后期很多人都会撞上一堵墙问题稍微绕一点传统的“检索-拼接-生成”三板斧就开始失灵。用户问的不是单一事实而是“帮我对比 A 和 B 在 C 场景下的差异再结合 D 给出建议”这时候你把文档切成块、算个相似度、塞给大模型得到的答案常常是东拼西凑的幻觉产物。我去年下半年开始把项目从普通 RAG 往 Agentic RAG 方向重构效果提升是肉眼可见的而且整套逻辑并不玄乎核心就是一句话让大模型自己决定怎么查、查什么、查几次。这篇博客就把我整理的 Agentic RAG 完整解析和一套可运行源码的思路分享出来适合已经做过基础 RAG、想往深处走的开发者参考。1. Agentic RAG 到底是什么说清楚这个概念之前得先聊聊传统 RAG 的逻辑。它本质上是一条流水线用户提问系统把问题向量化去向量库里捞 Top-K 相关片段拼成上下文交给大模型生成回答。这条流水线的隐含假设是“一次检索就够”但真实世界的查询根本不会这么乖。比如用户问“我们上季度在华东区的销售额为什么下滑和前年的同期数据比怎么样”这种问题涉及多表、多时间段、多维度对比一次检索根本捞不齐捞回来的片段大概率还互相矛盾。Agentic RAG 换了个思路把大模型从“被动接收上下文”变成“主动调度工具”。它不再假设一次检索能搞定问题而是让 Agent 自己判断——先查什么、查完再看结果、结果不够就换个关键词再查、查到了再综合分析。这个过程本质上把 RAG 的“检索策略”交给了模型来决策检索不再是固定的前置步骤而是 Agent 可以反复调用的“工具”之一。两者最核心的差异有四点决策权不同传统 RAG 的检索策略是开发者写死的Agentic RAG 的检索策略是模型实时决策的。交互次数不同传统 RAG 通常只有一轮检索Agentic RAG 支持多轮检索甚至查完还不满意会改写查询再查。信息组织方式不同传统 RAG 把所有片段一次性塞给模型Agentic RAG 会把中间结果作为“观察”一步步积累最后才统一生成答案。可解释性不同Agentic RAG 的思考轨迹是可以跟踪的你能清楚地看到模型先查了什么、为什么决定换个查法这对排查问题帮助极大。如果你还没接触过这个概念一个比较贴近的类比是传统 RAG 像你去图书馆请管理员直接抱一摞书给你搬来哪本你就读哪本Agentic RAG 像是管理员先问你大概找什么去书架上翻一翻发现不够又回头问你要不要换个关键词来回几次最后把真正有用的几页递给你。成本高了一点但准确率提升的逻辑很朴素。2. 可运行源码的整体设计与技术选型源码如果不给可运行的部分就等于纸上谈兵。我这套 demmo 设计时先定了几条硬性要求结构要简单到能一眼看懂依赖要少到不容易装崩同时要保留 Agentic RAG 的核心骨架方便后续往自己的项目里移植。2.1 技术栈怎么选我选了三件套LangGraph 负责 Agent 编排FAISS 做向量检索FastAPI 包一层服务接口。大模型接口用 OpenAI 兼容格式方便替换成任意本地模型。选 LangGraph 而不是直接裸写 LangChain Agent原因很实际LangGraph 把 Agent 的运行状态建模成一张显式的图节点和边都看得见对于“先路由、再检索、再生成”这种流程调试体验比隐式的 AgentExecutor 要舒服得多。而且 LangGraph 内置了StateGraph可以让每个节点往共享状态里写入内容Agent 的思考过程、中间检索结果都能留存排查问题的时候一目了然。FAISS 选它就是因为轻。项目早期没必要上 Milvus 或者 Qdrant 这种重服务FAISS 一个本地索引文件搞定够用到万级文档规模。以后量大了再换后端代码改动也就是替换一个 retriever 类的事。模型我默认用的 OpenAI 的 GPT-4o-mini因为便宜且工具调用能力稳。但代码里所有模型调用都走langchain_openai的标准接口你只要改环境变量里的 base_url 和 api_key直接切换成 Ollama 或其他兼容服务。2.2 目录结构与工作流设计源码的目录结构刻意保持了精简agentic_rag/ ├── main.py # FastAPI 入口文件 ├── agent/ │ ├── graph.py # 定义 Agent 状态图 │ ├── nodes.py # 各节点处理逻辑 │ └── state.py # Agent 状态数据结构 ├── retriever/ │ ├── vector_store.py # FAISS 索引构建与检索 │ └── tools.py # 检索工具封装 ├── docs/ │ └── sample_data.txt # 示例知识库文档 └── requirements.txt工作流的执行链路是接收用户问题先进入路由节点判断是否需要检索需要的话走“改写查询—检索—检查结果”的循环直到信息足够了再进入生成节点如果判断为纯闲聊就直接应答不走检索。这个设计的巧妙之处在于把“检索”变成了可重复执行的工具而不是必经之路既省 token 又避免无关检索对生成造成干扰。2.3 为什么需要状态图Agentic RAG 和传统 RAG 最不一样的地方就是每一步的结果都可能影响下一步的决策。这种“情况多样、需要中途判断”的流程最适合用状态图来描述。每一轮跑完之后Agent 手上都握着最新的状态——当前问题、历史检索记录、已有的上下文片段——然后决定下一步走哪个分支。LangGraph 的StateGraph对这一套表达得非常自然节点就是函数边就是条件判断。3. 核心模块的代码实现与拆解光有设计还不够我把核心代码逐一拆开讲每个关键点都会解释为什么这么写。这一节是整篇的干货核心建议边看边对照自己的需求想。3.1 Agent 状态定义状态对象是整个 Agent 运行时的“记忆中枢”所有节点之间的信息传递都靠它完成。# agent/state.py from typing import TypedDict, List, Optional class AgentState(TypedDict): question: str # 用户的原始问题 rewritten_question: Optional[str] # 改写后的问题可能经过多轮 retrieved_docs: List[str] # 已检索到的文档片段 search_count: int # 已执行的检索次数 need_more: bool # 是否需要继续检索 final_answer: Optional[str] # 最终生成的答案 chat_history: List[dict] # 多轮对话历史字段设计上我故意多留了一个need_more标志这是实现“多轮检索”的关键。检索完一轮后模型判断信息不够就把这个字段置为 True图就会重新走检索分支而不是直接进入生成。search_count用来限制最多检索几轮防止 Agent 陷入无限循环烧 token。3.2 路由节点路由节点解决的是“这个问题到底要不要检索”的问题。用户说“你好”你不该去向量库里捞文档用户问“去年营收多少”你不检索就是瞎编。# agent/nodes.py ROUTE_PROMPT 你是查询路由助手。判断用户问题是否需要检索知识库。 输入: - 问题: {question} 输出要求: - 如果问题涉及具体事实、数据、文档内容回答: RETRIEVE - 如果问题是一般寒暄、通用知识或与给定知识库无关回答: CHAT - 只输出 RETRIEVE 或 CHAT不要输出其他内容。 def route_node(state: AgentState) - dict: from langchain_openai import ChatOpenAI llm ChatOpenAI(modelgpt-4o-mini, temperature0) prompt ROUTE_PROMPT.format(questionstate[question]) resp llm.invoke(prompt).content.strip().upper() # LangGraph 的边决定走哪个分支 return {need_more: resp RETRIEVE}注意路由节点里temperature0这类判断任务不需要任何随机性稳定输出比什么都重要。实际跑下来这个路由准确率相当高大部分误判都发生在“问题打了擦边球、知识库里其实有相关内容”的情况所以我会在提示词里强调一句“只要不确定就检索”。3.3 查询改写节点查询改写是容易被忽略但价值极高的一步。用户的问题通常是口语化的、指代不清的直接拿原问题去向量检索召回效果往往不好。比如用户说“它上季度的表现怎么样”如果不结合历史消息把“它”替换成“华东区销售团队”检索出来的东西大概率跑偏。REWRITE_PROMPT 你是查询改写专家。根据对话历史将用户的追问改写为独立、明确的检索查询。 问题: {question} 对话历史: {chat_history} 要求: - 补全省略的指代和隐含信息 - 保持原意不要臆造不存在的条件 - 只输出改写后的查询语句。 def rewrite_node(state: AgentState) - dict: llm ChatOpenAI(modelgpt-4o-mini, temperature0) prompt REWRITE_PROMPT.format( questionstate[question], chat_historystate.get(chat_history, []) ) rewritten llm.invoke(prompt).content.strip() return {rewritten_question: rewritten}实际经验告诉我改写后的问题检索出来的片段和原问题检索出来的片段重合率往往不到一半。这意味着之前很多“检索不到好内容”的问题根源不在向量库而是查询本身就没表达清楚。加了这一步之后召回质量提升非常明显。3.4 检索工具封装检索这一步我封装成了一个带注解的工具函数方便模型后续按工具调用。这里的实现比较简单但生产环境你完全可以在工具里加权限校验、加日志、加多路召回只改这一个函数就行。# retriever/tools.py from langchain.tools import tool from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import FAISS tool def search_knowledge_base(query: str) - str: 在知识库中检索与查询最相关的文档片段用于回答用户问题。 embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vector_store FAISS.load_local( agentic_rag/data/index, embeddings, allow_dangerous_deserializationTrue ) docs vector_store.similarity_search(query, k4) return \n---\n.join([d.page_content for d in docs])这里有两个容易踩坑的点。第一是 FAISS 加载本地索引时新版本需要显式设置allow_dangerous_deserializationTrue不设就报错很多第一次接触的人会被这个拦住。第二是检索的k值我默认用的 4这个值可以根据文档粒度微调——如果你的文档块切得比较碎建议调大到 6 到 8保证信息覆盖如果切得很大4 就已经够了多了反而引入噪声。3.5 生成节点生成节点是最后一步但它的提示词设计会直接影响答案质量。关键是明确告诉模型只用检索到的内容回答别发挥。GENERATE_PROMPT 你是专业的知识库问答助手。请根据以下检索到的知识片段回答用户问题。 知识片段: {context} 问题: {question} 要求: - 只基于知识片段内容作答不要添加片段中没有的信息 - 如果片段不足以回答问题明确说知识库中未找到相关信息 - 回答结构清晰分点说明 def generate_node(state: AgentState) - dict: llm ChatOpenAI(modelgpt-4o-mini, temperature0.3) context \n\n.join(state[retrieved_docs]) prompt GENERATE_PROMPT.format( contextcontext, questionstate[question] ) answer llm.invoke(prompt).content.strip() return {final_answer: answer}temperature0.3是我试过多个值之后觉得最舒服的档位既不会太死板也不至于跑偏。0 会让回答过于机械0.7 以上在开放域任务里容易擅自加料。还有一个小细节如果检索结果为空我不会把空 context 传给模型而是直接返回“知识库中未找到相关信息”这样既省 token 又避免模型硬编答案。3.6 状态图的组装以上节点都定义好了之后组装成图是最后一步。这一步决定了整个 Agent 的运行逻辑。# agent/graph.py from langgraph.graph import StateGraph, END from agent.nodes import route_node, rewrite_node, retrieve_node, check_node, generate_node from agent.state import AgentState graph StateGraph(AgentState) graph.add_node(route, route_node) graph.add_node(rewrite, rewrite_node) graph.add_node(retrieve, retrieve_node) graph.add_node(check, check_node) graph.add_node(generate, generate_node) graph.set_entry_point(route) graph.add_conditional_edges(route, lambda state: rewrite if state[need_more] else generate ) graph.add_edge(rewrite, retrieve) graph.add_edge(retrieve, check) graph.add_conditional_edges(check, lambda state: rewrite if state[need_more] and state[search_count] 3 else generate ) graph.add_edge(generate, END) app graph.compile()流程上就是路由判断要不要查库要查就改写、检索、再让模型判断够不够不够且没超过三轮就再来一轮够了就生成答案。search_count 3这个上限很重要一旦超过就直接去生成避免死循环。4. 实操中的典型问题与排查技巧实录代码能跑通只是第一步真正在项目里用起来坑只多不少。这节我把实操中遇到的高频问题按严重程度整理出来每一个都是我实际踩过、花时间排查过的。4.1 重写后的查询和原意偏离这是改写节点最头疼的问题。模型在补全指代的时候偶尔会“脑补”出用户没提过的条件。比如用户问“上次说的那个方案有结论了吗”模型可能改写成“市场推广方案的最新进展”但实际上用户指的是“技术选型方案”方向直接跑偏。我的解决办法是在改写提示词里加了一句“如果不确定指代对象宁可保持原问题不变”并且对改写后的查询做个相似度校验——拿原问题和改写问题做向量相似度计算低于阈值就退回用原问题检索。这个兜底逻辑救回过很多次推荐你们也加上。4.2 多轮检索后上下文过长多轮检索是 Agentic RAG 的核心能力但它有个副作用状态里的retrieved_docs会越攒越多到第三轮可能已经积累了十几段文档全部塞给生成模型上下文长度直接爆炸而且无关内容越多模型越容易纠结。我的处理是对检索结果做“增量合并”每轮新检索的结果不直接追加而是先和已有内容做去重用 1-gram 重叠率过滤掉相似段落。另外在check_node里让模型输出一个判断结论如果已有信息够回答就直接结束检索不再空转第三轮。4.3 FAISS 索引文件损坏或版本不兼容FAISS 在版本升级时偶尔不兼容旧索引文件报错信息往往莫名其妙。这个问题没有银弹我能给的建议就是在索引构建脚本里固定写死 faiss-cpu 的版本号另外每次构建索引时导出一份 JSON 格式的文档列表作为备份万一向量索引废了还能拿纯文本重建。4.4 工具调用循环不收敛OpenAI 系列模型在工具调用模式下偶尔会出现“调了工具、但不把工具结果当作观察而是继续调”的怪异行为尤其是 temperature 调高之后。我的经验是工具调用场景下 temperature 别超过 0.2同时在check_node里加一个逻辑如果连续两轮检索返回的文档完全没有新增内容直接判定“信息已足够”强制跳转到生成节点。4.5 路由误判导致闲聊走检索有一类问题是“你叫什么名字”“帮我写一首诗”这类既不算纯闲聊也不算知识库问答。路由节点经常犹豫然后倾向走检索结果检索出来一堆无关文档生成答案自然乱七八糟。我最终的方案是在路由之前加了一道“意图分类提示词”把所有问题归为三类闲聊、通用问答、知识库检索。只有第三类才进入检索链路。通用问答让模型直接用自身知识回答闲聊走标准对话分支。三道判断比两道判断准确率高不少。4.6 实测效果的一些数据最后补充一组我自己的对照测试数据。同一组 120 条混合测试集包含多跳查询、指代消解、事实性查询和闲聊传统 RAG 的正确率达到 74%Agentic RAG 提升到了 89%其中多跳查询和指代类问题的准确率提升最明显。代价是平均响应时间从 1.8 秒涨到了 3.4 秒token 消耗大约多了 62%。这个换算是否值得取决于你的场景对延迟和成本的敏感程度但答案质量的提升是实打实的。源码整套跑通之后你会发现 Agentic RAG 最复杂的地方其实不是代码而是怎么设计好每一个节点的提示词和判断逻辑。代码框架是骨架提示词才是灵魂。我这边已经把这套源码整理成本地可运行的项目核心逻辑就这么多建议你自己把状态图打印出来跑一遍亲眼看看每一轮检索后模型是怎么决策的很多设计上的巧思会豁然开朗。本文还有配套的精品资源点击获取