ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AgenticRAG:从被动检索到主动决策,让RAG真正理解复杂查询

AgenticRAG:从被动检索到主动决策,让RAG真正理解复杂查询 前几天帮一个团队做技术评审他们搭了一套挺复杂的RAG系统向量库、重排序、混合检索全上了结果一测用户问“把上季度所有客户投诉里跟物流相关的问题汇总一下按严重程度排序”系统直接懵了——拆出的子问题全是散文式搜索检索结果七零八落。这不是个例传统RAG的瓶颈早就不是检索精度而是“智能决策”的缺失。这也是我写下这篇Agent系列第7篇的原因AgenticRAG或者说Agent化的RAG正在把检索增强生成从“查字典”升级成“做研究”。AgenticRAG的核心不是再换一个更强的embedding模型而是把LLM从“生成器”变成“调度者”——让模型自己决定要不要检索、分几步检索、查完怎么判断够不够、不够要怎么办。这篇文章我会结合最近在几个工业级项目里的实操经验把这套机制的主干脉络、路由与计划、查询改写、反思循环等关键组件全部拆开附上可落地的代码片段和调参细节希望对正在从传统RAG往Agent方向迁移的工程师有帮助。1. 整体设计思路从“检索-生成”到“规划-执行-反思”1.1 传统RAG的三个死穴痛点到了非改不可的地步先别急着上Agent得先认清传统RAG为什么在复杂的真实业务上会失灵。我把它总结成三个死穴。第一是“一次检索定生死”。传统流程因为要控制延迟基本是单轮检索、一次性取回Top-K文档然后塞给LLM生成。一旦问题本身是复合型的比如“帮我对比A产品今年和去年的退货率再看下B区域的客诉原因有没有变化”单次检索很难同时召回两个时间维度、两个实体维度的上下文结果就是答案只覆盖了问题的一半。第二是“没有判断力”。传统RAG对检索结果的质量是零感知的——哪怕召回了一堆完全不相关的文本系统也会硬着头皮让LLM基于这些噪声生成答案。Lilian Weng的博客和大量实验都验证过检索质量差的时候RAG的答案甚至不如纯靠LLM内部知识生成来得准。第三是“查询意图单一”。用户真实输入往往是模糊的可能是口语化表达可能是隐含指代可能一个问题里混着事实查询和统计计算。传统RAG没有能力对查询做改写、分解、补全直接拿原始query去检索命中率自然上不去。1.2 AgenticRAG的核心理念把LLM放回控制回路AgenticRAG的核心思路其实不复杂就是一句话让LLM成为整个检索过程的大脑而不是末端的话筒。它不再把“检索”当成一个固定的前置步骤而是把它变成模型可以自主调用的工具。在这套架构里LLM可以决定根本不需要检索直接凭内部知识回答比如常识性问题需要检索但先要把用户问题拆成两个子问题各自检索再汇总一次检索结果不够需要换关键词、换过滤器再检索第二轮当前检索到的文档互相矛盾需要把多个来源交叉验证最终答案要引用哪些文档、以什么结构组织。这种机制在学术界对应的是Self-RAG、CRAG这些概念工业界的LangGraph、LlamaIndex、Haystack也都有了比较成熟的Agentic RAG实现。用大白话说传统RAG是“司机按固定路线开车”AgenticRAG是“导航系统实时看路况自己改路线”。1.3 一个“高层决策”对比案例直观感受差异还是用前阵子一个电商客户的实际需求举例。用户问“各个仓库的库存周转率变化大吗主要是哪些SKU导致的”传统RAG的做法把问题整体embedding化送到向量库召回Top-K片段。结果大概率是召回了几段“库存周转率定义”和“某SKU某月周转率数字”。LLM只能把这些零散素材拼在一起输出一大堆含糊其辞的车轱辘话。AgenticRAG的做法先由规划模块判断这问题需要三步——第一步找出有周转率数据的仓库列表第二步检索各仓库各SKU的周转率变化趋势第三步把数据汇总成结构性对比。规划模块生成三个子任务分别执行检索甚至调用一个写好的计算工具去算周转变动率最后把三块信息揉成一张对比表还得给出“主要是哪些SKU导致”的归因结论。这个例子能明显看出来AgenticRAG不是简单的“多检索几轮”而是任务分解、工具调用、结果再加工的组合拳。下面我按核心机制逐层拆解。2. 核心机制拆解路由、改写、反思是三大支柱2.1 路由机制先判断“要不要检索”和“去哪检索”路由Routing是AgenticRAG区别于传统RAG最直接的一层。它做两件事判断查询是否需要检索以及判断应该走哪条检索路径。“要不要检索”听起来多余但在真实业务里非常关键。用户可能问“RAG的全称是什么”这种模型本来就会的题也可能问“你觉得我们该不该上微调”这种开放性咨询。这时候检索反而引入噪声直接让LLM基于内部知识作答效果更好。实现上用一个轻量LLM调用做意图分类一般不超过300ms也不会显著增加成本。“去哪检索”解决的是多数据源问题。一个正经的Agent系统通常会同时挂多个检索器一个向量库存文档一个倒排索引存工单记录一个SQL接口存经营数据甚至还能调用外部搜索API。路由模块根据查询内容决定走哪个或哪几个检索器。这里有个容易踩的坑路由不要做太“硬”。我见过有些团队直接用分类模型决定只走一路发现分类错了就没法补救。更稳妥的做法是给每个检索器打分而非选唯一允许多路并行再交给后面的融合排序模块去综合。这样容错率会高很多。2.2 查询改写与任务分解检索质量的第一道闸门路由决定了方向但这还不够。用户的原始query直接拿去检索往往效果很差因为口语化问题跟文档里书面化表述之间存在词汇鸿沟。所以AgenticRAG里通常有一个查询改写模块Query Rewriting常见的几种改写策略包括指代消解“它们的退货率分别是多少” - “A产品和B产品在2024年的退货率分别是多少”同义扩展“怎么把货退掉” - “退货流程 退货申请 退款政策”多子句拆分“帮我总结Q2的销售报告并和Q1对比” - “Q2销售报告总结”、“Q1销售报告总结”、“Q2与Q1对比分析”三个子查询。这块的实现可以参考Self-RAG里的工作让LLM先生成多个查询变体再用LLM或规则挑出最合理的组合。工业界更常用的是一个低温度temperature调到0.1左右的LLM调用配合few-shot示例。任务分解再往后走一步就进入了“规划”的范畴。简单查询不需要规划但如果Agent接的是一个“对多个文档做横向对比”类任务就需要把任务树建出来根任务是最终回答叶子任务是单个检索单元内节点是汇总加工。LangGraph里用图结构表达这种规划非常顺手后文实操部分我会给出示例。2.3 反思与自校正循环AgenticRAG的灵魂所在如果AgenticRAG只有路由和改写那它跟“加了意图分类的普通RAG”没什么本质区别。真正拉开差距的是反思Reflection和自校正Self-Correction机制。说白了就是让Agent在拿到检索结果后先别急着生成答案而是自己先评判一下“这批文档够不够用”。评判方式通常是给LLM一个Prompt要求它对检索结果做三件事相关性打分、信息充足度判断、冲突检测。如果发现检索结果不相关或不足Agent可以触发第二轮检索这次改写成另一组关键词或者换一个检索器。如果发现两个文档之间的数据互相冲突Agent需要决定是找第三个来源做交叉验证还是在答案里如实说明冲突。这个“反思-再行动”的循环让RAG从固定流程变成有闭环反馈的控制系统。它的代价是增加了调用次数和时延所以设计时一定要给循环次数设上限防止检索风暴。我一般在代码里把max_retrieval_rounds设成2~3超过就强制进入生成阶段。3. 工具选型与架构形态先想清楚你的场景要哪种3.1 三种主流架构形态按需选择而非盲目上最强不是所有场景都需要最复杂的AgenticRAG。我把它分成三个层级方便你对号入座。轻量级路由式RAGRouter Pattern。核心就是一个路由判断用户问题进来先分类命中“需要检索”就按条件走不同检索器否则直接LLM回答。适合数据源多但单源查询简单的中台系统。优点是改动小、容易上线缺点是基本没有多轮迭代能力。中量级规划式RAGPlanner Pattern。在路由基础上增加了一个规划步骤把复杂查询分解成多个子任务按依赖顺序执行每个子任务可以对应不同检索器或工具。合适的问题能一口气拆成三四个子问题分别检索完汇总成一个答案。这是目前性价比最高的形态也是下文实操部分要实现的。重量级反思式RAGReflective Pattern。在规划式基础上增加了反思和自校正闭环。每一步检索完Agent都要评判结果质量决定是继续补充检索还是推进到下一步。适合问答质量要求极高、数据噪声大的场景比如医疗、金融合规。缺点是延迟高、token成本大需要做严格的预算控制。选型原则很简单你的问题复杂度决定了你要不要上规划你的数据噪声程度决定了你要不要上反思。别一步到位先轻后重让评估数据说话。3.2 主流框架对比LangGraph、LlamaIndex、Haystack怎么选我在不同项目里轮番用过LangGraph、LlamaIndex和Haystack简单说下体验和取舍仅供参考。LangGraph是最贴近“手动挡”的选择。它的核心抽象是图结构节点就是“调用LLM”或“调用检索器”边就是条件跳转。你完全掌控流程适合对控制流有强需求的场景也方便给业务方画流程图讲清楚Agent在干嘛。代价是样板代码多一些需要自己管理状态。LlamaIndex的Agentic RAG能力更偏“开箱即用”。它把QueryPlanningTool、RouterQueryEngine都封装好了最适合快速搭原型。但它比较吃版本迭代API变动快上生产环境要锁定版本否则升级容易踩坑。Haystack在这三者里最“规范”pipeline的概念很清晰加了Agent功能后也支持工具循环调用。它在传统RAG领域积累了丰富的集成组件。如果你团队已经用了Haystack平滑升级到Agentic模式会比较容易。我的建议是团队熟悉图编程就选LangGraph想快速验证想法就LlamaIndex已有Haystack存量系统就继续用Haystack。框架只是工具真正拉开效果差距的是Prompt设计和流程编排这部分没有任何框架能替你完成。4. 实操落地从规划到代码搭一个最小可用的AgenticRAG4.1 定义场景和模块划分为了让例子不飘我以一个内部知识库问答场景为例知识库里有产品文档、工单记录、Q2销售报告用户可能会问跨文档对比类问题。需求很简单——让它能自主把复杂问题拆解检索不同数据源最后汇总成结构清晰的答案。模块上我分成四块路由模块、改写与规划模块、检索工具集、反思与生成模块。整个流程可以用状态机来描述LangGraph的StateGraph就是天然为此设计的。4.2 核心代码实现LangGraph版最小闭环下面是我在一个项目里验证过的最小实现去掉了一些业务细节保留核心链路方便你跑通后再扩展。先定义整体的状态结构这是Agent的“工作记忆”所有节点都往里面读写from typing import TypedDict, List, Dict, Any import operator class AgentState(TypedDict): question: str # 原始问题 sub_queries: List[str] # 分解出的子查询 retrieved_docs: Dict[str, List[str]] # 每个子查询对应的检索结果 generation: str # 最终生成的答案 rounds: int # 检索轮次计数防止无限循环 can_generate: bool # 是否满足生成条件 messages: List[Dict[str, str]] # 便于调试与追溯的对话记录接下来定义工具集。这里的检索函数建议封装统一接口后面在反思节点里调用时完全屏蔽底层的向量库、倒排索引差异# 一个简单的检索器抽象实际项目中可替换成向量库或ES def search_product_docs(query: str) - List[str]: return [产品文档片段1, 产品文档片段2] def search_ticket_records(query: str) - List[str]: return [工单记录片段1, 工单记录片段2] def search_sales_report(query: str) - List[str]: return [销售报告片段1, 销售报告片段2]然后写路由与规划节点。这里的核心是用Prompt让LLM决定要不要分解任务、怎么分解。我试过几个Prompt模板效果最稳的是给LLM一个明确的JSON输出格式约束能显著减少解析失败率from langchain_openai import ChatOpenAI import json llm ChatOpenAI(modelgpt-4o-mini, temperature0.2) router_prompt 你是查询规划和路由引擎。你的任务是分析用户的查询决定 1. query_type: 可选值为 simple 或 complex - simple: 单一事实性问题只需要一次检索 - complex: 需要多源检索、对比、汇总的复合问题 2. sub_queries: 如果query_type为complex将原问题分解为2-4个具体子查询否则为原问题 3. data_sources: 每个子查询应使用的数据源从以下选项选择product_docs, tickets, sales_report 用户查询{question} 请严格以JSON格式输出不要包含其他文本格式如下 {{query_type: ..., sub_queries: [...], data_sources: [...]}} def plan_and_route(state: AgentState) - AgentState: response llm.invoke(router_prompt.format(questionstate[question])) try: parsed json.loads(response.content) except json.JSONDecodeError: # 解析失败时降级为简单查询避免整个流程崩溃 parsed {query_type: simple, sub_queries: [state[question]], data_sources: [product_docs]} state[sub_queries] parsed[sub_queries] # 这里把data_sources存进state实际工程中可以加一个字段 state[messages].append({role: assistant, content: f规划结果: {parsed}}) return state检索节点负责把子查询分配给对应工具。需要注意子查询和检索器之间不是一一对应的一个查询可能需要在多个数据源里各查一遍这里用简单映射就能覆盖大多数场景def retrieve(state: AgentState) - AgentState: retrieved {} for query in state[sub_queries]: # 简化版检索三个源都查后续融合 combined [] combined search_product_docs(query) combined search_ticket_records(query) combined search_sales_report(query) retrieved[query] combined state[retrieved_docs] retrieved state[rounds] 1 return state最后是关键的反思节点。这里我让LLM判断检索结果是否足以生成最终答案。判断逻辑用Prompt引导LLM从“相关性”和“信息充足度”两个维度给分只要有一个维度不过关就触发重规划。实测下来这个二元判断比让LLM写长篇评估更稳定也更容易微调reflection_prompt 你是一个信息评估专家。请评估检索到的信息是否足以回答用户的问题。 用户问题{question} 检索到的信息 {context} 请从以下两个维度评估直接输出两行每行只输出一个数字范围0-10 相关性分数 信息充足度分数 只有当你认为相关性和信息充足度都在7分以上时才可以在最后一行输出可以生成答案。 否则输出需要补充检索并简要说明缺什么信息。 def reflect_and_generate(state: AgentState) - AgentState: if state[rounds] 3: # 超出最大轮次强制生成防止无限循环 state[can_generate] True else: context \n\n.join( f【子查询: {q}】\n \n.join(docs) for q, docs in state[retrieved_docs].items() ) response llm.invoke(reflection_prompt.format(questionstate[question], contextcontext)) result_text response.content state[can_generate] 可以生成答案 in result_text if state[can_generate]: # 常规生成把检索结果交给LLM输出最终答案 generate_agent_prompt 基于以下材料回答问题…请务必标注答案中每个部分引用的信息来源。 state[generation] llm.invoke(...) return state把节点组装到LangGraph里可以这样写from langgraph.graph import StateGraph, START, END graph StateGraph(AgentState) graph.add_node(plan, plan_and_route) graph.add_node(retrieve, retrieve) graph.add_node(reflect, reflect_and_generate) graph.add_edge(START, plan) graph.add_edge(plan, retrieve) graph.add_edge(retrieve, reflect) # 条件边反思后决定是再检索一次还是直接生成 def should_continue(state: AgentState): if state[can_generate]: return done else: return retry graph.add_conditional_edges( reflect, should_continue, {done: END, retry: plan} # 重规划重新拆分子查询 ) app graph.compile()跑一遍之后会看到类似这样的中间状态流“plan”节点输出了2个子查询分别对应产品文档和销售报告“retrieve”节点把两组文档拉回来“reflect”节点判断信息不足原因是缺乏工单数据“plan”节点第二次规划时调整了数据源映射“retrieve”节点补齐工单检索“reflect”节点这次打出了“可以生成答案”的结论。4.3 关键参数调优经验温度、轮次、上限我见过不少团队把AgenticRAG跑起来之后折腾半天最后发现是几个参数没调对。这里分享几个最影响稳定性的数值建议都是我踩过坑后总结出来的未必适用于所有场景但至少是一个靠谱的起点。规划节点temperature设在0.2以下。规划是结构化决策太高的temperature会让LLM频繁给出不同的拆分方案同一问题前后两次执行可能拆成完全不同的子查询线上排查问题会非常痛苦。建议直接在代码里硬编码低温度。检索轮次上限2到3轮。反思循环虽然强大但每多一轮就意味着多一次LLM调用和检索调用。我见过有Agent在失败案例里连续循环了7次生成结果质量没有提高token成本却翻了四倍。上限设成2到3轮后结合“把缺失信息写进生成Prompt”的兜底策略效果反而更稳定。每个节点前加超时和重试。LLM接口偶尔会抽风或超时Agent链路比传统RAG更长任何一个环节出问题都会导致整个流程失败。建议每个LLM调用点都包一层超时控制超时后自动把该节点的判断降级为默认值保底流程至少要能输出一个“不知道”的答案。对最终答案强制要求有引文标注。这一点非常反常识但效果出奇地好当你要求“每个回答段落必须附加来源”LLM会倾向于更谨慎地组织表述而且在答案和检索文档不一致时更容易暴露问题。排查线上问题的时候有引文和没引文的追踪难度完全是两个量级。5. 常见问题与排查技巧实录5.1 问题一Agent陷入检索死循环就是不生成答案这是我遇到最多的问题症状是反思节点一直打“需要补充检索”Agent在多轮检索里反复横跳最后要么超时要么token爆炸。排查思路先定位是“LLM真的觉得信息不足”还是“评分标准太严”。用langsmith或LangGraph自带的trace功能查看每一轮反思节点的输入输出。如果发现LLM一直在要求补充“更细节的数据”但检索结果里其实已经有了说明是Prompt措辞让LLM产生了“我应该再多查一点”的幻觉。解决办法有两个方向。第一把反思Prompt里的判断标准从“信息是否完美”改成“信息是否足够给出部分回答”同时加上“如果信息不完美但足够合理推测继续生成”的句式。第二从工程上强制设置最大轮次在第3轮之后无条件进入生成阶段并在Prompt里告诉LLM“这是最后一轮请尽可能基于现有信息作答”。5.2 问题二子查询拆分太碎结果互相之间对不齐规划模块把问题拆得过于碎之后每个子查询只拿到很小的一块上下文汇总时LLM很难把碎片信息拼成连贯的答案。典型症状是回答看起来像几个段落硬凑在一起逻辑断裂。解决办法是给规划模块加上“汇总粒度”约束。比如Prompt里明确要求“每个子查询的期望返回内容应该是完整的一段观点而不是一个事实碎片”。同时在检索阶段除了精确匹配子查询还可以把原问题也一起检索一遍把原问题的检索结果作为额外上下文提供给生成阶段。这个技巧叫“query expansion”实际效果非常明显。5.3 问题三多数据源结果冲突Agent无法取舍当一个数据源说“退货率下降”另一个数据源说“退货率上升”时简单的拼接式RAG会给出自相矛盾的回答。AgenticRAG本来应该解决这个问题但如果反思节点没有覆盖“冲突检测”照样会翻车。我的方案是在反思节点里显式增加一句如果发现多个文档的数据存在矛盾请指出矛盾点并优先采用时效性更新的数据源若无法判断则如实输出冲突说明。这一个简单的Prompt改动让答案质量从“互相打架”变成了“明确告知差异”用户满意度反而更高因为这符合人类研究者展示信息的方式。5.4 常见问题速查表收藏备用症状可能原因推荐排查动作Agent反复检索不生成反思Prompt标准太严格降低评分阈值增加最大轮次硬限制子查询碎片化、答案逻辑断裂规划粒度过细要求子查询输出完整观点块同时检索原问题多来源数据冲突未标注反思节点缺少冲突检测在反思Prompt显式增加冲突处理指令解析JSON频繁失败规划输出格式不稳定降低temperature尝试函数调用function calling方式输出结构化对象某一路检索一直不命中数据源映射配置错误检查路由模块的data_sources分配逻辑必要时增加默认检索兜底规则最终回答没有引出处生成Prompt未约束引用格式强制要求按段落附“来源文档ID”做好后端校验6. 一点经验之谈别急着上全套机制最后聊一点个人体会。现在大家看到Agent、AgenticRAG这类词很容易犯一个毛病——恨不得把所有的规划、反思、多智能体协作全塞进项目里。我之前也这么干过结果就是流程长得自己都调试不动线上出问题了根本定位不到是哪一环。我的建议是先搭“路由改写”这一层跑一个月看数据。如果发现效果已经提升明显再逐步加规划、加反思。每加一层机制之前都要先想清楚这层机制到底在解决哪个可量化的痛点是检索命中率上不去还是多轮问题答不全还是跨源信息冲突没有明确回答之前别急着加复杂度。AgenticRAG真正厉害的地方不在于它用了多少新潮技术而在于它把“决策能力”还给了模型——让检索这件事从固定的流水线变成灵活的对话。这种思路放到任何一个用到RAG的场景里都会有迁移价值。希望这篇文章能帮你在实际落地时少走弯路。
返回列表