RAG 系列(十七):Agentic RAG——让 Agent 主导检索过程

Pipeline RAG 的沉默失败

前面十几篇一直在优化一件事:怎么让检索结果更好。更好的分块、更精准的排序、更聪明的问法、CRAG 纠偏、Graph RAG 关系遍历......

但有一件事始终没变:无论检索结果好不好,都会被传给 LLM 生成答案。

Pipeline RAG 的流程是线性的、固定的:

css 复制代码
问题 → 向量检索 → top-4 文档 → LLM 生成

它没有一个步骤回头问:"这次检索到的内容,真的足以回答问题吗?"

结果是什么?当知识库没有相关内容时,LLM 拿着 4 篇不相关的文档,要么生成幻觉答案,要么说"根据参考资料无法回答"。系统不知道自己失败了,只是"安静地"交出了一个错误答案。

这是 Pipeline RAG 的沉默失败。


Agentic RAG 的核心思想

Agentic RAG 做了一件事:给系统加上能动性(agency)

具体体现在三点:

1. 检索是工具,不是固定步骤

向量检索、图谱遍历、网络搜索------不是"都用"或"固定用一种",而是根据问题类型动态选择。问事实问题用向量检索,问关系问题用图遍历,问时效性问题用网络搜索,问常识问题直接生成------这本来就是人类在检索时的做法。

2. 检索后有反思

执行检索后,Agent 不急着生成答案,而是先评估:这次拿到的上下文,对回答这个问题有多大帮助?如果分数低于阈值,就认为这次检索失败了。

3. 失败可以纠正

质量不够,换策略重试。向量检索没找到好内容?试试图遍历,或者直接去网上搜。这个重试循环有上限(本文是 2 次),防止无限循环。

这三点合在一起,就从"固定流水线"变成了"有反馈的决策循环"。


LangGraph 图结构

css 复制代码
问题
  ↓
[classify]  → 分析问题类型,选初始策略
             事实型  → vector
             关系型  → graph
             时效型  → web
             常识型  → direct
  ↓
[retrieve]  → 执行选定策略(三个独立节点)
  ↓
[evaluate]  → 打质量分(0.0~1.0),阈值 0.6
  ↓
 ≥0.6 ──yes──→ [generate] → 最终答案
  │
  no(尝试次数 < 2)
  ↓
[re_route]  → 按 vector → graph → web 顺序选下一个未用过的策略
  ↓
[retrieve] again...
  ↓
(最多 2 次后无论如何都进 generate)

直接生成路径(direct_generate)绕过所有检索节点,到达 END。


关键节点实现

State:执行轨迹是调试的眼睛

python 复制代码
class AgenticRAGState(TypedDict):
    question:         str
    strategy:         str           # "vector" | "graph" | "web" | "direct"
    tried_strategies: list[str]     # 已尝试过的策略列表,防止重复
    retrieved_docs:   list[Document]
    quality_score:    float         # evaluate 节点打分 0.0~1.0
    answer:           str
    path:             list[str]     # 执行轨迹,如 ["classify→graph", "graph_retrieve", ...]

path 是调试利器------运行结束后可以看到每条问题走了哪条路径,哪里触发了 re-route,质量分是多少。这在分析 Agent 行为时比最终指标更有价值。

classify 节点:问题分类决定策略起点

python 复制代码
CLASSIFY_PROMPT = ChatPromptTemplate.from_messages([
    ("system",
     "判断以下问题最适合哪种检索策略,只输出策略名,不加解释:\n\n"
     "vector  - 需要检索知识库,事实型(定义、参数、步骤、比较)\n"
     "graph   - 需要检索知识库,涉及多实体关系(来自哪里、谁开发了什么)\n"
     "web     - 需要最新信息(最新版本、近期论文、今日新闻)\n"
     "direct  - 不需要检索(常识、数学、翻译、编程语法)"),
    ("human", "问题:{question}"),
])

def classify_node(state):
    raw = classify_chain.invoke({"question": state["question"]}).strip().lower()
    strategy = "vector"  # 默认
    for s in ["vector", "graph", "web", "direct"]:
        if s in raw:
            strategy = s
            break
    return {
        **state,
        "strategy": strategy,
        "tried_strategies": [strategy],
        "path": [f"classify→{strategy}"],
    }

evaluate 节点:这是整个架构的核心

