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 的成本优势明显。
完整代码
代码已开源:
核心文件:
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,核心发现:
- 检索作为工具而非固定步骤,是 Agentic RAG 和 Pipeline RAG 最本质的区别------工具可以被选择、被评估、被替换
- 路由准确性令人满意:关系型问题自动走图遍历,常识型问题跳过检索,Agent 的问题分类基本符合预期
- re-route 触发了 4/6 次:evaluate 节点确实在做实质性的质量把关,而不只是走形式
- RAGAS +0.042,但价值不在数字里:指标改善小是因为知识库本来就够用;真正的价值在于对"知识库不覆盖"场景的兜底能力,这是 Pipeline RAG 做不到的
从这个系列走过来,Self-RAG 解决"要不要检索",CRAG 解决"结果够不够好",Graph RAG 解决"关系型问题"------Agentic RAG 是这三篇的集大成:把这些能力组合进一个有反馈循环的决策框架,让系统能主动应对不同场景,而不是被动执行固定流程。