LangChain Agentic RAG:让 Agent 自己决定要不要检索、检索几次

个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <

文章目录

  • [LangChain Agentic RAG:让 Agent 自己决定要不要检索、检索几次](#LangChain Agentic RAG:让 Agent 自己决定要不要检索、检索几次)
    • [一、两种 RAG 的结构对比](#一、两种 RAG 的结构对比)
    • [二、最小实现:把 retriever 包成工具](#二、最小实现:把 retriever 包成工具)
    • 三、进阶:加上"反思"节点
    • [四、什么时候该用 Agentic RAG](#四、什么时候该用 Agentic RAG)
    • 五、成本对比
    • [六、让 Agent 知道"检索不到就别硬找"](#六、让 Agent 知道"检索不到就别硬找")
    • [七、完整示例:带反思的客服 Agent](#七、完整示例:带反思的客服 Agent)
    • 八、常见坑
    • 九、小结

LangChain Agentic RAG:让 Agent 自己决定要不要检索、检索几次

传统 RAG 是"问一次,检索一次,答一次 "的固定流程。

但很多问题根本不是一次检索能解决的:

"对比一下 2023 和 2024 年的差旅制度有什么变化"------

这需要检索两次,还得分别定位两个版本。

Agentic RAG 把"检索"变成一个工具 ,

让 Agent 自己决定:要不要查、查什么、查几次、够了没有。

一、两种 RAG 的结构对比

传统 RAG(固定链):

复制代码
问题 → 检索 → 拼 prompt → 生成 → 答案

流程写死,无论什么问题都走一遍。

Agentic RAG:

复制代码
问题 → [Agent 思考] → 需要查吗?
          ├─ 不需要 → 直接回答(你好、谢谢这类)
          ├─ 需要 → 调用检索工具 → 结果满意吗?
          │           ├─ 不满意 → 换个查询再查
          │           └─ 满意 → 生成答案

核心区别:检索变成了 Agent 可支配的一个动作。

二、最小实现:把 retriever 包成工具

python 复制代码
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI

@tool
def search_knowledge_base(query: str) -> str:
    """在企业知识库中搜索相关内容。

    当需要查找公司制度、流程、产品规格、历史记录等信息时使用。
    query 应该是简洁的检索词,不要写完整句子。
    如果没找到有用内容,返回空字符串即可。
    """
    docs = retriever.invoke(query)
    if not docs:
        return "没有找到相关内容。"
    return "\n\n".join(
        f"[{i}] {d.page_content}" for i, d in enumerate(docs, 1)
    )

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_react_agent(
    llm,
    [search_knowledge_base],
    prompt="""你是企业知识库助手。

规则:
1. 需要查资料时必须调用 search_knowledge_base,不要凭记忆回答;
2. 可以多次调用,每次用不同的检索词;
3. 检索不到时就说"没有找到相关信息",不要编造;
4. 回答时在句末标注 [编号]。
""",
)

result = agent.invoke({"messages": [("user", "对比 2023 和 2024 年的差旅制度变化")]})

Agent 会自己跑出这样的轨迹:

复制代码
🤔 需要分别查两个版本
🔧 search_knowledge_base("2023 差旅制度")
📄 [1] 2023版:一线城市 500 元...
🤔 还需要 2024 版
🔧 search_knowledge_base("2024 差旅制度")
📄 [1] 2024版:一线城市 600 元...
🤔 信息够了
💬 主要变化:一线城市从 500 元上调到 600 元[2]...

传统 RAG 做不到这一点------它只有一次检索机会。

三、进阶:加上"反思"节点

让 Agent 显式判断"检索到的内容够不够":

python 复制代码
from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]
    documents: list
    retries: int

def retrieve(state: State):
    question = state["messages"][-1].content
    docs = retriever.invoke(question)
    return {"documents": docs}

def grade_documents(state: State) -> Literal["generate", "rewrite"]:
    """判断检索结果是否和问题相关"""
    docs = state["documents"]
    if not docs:
        return "rewrite"

    # 让 LLM 打分
    scores = grade_chain.batch([
        {"question": state["messages"][-1].content, "doc": d.page_content}
        for d in docs
    ], config={"max_concurrency": 5})

    relevant = [d for d, s in zip(docs, scores) if s == "yes"]
    if len(relevant) >= 2:
        return "generate"
    if state.get("retries", 0) >= 2:
        return "generate"      # 重试够了,硬着头皮答
    return "rewrite"

def rewrite_query(state: State):
    """换个说法再检索"""
    new_q = rewrite_chain.invoke({
        "question": state["messages"][-1].content
    })
    return {"messages": [HumanMessage(new_q)],
            "retries": state.get("retries", 0) + 1}

def generate(state: State):
    ctx = format_docs(state["documents"])
    ans = (prompt | llm | StrOutputParser()).invoke({
        "context": ctx,
        "question": state["messages"][0].content,
    })
    return {"messages": [AIMessage(ans)]}

graph = StateGraph(State)
graph.add_node("retrieve", retrieve)
graph.add_node("rewrite", rewrite_query)
graph.add_node("generate", generate)

graph.add_edge(START, "retrieve")
graph.add_conditional_edges("retrieve", grade_documents, {
    "generate": "generate",
    "rewrite": "rewrite",
})
graph.add_edge("rewrite", "retrieve")
graph.add_edge("generate", END)

app = graph.compile()

三个关键节点:

节点 作用
retrieve 检索
grade_documents 判断相关性,够就生成,不够就改写重查
rewrite 换个查询词,回到 retrieve

retries 上限很重要,否则会无限循环。

四、什么时候该用 Agentic RAG

场景 传统 RAG Agentic RAG
单跳问答("报销标准多少") ✅ 更快更省 过度设计
多跳问答("A 和 B 有什么区别") ❌ ✅
需要跨多个数据源 ❌ ✅
需要"先查再判断再查" ❌ ✅
简单闲聊("你好") 浪费一次检索 ✅ Agent 会跳过检索
延迟要求 < 1 秒 ✅ ❌ 至少多一轮

判断依据:你的问题是否需要"多步"。

如果 90% 的问题都是单跳问答,别上 Agentic RAG,

那 10% 单独处理就行。

五、成本对比

传统 RAG Agentic RAG
LLM 调用 1 次(生成) 2~4 次(思考 + 检索判断 + 生成)
检索 1 次 1~3 次
延迟 2~3 秒 5~15 秒
token 1 份上下文 多份上下文累积

Agentic RAG 明显更贵更慢。

所以只在需要多跳的场景用。

一个折中方案:路由------简单问题走传统链,复杂问题走 Agent:

python 复制代码
def route(question: str):
    if is_multi_hop(question):      # 规则或轻量分类
        return agent.invoke({"messages": [("user", question)]})
    return fast_chain.invoke(question)

六、让 Agent 知道"检索不到就别硬找"

这是 Agentic RAG 最容易出问题的地方 :

Agent 可能连续换 5 个查询词反复搜,最后还是编一个答案。

防护三招:

① 工具返回明确的空结果

python 复制代码
@tool
def search_knowledge_base(query: str) -> str:
    """..."""
    docs = retriever.invoke(query)
    if not docs:
        return "知识库中没有找到相关内容。请不要反复重试不同的检索词," \
               "直接告诉用户没有找到。"
    ...

在工具返回值里直接下指令 ,比在 system prompt 里说更有效

(因为它出现在模型"刚看到"的位置)。

② 限制步数

python 复制代码
agent.invoke({"messages": [...]}, config={"recursion_limit": 8})

③ 显式的"放弃"出口

python 复制代码
prompt = """
...
4. 如果两次检索都没找到,直接回答"没有找到相关信息",
   并说明已尝试过哪些检索词。不要编造,不要继续重试。
"""

七、完整示例:带反思的客服 Agent

python 复制代码
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent

@tool
def search_faq(query: str) -> str:
    """搜索常见问题库。用于查找退款、物流、发票等标准问题的答案。"""
    return "\n".join(d.page_content for d in faq_retriever.invoke(query)) or "无结果"

@tool
def search_order(order_id: str) -> str:
    """根据订单号查询订单状态和物流。订单号形如 SO20240115。"""
    return api.query_order(order_id)

@tool
def escalate_to_human(reason: str) -> str:
    """转人工客服。当无法解决用户问题,或涉及退款/投诉时使用。"""
    create_ticket(reason)
    return "已创建人工工单,客服将在 1 个工作日内联系用户。"

agent = create_react_agent(
    ChatOpenAI(model="gpt-4o-mini", temperature=0),
    [search_faq, search_order, escalate_to_human],
    prompt="""你是电商客服助手。

流程:
1. 先判断问题类型:标准问题 → search_faq;具体订单 → search_order
2. 找不到答案时,最多再换一次检索词
3. 仍然找不到 → 调用 escalate_to_human 转人工
4. 涉及退款、投诉的,直接转人工,不要自行处理

不要编造物流信息、退款金额、政策条款。
""",
)

for chunk in agent.stream(
    {"messages": [("user", "我的订单 SO20240115 什么时候到")]},
    stream_mode="values",
    config={"recursion_limit": 8},
):
    ...

给 Agent 一个"转人工"的工具 是很实用的设计------

它给模型一个明确的"我搞不定"的出口,比让它硬答好得多。

八、常见坑

现象 原因 解法
反复检索同一个意思 没有放弃出口 工具返回里下指令;限制步数
延迟很高 多轮调用 简单问题路由走传统链
简单问题也去检索 没区分 prompt 里写明"打招呼不用检索"
上下文爆炸 多轮检索结果都留着 只保留相关的;或用裁剪节点
明明有答案却说没有 工具返回"无结果"太早 检查检索阈值是否过严

九、小结

  • Agentic RAG 把检索变成工具,Agent 自己决定查不查、查几次;
  • 最小实现:@tool 包住 retriever + create_react_agent,
    工具描述要写清楚什么时候用;
  • 进阶加反思节点 :retrieve → grade → (generate | rewrite → retrieve),
    必须设重试上限;
  • 适合多跳问答、跨数据源;单跳问答用传统链更快更省;
  • 成本是 2~4 倍,可以用路由做快慢分离;
  • 防"反复检索 + 硬编"三招:工具返回值里下指令、限制步数、
    给一个 escalate 类的明确出口
    。

下一篇讲 Corrective RAG------检索质量不好时怎么补救。

相关推荐
木头科技2 小时前
【AI 工程化第十五篇】Spring AI 流式输出高并发性能优化实战:SSE、背压、限流、缓存与灰度发布
人工智能·spring·性能优化
AICoder码农王2 小时前
如何写 Skill 第4课,脚本不进门
人工智能
星云低代码开发平台2 小时前
AI 连接器运行健康度:指标、异常恢复和联调清单
大数据·运维·网络·数据库·人工智能·mcp
stereohomology2 小时前
用免费算力额度实现markdown TeX混合编码主题及转换
人工智能·语言模型·llm·薅羊毛
一切皆是因缘际会2 小时前
科技的变化
人工智能
aiAIman2 小时前
Jev 入门:没有 API Key,先看懂三种原语和请求结构(Datawhale Jev Cookbook )
人工智能·aigc
Data-Miner3 小时前
AI 做表数据分析知识百科:定义、原理、概念与应用
人工智能·数据挖掘·数据分析
昇腾CANN3 小时前
开发者说直播预告|10月13日19:00 社区任务开发经验分享
人工智能·昇腾·cann
阿明副业观察3 小时前
AI视频生成设备选型指南:平板 vs 电脑,怎么选更高效?
大数据·人工智能·ai作画·aigc·电脑·音视频·ai写作
Artdesign_Y3 小时前
2026 电商短视频生产 SOP:任务分层、工具组合与素材资产化
人工智能·工具分享·电商干货