最近把production-agentic-rag-course这个课程从头到尾啃了一遍,坦白说收获比想象中大得多。市面上的 RAG 教程大多停在“跑通一个 LangChain 脚本”的阶段,能端到端讲清楚 Agentic RAG 生产化落地的不多。这门课最值钱的地方不是给你一套能跑的代码,而是把“从原型到生产”这条路上所有容易被忽视的细节摊开讲:路由怎么做、规划怎么做、反思怎么做、可观测性怎么接、评估怎么搞。我自己的项目之前就卡在“building for production”这一步——demo 里效果好,一上真实数据和并发立刻现形。跟着课程重新梳理了一遍架构,才慢慢找到节奏。
如果你已经写过几个 RAG demo,或者正在为生产环境的“检索智能体”头疼,这篇内容值得往下看。我会从课程核心思想、系统拆解、复现路径、踩坑实录到最后的生产建议,尽量讲得实在一点,方便你直接参考。
1. 课程到底是什么,为什么值得专门写一篇
1.1 传统 RAG 和 Agentic RAG 的差别
先说个容易混淆的点:普通 RAG 和 Agentic RAG 不是“谁更高级”,而是解决问题的层级不同。
传统 RAG 是固定流程:“用户提问 → 向量检索 → 拼接提示词 → 大模型生成”。它适合一类比较稳定的问答场景,比如“从公司制度里查年假规则”。但一旦你的问题涉及多个数据源、需要多步推理、甚至要在检索结果不足时换一种方式查询,固定流程就很容易翻车。
Agentic RAG 的核心是让大模型扮演“指挥官”角色,自己决定下一步动作:要不要检索?去哪个库检索?用不用计算器?还是要把检索结果拿回来再反思一轮?每一轮动作都像一次工具调用,模型根据中间结果不断调整策略。可以类比成:普通 RAG 是去固定货架拿货,Agentic RAG 是一个手里拿着购物清单的助手,会先判断清单上的东西该去哪个仓库找,拿回来还检查一下是不是真的符合要求,不符合就换条路再找。
我把这个课程理解成一套 Agentic RAG 的“工程化指南”,它不只讲概念,更关注这些能力在生产环境里如何组织、如何容错、如何被监控。
1.2 什么样的人最适合跟这门课
课程内容更适合三类人:
- 已经写过基础 RAG,但一直没有把方案推上生产的开发者。这类人对向量数据库、Embedding 都不陌生,缺的是“如何设计一个可维护的 Agent 流程”。
- 在做多源知识库问答、企业内部资料检索等场景的工程师。当公司里有十几个数据源、权限也不一样的时候,一次性检索所有库显然不现实,这时候 Agentic RAG 的路由和规划能力就派上用场了。
- 对 LangGraph 这类编排框架感兴趣,想了解生产级状态管理、并行执行和超时控制的人。
我个人的体会是:如果只是学概念,看几篇博客就够了;但如果你想自己搭一个能扛请求、能出问题还能查日志的系统,这门课的复现过程非常值得完整走一遍。
2. 生产级 Agentic RAG 的系统拆解:路由、规划、执行、反思
课程里把 Agentic RAG 拆成四个核心环节:路由、规划、执行、反思。这四块每块都有独立的坑,也是生产环境是否稳定的关键。
2.1 路由(Routing)真的没你想的那么简单
很多人在 demo 阶段只做一个向量库,所以路由显得多余。但只要数据源多了,路由就必须第一个面对。
路由解决的是“这个查询应该去哪取数”。常见做法是给大模型一个候选数据源列表,让它输出 JSON,指定选择哪个数据源。课程里强调的点是:不要只给数据源一个名字,要给出完整的 description,包括它覆盖什么内容、什么情况下该选它、什么情况下不该选它。
例如,我当时构建的一个系统里有两个数据源,一个是技术文档库,一个是产品报障记录。如果我们定义:
{ "reason": "用户询问的产品故障现象与历史报障记录相关,应查询报障记录库", "datasource": "incident_reports" }模型通过 JSON 输出,再用一个校验层解析,比直接生成自然语言路由要稳定得多。生产环境中,我建议用 Pydantic 或 JSON Schema 做严格校验。如果模型输出了不存在的datasource,应当触发重试或兜底到默认源。
还有一点容易被忽略:路由的 few-shot 示例很重要。你可以在系统提示词里放两三个典型例子,比如“查工资单请选择 HR 政策库,而非项目文档库”。课程里给的做法是做一个小型分类集,每类问题准备 10~20 条样本,跑到准确率满意为止。
2.2 规划(Planning):用 Planner 拆解复杂查询
不是所有问题都一步能查完。用户可能问“上次我们讨论的那个线上事故,导致支付的模块出了什么 bug?后来修复方案里涉及哪些文档?”这背后至少需要两步:先去“会议纪要库”找到事故主题,提取关键术语,再去“研发文档库”搜索相应修复方案。
课程里的 Planner 就是负责把这类复合请求拆成一个多步动作序列。它和普通“ReAct”循环的区别是:Planner 不是每调一个工具就想一下,而是先做出整体计划,再逐步执行,执行中可以修正计划。
具体的实现思路:
- 使用
plan-and-execute模式,先由 LLM 生成计划节点列表,例如["search_minutes", "extract_terms", "search_docs"]。 - 把计划存在状态里,每执行完一步就评估下一步是否仍然合理。
- 设置最大步数,防止计划无限膨胀。
实际生产中对规划要求比较高的场景,我习惯在 Prompt 里明确“每一步只能做一件事”,并限制动作类型。不然模型容易把两个检索任务硬塞进一个工具调用,导致参数混乱。
2.3 执行(Execution):工具调用必须用规范约束
到了执行层,最关键的是工具函数的定义和检索器的行为。课程用 LangGraph 的状态图来管理执行流程,工具函数全部按照 OpenAI Function Calling 的 schema 注册。
以检索工具为例,工具定义大概长这样:
{ "name": "retrieve_docs", "description": "从技术文档库中检索与用户问题相关的片段,用于回答技术类问题", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "用于向量检索的查询语句"}, "top_k": {"type": "integer", "description": "返回片段数量,默认5"}, "source": {"type": "string", "enum": ["docs", "incident_reports", "hr_policy"]} }, "required": ["query", "source"] } }这里有几个生产要点:
- 给检索器的
query尽量是经过重写或抽取关键信息的语句,而不是用户的原始句子。原始句子口语太重,向量召回质量不稳定。 top_k不是越大越好。通常 5~10 足够,太多会引入噪声。你可以在回调阶段再用条件过滤分数。- 检索时一定要有 score threshold。我一般设 0.3 左右(视 embedding 模型而定)。低于阈值的片段直接丢弃,避免“垃圾拼接”。
执行层的另一个大坑是工具调用的死循环。如果模型反复调用同一个工具,或者调用参数一直触发出错,整个 Agent 会卡住。因此,在状态图中必须要有一个“迭代计数”字段,到达上限就强制进入生成环节,并把已收集的内容作为上下文输出。
2.4 反思(Reflection):不追求一次到位
反思是 Agentic RAG 比普通 RAG 强很多的地方。反思环节做的事情是:在最终生成回答前,评估已经检索到的文档是否足够回答用户问题。不够就再补一轮检索或换个关键词。
课程里提供了一个很轻量的反思方式:单独用一次 LLM 调用,输入用户原始问题、当前答案草稿和参考文档片段,让模型输出{"sufficient": true/false, "missing_points": [...]}。如果sufficient为 false,就提取missing_points重新生成查询并继续检索。
听起来很简单,但如果不做约束,反思会成为性能黑洞。每一轮反思都要消耗时间和 token。所以我在自己的项目里给反思设了两条底线:
- 反思最多执行两次,第三次直接出场。
- 反思输出的
missing_points必须压缩成一条检索 query,而不是把几个点都堆进一次检索。
反思的本质是让系统有“承认自己不足”的能力。做得好,回答会显得更可靠;做不好,就是给用户多等待几秒钟,最后回答还和之前一样。所以建议对反思逻辑做单独的离线评测,而不是拍脑袋上线。
3. 从课程代码到生产落地:我的复现路径与关键参数
这部分我按自己实际跑通的过程整理,步骤上尽量和课程主线一致,但也补充了一些我在本地环境里的调整。
3.1 环境准备与依赖
我建议使用 Python 3.10 以上版本,配合uv或poetry做依赖管理,比 pip 一步步装省心得多。核心依赖如下:
langgraph>=0.2.0 langchain>=0.2.0 langchain-openai>=0.1.0 qdrant-client>=1.9.0 fastapi>=0.110.0 uvicorn>=0.29.0 pydantic>=2.6.0 unstructured>=0.14.0如果你不想一直烧 OpenAI 的 API 费用,也可以把 LLM 换成本地 Ollama 的模型。我复现时用 OpenAI 跑通了一遍,再用ollama拉一个 7B 模型做了一次完整流程,效果差异主要在路由准确率上。生产环境如果预算宽裕,建议路由这种高敏感环节用强模型,文本生成环节可以用中等模型。
安装完成后,我用 FastAPI 写了一个服务壳子,但一开始并没有接完全体的 Agent,而是先把检索接口单独调试好。这一步很重要,先保证数据链路稳定,再往上加 Agent 逻辑,否则排查问题时分不清是检索问题还是编排问题。
3.2 数据切分与索引构建
课程里没有刻意强调切分方式,但我实践中发现分块策略直接影响召回质量。我当时的数据包含 Markdown 文档和部分 PDF 合同,两种格式分开处理。
对于 Markdown 文档,我用RecursiveCharacterTextSplitter,设置了:
chunk_size=400chunk_overlap=50separators=["\n## ", "\n### ", "\n\n", "\n", "。", " "]
这个配置是综合平衡了信息完整性和检索粒度。段落之间留一点 overlap,能避免句子被切断导致语义不完整。对于 PDF 合同,我使用了unstructured先做格式解析,再同样切分。
Embedding 我选的是text-embedding-3-small,维度 1536,存入 Qdrant。创建集合时直接指定:
from qdrant_client import QdrantClient, models client = QdrantClient(url="http://localhost:6333") client.create_collection( collection_name="docs", vectors_config=models.VectorParams( size=1536, distance=models.Distance.COSINE ) )这里有个细节:多个数据源尽量放在同一个 collection 里,用 payload 的source字段区分。这样路由层只需传source参数过滤,而不是维护多个向量集合,运维更简单。查询时用query_filter按source过滤,效率也足够。
3.3 Agent 编排核心代码实现
我用 LangGraph 实现了课程里的四段流程。状态定义如下:
from typing import TypedDict, List class AgentState(TypedDict): query: str source: str iterations: int documents: List[str] answer: str missing_points: List[str]节点包括:
route_node: 分类数据源,写回state["source"]retrieve_node: 调用检索工具,写回state["documents"]reflect_node: 判断是否足够,写回missing_pointsgenerate_node: 生成最终答案
条件边这样设计:
from langgraph.graph import StateGraph, END graph = StateGraph(AgentState) graph.add_node("route", route_node) graph.add_node("retrieve", retrieve_node) graph.add_node("reflect", reflect_node) graph.add_node("generate", generate_node) graph.set_entry_point("route") graph.add_edge("route", "retrieve") graph.add_edge("retrieve", "reflect") graph.add_conditional_edges( "reflect", should_continue, { "retry": "retrieve", "generate": "generate" } ) graph.add_edge("generate", END)其中should_continue的逻辑是:
def should_continue(state: AgentState) -> str: if state["iterations"] >= 2: return "generate" if state["missing_points"]: return "retry" return "generate"检索节点里的查询改写,我根据 missing_points 重新生成一个更明确的 query。示例:
retry_query_prompt = f""" 根据用户原始问题:{state['query']} 以及缺失点:{state['missing_points']} 生成一个搜索关键词,要求简洁,不超过20个字。 """这一步能明显提升第二轮召回的相关性。课程里用的检索器是自带的函数,我在生产里把它替换成了 Qdrant 接口,业务上完全无感。
3.4 生产化接口与可观测性
课程后段专门讲了生产化。我用 FastAPI 把 Agent 包成了一个 POST 接口:
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class QueryRequest(BaseModel): question: str @app.post("/agent") def agent_endpoint(req: QueryRequest): result = graph.invoke({"query": req.question, "iterations": 0}) return {"answer": result["answer"], "source": result["source"]}生产部署时,有几个参数必须设置:
- 请求超时:我用 30 秒,超过直接返回 504。如果 Agent 设计的步数少,这个值可以更小。
- 最大并发:看你的 LLM API 限流情况。我前期只开 10 并发,观察延迟和错误率再逐步上调。
- 跟踪:接入了 LangSmith,每个请求都能看到完整的工具调用链和 token 消耗。
在这个环节我建议一定要加“请求 ID”。每次进入 Agent 生成一个 UUID,作为日志关联字段,后续不管是排查超时还是查召回问题,都靠这个 ID 串起来。
4. 常见问题与排查实录
下面这些坑是我在实际复现和压测中真实遇到的,分享出来给各位省点时间。
4.1 问题一:Agent 陷入检索循环,迟迟不回答
我最早跑通代码后,用了一组测试问题,发现有一类问题会不断触发检索,一直输出“需要更多信息”,把 iterations 上限调到 5 才停。一查 trace,原来是反思环节对“足够”的判断过于严格,每次总能挑出一些小毛病,于是不断重试。
解决方案是两层:
- 把
should_continue条件里的最大迭代次数设为 2。业务上允许的范围是 1~3 次,超过就强制生成。 - 反思的提示词里补充一条规则:“如果当前文档已经包含与问题核心直接相关的信息,立即返回 sufficient=True,不要追求覆盖所有细枝末节。”
改完之后,平均请求耗时从 12 秒降到 5 秒,回答质量反而没怎么下降。
4.2 问题二:路由判断错数据源导致答案质量差
有几次查询明明是关于 HR 政策的问题,模型却跑去检索项目文档,回答自然一塌糊涂。我检查了路由的提示词,发现我把数据源描述写得太简单,比如“项目文档”就只写了“项目文档”,模型根本不知道里面包含什么内容。
后来参考课程建议,把描述改成了带明确边界的长描述:“项目文档库:包含产品需求、研发设计、故障复盘;不包含人事和财务政策。”并在 few-shot 里加了两个反例:“问年假规则时不要用项目文档库。”改完后路由准确率从 82% 提升到 94%。
4.3 问题三:召回结果相关性低
另一类高频问题是:检索召回的片段确实和关键词有关,但语义上并不是用户想要的。比如查“支付超时如何排查”,召回的是“支付超时背景说明”,而非“排查步骤”。原因是 embedding 模型理解的是语义相似,不是意图匹配。
我用的处理方案是加一个 reranker(重排模型)。第一轮用向量检索召回 20 条,再用 reranker 按相关性打分取前 5。虽然多了一次模型调用,但最终答案质量提升非常明显。课程里没有强制要求复用 reranker,但在生产场景我强烈建议加。如果不想引入额外的模型,至少要把分数阈值调高,并且对检索的 top_k 适当放宽后二次过滤。
4.4 问题四:并发一高就超时和报错
压测时发现 QPS 一超过 5,接口就开始超时。原因有两个:一是 LLM 的响应时间本身就长,二是存在重复的 embedding 调用。后来我做了两层优化:
- 在 LLM 调用前接入简单的语义缓存。相似问题在短时间内直接命中缓存,显著降低压力。
- 把路由和反思用的模型换成响应更快的
gpt-4o-mini,只在最终生成时用更强模型。这样整个 Agent 的延迟下降了一半以上。
同时建议把 Qdrant 和编排服务分开部署,至少不要放在同一台开发机上跑压测,不然磁盘 I/O 互相影响。
下面把常见问题整理成速查表:
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| Agent 不结束,反复调用检索 | 反思条件太严格,迭代无上限 | 设置最大迭代次数,放宽 sufficient 判断 |
| 检索结果不相关 | 切分过大/过小、Embedding 不适配 | 调整 chunk 大小,增加 reranker |
| 回答内容牛头不对马嘴 | 路由选错数据源 | 优化数据源描述,补充 few-shot 反例 |
| 高并发下延迟高 | 强模型串行调用、缓存缺失 | 用小模型承担低阶环节,增加语义缓存 |
| 工具调用报参数错误 | schema 描述不清晰 | 严格按 Function Calling 规范校验生成参数 |
| 文档格式解析错乱 | PDF 表格被切碎 | 用 unstructured 做结构化解析,不要直接切分 PDF |
5. 课程之外:给想直接上生产的人的几条实践建议
最后聊点课程讲得比较轻、但我在真实项目里觉得极其重要的几点。
第一,不要照搬课程代码直接上生产。课程提供的代码是“教学最优解”,但不同公司的数据分布、检索场景、安全要求差异巨大。最好的做法是先搭一个最小闭环,把路由和生成两件事跑通,再逐步加规划、反思这些高级能力。一开始就把 Agent 做成全功能,排查问题时你会疯掉。
第二,在开始调 Agent 之前,先建一个小规模评估集。我通常会给每个关键场景准备 30~50 条问题,标注好标准答案或核心文档来源。之后每一次改 Prompt、改检索参数,都在这个评估集上跑一遍。没有评估集的 Agent 优化就像在摸黑开车。
第三,成本控制要提前算。Agentic RAG 比普通 RAG 的 token 消耗要高很多,因为每次路由、反思、规划都是模型调用。一个复杂请求可能消耗 3000~5000 token。如果每天几万请求,就是一笔不小的开销。建议给每个请求记录 token 数,设置告警阈值,比如单请求超过 10000 token 就触发人工检查。
第四,安全与权限千万别漏。如果你的数据源涉及多部门,必须在路由或检索阶段就控制权限,不能让用户检索到无权访问的内容。可以在检索 payload 里增加scope字段,查询时根据用户角色过滤。课程里没有过多涉及权限,但生产环境这是底线。
第五,也是我踩过很多次坑之后最深的体会:尽量把“反思”做得轻一点、收敛一点。反思能力强是好事,但每多一轮反思,就是一次模型往返。生产系统需要的不是最强智能体,而是“足够聪明且可预测”的智能体。我见过太多团队把 Agent 调得很聪明,结果上线一周就因为超时率太高被迫回滚。
如果你正准备做生产级 Agentic RAG,希望这篇文章能帮你少走几个弯路。像我前面说的,从“能跑”到“生产可用”,中间那层窗户纸不是几条代码补丁能捅破的,而是一套完整的工程思维。把路由、规划、执行、反思四件事理顺,评估集和可观测性跟上,你的系统才有资格拿给真实用户用。