ARTICLE DETAIL

资讯详情

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

从零搭建生产级Agentic RAG系统:架构设计、核心模块与调优实践

从零搭建生产级Agentic RAG系统:架构设计、核心模块与调优实践 1. 从零搭建一套生产级 Agentic RAG 系统我踩过的坑和最终跑通的方案RAG 这个词这两年已经被说烂了但真正在生产环境里跑过的人都知道Demo 和 Production 之间隔着的不是一条街而是一整个太平洋。我最初接触 RAG 的时候觉得不就是“检索生成”嘛向量库一接、Prompt 一写就完事了。结果上线第一天就被真实用户的提问打脸多跳问题答不对、表格数据检索出来是乱的、知识库更新之后旧答案还在飘。后来我开始系统性地研究 Agentic RAG 这个方向也就是让 RAG 系统具备 Agent 的自主决策能力不再是一条死板的“检索→拼接→生成”流水线而是能自己判断该不该检索、该查哪个库、检索结果够不够、要不要换个策略再来一轮。这套思路落地之后效果提升非常明显。我把自己从零搭建一套生产级 Agentic RAG 系统的完整过程整理出来包括架构设计、技术选型、核心模块实现、参数调优、以及那些只有真正跑过才会知道的坑。不管你是刚接触 RAG 的新手还是已经做过基础 RAG 想往 Agentic 方向升级的开发者这篇文章里的内容都可以直接参考复现。我不会只讲概念每个关键环节都会给出具体的实现思路和参数依据让你看完就能动手。2. 为什么基础 RAG 在生产环境里不够用2.1 基础 RAG 的三个致命瓶颈先说说我一开始用的最朴素的 RAG 架构用户提问 → 向量化 → 向量库相似度检索 Top-K → 拼接 Prompt → LLM 生成答案。这套流程在 Demo 阶段看起来很美好但生产环境里很快就暴露了三个瓶颈。第一个瓶颈是检索决策缺失。不是所有问题都需要查知识库。用户问“你好”或者“帮我写一段代码”你非要去向量库里捞一圈不仅浪费资源还可能把不相关的文档片段塞进 Prompt 里反而干扰了 LLM 的判断。基础 RAG 没有“要不要检索”这个决策环节来什么 query 都走同一条路。第二个瓶颈是单轮检索不够用。很多真实问题需要多跳推理。比如用户问“我们公司去年Q3推出的那款产品的核心技术方案和竞品相比有什么优势”这个问题需要先找到“去年Q3推出的产品”是哪个再去找它的技术方案文档再去找竞品分析文档最后做对比。基础 RAG 一次检索只能捞一批文档根本覆盖不了这种多跳需求。第三个瓶颈是检索质量无法自评估。向量相似度高不代表内容真的相关。我遇到过很多次检索出来的文档和问题在语义空间里距离很近但实际内容答非所问。基础 RAG 没有机制去判断“我捞到的这些东西到底能不能回答问题”直接就往 LLM 里塞生成质量自然不稳定。2.2 Agentic RAG 的核心思路让系统自己决定怎么查Agentic RAG 的核心思路其实很直观把 RAG 流程中的每个关键环节都交给 Agent 来决策。具体来说系统需要具备以下几种能力路由决策判断用户问题是否需要检索知识库如果需要该查哪个知识库可能有多个不同领域的库。查询改写把用户的原始问题改写成更适合检索的形式包括同义词扩展、指代消解、子问题拆解等。多轮检索根据第一轮检索结果判断信息是否充足不足则生成新的查询继续检索。结果评估对检索到的文档进行相关性评分过滤掉低质量内容。生成验证生成答案后检查是否有幻觉是否忠实于检索到的内容。这些能力组合起来就形成了一个有自主决策能力的 RAG 系统。和基础 RAG 最大的区别在于Agentic RAG 不是一条固定流水线而是一个带反馈回路的动态系统。2.3 什么场景适合上 Agentic RAG不是所有场景都需要 Agentic RAG。如果你的知识库很小、问题类型单一、对延迟要求极高那基础 RAG 可能就够了。但如果你遇到以下情况就值得考虑升级知识库覆盖多个领域不同领域的问题需要查不同的库用户问题复杂度高经常需要多步推理对答案准确性要求高不能容忍太多幻觉知识库更新频繁需要系统能自适应我这次搭建的系统主要面向企业内部的文档问答场景知识库包含产品文档、技术方案、会议纪要、客户反馈等多种类型问题复杂度跨度很大。这种场景下Agentic RAG 的优势非常明显。3. 整体架构设计与技术选型3.1 系统分层架构我把整个系统分成了四层从下到上依次是存储层负责文档的存储和索引。这里我用了两种存储方式——向量库用于语义检索Elasticsearch 用于关键词检索。两者结合做混合检索效果比单用向量库好很多。检索层封装了检索相关的所有逻辑包括查询改写、混合检索、重排序、结果过滤。这一层是 Agentic RAG 的核心Agent 的决策逻辑主要在这里体现。编排层负责整个流程的调度。我用了一个状态机来管理 Agent 的执行流程每个状态对应一个决策节点根据当前状态决定下一步做什么。接口层对外提供 API处理用户请求和返回结果。这一层还包括流式输出的处理因为生产环境里用户不可能等十几秒才看到第一个字。3.2 为什么选 LangGraph 而不是 LangChain技术选型这块我纠结了很久。LangChain 的生态确实丰富但它的 Chain 抽象对于 Agentic RAG 这种需要动态决策的场景来说太僵硬了。Chain 本质上是线性的虽然可以用各种 Router 来做分支但一旦流程复杂起来代码就会变得非常难维护。LangGraph 的思路不一样它把整个流程建模成一个图节点是执行单元边是状态转移。这种模型天然适合 Agentic RAG因为 Agent 的决策过程本身就是一个状态机。我可以用条件边来实现“如果检索结果不够好就回到查询改写节点”这种循环逻辑用 LangGraph 写起来非常自然。当然 LangGraph 也不是没有缺点。它的学习曲线比 LangChain 陡一些文档也没有那么完善。但一旦理解了它的核心概念——State、Node、Edge、Checkpoint——用起来还是很顺手的。3.3 向量库选型Qdrant vs Milvus vs pgvector向量库的选型我对比了三个主流方案维度QdrantMilvuspgvector部署复杂度低中低检索性能高高中过滤能力强强中与 PostgreSQL 集成无无原生分布式支持支持强弱社区活跃度高高高最终我选了 Qdrant。原因有几个一是它的过滤检索做得很好支持在向量检索的同时做复杂的 payload 过滤这对多知识库场景很重要二是它的部署确实简单一个 Docker 容器就能跑起来三是它的 Rust 底层让性能很稳我实测在百万级向量下检索延迟依然在毫秒级。pgvector 其实也很有吸引力毕竟可以直接用 PostgreSQL 的生态。但它的检索性能在数据量上去之后下降比较明显而且过滤和向量检索的结合不如 Qdrant 灵活。如果你的数据量在十万级以下pgvector 完全够用而且运维成本最低。3.4 Embedding 模型选择Embedding 模型直接决定了检索质量的上限。我对比了几个主流方案OpenAI text-embedding-3-large效果确实好但成本高而且有网络延迟BGE-M3开源模型里效果最好的之一支持多语言可以本地部署GTE-large阿里开源中文效果不错Cohere embed-v3多语言支持好但同样有 API 成本最终我选了 BGE-M3 做本地部署。原因很简单数据不出本地成本可控而且效果和 OpenAI 的差距在可接受范围内。BGE-M3 还有一个好处是它同时支持稠密检索、稀疏检索和多向量检索一个模型就能覆盖多种检索模式。不过本地部署 Embedding 模型需要注意硬件。BGE-M3 的模型大小是 2.2GB 左右推理时需要 GPU 显存至少 4GB。如果没有 GPU用 CPU 推理也可以但吞吐量会低很多。我建议至少用一张 T4 或者 RTX 3060 级别的卡。4. 核心模块实现细节4.1 查询理解与路由模块这个模块负责判断用户问题的意图决定后续走哪条路径。我把它设计成一个轻量级的分类器加规则引擎的组合。分类器用 LLM 来做Prompt 大概是这样的ROUTER_PROMPT 你是一个查询路由器。根据用户问题判断应该走哪条路径 1. direct_answer问题不需要查知识库可以直接回答如打招呼、简单计算、通用知识 2. single_retrieval问题需要查一次知识库就能回答 3. multi_retrieval问题需要多轮检索和推理才能回答 4. clarify问题太模糊需要向用户澄清 用户问题{question} 只输出路径名称不要解释。这个分类器看起来简单但实际效果很好。关键是要给 LLM 足够的上下文来说明每条路径的含义。我试过用更复杂的 Prompt加了很多 few-shot 示例但效果反而没有这个简洁版本好。原因是 few-shot 示例容易让 LLM 过拟合到示例的模式上遇到稍微不同的问法就判断错误。规则引擎作为兜底处理一些明显的情况。比如问题长度小于 5 个字符直接走 direct_answer问题里包含“对比”“比较”“区别”等词优先走 multi_retrieval。注意路由模块的准确率直接影响整个系统的效率和效果。我建议在初期把路由决策的日志都记录下来定期分析错误案例不断优化 Prompt。我上线第一周就发现了十几个路由错误修正之后整体延迟下降了 30%。4.2 查询改写与子问题拆解查询改写是提升检索召回率的关键步骤。用户的原始问题往往包含指代、省略、口语化表达直接拿去检索效果很差。我实现了三种改写策略指代消解把“它”“这个”“那个”等指代词替换成具体内容。这需要结合对话历史来做。比如用户上一轮问“BGE-M3 的模型大小是多少”这一轮问“它支持多语言吗”改写后应该是“BGE-M3 支持多语言吗”。同义词扩展把问题中的关键词扩展成同义词集合。比如“部署”扩展成“部署、安装、搭建”“性能”扩展成“性能、效率、速度”。这一步我用了同义词词典加 LLM 结合的方式词典保证常用词的覆盖率LLM 处理词典里没有的词。子问题拆解对于复杂问题拆解成多个子问题分别检索。比如“我们公司去年Q3推出的那款产品的核心技术方案和竞品相比有什么优势”拆解成子问题1我们公司去年Q3推出了哪款产品子问题2这款产品的核心技术方案是什么子问题3竞品的核心技术方案是什么子问题4两者对比有什么优势拆解之后每个子问题独立检索最后把结果汇总给 LLM 做综合推理。这一步的效果提升非常明显多跳问题的回答准确率从 40% 左右提升到了 75% 以上。4.3 混合检索与重排序混合检索就是把向量检索和关键词检索的结果融合。我用的融合算法是 Reciprocal Rank FusionRRF公式很简单RRF_score(d) Σ 1 / (k rank_i(d))其中 k 是一个常数通常取 60rank_i(d) 是文档 d 在第 i 个检索器中的排名。RRF 的好处是不需要归一化不同检索器的分数直接基于排名融合鲁棒性很好。重排序我用了一个 Cross-Encoder 模型具体是 BGE-Reranker-v2-m3。它的原理是把 query 和 document 拼在一起输入模型输出一个相关性分数。和向量检索的 Bi-Encoder 相比Cross-Encoder 能捕捉 query 和 document 之间的细粒度交互精度更高但速度慢。所以我先用混合检索召回 Top-50再用 Cross-Encoder 重排序取 Top-5这样在精度和速度之间取得平衡。重排序这一步的效果提升也很明显。我做过对比测试不做重排序的情况下Top-5 里平均只有 2.8 个是真正相关的做了重排序之后Top-5 里平均有 4.5 个是相关的。这个提升直接反映在了最终答案的准确率上。4.4 检索结果评估与迭代这是 Agentic RAG 区别于基础 RAG 的关键模块。系统需要判断检索到的内容是否足够回答问题如果不够要能自动发起新一轮检索。我实现了一个基于 LLM 的评估器Prompt 大概是这样的EVAL_PROMPT 你是一个检索质量评估器。根据用户问题和检索到的文档判断这些文档是否足够回答问题。 用户问题{question} 检索到的文档 {documents} 请输出 - sufficient文档是否足够回答问题yes/no - missing如果不够还缺少什么信息 - next_query如果不够建议的下一步查询 以 JSON 格式输出。这个评估器会输出三个字段sufficient 表示是否足够missing 表示缺少什么next_query 表示建议的下一步查询。如果 sufficient 是 no系统就会用 next_query 发起新一轮检索最多迭代 3 次。这里有个经验迭代次数不要设太多。我一开始设了 5 次结果发现很多情况下系统会陷入“检索-评估-再检索”的循环每次都觉得不够但实际已经捞不到更多有用信息了。后来改成 3 次并且在评估器里加了一个判断“如果连续两轮检索结果高度重叠就强制停止”效果好很多。4.5 生成与幻觉检测生成模块的 Prompt 设计很关键。我的原则是严格约束 LLM 只能基于检索到的内容回答不允许自由发挥。GENERATION_PROMPT 你是一个严谨的问答助手。请严格基于以下检索到的文档回答用户问题。 规则 1. 只使用文档中明确提到的信息不要添加文档之外的知识 2. 如果文档中没有足够信息回答问题直接说“根据现有资料无法回答” 3. 引用具体文档时标注文档来源 4. 如果文档之间存在矛盾指出矛盾并说明 检索到的文档 {documents} 用户问题{question} 回答生成之后我还会做一次幻觉检测。检测方法是用另一个 LLM 调用把生成的答案和检索到的文档一起输入判断答案中的每个事实性陈述是否都能在文档中找到依据。如果发现幻觉就重新生成或者降级返回“根据现有资料无法回答”。这一步会增加一些延迟但对于企业级应用来说准确性比延迟更重要。我实测幻觉检测能把幻觉率从 8% 左右降到 2% 以下。5. 完整实操流程与关键配置5.1 环境准备与依赖安装先说一下我的环境Ubuntu 22.04Python 3.11一张 RTX 409024GB 显存。如果你没有 GPUEmbedding 和重排序模型可以用 CPU 跑但速度会慢很多。核心依赖pip install langgraph langchain-core qdrant-client elasticsearch pip install sentence-transformers FlagEmbedding pip install fastapi uvicorn pip install ragas # 用于评估Qdrant 和 Elasticsearch 用 Docker 部署docker run -d --name qdrant -p 6333:6333 -v ./qdrant_data:/qdrant/storage qdrant/qdrant docker run -d --name elasticsearch -p 9200:9200 -e discovery.typesingle-node -e xpack.security.enabledfalse elasticsearch:8.11.05.2 文档处理与索引构建文档处理流程加载 → 分块 → 向量化 → 索引。分块策略我试过很多种最终用的是语义分块重叠窗口的组合。具体来说先用 LLM 判断文档的自然段落边界在边界处切分然后每个块保留前后各 100 个 token 的重叠。块大小控制在 512 个 token 左右。为什么是 512我做过实验块太小256会导致上下文不完整检索出来的片段缺少关键信息块太大1024会导致检索精度下降因为一个块里混了太多主题。512 是一个比较平衡的值适合大多数文档类型。索引构建代码的核心逻辑from FlagEmbedding import BGEM3FlagModel from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct model BGEM3FlagModel(BAAI/bge-m3, use_fp16True) client QdrantClient(hostlocalhost, port6333) client.create_collection( collection_nameknowledge_base, vectors_configVectorParams(size1024, distanceDistance.COSINE) ) def index_documents(chunks): embeddings model.encode( [c[text] for c in chunks], batch_size32, max_length512 )[dense_vecs] points [ PointStruct( idi, vectorembeddings[i].tolist(), payload{ text: chunks[i][text], source: chunks[i][source], doc_type: chunks[i][doc_type] } ) for i in range(len(chunks)) ] client.upsert(collection_nameknowledge_base, pointspoints)注意BGE-M3 输出的稠密向量维度是 1024。如果你换用其他模型记得同步修改 Qdrant collection 的向量维度配置否则会报错。5.3 LangGraph 状态机编排整个 Agentic RAG 流程用 LangGraph 编排核心状态定义from typing import TypedDict, List class RAGState(TypedDict): question: str rewritten_queries: List[str] retrieved_docs: List[dict] retrieval_round: int is_sufficient: bool answer: str route: str节点定义包括route_node路由决策、rewrite_node查询改写、retrieve_node检索、evaluate_node结果评估、generate_node生成、verify_node幻觉检测。条件边的逻辑def should_continue(state: RAGState): if state[is_sufficient]: return generate if state[retrieval_round] 3: return generate return rewrite graph.add_conditional_edges( evaluate, should_continue, { generate: generate, rewrite: rewrite } )这个状态机的核心在于 evaluate 节点之后的条件边。如果检索结果足够直接走生成如果不够且还没到最大轮次回到改写节点继续检索如果到了最大轮次强制走生成并在生成时告知 LLM 信息可能不完整。5.4 关键参数调优记录参数调优这块我花了很多时间做实验这里把关键参数和调优结论整理出来参数初始值最终值调优依据向量检索 Top-K2050召回率优先后续有重排序过滤重排序 Top-K105太多会引入噪声5 个足够覆盖分块大小256512256 上下文不完整512 平衡最好分块重叠50100100 能更好保持跨块语义连贯最大检索轮次535 容易陷入循环3 足够覆盖多跳RRF 常数 k6060标准值实测效果稳定评估阈值0.70.750.7 太宽松0.75 过滤效果更好这些参数不是绝对的需要根据你的具体数据和场景调整。但调优的方向是一致的召回阶段宁多勿少精排阶段宁精勿滥。6. 常见问题与排查技巧实录6.1 检索结果不相关怎么办这是最常见的问题。排查思路按优先级来第一步检查 Embedding 模型是否适合你的语言和领域。如果你的文档主要是中文但用的是英文为主的 Embedding 模型效果肯定差。BGE-M3 和 GTE-large 对中文支持都很好。第二步检查分块策略。我遇到过很多次检索出来的块把关键信息切断了。比如一个表格被从中间切开检索到的是上半部分但答案在下半部分。解决办法是调整分块边界或者在分块时识别表格、代码块等特殊结构整块保留。第三步检查查询改写是否到位。用户的口语化表达和文档的书面表达之间往往有语义鸿沟。查询改写就是架这座桥的。如果改写没做好检索效果会大打折扣。第四步考虑加混合检索。纯向量检索对关键词匹配不敏感。比如用户搜一个产品型号“XYZ-2000”向量检索可能返回一堆语义相似但型号不同的文档。加上关键词检索就能精准匹配。6.2 多跳问题答不对怎么破多跳问题的核心难点在于第一轮检索到的信息可能只是中间答案需要基于它生成第二轮查询。我的解决方案是在评估节点里显式地做子问题拆解。当评估器判断信息不足时不仅输出 next_query还输出一个“已解决子问题”和“待解决子问题”的列表。下一轮检索只针对待解决的子问题避免重复检索已经覆盖的内容。另外多跳问题的生成阶段也需要特殊处理。我会在 Prompt 里明确告诉 LLM“这是一个多跳问题你需要综合多轮检索的结果进行推理推理过程要显式展示。”这样 LLM 会更倾向于做逐步推理而不是直接给答案。6.3 知识库更新后旧答案还在飘这是向量库的经典问题。文档更新了但旧的向量还在库里检索时新旧内容一起返回LLM 可能采信旧内容。解决方案是给每个文档块打上版本号和时间戳检索时优先返回最新版本。具体做法是在 payload 里加version和updated_at字段检索时用 Qdrant 的过滤功能from qdrant_client.models import Filter, FieldCondition, MatchValue filter_condition Filter( must[ FieldCondition( keyversion, matchMatchValue(valuelatest) ) ] )文档更新时先把旧版本的 version 改成 archived再插入新版本。这样检索时只返回最新版本旧版本保留用于审计。6.4 延迟太高怎么优化生产环境里延迟是硬指标。我总结了几条优化经验流式输出生成阶段用流式输出用户能更快看到第一个字。虽然总时间没变但感知延迟大幅降低。并行检索多个子问题的检索可以并行执行。我用 asyncio 把检索请求并发出去整体检索时间从串行的 2 秒降到了 500 毫秒左右。缓存高频问题的检索结果和生成答案都做缓存。我用 Redis 做缓存命中率大概在 30% 左右对整体延迟改善明显。模型量化Embedding 和重排序模型用 FP16 甚至 INT8 量化推理速度能提升 2-3 倍精度损失在可接受范围内。减少不必要的 LLM 调用路由决策和结果评估不一定每次都要用 LLM。我加了一层规则引擎做预判能规则处理的就不调 LLMLLM 调用次数减少了 40% 左右。6.5 常见问题速查表问题现象可能原因排查方向解决方案检索结果不相关Embedding 不匹配/分块不合理检查模型语言支持、分块边界换模型、调分块策略多跳问题答不对缺少子问题拆解检查评估器输出加子问题拆解逻辑旧答案还在飘版本管理缺失检查 payload 字段加版本过滤延迟太高串行调用太多分析各阶段耗时并行化、缓存、量化幻觉严重Prompt 约束不够检查生成 Prompt加强约束、加幻觉检测路由错误Prompt 不够清晰分析路由日志优化 Prompt、加规则兜底7. 效果评估与持续迭代7.1 评估指标与测试集构建RAG 系统的评估不能只看“感觉好不好”要有量化指标。我用的核心指标有三个检索指标RecallK 和 MRRMean Reciprocal Rank。RecallK 衡量前 K 个结果里有多少是相关的MRR 衡量相关结果的平均排名。我的目标是 Recall5 大于 0.9MRR 大于 0.8。生成指标Faithfulness忠实度和 Answer Relevancy答案相关性。Faithfulness 衡量答案是否忠实于检索内容Answer Relevancy 衡量答案是否切题。这两个指标我用 Ragas 框架来算。端到端指标人工评估的准确率和用户满意度。我每周会抽 100 个真实问题做人工评估分成“完全正确”“部分正确”“错误”三档。测试集的构建很关键。我从真实用户问题里采样了 500 个问题覆盖各种类型简单事实查询、多跳推理、对比分析、否定问题、模糊问题等。每个问题都标注了标准答案和相关的文档 ID。这个测试集是我迭代优化的基础。7.2 持续迭代的节奏RAG 系统不是一次搭好就完事的需要持续迭代。我的迭代节奏是每周分析路由日志和评估日志找出错误案例优化 Prompt 和规则。每两周跑一次完整评估对比各项指标的变化决定是否调整参数。每月更新测试集加入新的真实用户问题保持测试集的代表性。每季度评估是否需要升级模型或调整架构。这个节奏看起来慢但实际执行下来效果很好。我上线三个月端到端准确率从最初的 62% 提升到了 85% 以上。7.3 一个真实的优化案例分享一个我印象最深的优化案例。上线初期用户反馈“问产品价格的时候经常答错”。我排查之后发现价格信息在文档里是以表格形式存在的而我的分块策略把表格切碎了检索到的片段只有表头没有数据。解决方案是加了一个表格识别模块在分块之前先识别文档中的表格把整个表格作为一个块处理并且在表格块的文本里加上表头信息让检索时能匹配到。改完之后价格相关问题的准确率从 45% 提升到了 92%。这个案例让我意识到RAG 系统的效果瓶颈往往不在模型而在数据处理。文档解析、分块、索引这些“脏活累活”做得好不好直接决定了系统的上限。8. 一些个人体会和后续扩展方向这套系统跑到现在我最深的体会是Agentic RAG 的核心价值不在于用了多先进的模型而在于把决策权交给了系统本身。基础 RAG 是一条死板的流水线Agentic RAG 是一个有反馈回路的动态系统。这个思路上的转变带来的效果提升比换任何模型都大。后续我计划往几个方向扩展。一是引入知识图谱把结构化的实体关系和非结构化的文档检索结合起来处理需要关系推理的问题。二是做多模态 RAG支持图片和表格的检索。三是优化成本目前 LLM 调用还是主要成本来源我在研究用更小的模型做路由和评估把大模型只用在最终的生成环节。如果你也在做 RAG 相关的项目我的建议是先把基础 RAG 跑通理解每个环节的作用和瓶颈再逐步引入 Agentic 的能力。不要一上来就追求大而全的架构从最简单的路由决策开始一步步加每一步都做评估确保每次改动都有正向收益。RAG 这个方向变化很快但底层的数据处理和检索质量永远是根基把根基打牢上层怎么变都不慌。
返回列表