1. 从“能跑通”到“敢上线”:Agentic RAG 到底难在哪
做过 RAG 的人大概都有过这种体验:本地拿几十篇文档,接个向量库,套个“检索-拼接-生成”的模板,Demo 跑得漂漂亮亮,回答也像模像样。可一旦把文档量拉到几万篇、把用户换成真实业务方、把问题换成“帮我对比一下去年和今年两份合同里关于违约责任的差异”这种带推理、带多跳、带时效的活儿,系统立马原形毕露——要么检索回来一堆似是而非的片段,要么模型拿着残缺上下文硬编,要么一个简单问题绕了七八次工具调用还没收敛。
这就是Agentic RAG要解决的核心矛盾。传统 RAG 是一条直线:query 进、向量检索、top-k 拼接、LLM 出答案。而 Agentic RAG 把“检索”这件事从一次性动作,升级成了由智能体主导的多轮决策过程:它要判断这个问题该不该检索、该查哪个知识源、检索结果够不够、不够要不要改写 query 再来一轮、要不要调用外部工具、什么时候停下来给答案。标题里的production-agentic-rag-course,关键词落在production上,意思很明确——这不是教你搭个玩具,而是教你搭一个能扛住真实流量、真实脏数据、真实长尾问题的生产级系统。
我前后在三个不同规模的项目里落地过 RAG,从最初级的 Naive RAG 到后来带规划、带反思、带多源路由的 Agentic 版本,踩的坑基本能写一本书。这篇就围绕这个课程标题,把 Agentic RAG 从架构设计、核心组件、实操落地到线上排障,掰开揉碎讲一遍。适合两类人看:一类是已经跑通过基础 RAG、想往生产环境推进的工程师;另一类是正在选型、纠结要不要上 Agent 架构的技术负责人。哪怕你只是想知道“RAG 知识库到底能存什么、Agentic 比普通 RAG 强在哪”,看完也能有个清晰的判断。
2. 先想清楚架构:Agentic RAG 的整体设计与选型逻辑
2.1 为什么普通 RAG 会在生产环境翻车
普通 RAG 的假设非常理想化:用户问题清晰、知识库里恰好有答案、一次检索就能命中、模型不会瞎编。但生产环境的现实是,这四个假设几乎全不成立。
我拿一个真实场景举例。某次做内部制度问答,用户问“出差住宿超标了怎么报销”。普通 RAG 的做法是把这句话向量化,去检索最相似的片段。结果检索回来的全是“差旅费管理办法”里关于标准的条款,却没有“超标处理流程”那一段——因为语义相似度最高的不是答案所在段落。模型拿到一堆“标准是多少”的片段,只能硬着头皮回答标准,用户真正想问的“超标怎么办”根本没被覆盖。
问题的根子在于:检索质量取决于 query 和文档的语义匹配,而真实问题往往需要拆解、改写、多跳才能匹配到正确文档。Agentic RAG 的价值就在这儿——它把“检索”变成一个可以反复试错、可以规划、可以验证的过程,而不是一锤子买卖。
2.2 Agentic RAG 的核心决策循环
一个生产级的 Agentic RAG,本质是一个带工具调用能力的决策循环。我把它拆成五个关键节点,这也是课程里最该吃透的部分:
- 路由(Routing):判断问题该走哪条路。是查结构化数据库、查向量知识库、查图谱,还是直接让模型回答?很多问题其实不需要检索,硬检索反而引入噪声。
- 查询规划(Query Planning):把复杂问题拆成子问题。比如“对比 A 和 B 两份合同的违约责任”,要拆成“查 A 的违约责任”“查 B 的违约责任”“对比”三步。
- 检索与工具调用(Retrieval & Tool Use):执行检索,必要时调用计算器、API、代码解释器等外部工具。
- 结果评估(Grading/Reflection):判断检索回来的内容是否相关、是否足够。这一步是 Agentic RAG 和普通 RAG 最大的分水岭。
- 生成与自检(Generation & Self-Check):生成答案,并检查是否有幻觉、是否引用了不存在的来源。
这五步不是线性的,而是一个可以回退的循环。评估不通过就改写 query 重来,这叫self-correction;规划出来的子问题可以并行检索,这叫parallel retrieval。理解了这套循环,你再看任何 Agentic RAG 框架,都能一眼看穿它在哪个环节做了增强。
2.3 框架选型:LangGraph、LlamaIndex 还是自研
选型这块我给个实在的建议,别一上来就纠结框架。
如果你的团队对流程控制要求高、需要精细管理每个状态节点,LangGraph是目前最合适的选择。它把 Agent 流程建模成状态图,每个节点是一个函数,边是条件跳转,调试的时候能清楚看到数据在哪个节点、走了哪条边。缺点是学习曲线陡,状态管理写起来啰嗦。
如果你想快速验证、组件复用多,LlamaIndex的 Agent 模块上手更快,内置了大量检索器和工具封装,适合做原型。但它的抽象层比较厚,出问题的时候排查链路长,生产环境深度定制会有点束手束脚。
如果你们有特殊需求,比如要接入自研的检索服务、要严格控制延迟和成本,那自研反而是最稳的。Agentic RAG 的核心逻辑其实不复杂,一个 while 循环加几个判断函数就能搭起来,可控性最高。我个人的经验是:原型阶段用 LlamaIndex 快速验证,生产阶段用 LangGraph 或者自研重写核心链路。
提示:框架只是脚手架,真正决定系统上限的是你的检索质量、评估策略和 prompt 设计。别指望换个框架就能解决召回不准的问题。
3. 核心组件拆解:知识库、检索器与评估器怎么搭
3.1 RAG 知识库到底能存什么,图片行不行
这是被问得最多的问题之一。先说结论:向量知识库本身存的是向量,原始内容(文本、图片、表格)存在哪取决于你的架构设计。
文本是最基础的,切块、embedding、入库,这套流程大家都熟。图片能不能存?能,但要分两种情况。第一种是图片里有文字(比如扫描件、截图),你可以用 OCR 或者多模态模型把图片转成文本描述再入库,检索的时候按文本匹配。第二种是图片本身是内容(比如产品图、设计稿),那就需要用多模态 embedding 模型(如 CLIP 类)把图片编码成向量,检索时用文本 query 去匹配图片向量,这叫跨模态检索。
但这里有个生产环境的坑:多模态检索的召回质量普遍不如纯文本稳定,而且存储成本高。我的建议是,如果图片里的信息能用文本表达,优先转文本;只有当图片的视觉信息本身是核心(比如“找出所有红色的包装设计”)时,才上多模态。另外,图片的元数据(来源、时间、关联文档)一定要单独存一份结构化记录,方便过滤和溯源。
3.2 向量库、结构化库与知识图谱的分工
热词里提到“rag知识库和结构知识库区分以及应用场景”,这个问题特别关键。很多人一上来就想把所有东西塞进向量库,结果查询效率和质量都上不去。
| 知识库类型 | 擅长场景 | 典型查询 | 短板 |
|---|---|---|---|
| 向量知识库 | 非结构化文本、语义相似检索 | “关于XX政策的描述” | 精确匹配、聚合统计弱 |
| 结构化库(SQL) | 数值、枚举、精确条件 | “2023年销售额超过100万的客户” | 语义理解弱 |
| 知识图谱(KG) | 实体关系、多跳推理 | “A公司的母公司旗下有哪些产品” | 构建成本高、覆盖有限 |
生产级 Agentic RAG 通常是三者混合。Agent 的路由模块先判断问题类型,数值类走 SQL,关系类走图谱,描述类走向量库。这就是所谓的Hybrid RAG。我做过一个项目,把产品文档放向量库、订单数据放 SQL、供应链关系放图谱,Agent 根据问题自动路由,准确率比纯向量方案提升了将近 30%。
至于ontology RAG(本体 RAG),可以理解为知识图谱的进阶版——它不只存实体和关系,还定义了概念之间的层级和约束。适合领域知识高度结构化的场景,比如医疗、法律。但构建和维护成本很高,中小项目慎入。
3.3 检索器的分层设计
一个成熟的检索链路不该只有一层向量检索。我通常设计成三层:
- 粗排(Recall):向量检索 + BM25 关键词检索,各召回一批,保证不漏。
- 融合(Fusion):用 RRF(Reciprocal Rank Fusion)把两路结果合并排序。
- 精排(Rerank):用 cross-encoder 类的重排模型对候选做精细打分,取 top-n 给模型。
为什么要有 BM25?因为向量检索对专有名词、型号、代码这类精确 token 不敏感。用户问“ERR-5021 报错怎么解决”,向量检索可能召回一堆泛泛的错误处理文档,而 BM25 能精准命中包含这个错误码的片段。两路结合,召回率明显更稳。
Rerank 这一步很多人省掉,觉得费时。但实测下来,加了 rerank 之后,喂给模型的上下文质量提升非常明显,幻觉率能降一大截。代价是延迟增加几百毫秒,这个取舍在生产环境通常值得。
3.4 评估器:Agentic RAG 的“质检员”
评估器是 Agentic RAG 区别于普通 RAG 的灵魂组件。它至少要做两件事:
- 相关性评估:检索回来的每个片段,和问题相关吗?不相关的直接丢掉,别污染上下文。
- 充分性评估:现有片段加起来,够回答问题吗?不够就触发 query 改写或补充检索。
实现上,可以用一个轻量 LLM 做打分,也可以用专门的 rerank 模型。关键是评估标准要写清楚,prompt 里明确告诉模型“什么算相关、什么算充分”。我见过太多项目评估器形同虚设,就是因为 prompt 太模糊,模型打分全靠猜。
注意:评估器本身也会消耗 token 和延迟。生产环境要控制评估的粒度,别对每个片段都单独调一次模型,可以批量评估或者用更小的模型。
4. 实操落地:从零搭一个可上线的 Agentic RAG
4.1 环境准备与依赖安装
先明确技术栈。我以 Python 为例,核心依赖包括:向量库(Milvus 或 Qdrant)、embedding 模型(BGE 或 OpenAI 系)、LLM(本地或 API)、编排框架(LangGraph)。Mac 上搭建完全没问题,M 系列芯片跑本地 embedding 模型很流畅。
# 创建虚拟环境 python -m venv rag_env source rag_env/bin/activate # 核心依赖 pip install langgraph langchain langchain-community pip install qdrant-client sentence-transformers pip install rank-bm25 jieba pip install fastapi uvicorn向量库我推荐 Qdrant,本地用 Docker 一条命令就能起,生产环境也扛得住。Milvus 功能更强但部署重,看团队运维能力选。
docker run -d -p 6333:6333 -v $(pwd)/qdrant_data:/qdrant/storage qdrant/qdrant4.2 文档切块:别小看这一步
切块(chunking)是 RAG 里最容易被忽视、却最影响效果的环节。切太大,检索精度下降,噪声多;切太小,语义不完整,模型拼不出答案。
我的经验参数:中文文档 300-500 字一块,英文 200-400 token 一块,重叠 10%-15%。重叠是为了防止关键信息正好被切断。但这不是死规矩,要看文档结构。技术文档按标题层级切,合同按条款切,聊天记录按对话轮次切,效果都比机械按字数切好。
from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=400, chunk_overlap=60, separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""] ) chunks = splitter.split_text(document)注意 separators 的顺序,优先按段落切,再按句子切,最后才按字符切。这样能最大程度保证语义完整。
4.3 构建混合检索链路
先建向量索引,再建 BM25 索引,最后融合。
from sentence_transformers import SentenceTransformer from qdrant_client import QdrantClient from rank_bm25 import BM25Okapi import jieba # 向量检索 embedder = SentenceTransformer('BAAI/bge-base-zh-v1.5') client = QdrantClient(host="localhost", port=6333) def vector_search(query, top_k=10): q_vec = embedder.encode(query) hits = client.search( collection_name="docs", query_vector=q_vec.tolist(), limit=top_k ) return [(h.payload["text"], h.score) for h in hits] # BM25 检索 tokenized_corpus = [list(jieba.cut(c)) for c in chunks] bm25 = BM25Okapi(tokenized_corpus) def bm25_search(query, top_k=10): tokens = list(jieba.cut(query)) scores = bm25.get_scores(tokens) idx = scores.argsort()[::-1][:top_k] return [(chunks[i], scores[i]) for i in idx]融合用 RRF,它对不同检索器的分数尺度不敏感,比简单加权稳。
def rrf_fusion(results_list, k=60): scores = {} for results in results_list: for rank, (text, _) in enumerate(results): scores[text] = scores.get(text, 0) + 1 / (k + rank + 1) return sorted(scores.items(), key=lambda x: x[1], reverse=True)4.4 用 LangGraph 编排 Agent 决策循环
这是核心部分。把前面说的五个节点用状态图串起来。
from langgraph.graph import StateGraph, END from typing import TypedDict, List class RAGState(TypedDict): question: str queries: List[str] contexts: List[str] answer: str retry_count: int def route_node(state): # 判断问题类型,决定检索策略 return {"queries": [state["question"]]} def retrieve_node(state): all_ctx = [] for q in state["queries"]: v = vector_search(q) b = bm25_search(q) fused = rrf_fusion([v, b])[:5] all_ctx.extend([t for t, _ in fused]) return {"contexts": all_ctx} def grade_node(state): # 评估检索结果是否充分 if is_sufficient(state["question"], state["contexts"]): return "generate" if state["retry_count"] >= 2: return "generate" return "rewrite" def rewrite_node(state): new_q = rewrite_query(state["question"], state["contexts"]) return {"queries": [new_q], "retry_count": state["retry_count"] + 1} def generate_node(state): answer = llm_generate(state["question"], state["contexts"]) return {"answer": answer} graph = StateGraph(RAGState) graph.add_node("route", route_node) graph.add_node("retrieve", retrieve_node) graph.add_node("rewrite", rewrite_node) graph.add_node("generate", generate_node) graph.set_entry_point("route") graph.add_edge("route", "retrieve") graph.add_conditional_edges("retrieve", grade_node, { "generate": "generate", "rewrite": "rewrite" }) graph.add_edge("rewrite", "retrieve") graph.add_edge("generate", END) app = graph.compile()这段代码的关键在于grade_node和rewrite_node构成的自纠正循环。retry_count是必须的,防止无限循环烧钱。我一般设 2 次上限,超过就直接生成,哪怕上下文不完美。
4.5 参数选择与成本控制
生产环境必须算清楚账。几个关键参数:
- top_k:粗排召回 10-20,精排后留 3-5 给模型。给太多上下文,模型反而抓不住重点,还费 token。
- chunk_size:前面说了,400 字左右是中文的甜点区。
- retry 上限:2 次。再多收益递减,成本线性涨。
- 评估模型:用小的(如 7B 级别)做评估,大的做生成,成本能省一半。
延迟方面,一次完整循环(含一次重试)大概 3-6 秒,取决于模型和检索速度。如果业务要求 2 秒内响应,就得砍掉重试、用更快的模型、或者做缓存。
5. 线上排障实录:那些文档里不会写的问题
5.1 检索召回不准的排查顺序
召回不准是最常见的问题。我的排查顺序是固定的:
- 先看切块:把命中的片段打出来,看是不是被切碎了。十有八九是切块问题。
- 再看 embedding 模型:中文场景用中文优化的模型,别用纯英文模型硬套。
- 然后看 query:用户问题是不是太口语化、太短?考虑做 query 改写或扩展。
- 最后看融合策略:BM25 和向量的权重、RRF 的 k 值是否合理。
我遇到过一个典型案例:用户问“怎么退订”,检索死活召回不到“取消订阅”的文档。原因是“退订”和“取消订阅”在向量空间里距离不够近。解决办法是在 query 改写阶段做同义词扩展,把“退订”扩展成“退订 取消订阅 关闭服务”,召回立马就上来了。
5.2 模型幻觉的三种典型表现与对策
Agentic RAG 的幻觉比普通 RAG 更隐蔽,因为它会“自信地”调用工具、编造检索结果。三种典型表现:
- 编造来源:答案里引用了一个根本不存在的文档编号。对策是强制模型只引用上下文里出现过的来源,生成后做一次来源校验。
- 过度推理:上下文只说了 A,模型推出 B。对策是在 prompt 里明确“只基于给定上下文回答,不确定就说不确定”。
- 工具误用:该查数据库的时候去查了向量库。对策是路由节点的判断逻辑要写细,给出明确的判断规则和示例。
5.3 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 召回为空 | 切块过碎/embedding 不匹配 | 检查切块和模型 |
| 答案答非所问 | 上下文噪声多 | 加 rerank、收紧评估 |
| 响应超时 | 重试次数过多/模型太慢 | 限制 retry、换小模型 |
| 成本飙升 | 评估调用过频 | 批量评估、缓存结果 |
| 多跳问题失败 | 未做 query 拆解 | 加规划节点 |
| 数值问题答错 | 走了向量库 | 路由到 SQL |
提示:线上一定要打日志,把每次的 query、检索结果、评估分数、最终答案都记下来。出问题的时候,这些日志就是你的救命稻草。
5.4 几个我踩过的坑
第一个坑:过度依赖向量检索。早期我所有查询都走向量,结果数值类、精确匹配类问题全军覆没。后来加了路由和 BM25,才稳住。
第二个坑:评估器 prompt 太宽松。一开始评估器几乎从不触发重试,因为它觉得啥都“相关”。后来把评估标准改成“必须能直接回答问题的某个部分才算相关”,重试才真正起作用。
第三个坑:忽略缓存。生产环境里大量问题是重复或高度相似的,加一层语义缓存(相似问题直接返回缓存答案),能省下大量成本。我用 Redis 加向量相似度做缓存,命中率能到 30% 以上。
6. 从课程到生产:Agentic RAG 的扩展方向
production-agentic-rag-course这个标题里的 “course” 意味着它是一套体系化的学习路径,但学完之后真正要面对的,是把知识迁移到自己的业务里。我分享几个实际扩展的方向。
多模态扩展:前面聊过图片存储,进一步可以接入语音、视频的转写和检索。比如客服场景,把通话录音转文本入库,Agent 就能检索历史通话内容。
记忆机制:给 Agent 加长期记忆,记住用户的偏好和历史交互。这样同一个用户再问相关问题,不用从头检索,直接调用记忆。实现上可以用一个独立的记忆库,按用户 ID 分区。
人机协同:生产环境别追求全自动。对于低置信度的回答,让 Agent 主动转人工,或者给出“我不太确定,建议咨询 XX”的兜底。这比硬编一个错误答案强得多。
持续评估:上线不是终点。要建立一套离线评估集,每次改 prompt、换模型、调参数,都跑一遍评估集,看指标有没有退化。我一般维护 100-200 条覆盖各类场景的测试问题,作为回归测试。
最后说个我自己的体会:Agentic RAG 的复杂度是普通 RAG 的好几倍,不是所有场景都值得上。如果你的问题简单、知识库稳定、召回率本来就高,那普通 RAG 加个 rerank 就够了。只有当问题需要多跳推理、多源融合、或者对准确性要求极高时,Agentic 架构的投入才划算。别为了“Agentic”这个词而 Agentic,能解决问题的架构才是好架构。