🧠 Agentic RAG
AD3 的 RAG 是"无脑检索+生成"——检索到什么就用什么。但检索结果可能不相关、生成可能幻觉。Agentic RAG 让系统自我反思:检索后判断质量、不相关就改写 query 重试、生成后验证忠实度。本章精读 LangGraph 官方三种反思 RAG 范式,并用 StateGraph 从零实现 Corrective RAG。
本章目标
- 理解 Agentic RAG 的核心思想:检索-评估-修正的反思循环
- 掌握三种反思范式:CRAG / Self-RAG / Adaptive RAG 的差异
- 精读 LangGraph 官方 CRAG notebook 的图结构
- 用 StateGraph 从零实现一个 Corrective RAG(含评分/路由/改写/web回退)
- 理解"检索即工具"的 Agentic RAG 范式
为什么需要 Agentic RAG
AD3 解决了"检索质量",但仍是单次流水线——检索完就生成,不管结果好不好。真实问题:
| 问题 | 流水线 RAG 的反应 | Agentic RAG 的反应 |
|---|---|---|
| 检索到的文档不相关 | 硬塞给 LLM,生成废话 | 评分→不相关→改写 query 重试 / 转 web 搜索 |
| LLM 生成的内容与文档不符(幻觉) | 无检测,直接返回 | 验证生成 vs 文档一致性,不符则重新生成 |
| 问题不需要检索(闲聊) | 也走检索流程(浪费) | 先路由判断:该检索还是直接回答 |
把 RAG 从"线性流水线"升级为"带条件分支和循环的图"——这正是 LangGraph 擅长的。关键转折:在检索前后插入"判断节点",用 LLM 做评估决策。这把 RAG 变成了一个微型 Agent。
三种反思范式
LangGraph 官方提供了三种自我反思 RAG 的实现(examples/rag/),反思深度递增:
| 范式 | 反思点 | 核心机制 | 源码 notebook |
|---|---|---|---|
| CRAG | 检索后 | 文档相关性评分→不相关转 web | langgraph_crag.ipynb |
| Self-RAG | 检索后 + 生成后 | + 幻觉检测 + 有用性检查 | langgraph_self_rag.ipynb |
| Adaptive | 前置 + 检索后 + 生成后 | + 问题路由(该不该检索) | langgraph_adaptive_rag.ipynb |
精读 CRAG 的图结构
Corrective RAG 是最经典、本章要实现的。先看官方 notebook 的图结构(examples/rag/langgraph_crag.ipynb):
# CRAG 的 5 个节点
workflow.add_node("retrieve", retrieve) # 检索文档
workflow.add_node("grade_documents", grade_documents) # ★ 评分文档相关性
workflow.add_node("generate", generate) # 生成答案
workflow.add_node("transform_query", transform_query) # 改写 query
workflow.add_node("web_search_node", web_search) # web 搜索回退
# 关键条件边:评分后决定走哪
workflow.add_conditional_edges(
"grade_documents",
decide_to_generate, # :473 路由函数
{
"transform_query": "transform_query", # 不相关→改写
"generate": "generate", # 相关→生成
},
)
# 改写后重新检索(形成反思循环)
workflow.add_edge("transform_query", "retrieve")
CRAG 的核心:grade_documents + decide_to_generate
def grade_documents(state):
"""★ 核心反思:用 LLM 逐文档判断是否相关。"""
filtered_docs = []
for doc in state["documents"]:
# 用结构化输出让 LLM 判断 yes/no
score = retrieval_grader.invoke({"question": q, "document": doc})
if score.binary_score == "yes":
filtered_docs.append(doc) # 保留相关文档
return {"documents": filtered_docs}
def decide_to_generate(state):
"""★ 路由:相关文档够不够?"""
if state["documents"]: # 有相关文档
return "generate" # → 直接生成
return "transform_query" # 无相关文档 → 改写 query 重试
注意 grade_documents 是额外的一次 LLM 调用,专门用来"评估检索质量"。这就是反思的代价——更准但更贵更慢(多了评分调用 + 可能的改写重试循环)。生产中要权衡:简单问答用流水线 RAG,高准确性要求用 Agentic RAG。
实战:从零实现 Corrective RAG
pip install langgraph langchain-openai。这是 CRAG 的完整 StateGraph 实现。
# pip install langgraph langchain-openai
# export OPENAI_API_KEY="sk-..."
import operator
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
model = ChatOpenAI(model="gpt-4o-mini")
# ============ ① 状态 ============
class State(TypedDict):
question: str
documents: Annotated[list, operator.add] # 检索到的文档(累积)
generation: str # 生成的答案
web_search_needed: bool # 是否需要 web 回退
# ============ ② 检索节点(假装检索,实际接向量库)============
def retrieve(state: State) -> dict:
"""检索文档。这里用假数据模拟。"""
fake_docs = ["文档A:关于LangChain的内容...", "文档B:无关内容..."]
return {"documents": [{"content": d} for d in fake_docs]}
# ============ ③ ★ 评分节点(核心反思)============
from pydantic import BaseModel, Field
class Grade(BaseModel):
"""文档相关性评分。"""
binary_score: str = Field(description="yes 或 no")
grader = model.with_structured_output(Grade)
def grade_documents(state: State) -> dict:
"""★ 反思核心:逐文档评估相关性,过滤掉不相关的。"""
question = state["question"]
filtered = []
for doc in state["documents"]:
grade = grader.invoke(
f"问题:{question}\n文档:{doc['content']}\n这文档与问题相关吗?只回 yes/no"
)
if grade.binary_score.lower() == "yes":
filtered.append(doc)
# 关键:如果过滤后没有相关文档,标记需要 web 回退
return {
"documents": [], # 清空旧文档(避免重复累积,这里简化处理)
"web_search_needed": len(filtered) == 0,
}
# ============ ④ 路由:决定生成还是改写回退 ============
def decide_after_grade(state: State) -> str:
"""评分后的路由决策。"""
if state.get("web_search_needed"):
return "transform_query" # 无相关文档 → 改写重试
return "generate" # 有相关文档 → 生成
# ============ ⑤ 改写 query(反思循环的"修正")============
def transform_query(state: State) -> dict:
"""改写 query 以更好检索。"""
rewriter = (
ChatPromptTemplate.from_messages([
("system", "改写下面的问题使其更适合检索。只输出改写后的问题。"),
("human", "{question}"),
])
| model | StrOutputParser()
)
better = rewriter.invoke({"question": state["question"]})
return {"question": better} # 用新 query 重试检索
# ============ ⑥ 生成节点 ============
def generate(state: State) -> dict:
"""基于文档生成答案。"""
context = "\n".join(d["content"] for d in state["documents"]) or "(无相关文档)"
chain = (
ChatPromptTemplate.from_messages([
("system", "根据上下文回答。{context}"),
("human", "{question}"),
])
| model | StrOutputParser()
)
answer = chain.invoke({"context": context, "question": state["question"]})
return {"generation": answer}
# ============ ⑦ 构建 CRAG 图 ============
graph = StateGraph(State)
graph.add_node("retrieve", retrieve)
graph.add_node("grade_documents", grade_documents)
graph.add_node("generate", generate)
graph.add_node("transform_query", transform_query)
graph.add_edge(START, "retrieve")
graph.add_edge("retrieve", "grade_documents")
# ★ 条件边:评分后路由
graph.add_conditional_edges("grade_documents", decide_after_grade,
{"generate": "generate", "transform_query": "transform_query"})
# 改写后回到检索(反思循环!)
graph.add_edge("transform_query", "retrieve")
graph.add_edge("generate", END)
app = graph.compile()
# ============ ⑧ 运行 ============
result = app.invoke({
"question": "LangChain 的 LCEL 是什么?",
"documents": [], "generation": "", "web_search_needed": False,
})
print("问题:", result["question"])
print("答案:", result["generation"])
# 流程:retrieve → grade → (不相关?) → transform_query → retrieve → grade → (相关) → generate
Self-RAG:生成后双重验证
CRAG 只在检索后反思。Self-RAG(langgraph_self_rag.ipynb)在生成后也反思——两个检查:
# Self-RAG 在 generate 之后加两个验证节点:
# 验证①:幻觉检测——生成的内容是否忠实于文档?
def grade_generation_vs_documents(state):
"""生成 vs 文档:有没有编造(幻觉)?"""
grade = hallucination_grader.invoke({
"documents": state["documents"],
"generation": state["generation"],
})
if grade.binary_score == "no": # 检测到幻觉
return "not supported" # → 重新生成
# 验证②:有用性——生成的内容是否回答了问题?
grade = answer_grader.invoke({
"question": state["question"],
"generation": state["generation"],
})
if grade.binary_score == "yes":
return "useful" # 有用 → 结束
return "not useful" # 没回答问题 → 改写重试
# 条件边:三态路由
graph.add_conditional_edges("generate", grade_generation_vs_documents,
{"not supported": "generate", # 幻觉→重生
"useful": END, # 有用→结束
"not useful": "transform_query"}) # 没用→改写
Self-RAG 比CRAG 更严格:既查"对不对"(忠实度,防幻觉),又查"答没答"(有用性,防跑题)。但每次验证都是一次 LLM 调用,一个完整循环可能调用 5-8 次 LLM。适合高准确性场景(医疗/法律/金融问答),不适合高频低成本场景。
Adaptive RAG:前置路由
Adaptive(langgraph_adaptive_rag.ipynb)在最前面加一个路由——先判断问题类型,决定走哪条路:
def route_question(state):
"""问题路由:根据问题类型决定检索方式。"""
source = question_router.invoke({"question": state["question"]})
if source == "web_search":
return "web_search" # 时事/外部知识 → web
elif source == "vectorstore":
return "retrieve" # 内部文档 → 向量检索
# 然后接 CRAG/Self-RAG 的反思流程
Agentic RAG:检索即工具
最彻底的范式:把检索本身变成 Agent 的一个工具(langgraph_agentic_rag.ipynb)。Agent 自己决定何时检索、检索什么、检索几次:
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
@tool
def retrieve(query: str) -> str:
"""检索知识库。当你需要查找信息时使用。"""
docs = vectorstore.invoke(query)
return "\n".join(d.page_content for d in docs)
@tool
def web_search(query: str) -> str:
"""搜索网络。当知识库没有时使用。"""
...
# 把检索变成工具,交给 ReAct Agent 自主决策
agent = create_react_agent(model, tools=[retrieve, web_search])
# Agent 自己决定:调几次 retrieve?要不要 web_search?什么时候够了?
# 这就是 OpenCode 的范式(用 grep/glob/read 检索代码)
OpenCode 不用向量 RAG,它用 grep/glob/read 作为 Agent 工具——Agent 自己决定搜什么代码、读哪个文件、搜几次。这就是 Agentic RAG 范式在编码领域的体现。相比 CRAG/Self-RAG 的固定图结构,"检索即工具"更灵活,但依赖 Agent 的判断力。
与生产实践对照
| Agentic RAG 概念 | OpenCode 对应 | |
|---|---|---|
| grade_documents 反思评分 | → | LLM 判断读到的文件是否有用 |
| transform_query 改写重试 | → | 换个关键词重新 grep |
| web_search 回退 | → | webfetch/websearch 工具 |
| 检索即工具(最彻底) | → | ★ grep/glob/read 都是 Agent 工具 |
| 路由判断(Adaptive) | → | LLM 决定用 read 还是 grep 还是 bash |
何时用哪种
| 场景 | 推荐范式 |
|---|---|
| 简单文档问答,准确度要求一般 | 流水线 RAG(AD3) |
| 文档质量参差,需要过滤噪声 | CRAG(本章实现) |
| 高准确性(医疗/法律/金融),防幻觉 | Self-RAG |
| 问题类型多样(有的要检索有的要 web) | Adaptive RAG |
| 数据源动态/可操作(如代码库) | Agentic RAG(检索即工具) |
小结
- Agentic RAG = 在 RAG 流水线中插入LLM 评估/路由/修正的反思节点,把线性流程变成图。
- 三种范式反思深度递增:CRAG(检索评分)→ Self-RAG(+生成验证)→ Adaptive(+前置路由)。
- CRAG 核心:
grade_documents评分 +decide_to_generate路由 + 改写重试循环。 - Self-RAG 双重验证:忠实度(防幻觉)+ 有用性(防跑题)。
- 最彻底:检索即工具——把检索交给 Agent 自主决策(OpenCode 范式)。
- 反思的代价:更准但更贵更慢,按准确性需求选择。
下一个主题 AD5 · 短期记忆深度:深入 Agent 的记忆机制。精读 BaseCheckpointSaver 四方法、thread_id 语义、时间旅行调试、状态分支,把 LG6 的检查点讲深讲透。