python 复制代码
QUALITY_PROMPT = ChatPromptTemplate.from_messages([
    ("system",
     "评估检索到的上下文对回答问题的帮助程度,"
     "输出 0.0~1.0 的数字,不要任何解释:\n"
     "1.0 = 完全覆盖,可直接回答\n"
     "0.5 = 部分相关,勉强可以回答\n"
     "0.0 = 完全不相关,无法回答"),
    ("human", "问题:{question}\n\n上下文:{context}"),
])

def evaluate_node(state):
    context = "\n\n".join(d.page_content[:300] for d in state["retrieved_docs"])
    raw = quality_chain.invoke({
        "question": state["question"],
        "context": context,
    })
    try:
        score = max(0.0, min(1.0, float(raw.strip())))
    except ValueError:
        score = 0.5  # 解析失败取中间值
    return {**state, "quality_score": score}

路由逻辑:evaluate 后的分叉

python 复制代码
QUALITY_THRESHOLD = 0.6
MAX_ATTEMPTS      = 2

def route_after_evaluate(state) -> str:
    score    = state["quality_score"]
    attempts = len(state["tried_strategies"])
    # 够用,或已经重试满,直接生成
    if score >= QUALITY_THRESHOLD or attempts >= MAX_ATTEMPTS:
        return "generate"
    return "re_route"

def re_route_node(state):
    tried = set(state["tried_strategies"])
    # 按优先级顺序找第一个没试过的策略
    for s in ["vector", "graph", "web"]:
        if s not in tried:
            new_tried = list(tried) + [s]
            return {
                **state,
                "strategy": s,
                "tried_strategies": new_tried,
                "path": state["path"] + [f"re_route→{s}"],
            }
    return {**state, "strategy": "vector"}  # 保底

图组装

python 复制代码
graph.add_conditional_edges(
    "classify",
    route_after_classify,
    {"vector": "vector_retrieve", "graph": "graph_retrieve",
     "web": "web_search", "direct": "direct_generate"},
)
graph.add_edge("vector_retrieve", "evaluate")
graph.add_edge("graph_retrieve",  "evaluate")
graph.add_edge("web_search",      "evaluate")
graph.add_conditional_edges(
    "evaluate",
    route_after_evaluate,
    {"generate": "generate", "re_route": "re_route"},
)
graph.add_conditional_edges(
    "re_route",
    route_after_reroute,
    {"vector": "vector_retrieve", "graph": "graph_retrieve", "web": "web_search"},
)

实验结果

路由行为分析

8 条测试问题,覆盖四种问题类型:

yaml 复制代码
初始策略分布:
  vector:  4 条  (事实型:RAGAS指标、向量数据库场景选型)
  graph:   2 条  (关系型:BAAI两模型关系、三种高级RAG对比)
  direct:  2 条  (常识型:中译英、Python列表均值)
  web:     0 条  ← 待解释

re-route 触发:4 / 6 次检索问题

Agent 做对的部分:

关系型问题("bge-large-zh-v1.5 和 bge-reranker-v2-m3 都来自哪个机构,各自用于哪个阶段")→ 正确路由到 graph,利用上一篇构建的知识图谱,沿实体边直接找到答案。

常识型问题("把'检索增强生成'翻译成英文是什么")→ 正确路由到 direct,连检索都不需要,直接生成,节省资源。

一个值得诚实谈的路由偏差:

"2025 年最新发布的 RAG 相关论文有哪些?"------被 GLM-4-flash 归类成了 vector,没有走 web

这不是框架设计的问题,是 classify 节点 prompt 的调优空间。"最新论文"包含了"论文"这个知识库相关词,LLM 把它理解成了需要知识库检索的问题。加一条规则"包含'最新'、'今年'、'近期'等时间词汇的问题优先走 web",就能修正这类偏差。

re-route 触发了 4 次:这是真实的工作

6 条检索问题里 4 条触发了 re-route,触发率 67%。这说明 evaluate 节点不是摆设------它确实在评估上下文质量,拒绝不够好的结果,推动 Agent 换策略重试。

RAGAS 指标

diff 复制代码
======================================================================
  RAGAS 指标对比(固定向量检索 vs Agentic RAG)
======================================================================

  指标                    固定向量      Agentic RAG    变化
  ──────────────────────────────────────────────────────
  context_recall          0.611        0.611      →+0.000
  context_precision       0.639        0.681      ↑+0.042  ◀
  faithfulness            0.625        0.625      →+0.000
  answer_relevancy        0.431        0.433      →+0.002
======================================================================

context_precision +0.042,其余几乎不变。


为什么 RAGAS 改善这么小?

