十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

从Naive RAG到Agentic RAG:生产级检索增强生成系统的工程实践

从Naive RAG到Agentic RAG:生产级检索增强生成系统的工程实践 1. 先复盘Naive RAG看起来很美一上线就露馅如果你过去半年在认真调RAG大概率经历过这样的场面本地跑Demo时代码链路顺滑得像德芙随便问一句都能给出不错答案可一旦接上真实业务文档、扔进生产环境效果立刻变得薛定谔——同一个问题换个问法答案就跑偏用户问点带时间、带条件、跨章节的问题更是直接翻车。我从Naive RAG一路做到Agentic RAG最大体会是RAG的复杂度不是线性增加的而是当你把“检索出的内容对不对”当作核心问题时整套系统的设计逻辑都要跟着变。这篇不是论文复述也不是开源项目洗稿而是我基于FastAPI LangChain LangGraph pgvector这条技术栈从零搭起一套生产级Agentic RAG的真实记录。适合两类人看一类是已经用Naive RAG做过原型、正被召回质量折磨的工程师另一类是想直接切入Agentic RAG但不想被眼花缭乱的概念带偏的初学者。你会看到Naive RAG到底死在哪、中间态做了哪些关键演进、Agentic RAG用什么样的架构把“检索”从一次性函数变成决策循环以及每一步在工程上到底怎么落地。1.1 一次检索打天下的三个硬伤Naive RAG的标准流程很多人闭着眼都能背文档切块、Embedding入库、用户提问后做一次向量检索、取Top-K拼进Prompt、交给LLM生成。这个流程对“单点事实型问题”确实够用比如“公司去年营收是多少”只要文档里有这句话无论向量检索还是全文检索都能把它捞出来。但真实业务里用户不会总问单点事实他们会问三类Naive RAG根本扛不住的问题第一类语义漂移型。用户问“去年Q3各产品线的营收对比”知识库里对应表述是“2023年7月至9月A/B/C三条产品线的分季度收入分别为……”。两句话在字面上几乎没有重合的Token向量相似度可能只有0.5多一点Top-5里很可能只有一条沾边其余全是噪声。第二类多跳/聚合型。“我们华东区哪些客户同时采购过A产品和B产品各自合同金额是多少”这种问题需要先定位A产品相关的客户名单再交叉筛选B产品的合同再走一遍金额汇总单次向量检索拿到的是半截信息。第三类动态条件型。用户带着明确的条件和约束“最近30天处理的工单里有多少条属于P0级别且尚未关闭”如果知识库只是按固定方式切块的静态文本这类问题根本没有对应的“原生答案块”Naive RAG检索得再准也是空中楼阁。我用一个比喻来解释Naive RAG就像你让一个实习生拿着索引卡去档案室找答案每次只允许他跑一趟、只准带回几张卡。问题简单、索引卡上刚好有答案时他表现很好可一旦答案被分散在好几张卡里或者问题本身需要“先找客户名单、再找合同、再算加总”的逻辑链他连第一步该用哪张卡都很迷茫。1.2 我实测过的“可解释失败”案例理论说再多不如一个具体的失败案例有说服力。我给客服知识库做过一套Naive RAG文档是产品手册和工单记录混合体。测试时我问了一个非常业务的问题“某客户报错E-1024说是断电后出现的怎么排查”按人类直觉正确答案应该融合三块信息E-1024错误码的定义、断电场景的特有处理流程、客户设备型号对应的说明书章节。Naive RAG的结果呢它召回了三条一条是E-1024错误码定义一条讲的是“电源模块更换步骤”还有一条是另一个客户用相同错误码的工单记录。生成时LLM在三条碎片里硬拼居然编出了一套“先换电源模块再重置系统”的流程。单独看每步操作都来自文档但整体流程是幻觉级别的拼接——文档里根本没有“E-1024与断电场景绑定”的因果描述。问题出在哪出在切块把因果关系切断了而向量检索又不具备“发现文档间逻辑关联”的能力。另一个典型翻车来自跨文档聚合。知识库里有一份Q2销售总结、一份客户拜访纪要、一份产品白皮书。用户问“上季度Top客户对新产品的主要顾虑是什么”答案实际上分散在三份文档里销售总结里提到“某客户签约延迟”纪要里写了“担心新老系统迁移成本”白皮书里有些功能承诺。Naive RAG把三份文档各捞出来一段LLM生成的回复看似全面可每个信息点的来源和信任等级完全没被标注用户没法分辨哪些是事实、哪些是推测。这是Naive RAG在生产环境被业务方诟病最多的问题它给出的回复没有证据链条所以无法审计。1.3 别急着上Agent先把Vanilla链路量化到能数清楚代价我在接Agentic架构之前花了整整两周做一件事给Naive RAG的每一环装上“仪表盘”。因为你不上Agent还好一上Agent所有错误都会被放大——如果查询改写不准工具调用就会选错如果初始检索质量差后面的反思评估就要多迭代好几轮如果检索上下文太杂LLM的生成质量立刻断崖下跌。没有基线数据你根本说不清是Agent决策错了还是最底层的检索和质量评估坏了。我建议至少在这几个维度打点召回的Top-5里有多少条真实相关Context Precision答案里有多少内容能在原文中找到证据Faithfulness以及不同Embedding模型在你这套文档上的A/B效果。不用一次做全先用最便宜的方案挑20~50条有标准答案的测试问题人工标注每条问题的“理想检索结果”再对比Naive RAG实际返回结果和最终答案把失败原因粗分成“检索漏了”“检索到但排序太靠后”“内容相关但答案生成错了”三类。做完这个分类你心里就有底了后面每一步优化也都能精确归因。我见过很多人一步跳到Agentic RAG结果迭代了半天发现最差的一环其实是Chunk Size设置不合理或者Embedding模型跟领域文本严重不匹配——这种问题加再多的Agent节点也救不回来。2. 演进必经的过渡带Query改写、Hybrid Search与Re-rankRAG从Naive走向Agentic中间的真正转折点不是某一天突然开始用LangGraph而是一步一步把“检索”做厚。如果你问我哪一项投入产出比最高我毫不犹豫选查询路由与改写——绝大多数检索失败根因不是Embedding太差而是LLM还不具备人类的“改写直觉”。2.1 检索前的第一道闸查询路由与改写查询路由解决的是方向问题改写解决的是精度问题。拿上面那个E-1024的例子原始问题“某客户报错E-1024说是断电后出现的怎么排查”如果直接拿去做Embedding检索向量空间里“E-1024”的权重会被“断电”“排查”这些高频词稀释。我做的第一个改造是加一个Query Rewrite节点用LLM把原始问题拆解成两条独立检索子查询——“E-1024 错误码 定义”和“断电后 恢复 E-1024 不能 启动 重启 解决步骤”分别去检索再合并结果。检索命中率肉眼可见地提升了Top-5里的有效上下文从一条变成两三条。这里的关键点是改写不是简简单单重写一遍而是要跟你的知识库结构对齐。如果知识库是按产品型号拆分文档的你需要从问题中解析出型号并加到查询条件里如果用户问题带了模糊的时间“上一季度”“最近一个月”你需要让改写器输出规范化的时间范围方便后面的Metadata Filter用。我在工程上通常让LLM输出结构化JSON比如{ rewritten_queries: [E-1024 错误码 含义, 断电 后 重启 E-1024 无法 进入系统], filters: {product_line: A1, doc_type: manual} }这样检索层拿到的就是明确的查询词和过滤条件而不是一段口语化的问题原文。查询路由则可以理解成一个if-else问题是算财务指标走表格类工具问题是查合同条款走全文检索问题是开放性的技术咨询走向量检索加生成。路由错了后面再好的检索也都是空转。2.2 多路召回向量和全文检索不是替代关系我见过很多团队做RAG时从第一天就只上向量检索完全忽略BM25或PostgreSQL自带的全文检索。但向量检索有个天然缺陷它适合“语义相似”的召回却不擅长“关键词精确命中”。业务场景里大量问题其实是指令式的清晰关键词比如查“提单号BL2024001234”“工单号INC-778899”这种精确ID型的查询纯向量召回的表现非常不稳定有时候相似ID会混淆有时候关键数字会被语义向量稀释。所以我在做中间态演进时毫不犹豫上了Hybrid Search一路走pgvector的向量检索一路走PostgreSQL的tsvector全文检索两路结果用RRFReciprocal Rank Fusion合并。RRF的公式不复杂对每篇文档在所有检索结果中的排名取倒数将多路排名的倒数相加最终按总分排序。它最大的好处是不需要调权重对不同路的分数分布不敏感这在多路召回场景里省了很多调参的头痛时间。你只需要给不同来源的文档打上来源标记RRF把名次信息汇总即可。从实际效果看混合检索把我的客服知识库的Recall5从0.61提升到0.79。原因也很简单向量检索负责“语义相关”全文检索负责“关键词精确命中”两者重叠的部分用RRF放大排名不重叠的部分互为兜底。这条经验后来一直带到了Agentic架构里——两个检索工具dense_search和keyword_search根据需要分别调用或并行调用。2.3 精排兜底是性价比最高的一环即使做了多路召回Top-10里仍然可能有四到五条是无关片段。如果不做精排把这些内容全塞给LLM一方面浪费Token更重要的是无关内容会干扰生成甚至诱发幻觉。精排Re-rank是这整个演进过程里性价比最高的投资。我用的是Cross-Encoder结构的Re-rank模型它在CPU上跑即可对每条候选Query-Doc对打分输出0到1之间的相关性分数。为什么用Cross-Encoder而不是向量相似度再算一遍因为Cross-Encoder让Query和Doc在注意力层做了完整交互能建模“这个文档是否真正回答了这个问题”而向量检索只是“文档与问题的隐空间距离接近”。我用一段本地模拟测试做了直观对比向量检索Top-10里排第三的是错误码定义但与问题的相关性其实不如排第七的断电恢复流程Cross-Encoder精排后正确顺序被大幅纠正相关文档被提到最前面。精排还有一个附加好处它可以成为Agentic循环里“相关性评估”的轻量替代品。如果你不想在每个节点都调用LLM做判断贵、慢、不稳定完全可以用一个Re-rank模型的threshold来卡——低于0.3的候选不进入上下文。这样重排模型既是检索的后处理也是Agent判断“检索是否成功”的哨兵。3. Agentic RAG的本质检索从“函数”变成“决策循环”走到Hybrid Search加Re-rank这块其实已经比Naive RAG能扛事多了。但对于开头说的那三类问题——动态条件、多跳聚合、跨文档推理它依然是“一次检索打天下”的变体只不过把“一次”做得更好。真正拉开代差的是让LLM具备计划、执行、评估、再执行的闭环能力。这就是Agentic RAG。3.1 为什么LangGraph适合做这个循环我用LangGraph之前试过两条路一条是用LangChain的AgentExecutor串工具另一条是自己在FastAPI里手写状态机。它们的致命问题都很一致流程一旦复杂控制逻辑就变成一团乱麻。LangChain AgentExecutor的抽象太黑了——它默认的行为是“LLM决定下一步然后行动再把观察结果喂回去”看起来很智能但它不擅长让开发者严格定义“什么情况下必须走哪条边”。我在需要“检索结果不合格时进行查询改写并重试”这个场景时发现AgentExecutor的思考链路常常不稳定同一个问题跑五次可能有三种不同的工具调用顺序。手写状态机的问题则是反方向的逻辑完全透明了但状态维护和回溯逻辑全堆在业务代码里维护成本越来越高。比如中间要加一个“如果重试超过2次就转人工”的边手写代码要改一大片而LangGraph只需改一行条件边。LangGraph解决的是“可控的图结构灵活的状态传递”。它把整个流程建模成一张图节点就是函数Plan、Retrieve、Grade、Rewrite、Generate边可以是普通边或条件边状态就是一张全局快照所有节点都可以读写。它对Agentic RAG来说最合适的点在于允许你把分析判断显式建模成“节点”而不是藏在Prompt里的隐形逻辑。比如我明确建了一个Grade节点验证库里的文档是不是真能回答问题不够就走到Rewrite节点够了就直接去Generate。每一个决策点都在图里一目了然出了问题直接看图定位这是AgentExecutor给不了的。3.2 一个生产级Agentic RAG的最小状态机我们用一个可运行的简化版来拆解。先定义状态from typing import TypedDict, List, Optional class AgentState(TypedDict): question: str plans: List[str] retrieved_docs: List[dict] filtered_docs: List[dict] answer: str iterations: int max_iterations: int reflection: str整体Flow是这样一条循环链PlanNode根据问题生成检索计划拆子问题、选工具RetrieveNode按计划执行多路检索GradeNode判断当前上下文是否足够回答如果不够RewriteNode改写查询或调整工具然后回到Retrieve如果够了GenerateNode输出终答。from langgraph.graph import StateGraph, END graph StateGraph(AgentState) graph.add_node(plan, plan_node) graph.add_node(retrieve, retrieve_node) graph.add_node(grade, grade_node) graph.add_node(rewrite, rewrite_node) graph.add_node(generate, generate_node) graph.add_edge(plan, retrieve) graph.add_edge(retrieve, grade) graph.add_conditional_edges( grade, decide_next_step, { generate: generate, rewrite: rewrite, retrieve: retrieve }, ) graph.add_edge(rewrite, retrieve) graph.add_edge(generate, END) graph.set_entry_point(plan)这里最重要的不是每行API而是那个decide_next_step——它才是Agent的“大脑”。这个函数接收当前状态返回下一个节点名。我在生产里不会直接让LLM自由发挥而是给一个结构化决策模板让它输出一个JSON{ verdict: sufficient | insufficient | conflict, reason: 已有两段文档互相矛盾需要额外检索第三方来源, next_action: retrieve_with_modified_query }同样PlanNode也不需要LLM从零自由发挥而是基于工具清单来规划。如果注册了3个工具PlanNode的任务就是从工具清单里选1到2个加上改写后的query形成一个可执行的工具调用计划。这样既保留了Agent的灵活性又避免了完全自由带来的不可控。3.3 工具设计把“检索”拆成Agent手里的可调用工具传统Naive RAG只有一个隐式工具“向量检索Top-K”。Agentic RAG里工具设计成了开放集合我把它分成四个类型工具类型作用典型场景语义检索工具走Embeddingpgvector开放式问题、语义相关查找关键词检索工具走PostgreSQL tsvector精确ID、型号、错误码结构化数据工具走SQL/Pandas统计、聚合、条件筛选外部知识工具走Web/API知识库覆盖不到的最新资讯这四种工具统一交给PlanNode选择。比如用户问“最近30天P0工单的关闭率是多少”PlanNode直接选择结构化数据工具不再走“先检索文档片段再让LLM瞎编”的路子用户问“E-1024报错怎么处理”PlanNode同时选择语义检索加关键词检索多路结果融合后再送Grade节点评估。工具拆分带来的另一个好处是可观测性。哪个工具被调用、返回了什么、耗时多久、被Grade节点打了什么分全都可以记录在LangGraph的状态里。上线后我通过查看P50/P90工具调用链能快速定位是哪一步拖了回答质量的后腿。这种“万物皆工具、调用可追溯”的设计是Agentic RAG与Naive RAG在生产运维层面的本质差别。4. 完整实战FastAPI LangChain LangGraph pgvector落地纸上谈兵聊完进入实战环节。我用一条完整技术栈搭了一套生产级Agentic RAG服务FastAPI做服务层LangGraph做Agent编排LangChain做工具和模型抽象PostgreSQLpgvector做存储和向量检索。下面按存储、Agent、服务、配置四层拆开讲。4.1 存储层pgvector的schema与索引设计先建表。文档切片表是整套系统的地基CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE documents ( id BIGSERIAL PRIMARY KEY, chunk_text TEXT NOT NULL, metadata JSONB, embedding vector(1024), created_at TIMESTAMPTZ DEFAULT now() ); CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);选1024维是为了适配bge-m3这类开源中文Embedding模型如果你用OpenAI的text-embedding-3-large那维度是3072也可以做降维但要注意降维后的质量损耗。数据库侧我用HNSW索引而不是IVFFlat原因是HNSW在数据量中等几十万到几百万且写入频繁时表现更稳查询耗时能稳定在几十毫秒内而IVFFlat则要定期重建训练聚类。对RAG这种“写入持续、查询并发”的形态HNSW更省心。metadata这一列是Agentic RAG的胜负手。因为它可以存文档来源、日期、产品线、文档类型等结构化字段才能支撑后续的Metadata Filter。比如用户问“2024年Q1的E系列产品故障率”如果只做纯向量检索所有文档一视同仁这个month/产品线的约束根本进不了检索条件但有了JSONB字段和GIN索引就可以在SQL里加上WHERE metadata-product_line E AND metadata-period 2024Q1把范围大幅收窄。没有metadata结构化的向量库谈不上真正的Agentic RAG因为Agent无法按条件筛选工具结果。4.2 Agent层plan-retrieve-grade-rewrite-generate的工程实现LangChain在这里的作用是把底层模型和工具抽象成统一接口。我用LangChain的create_react_tool封装检索工具用ChatOpenAI或本地的Ollama/LLaMA按企业合规选型做LLM。LangGraph负责状态机编排。RetrieveNode的内部实现是混合检索加精排的整合def retrieve_node(state: AgentState) - AgentState: docs [] for q in state[plans]: # dense retrieval dense_hits pgvector_search(q, top_k10) # keyword retrieval via tsvector keyword_hits tsvector_search(q, top_k10) docs.extend(dense_hits keyword_hits) # deduplicate and rerank reranked bge_reranker.rerank(state[question], docs) filtered [d for d in reranked if d[score] 0.4] return {**state, retrieved_docs: filtered[:5]}GradeNode用LLM做判断题输出一个结构化结果grade_prompt 你是检索质量评估员。根据给定的问题与检索到的文档判断这些文档是否足以回答问题。 输出JSON{verdict: sufficient或insufficient或conflict, reason: ..., missing_keywords: 补充检索的关键词} 这里还有个细节GradeNode不是我随便拍拍脑袋设计的而是直接对标了RAGAS里的answer_relevancy和faithfulness这两个指标。GradeNode的verdict相当于在线版的“context sufficiency”评判离线评估与在线判断用同一套标准这样你才可能用离线的指标去预测在线体验。RewriteNode则是把GradeNode给的重试建议转成新的检索Query。它会读取上一次的missing_keywords结合原问题进行扩展改写同时调整metadata过滤条件。我限制最大迭代次数为3——超过3次还检索不到高质量内容就直接进入GenerateNode让LLM在提醒“资料不足”的前提下基于已有信息作答避免无限循环拖垮响应时间。这个边界非常重要后面会细说。4.3 服务层FastAPI如何接入LangGraph并管理多用户会话FastAPI侧要做的核心事情有三件接收请求、调用Agent图、返回流式结果。这里的坑集中在异步与并发上。在Async路由里直接调用graph.invoke()会阻塞事件循环正确做法是用graph.ainvoke()或者在同步函数里套run_in_executor。多用户会话管理是我踩过最多坑的地方。RAG在生产环境是会被很多用户同时调的LangGraph State如果直接存在内存里进程重启后用户会话就丢了。我的做法是把会话状态存到PostgreSQL每轮对话结束后把AgentState序列化为JSON存进一张agent_sessions表下次用户发消息时先把State加载回来再作为初始状态传给LangGraph。app FastAPI() app.post(/api/chat) async def chat(req: ChatRequest): state load_session(req.session_id) state[question] req.message final_state await agent_graph.ainvoke(state) save_session(req.session_id, final_state) return {answer: final_state[answer], trace: final_state[trace]}这样设计还有一个好处调用链Trace也跟着State一起落库了。每次用户问答哪个工具被选、检索返回了什么、Grade给了什么分、迭代了几轮全都能在管理后台回放。出了问题你不用靠用户截图反馈直接查Trace日志就能复现。4.4 让流程真正能跑起来的三个配置细节这块内容很碎但经验值最高。第一个是RecursionLimit。LangGraph默认的递归上限是25步如果你没显式配置一个包含4到5次迭代的Agent流程很容易触发RecursionLimit异常。我在初始化图时直接设app graph.compile(checkpointerpg_checkpointer) graph_config {recursion_limit: 30}第二个是LLM的temperature和输出格式。Plan、Grade、Rewrite这类中间决策节点temperature一律调到0并让模型按JSON Schema输出只有最后的Generate节点可以保留0.3左右保证回答有自然语言多样性。别在决策节点追求创造力否则Agent会“发挥”出你意想不到的工具选择。第三个是pgvector的查询超时和连接池。检索响应慢很多时候不是Agent慢而是数据库连接池打满。我至少会用psycopg_pool的连接池配置成20个连接并给慢查询设超时阈值防止某个超大metadata过滤条件把CPU拖死。向量检索和常规CRUD服务共用数据库时尤其要注意优先保证SQL关键路径的稳定性。5. 效果评估与踩坑记录从Demo到可上线之间隔着什么Agentic RAG最让人头疼的问题就是Demo跑起来“看起来什么都会”上线之后“什么都救不了”。因为Demo问题少、范围窄Agent随便转转就能答对而真实用户的问题是长尾分布你要验证它不是靠感觉而是靠一整套评估体系和足够的边界测试。5.1 用RAGAS和你自己的case set做纵向对比我强烈建议在项目一开始就把评估框架搭起来而不是等到功能做完了再补。我用的是RAGAS这套工具主要衡量四个维度Faithfulness忠实度看答案是否能在上下文中找到证据Answer Relevancy答案相关性看答案是否切题Context Precision上下文精确性看检索到的内容里有多少真正相关Context Recall上下文召回率看相关文档是否都被检索到了。但光有公开指标不够一定要建自己的Gold Case Set。我从真实用户日志里挑了100条problematic question每条标注了“理想工具调用链”和“理想答案关键点”做成回归集。每次改查询改写提示词、换Embedding模型、调整Re-rank阈值都在这100条上跑一遍观察指标有没有回退。纵向对比表格最直观版本FaithfulnessAnswer RelevancyContext Precision平均迭代轮数P95响应时间Naive RAG纯向量0.720.580.4311.8sHybrid SearchRe-rank0.810.760.6412.1sAgenticPlan-Grade-Rewrite0.870.830.722.43.6s这套数据说明两件事第一Agentic RAG确实带来了质量提升第二代价是响应时间和迭代轮数上去了。所以在生产环境我不会对所有流量都跑完整Agent循环而是先用查询路由做个分类简单事实性问题走轻量链路直接Hybrid SearchRe-rankGenerate多跳复杂问题才走完整Agent循环。这和很多商业产品的“Strict/Moderate/Open”分级思路是一致的——不是所有用户问题都值得花3.6秒去求解。5.2 我在落地过程中踩过的五个坑第一个坑是Pydantic v1/v2与LangChain LS的兼容性问题。新项目直接上Pydantic v2问题不大但老项目里如果还留着Pydantic v1的模型LangChain在序列化时会报各种莫名的AttributeError排查起来相当费劲。建议LangGraph和LangChain的版本锁死用同一套虚拟环境隔离干净别混装。第二个坑是Grade节点的LLM判断不稳定。同一个问题、同一批文档跑三次Grade有时返回sufficient有时返回insufficient。我反复调prompt后发现根源在于decision prompt没有给模型“正反例”。修改方式是先给两个few-shot示例并明确告诉模型“只有回答所需的所有关键实体都出现在文档中才判定sufficient”。同时把llm temperature设为0并要求输出JSON结构。改完以后同一输入的判断一致性明显提升。第三个坑是metadata过滤条件在pgvector里没有正确下推。早期我图省事先用向量检索取Top-100再在应用层按metadata过滤结果大量之前没有元数据约束的文档挤占Top-100名额即使后来做精排也救不回来——因为真正的相关文档早被过滤掉了。正确做法是把metadata过滤条件写进SQL的WHERE子句让数据库在向量搜索之前先按字段缩小候选集。第四个坑是多轮对话里的历史污染。用户连续问了三轮之后第四轮的Query如果不做上下文清洗经常变成“那它呢”这种指代词Agent改写时会把前几轮的所有信息都带进Embedding检索出来的结果一片混乱。我的处理方式是每一轮的用户消息发送前先从聊天历史里抽取出“对话摘要”只把摘要和当前问题合并作为检索Query而不是把完整历史全部拼接进去。第五个坑是工具选型被“结构化陷阱”绑架。结构化数据工具确实好但如果业务数据并没有一个整洁的关系库硬把它做成SQL工具Agent会不断生成SQL然后报错重试浪费大量迭代次数。我后来给SQL工具前面加了一层“表结构预览”让PlanNode先看表名、字段名、样例值再决定要不要走SQL。这个看似不起眼的修改把SQL工具的首轮成功率提升了三分之一。5.3 上线前必须验证的边界条件除了指标我还会做一组边界测试。这些case通常不会被写进标准评估集但它们最能暴露系统的底层脆弱性。空检索测试当知识库里没有任何相关内容时Agent会不会硬答我要求Generate节点在Grade判定insufficient后输出格式必须是“抱歉我未找到相关资料”并附上已检索过的关键词列表。这样用户至少能知道系统“尝试过什么”而不是被幻觉答案误导。超时熔断测试Agent最多迭代3次如果3次之后Grade仍判定不满足必须直接进入Generate或返回兜底回复。千万不能让单个请求无限循环否则下游服务和数据库连接都会被打爆。长文本上下文测试当多轮检索结果为条目越来越多时会不会超出模型上下文窗口现在主流模型支持128K上下文但窗口大不代表生成效果好——上下文越长检索噪声越多。我给最终生成阶段设置了“最大上下文条数”和“单条最大字符数”两个上限超出的部分直接截断优先保留Re-rank分数最高的那几条。并发测试用Locust模拟50并发用户同时提问观察LangGraph的checkpointer、pgvector连接池、LLM的rate limit三者之间会不会互相拖垮。这类测试做到后面基本都是在调限流和缓冲队列而不是调模型效果了。很多人做Agentic RAG死在最后一步就是只测了单条质量没测并发下的稳定性。最后说一点个人体会Agentic RAG不会天然解决Naive RAG的缺陷它只是给你提供了一套系统框架来暴露和迭代这些缺陷。如果底层文档切得不合理、Embedding模型选得不对、评估集不够贴业务那么Agent的每一次循环都会把这些问题放大一遍。我后来能在线上稳定运行这套系统靠的不是迷信Agent概念而是把评估集、灰度、Trace、监控这些“工程基建”也当成RAG系统的一部分来做。哪怕你的场景还没有复杂到需要完整Agent循环也可以先把Grade和Rewrite这两个节点想清楚——它们真的会是整个链路中最有价值的部分。
返回列表