尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从RAG到Agentic RAG:让知识库从问答机变成办事员

从RAG到Agentic RAG:让知识库从问答机变成办事员 很多团队第一次把RAG知识库跑通上线的时候都会有一种接近真实的幻觉系统能从文档里引经据典感觉自己已经建成“AI助手”了。但实际用下来你会发现它更像一个“带原文引用的搜索引擎”。用户真正想问的往往是“这件事能不能办、怎么办、帮我办”不是“你给我读一段文档”。这个差别的本质决定了RAG知识库要不要继续往Agent方向走。我去年帮一家做售后服务的客户升级知识库系统感触特别深朴素RAG只能完成“信息检索型”问答比如“退货政策是什么”而真实业务场景是“根据某个订单的实际状态判断能否退货、生成退换方案、并同步到客服工作台”——这里面有查询、有判断、有动作。论文里的RAG做不到工程上也做不到。后来我把架构改成基于FastAPI、LangChain、LangGraph、pgvector的Agentic RAG并把业务动作封装成Skill给Agent调用系统才算从“问答机”变成了“办事员”。所以这篇文章不抠概念直接讲清楚三件事为什么要从RAG走向Agent和Skill技术选型与执行链路怎么搭落地过程中那些文档不写的坑怎么处理。核心问题不在技术而在你设定的用户预期。如果你做的是知识库问答那就老老实实做检索如果你做的是“助手”就必须把知识变成决策和动作的基础。1. 题眼在“助手”两个字朴素RAG到Agentic RAG差在哪1.1 从“回答正确”到“任务完成”的跨越朴素RAG的标准流程大家都知道文档加载、切块、向量化、存库、召回、重排、让LLM基于上下文生成回答。这套链路解决的是“信息获取”问题做得好不好核心指标是答案是否忠于知识库、是否有引用依据。但“助手”这个词的标准完全不同。助手要处理的是“任务”任务分三步理解意图、收集证据、执行动作。举个售后客服的例子。用户说“我上周买的耳机坏了订单号是SU20240115帮我申请换货。”朴素RAG的做法是把这句话当检索query去知识库找“换货政策”然后生成一段“根据政策7天内可以换货”的文字。它不会也不敢去查订单系统不会更新工单不会告诉用户“你的订单符合换货条件我已经提交了申请物流取件码将在明天10点前发送”。真正的问题在于知识库里的静态文档无法覆盖动态业务数据。订单状态、库存数量、审批流程、用户身份这些信息永远不在切好的文档chunk里。你要么提前把它们整理成结构化数据塞进知识库要么让系统具备实时调取外部系统的能力。后者就是Agent天然要承担的角色。1.2 朴素RAG的三道坎把这三道坎拆开你就能理解为什么要在RAG外面套Agent。第一道坎是“信息边界狭窄”。知识库只是存量知识的快照查不到订单、查不到审批流、查不到实时库存。第二道坎是“流程断裂”。回答完问题后续动作没人接用户需要自己拿着政策去点按钮、填表单。第三道坎是“状态缺失”。多轮对话里“这个订单”指代哪个“按前面说的来”到底是什么意思朴素RAG没有记忆结构更谈不上跨轮推理。Agentic RAG的贡献不是把RAG扔掉而是把RAG压缩成一个能力当Agent需要场景知识时它去查知识库当Agent需要业务事实时它去调Skill当外界信息既不在知识库也调不到时它明确告诉你“我办不到原因是……”。1.3 为什么还需要“Skill”这个概念那Agent为什么不直接写一堆工具函数这里有个工程化问题Agent面对的LLM输出是非确定性的如果工具参数定义得模糊它就会瞎猜参数、瞎调接口。Skill在这里承担的角色是把“知识依据”和“业务动作”绑定在一起。打个比方知识库是“操作手册”Skill是“按钮”。Agent看懂操作手册后按按钮用户不用自己研究流程。这个拆法比一上来就让Agent直接生成SQL查业务库要安全得多也容易审计得多。2. FastAPI LangChain LangGraph pgvector这套组合的选型逻辑2.1 每个组件负责哪个环节先说结论这套组合不是为了“新而新”也不是某种被困绑定的技术全家桶。它刚好把Agentic RAG里最麻烦的工程问题拆开了。FastAPI负责对外暴露HTTP接口。Agent任务的平均耗时普遍比单一RAG高可能20到40秒FastAPI的异步特性不会阻塞其他请求配合流式输出能很大程度缓解用户等待感。LangChain负责与模型、工具、文档加载器的粘合。LangChain的Chain抽象我其实用得不多但它的Tool抽象、DocumentLoader生态、以及各种模型的统一调用方式确实能省掉不少重复代码。LangGraph接管工作流。这是和朴素RAG最核心的区别。你不再用代码if-else控制“要不要检索、要不要调工具”而是定义一张状态图让LLM在图上的节点之间做决策。pgvector处理知识存储。它不是独立向量库而是PostgreSQL的插件。业务数据、向量、Agent运行日志可以在同一个库里非常方便事务管理。2.2 为什么先选pgvector而不是独立向量库我当时确实犹豫过数据量到百万级了是不是该上Milvus但做完评估后我仍然建议先用pgvector。原因很简单企业知识库不只是纯文本。它天然包含订单表、用户表、权限表、工单表。如果用独立向量库业务数据和向量数据分离你就得自己管理同步、解决两个库之间“join不了”的问题。pgvector的性能边界很清晰百万级向量、HNSW索引、单机内存足够它的响应延迟通常在几十毫秒到一两百毫秒之间这完全能满足RAG场景。大多数企业知识库根本到不了千万级。真到了再迁移到专用向量库也不迟反正LangChain的VectorStore接口是统一的。用pgvector最大的价值是你在还没成为AI公司之前不用为了AI专门增加一个昂贵的数据库依赖。2.3 一套完整的调用链路长什么样把组件拼起来之后线上调用的主链路是这样走的用户在对话端输入问题。FastAPI Gateway接收请求解析会话上下文。LangGraph状态图冷启动进入意图识别节点。Agent决定这个问题需要知识库支持还是需要调用业务Skill还是两者都要。如果走RAG分支LangChain调用pgvector检索召回候选文档经过重排后把最相关的几段内容放进上下文。如果走Skill分支Agent从Skill列表中选择合适的工具补充参数调用工具节点。生成最终答复写回运行日志和会话状态。很多人只把RAG视为“输入文档、输出答案”的离线流程但Agentic RAG里RAG更像是一个“按需触发的证据收集器”。Agent先判断“我需要什么”再决定“去哪里拿”最后才生成语言。这个顺序一旦理清楚选型就顺了。3. 业务Skill的正确落地姿势把回答变成可执行的业务动作3.1 Skill不是prompt是标准接口我见过很多团队的“Skill”写法和写prompt没两样给模型一段“当用户要求退款时你要调用退款接口参数从对话中提取”的文字。这种搞法在Demo里能跑到线上就会露馅。因为没有任何参数校验模型一旦把parameter名猜错或者传了不该传的值轻则接口报错重则产生脏数据。真正的Skill要具备三类信息入参说明、出参结构、触发条件。最好还带一个失败兜底策略。以售后场景为例我把Skill封装成这样from langchain_core.tools import tool from pydantic import BaseModel, Field class AfterSaleInput(BaseModel): order_id: str Field(description订单号用户提供或从会话上下文提取不能为空) apply_reason: str Field(description用户填写的售后原因) policy_evidence: str Field(description从知识库召回的售后服务政策原文用于工单留痕) tool(args_schemaAfterSaleInput) def create_after_sale_order(order_id: str, apply_reason: str, policy_evidence: str): 创建售后处理工单。仅当用户明确要求办理退货、换货、维修且知识库政策确认该场景可受理时调用。 # 在这里调售后系统OpenAPI return {ticket_id: ..., status: submitted, estimated_time: 24h}关键细节在description里。你要写清楚“什么时候不用”这比“什么时候用”更让模型省心。比如售后创建工单如果用户只是问政策不要求办理就不该调用如果订单不存在应该返回“查不到订单”。这些描述会影响LLM的工具选择准确率。3.2 Skill的分类管理我把业务Skill分成三类查询型、动作型、复合型。查询型Skill是无副作用的比如查订单状态、查库存、查物流。动作型Skill会写数据和推进流程比如创建工单、发审批、调整额度这类Skill必须加操作确认。复合型Skill是几个原子Skill的组合一般放在LangGraph里编排不在工具层处理。在代码组织上我建议把每个Skill单独建目录。目录里放工具函数、参数Schema、单元测试、调用示例。这样每个Skill就能独立评审、独立发布、独立回滚。Agent接入新能力时只需要在Skill registry里注册一下。from langchain_core.tools import tool SKILL_REGISTRY { query_order: query_order, create_after_sale_order: create_after_sale_order, check_inventory: check_inventory, send_coupon: send_coupon, }选型上一开始就可以用LangChain的tool装饰器。如果团队微服务用的是多语言也可以用FastAPI把每个Skill包成独立的HTTP服务Agent通过统一网关调用。只要入参出参是JSON Schema模型侧其实不关心你背后是什么。3.3 失败的降级设计Skill调用失败是最容易被忽略的环节。LLM调工具失败之后如果没有任何fallback它就会自己编一个结果这是灾难。我的做法是给所有Skill调用包一个统一错误处理超时返回“系统繁忙请稍后重试”不落地任何状态。校验失败把具体缺失参数返回给Agent让Agent追问用户。业务失败比如订单不存在、订单已关闭Skill返回结构化错误码Agent根据错误码生成对应回答。这一步一定要在开发期就闭环。否则上线后一定会出现“系统说工单创建成功了但后台根本没数据”的情况。到时候你没法复盘因为日志里只有一句模型生成的话工具调用记录被覆盖了。4. LangGraph显式编排把“要不要查、要不要动”变成Agent决策4.1 状态图先于代码我在搭建Agentic RAG的时候第一件事不是写代码而是画一张状态流转图。不是说不能用mermaid这类工具而是你要想清楚节点有哪些边有哪些分支。我的LangGraph状态图大概是这样意图识别节点、知识库检索节点、Skill执行节点、回复生成节点、转人工节点外加一个人工审批旁路。重点在于条件边怎么设计。很多团队把“是否检索”写成固定规则比如“所有问题都先检索”。这产生了一个问题用户问“今天天气怎么样”系统也去知识库里捞一遍浪费算力还答不对。正确做法是让LLM在意图识别节点输出两个标志need_retrieval和need_tool。然后图根据这两个标志走不同分支。from typing import TypedDict from langgraph.graph import StateGraph, END from langchain_core.messages import BaseMessage class AgentState(TypedDict): messages: list[BaseMessage] need_retrieval: bool need_tool: bool selected_tool: str tool_input: dict retrieved_docs: list[str] answer: str定义好状态之后图结构就跟着状态走了def intent_node(state: AgentState) - dict: # 通过LLM判断识别意图、决定是否需要检索或调用工具 # 返回 {need_retrieval: True, need_tool: False} 等标志 ... def retrieval_node(state: AgentState) - dict: # 调用pgvector检索筛选最相关文档 ... def tool_node(state: AgentState) - dict: # 从SKILL_REGISTRY中取对应Skill并执行 ... def generate_node(state: AgentState) - dict: # 基于检索结果或工具结果生成最终回复 ... graph StateGraph(AgentState) graph.add_node(intent, intent_node) graph.add_node(retrieval, retrieval_node) graph.add_node(tool, tool_node) graph.add_node(generate, generate_node) def route_to_retrieval(state: AgentState): if state[need_retrieval] and not state[need_tool]: return retrieval if state[need_tool] and not state[need_retrieval]: return tool return retrieval # 两个都需要时先检索再走工具 graph.add_conditional_edges(intent, route_to_retrieval, ...) graph.add_edge(retrieval, generate) graph.add_edge(tool, generate) graph.add_edge(generate, END)有人会说这不就是if-else吗区别在两点第一if条件不是程序员手写死的是LLM实时判断的第二LangGraph允许你在任意节点之间增加循环和人工确认这要比在代码里维护复杂状态机简单得多。人工审批这类需求纯函数式编排很难写图结构天然支持“挂起、等待、恢复”。4.2 循环和人工审批节点业务Skill里有不少“动作型”操作会涉及钱和流程不能一上来就自动执行。我给这类Skill加了一个approval节点Agent调用前先生成待审批内容推给主管确认主管同意了图才继续往下走。LangGraph实现这个方案有标准模式节点执行到一半返回一个特殊状态表示“等待人工”系统把节点状态持久化到数据库用户端回复“已提交审批待确认”。人工在审批后触发恢复接口图从断点继续跑。这块是纯RAG完全无法触碰的领域。纯RAG根本没有“执行”和“等待”的概念它只负责文本。而有了LangGraph知识库问答才真正具备“业务流程引擎”的雏形。4.3 检索质量仍然是整条链的底座最后强调一点节点编排再好RAG的召回质量不过关整个Agent给出的答案就是空中楼阁。我在此处用的还是经典的“切块embedding向量召回”然后加一个重排模型。重排这一步不能省。第一次建工程时我只用向量召回的top 5直接喂给LLM结果答案经常“打了擦边球”。后来加了一个重排模型把候选从50条缩小到5条准确率提升非常明显。pgvector侧有一些要注意的参数。HNSW索引满意后要结合文档规模调m和ef_search。ef_search太小召回不足太大延迟上去了。线上数据量在十万级时我通常会把ef_search设为128这个值在延迟和召回率之间比较平衡。切块长度一般设400到800个字符并带100到200个字符的重叠太碎会让语义不完整太长又会让embedding向量不聚焦。5. 踩坑实录Agentic RAG上线前我踩过的几个真坑5.1 切块太碎召回结果像被打碎的图片第一次做知识库切块我图省事按固定500字符切没考虑段落标题。结果一个完整的“售后流程五步”被切成三块向量检索时第一块讲“准备材料”第二块讲“提交申请”第三块讲“退款到账时间”。用户问“申请退货要准备什么”系统回答时只拿到了第一块给出的材料清单缺了两项。这个问题的排查链路是这样的先看召回命中文档的原文片段发现内容上下不连贯再看切块的分隔符发现切在段落中间。后来我改成了按Markdown标题切块再对过长的段落做二次切分同时在每个chunk里额外存了section_title和source_page两个metadata字段。检索时不仅能定位到相关内容还能给用户标明出处。5.2 Agent无脑循环调用工具这是Agent化之后最让人头疼的坑。我的Agent在测试时出现过连续调用同一个Skill四次的情况第一次查询失败返回“订单不存在”模型不死心又换参数查了一次第二次还是失败第三次模型直接编了一个订单编号工具调用又失败。最后回复“该订单可能存在系统延迟请稍后再试”。根因是工具返回的错误码没有进入模型的判断闭环。模型看到的是一个莫名其妙的结果不知道这代表“业务上不允许重试”导致它不断幻想新参数重试。修复方式是在Skill返回结构里加了两个字段retryable和suggestion。retryablefalse表示这个错重试也没用suggestion则告诉模型该对用户说什么。同时给整个Agent加了最大工具调用次数限制超过3次就强制进入“转人工”节点。这样至少不会出现浪费几十秒算力却毫无产出的情况。5.3 召回命中“相关文档”但它不能作为行动依据这个坑更隐蔽。用户问“我这种情况能退款吗”知识库召回了一段“不支持7天无理由退款的商品清单”但用户问的是耳机耳机并不在清单里。这时候Agent容易犯两类错一类是认为清单里没有耳机所以直接回答“可以退款”忽略了还要看其他条件另一类是看到“不支持”标题就回答“不能退款”完全没有看正文。我最后给出的方案是两段式生成第一段让LLM只做事实提取判断召回文档与问题是否真正相关、是否冲突第二段才让LLM基于事实判断金额、期限和路径。中间加了一个“文档相关性校验”节点凡是相关度低于阈值的就明确禁止作为工具调用的依据。5.4 FastAPI长任务会把前端活活等死Agentic RAG的一次完整调用可能要跑20到40秒。如果FastAPI接口是同步的连接会被占用网关超时前端等不到结果用户看到的就是一片空白。这个问题的解法我试过两种第一种是FastAPI接口改异步用asyncio.to_thread跑LLM调用配合SSE流式输出至少能让用户看到token逐步生成的过程第二种是任务提交模式接口先返回task_id客户端轮询拉结果适合后台离线处理。如果面向实际客服场景我推荐第二种因为客服工作台不一定有强交互的流式界面任务轮询更稳定。但面向普通用户聊天窗的话SSE更友好。5.5 评估只能看到“答得干不漂亮”看不到动作对不对我评估过一阵子基于LLM的自动打分发现翻车很严重模型觉得你回答得有理有据实际上工具调用参数全是乱编的。后来我在评估里加了三层指标第一层工具调用正确率选中的Skill是否真的解决了问题参数是否合法返回是否落地成功。这一层用自动化脚本就能测。第二层业务链路完成率一个复杂问题是否走完了查询、判断、创建工单、回复提醒的完整链路。第三层回复质量只抽检部分样本由有经验的员工打分不再全量自动化。这套分层评估上线之后我才真正看得清系统在什么地方弱。前面两层没过关后面谈“回复好”没意义。6. 上线后的效果验证与迭代思路用什么指标判断“助手”够格6.1 离线测试集要覆盖“非标准提问”业务方一开始给的知识库问答测试集大多是标准提问比如直接写“退货政策是什么”。但用户真实表达千奇百怪“我耳机坏了想退”“那个能不能换新的”“刚买的就出问题我不想要了”。如果你测试集里没有这些变体系统召回再好也会在意图识别上卡住。我的做法是把过去半年的客服对话记录全部脱敏挑出1000条高频真实问题人工打标成“知识问答”“业务办理”“闲聊寒暄”“需人工服务”四类再抽一部分做回归测试。每次模型或Skill更新先跑这1000条看各分类的准确率变化。这样做的好处是你升级一个Skill时不用肉眼去点几十遍。6.2 线上日志是迭代的富矿一定要把每次调用的完整链路日志记下来用户输入、意图识别结果、检索候选、召回分数、重排结果、Skill名、入参、出参、错误码、最终回复、用户反馈。我用pgvector存文本同时也在PostgreSQL里建了运行日志表按会话ID关联。查问题的时候你才会知道哪些问法总是走错分支哪些Skill触发率极低哪些回复在“强行道歉”。这些日志的价值比任何监控面板都大。没有留存日志的Agent系统进步曲线就是一条横线。6.3 迭代顺序先修链路稳定性再提升“聪明度”上线初期我犯过急躁总想优化prompt让回答看起来更聪明。后来发现问题不在prompt而在链路。Agent偶尔会把并不支持的场景“脑补”成可以办理工单表里出现垃圾数据。这时候首要任务是加强Skill参数校验、收紧工具调用条件、增加人工确认节点而不是去调温度参数。它们可以并行第一周做链路的硬约束第二周处理召回的边界案例第三周再回头调回复模板。一个“笨但稳定”的助手比一个“聪明但乱来”的助手更值得上线。6.4 别忘了给用户一个“退出通道”最后一点语音很轻但很重要所有Agent回复里都要预留“转人工”的出口。模型的能力是有限度的超出Skill覆盖范围时强行生成答案不如直接说“目前这个问题需要人工业务专席处理我来帮你转接”。我在实际系统里观察到加了转人工作为兜底后用户投诉比例反而下降了。因为用户真正愤怒的不是助手不能解决而是助手明明解决不了还一直绕圈子。把这条兜底逻辑写进LangGraph的最终判断节点比训练模型一千遍“不知道怎么回答就要承认”要可靠得多。这套从RAG到Agentic RAG的改造简单说就是把“会说话的知识库”变成“会办事的助手”。技术选型上FastAPI、LangChain、LangGraph、pgvector的组合足够承担大部分企业场景业务价值上Skill让知识库和真实业务流程连了起来工程难点上链路稳定性、工具调用正确率、日志评估体系每一步都要比“跑通Demo”做得更细。你踩过坑再多只要评估闭环在系统就会持续像一个真正的助手那样成长。
返回列表