这是本文最重要的讨论点,也是容易误读结果的地方。

RAGAS 衡量的是最终答案质量,不衡量过程健壮性。

我们的测试知识库对这 6 条检索问题的覆盖还算充分------即使 evaluate 觉得质量不够好,换了策略之后,最终答案的质量也没有大幅提升,因为知识本来就在那里。在知识库覆盖好的场景下,Agentic RAG 和 Pipeline RAG 生成的答案质量接近。

真正的价值体现在知识库不覆盖的情况:

场景 Pipeline RAG Agentic RAG
知识库有答案 ✅ 正常回答 ✅ 正常回答
知识库没有答案 ❌ 用无关文档生成(可能幻觉) ✅ 换 web 搜索,或明确说不知道
问题需要关系推理 ⚠️ 语义相似但可能遗漏关系 ✅ 路由到图遍历
问题不需要检索 ⚠️ 浪费一次检索 ✅ 直接生成,节省资源

RAGAS 只测了第一行。第二、三、四行的价值,RAGAS 数字反映不出来。

这也呼应了整个系列的观察:每种优化方法都有其目标场景,脱离场景谈指标没有意义。


Pipeline RAG vs Agentic RAG

维度 Pipeline RAG Agentic RAG
流程 固定线性 动态循环
检索策略 固定(通常向量) 按问题类型动态选择
结果评估 有质量打分
失败处理 将就生成 换策略重试
直接生成 不支持 常识型问题跳过检索
额外 LLM 调用 0 次 classify + evaluate(+ re-route)
适用场景 知识库覆盖好、问题类型单一 混合意图、覆盖有盲区

成本是真实的代价。每条问题多出至少 2 次 LLM 调用(classify + evaluate),触发 re-route 时还要再加。如果问题类型单一且知识库覆盖全面,Pipeline RAG 的成本优势明显。


完整代码

代码已开源:

github.com/chendongqi/...

核心文件:

  • agentic_rag.py --- 完整实现,含图谱构建、LangGraph Agent、RAGAS 评估

运行方式:

bash 复制代码
git clone https://github.com/chendongqi/llm-in-action
cd 17-agentic-rag
cp .env.example .env
pip install -r requirements.txt
python agentic_rag.py

小结

本文实现了 Agentic RAG,核心发现:

  1. 检索作为工具而非固定步骤,是 Agentic RAG 和 Pipeline RAG 最本质的区别------工具可以被选择、被评估、被替换
  2. 路由准确性令人满意:关系型问题自动走图遍历,常识型问题跳过检索,Agent 的问题分类基本符合预期
  3. re-route 触发了 4/6 次:evaluate 节点确实在做实质性的质量把关,而不只是走形式
  4. RAGAS +0.042,但价值不在数字里:指标改善小是因为知识库本来就够用;真正的价值在于对"知识库不覆盖"场景的兜底能力,这是 Pipeline RAG 做不到的

从这个系列走过来,Self-RAG 解决"要不要检索",CRAG 解决"结果够不够好",Graph RAG 解决"关系型问题"------Agentic RAG 是这三篇的集大成:把这些能力组合进一个有反馈循环的决策框架,让系统能主动应对不同场景,而不是被动执行固定流程。


参考资料

相关推荐
结构化知识课堂2 小时前
AI产品经理入门实战:如何理解计算机视觉?
人工智能·计算机视觉·产品经理·ai产品经理·ai产品设计
我没胡说八道2 小时前
2026论文工具选购指南:降重、降AI率、排版一站式筛选
人工智能·经验分享·深度学习·考研·aigc·学习方法
初心未改HD2 小时前
深度学习之MLP与反向传播算法详解
人工智能·深度学习·算法
刀法如飞2 小时前
【Go 字符串查找的 20 种实现方式,用不同思路解决问题】
人工智能·算法·go
谙弆悕博士2 小时前
【附C++源码】从零开始实现 2048 游戏
java·c++·游戏·源码·项目实战·2048
阿正的梦工坊2 小时前
ALiBi:让大语言模型“免训练“外推到更长序列的位置编码方法
人工智能·语言模型·自然语言处理
极客老王说Agent2 小时前
2026供应链革命:实在Agent货物智能入库智能助理使用方法与库位优化全指南
人工智能·ai
沪漂阿龙2 小时前
面试题:训练-蒸馏详解——知识蒸馏、Teacher-Student、强弱蒸馏、Qwen3 强到弱蒸馏流程全解析
人工智能·深度学习·机器学习