ARTICLE DETAIL

资讯详情

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

RAG与Agent深度融合:构建可靠的大模型知识检索增强管道

RAG与Agent深度融合:构建可靠的大模型知识检索增强管道 把思考过程列出来方便我梳理内容和结构不需要——我直接产出最终博文。这篇系列写到第四篇前几篇讲了大模型调用、提示词工程、Agent工作流设计但一直绕不开一个问题Agent在回答用户问题时它的知识到底从哪来今天这篇就把这个环节单独拎出来讲透也就是RAG——检索增强生成。在Agent体系里它承担的是外部知识和模型能力之间的管道角色说白了就是让Agent学会不知道就去查查完再回答。先给这篇内容定个位适合正在搭建AI Agent、但发现模型凭空编造内容、或者自己的业务数据没法被模型天然理解的朋友。我会从RAG为什么存在讲起把索引、检索、生成这三个阶段逐个拆开再给出一套可以直接跑通的本地RAG管道代码最后聊聊评估指标和常见的坑。如果你是第一次接触RAG这篇可以当入门地图如果你已经写过简单的RAG demo重点看第四、第五部分那里有很多我不会在演示代码里写的实操教训。1. 为什么Agent需要一条知识获取管道1.1 大模型的记忆到底缺什么很多人会误以为大模型什么都知道但真正把Agent放到业务环境里跑一段时间就发现它压根不是知道而是背过。模型的参数里确实压进了一部分海量文本的统计规律可这份规律是有截止日期的。你今天问它昨天新发布的接口文档它只能靠猜你问它你们公司内部的项目规范、客户历史沟通记录、最新一版的产品报价单它更是一无所知因为这些内容压根没出现在它的训练语料里。这里要理清一个关键点模型知识不足不是它答错了而是它在没有线索的情况下强行作答。这种状态下产生的输出我不太愿意直接叫它幻觉我更喜欢叫它没有依据的推测。因为模型本质上是一个概率生成器给它一个提问它就会顺着最可能的文本路径往下走至于这条路对不对、是不是事实它自己并没有校验手段。这带来的问题很现实你让Agent帮你做客户问答它可能把产品参数说得头头是道但全是编的比不说还麻烦。所以Agent真正的缺口不是模型能力而是数据通道。模型已经有了生成和组织语言的能力缺的是在特定时刻拿到特定知识的能力。这就是我把RAG叫作知识获取管道的原因——它不负责教模型说话它负责在模型说话之前把相关的事实材料递到模型手里。1.2 为什么选择RAG而不是直接微调提到让模型知道我的数据很多人的第一反应是微调。这个思路不能算错但放在Agent场景里性价比和时效性都存在问题。微调的本质是把知识压进模型权重里这意味着每次数据一更新你就要重新训练一次模型而且训练过程消耗的算力、时间和调试成本都会随着数据量增长迅速膨胀。更麻烦的是你很难通过微调去控制模型在什么时候该用哪份知识数据混在一起模型只能用概率去猜哪个更相关。RAG的选择完全不同。知识本身只存放在外部模型不需要记住每一个细节它只需要在回答前实时检索、把检索到的内容夹在提示词里、然后基于这些内容生成答案。这么设计有三个非常现实的好处第一知识可以随时增删改今天新写一篇文档索引刷新一下Agent立刻就能用到第二可以精确追踪模型到底是基于哪段知识在回答出现问题能追溯到源头第三成本低不需要动模型权重一套检索管道可以服务于不同模型。打个比方微调像是让员工把整本手册背下来RAG像是允许员工在回答问题时翻手册。前者适合知识长期稳定、对延迟不敏感、使用频率极高的场景后者适合知识快速变化、需要追溯来源、没法反复训练的场景——而Agent恰好是后者。这也是为什么现在聊Agent架构基本都默认RAG是标配不是因为它新而是因为它最贴合Agent动态决策、实时取用的特性。2. RAG的核心三阶段索引、检索、生成2.1 索引阶段知识怎么变成可检索的格式很多人第一次做RAG就栽在索引上。原因很简单模型和检索器都不认识原始文档——你扔给它一个几十页的PDF它不能直接拿去跟用户问题做匹配。你需要先把这份文档切成小段再把每一段转成一组向量数字最后存进向量数据库这个过程就是索引。索引阶段的第一步是切分。切分看起来简单其实是个非常讲究的活儿。按固定字符数切比如每200个字符一段实现最快但很容易把一个完整的概念拦腰斩断导致检索召回时拿到的是半句话按段落切语义完整性更好但有些文档的段落可能特别短、也可能特别长长度不均衡又会影响后续向量化的效果。我在实际项目里常用的是递归切分加重叠窗口即先按段落层级切再对超出长度限制的段落做二次切分同时让相邻片段保留一小段重叠文字。这个小技巧可以明显降低检索到的内容刚好缺一半上下文的概率。第二步是向量化。这一步要做的是把切好的文本块交给嵌入模型得到一串浮点数。这串数字的含义是这段文本在语义空间里的坐标——意思相近的文本坐标也相近。所以向量化模型的选择直接决定了检索能不能看懂你的文本。通用场景选主流的Embedding模型通常够用但如果你处理的是专业领域的语料比如法律条文、医疗文书或者老旧的内部系统导出的数据用通用模型可能匹配得很差需要结合领域样本去做微调或者尝试领域化模型。第三步就是入库。向量数据库负责存储这些向量并提供给一个查询向量快速返回最相近的若干向量的能力。这一步的选型要考虑数据量、并发量和部署方式是本地单机用还是集群高可用是追求毫秒级响应还是能接受秒级批量处理。没有绝对最好的库只有适合你当前阶段的库。2.2 检索阶段召回与重排的取舍索引做完管道进入检索阶段。用户在Agent里提出的问题会先被转成同样的向量然后去向量数据库里做相似度检索从成千上万个片段里找出最接近的top-K个。这个环节的核心矛盾是召回率和精准率的取舍——你想多找回一些候选片段来保证不漏掉正确答案但找回越多后面塞给模型的内容就越臃肿而且不相关内容多了反而会干扰模型生成答案。我在前面踩过一个很典型的坑一开始为了怕漏把K值设得很大结果检索回来的片段五花八门模型在生成时试图把明显无关的内容也编进答案里回答质量反而下降了。后来我养成了习惯用K值控制总量同时引入重排环节。第一步先用向量检索快速筛选出上百个候选第二步用一个更强的排序模型或者基于规则的方法对这上百个候选做精细排序只留下最相关的那几个扔给生成模型。这里要特别提醒类别不同的索引混合在一起检索时需要考虑索引隔离。比如你把产品文档、售后工单、内部聊天记录都放在同一个向量库里检索时会因为语义相近而相互干扰。我实践里更推荐按文档类别分别建索引或者给每条记录打上标签检索时先过滤掉与场景不匹配的范围再做相似度匹配。这比单纯依赖向量相似度靠谱得多。2.3 生成阶段让大模型学会查完再答检索不是目的让模型基于检索内容生成答案才是。这个阶段的实现方式是把检索到的文本片段、原始用户问题、再加上一段系统指令拼接成提示词一起交给大模型。有一个关键设计容易被忽略——你必须明确告诉模型只依据提供的资料回答不要自行发挥如果资料里没有答案就直说不知道。否则模型还是会忍不住动用它背过的知识来强行补全那样RAG的意义就打了个对折。我见过不少RAG项目效果不好的原因不在检索环节而在生成提示词写得过于随意。模型面对一堆检索片段不知道怎么权衡也不知道哪些该优先采纳于是只能平均用力输出一个四不像的答案。好的生成指令应当包含几个要素首先是角色设定比如你是企业内部的客服助手其次是资料使用规则比如优先参考资料中时间最新的内容矛盾时给出多版本说明最后是答案格式要求比如如果未找到明确答案请直接回复未找到相关信息不要编造。到这里RAG的基础闭环——索引、检索、生成——已经形成了。但请注意这只是单轮问答的闭环。到了Agent场景里事情会变得更复杂比如需要多轮检索、需要结合多个来源交叉验证、需要判断当前检索结果是否足够回答用户问题这些我会在第六部分展开。3. 从零搭一条本地RAG管道3.1 数据准备与选型讲完理论直接用代码走一遍实操流程。我用的是Python环境依赖库选择LangChain作为胶水层向量数据库用Chroma本地运行、零配置适合学习和中小型项目Embedding模型用HuggingFace上的通用模型以保持离线可用。数据方面我会准备几段关于一款虚构产品的说明书文本大家用自己手头的文档替换也行。在动手前要对数据说话你手头的文档质量决定了RAG的上限。就算管道代码写得再漂亮如果源文档本身是格式混乱的扫描件、意思含糊的碎片笔记检索到的内容也绝不会是干净的。所以我每次接项目的第一件事不是写代码而是先清洗数据去掉页眉页脚、修正乱码、把表格转换成可读的文本。有些朋友觉得这一步浪费时间但我的经验很明确这恰恰是性价比最高的一步。3.2 索引构建向量存储from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 1. 加载文档 loader TextLoader(product_manual.txt, encodingutf-8) documents loader.load() # 2. 切分每段500字符重叠100字符 text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap100, separators[\n\n, \n, 。, , , , ] ) chunks text_splitter.split_documents(documents) # 3. 向量化 embeddings HuggingFaceEmbeddings(model_nameBAAI/bge-small-zh-v1.5) # 4. 入Chroma库 vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./chroma_db )我在切分这里用的参数很有代表性chunk_size等于500chunk_overlap等于100。这个参数组合对中文内容比较友好因为中文一个字符就是一个完整语义单位500字大约是普通文档自然段落的长度既不会让语义单元过碎也不会让单段内容过长导致向量化后语义被稀释。重叠100字则是为了弥补切分边界可能截断语义的问题。Embedding模型的选型我刻意挑了BAAI/bge-small-zh-v1.5因为它体积小、离线可用、中文支持好日常原型开发足够。如果你处理的是纯英文内容换成对应的英文Embedding模型性能会更好。另外提醒一点尽量保证入库和查询用的是同一个Embedding模型否则向量空间的坐标体系不一致检索效果会急剧恶化。3.3 检索与问答串联索引完成后写一个最简的检索问答函数。from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI def ask_with_rag(question, vectorstore, k4): # 1. 检索取回最相关的k段 retriever vectorstore.as_retriever(search_kwargs{k: k}) docs retriever.invoke(question) # 2. 拼接上下文 context \n\n---\n\n.join([doc.page_content for doc in docs]) # 3. 构造生成提示词 prompt ChatPromptTemplate.from_messages([ (system, 你是一个严谨的问答助手。请仅依据提供的上下文回答问题。 如果上下文中没有明确信息请回复未找到相关信息不要编造。 回答时请注明你参考的片段序号。), (human, 上下文\n{context}\n\n问题{question}) ]) # 4. 生成 llm ChatOpenAI(modelgpt-4o-mini, temperature0) chain prompt | llm return chain.invoke({context: context, question: question})这段代码把整个RAG管道压成了四个步骤。第一步是检索特别注意k值的设置我目前设成4意味着最多带4段文档给模型第二步是把检索结果拼接成一个临时知识包第三步是提示词构造这里有几处细节加了不要编造的硬约束加了注明参考片段序号的要求方便你做事实追踪第四步是生成temperature设成0让模型输出尽量保守和稳定。实际跑的时候建议先单独测试检索环节也就是先看看retriever返回的片段是不是真的和问题相关。如果检索返回的内容本身就跑偏了生成结果一定是不合格的但很多人拿到错误答案后第一反应是调提示词这其实是找错了方向。记住一条经验先调检索再调生成顺序不能反。4. 评估RAG别只看答得对不对4.1 Hit Rate与MRR很多RAG项目做完了被问效果怎么样答不上来。原因在于大家常用肉眼判断随机挑几个问题看一眼答案是否顺眼这根本算不上评估。这个过程如果做一点规范化处理检索质量就能变成可测量的数字。我平时评估RAG至少会看两个层面的指标。检索层面最常用的是Hit Rate就是正确答案有没有出现在召回的top-K结果里还有MRR它衡量正确答案在结果列表中的序位——两个都能回答对如果你每次答案都排在第一MRR就高如果每次都排在第五MRR就低。对Agent应用来说MRR尤其重要因为你通常只把最高分的两三个片段塞给生成模型排名越靠前被模型采纳的概率越大。生成层面常用指标包括忠实度、答案相关性和完整性。忠实度是最核心的关注点——生成答案里有多少内容能够被检索到的上下文支持。我用过一个比较笨但很有效的方法把生成答案和检索到的上下文一起丢给一个更强的大模型让它逐句判断哪些内容有依据、哪些没有然后算一个有依据句子的占比。这比人工打分快得多也能规模化。4.2 从指标到清单指标不是做给自己看的它要能指导优化方向。Hit Rate低说明检索环节有问题要么切分太碎、要么Embedding模型不合适、要么重排策略太弱忠实度低说明生成提示词约束不够模型在自由发挥。把这些归因关系整理成一张排查清单排起错来会特别顺。现象可能原因排查方向检索到的片段和问题不符向量模型不适合该领域文本换领域模型或微调答案内容正确但顺序混乱切分导致语义序列断裂调大chunk_overlap或按语义切分模型明显在编造提示词约束力不足增加硬性约束并降低temperature检索耗时太长向量库数据量过大且无索引建立分区索引或考虑集群方案答案时效性差旧文档权重过高引入时间戳排序或手动筛选我在一次内部知识库项目里把Hit Rate从0.62提到0.89主要动作就是两件事把统一的大索引拆成按部门划分的小索引检索前先用部门标签做过滤同时把切分的分隔符优先级从固定字符改成按句子边界。这两步看起来都不复杂但效果非常显著。5. 实操中常见的坑与排查实录5.1 召回为空向量库里查不到的真相最让人崩溃的问题是用户问的问题非常正常可检索返回结果为空。排查下来原因也五花八门。第一种是数据入库时没有正确处理向量库是空的第二种是查询Embedding模型和入库Embedding模型不一致查询向量和库里的向量不在一个空间里第三种是数据切分太碎导致每个片段的语义太弱和具体问题匹配不上。第一种问题最容易排查直接查询向量的文档数量即可。第二种问题我在前面强调过但很多人还是会在实验时将不同模型混着用。第三种问题我通常建议把切分粒度调大一点让每个片段包含足够的信息量同时适度调大chunk_overlap保证语义连续性。如果片段里只有一句话向量化后很难表达完整意思检索器自然不知道它在说什么。还有一个大家容易忽略的细节用户问法跟文档表达方式差异过大。比如文档里写的是离职流程用户问辞职手续怎么办如果检索完全基于向量相似度可能因为表面措辞不同而召回失败。解决这类问题有几个思路一是建立同义改写层让Agent先对用户问题做一次转换再拿去检索二是补充关键词索引和向量检索并行做混合检索三是维护一个别名映射表把常见口语化问法映射到文档中的标准术语上。5.2 切分不合理上下文割裂的隐性代价切分不当是RAG里最常见也最隐蔽的问题。它不会让程序报错也不会让检索完全为空它让返回结果看起来相关但实际残缺。我做第一版RAG的时候用固定256字符切分自测时发现检索到的片段单独看都是通顺的但组合到上下文里模型的回答总是缺关键细节。后来排查发现一个完整的产品规格说明被切成了三段第一段是品名和概述第二段是核心参数第三段是适用场景模型只检索到了第二段自然就答非所问。这个案例的教训是切分策略必须跟着文档结构走。如果文档本身有清晰的章节、标题、列表结构优先用结构边界切分这能最大程度保证语义完整性如果文档是纯叙述性的长文本再用固定长度加重叠窗口兜底。做RAG开发的人需要把切分当成一个正经问题来对待——建议每次修改切分参数后都把召回结果样例打印出来肉眼检查一遍不要只盯指标数字。5.3 知识更新的代价索引生命周期管理纯文本类知识库对更新不敏感但业务型知识库非常敏感。比如产品文档上周更新了索引如果不重新跑Agent给出的还是旧参数。我见过几次这样的线上事故用户咨询最新版本特性Agent依据旧文档给出错误信息追问才发现索引构建任务忘记调度了。对这个问题的处理我习惯在索引阶段就给每条数据加上时间戳或版本号检索时把时间作为一个过滤条件同时把索引更新做成一个独立可调度的任务比如每天凌晨跑一次增量更新更新完成后再切换到新索引。这里还有个小细节向量数据库不支持单条记录的删除再覆盖这样的原子操作需要先删除旧数据再写入新数据顺序反了可能出问题。我踩过坑之后现在写更新任务都会提前在测试环境跑一遍。6. RAG与Agent结合从一次检索到Agentic RAG6.1 多轮检索与工具调用基础RAG是一问一查一答但在真正的Agent场景里这个流程太线性了。Agent在回答复杂问题时往往需要把问题拆成多个子问题每个子问题可能对应不同的知识源有的需要查产品文档有的需要看实时工单甚至有的需要调用外部API。这时候RAG就不再是单独一个环节而是变成了Agent手里可调用的一个工具。这种将检索能力工具化的RAG现在业内叫Agentic RAG。它的核心变化是不再每次用户提问都盲目检索而是由Agent判断这个问题需不需要查资料、查哪个库、查几轮、查完够不够回答。我给一个客户做故障排查Agent时第一版是简单检索产品手册效果一般第二版改成Agent先根据故障现象判断可能需要涉及的模块再去对应索引里做多路检索最后把多路结果汇总给模型准确率提升了接近一倍。实现方式上Agentic RAG通常会借助工具调用机制Agent决策引擎决定调用哪个检索工具检索工具返回结果后再带进下一轮模型调用。这个设计让RAG从固定的管道变成了灵活的知识获取能力也让它能和其他工具计算器、数据库查询器、日历API串联起来形成更复杂的工作流。不过这一切的前提仍是基础RAG各个阶段做得足够稳否则Agent决策得再聪明底层知识管道一塌糊涂输出照样是垃圾。6.2 知识管道的边界思考RAG不是万能的。它擅长的是从已知知识库中找出相关内容并生成答案但有很大一类问题它处理不了需要跨文档推理并得出新结论、需要对多个知识源做冲突消解和取舍、需要结合多模态资料图片、表格、视频等。所以做RAG与Agent的结合时要有意识地区分哪些问题适合直接RAG哪些问题需要Agent编排多次RAG哪些问题根本不该RAG参与。还有一个值得留意的问题即便检索效果很好RAG本质上是复述与重组能力不是推理与创造能力。模型在生成时会把检索到的内容当作事实依据但多个检索片段之间的逻辑关系模型是有可能搞错的。因此在设计Agent流程时如果问题涉及多跳推理比如产品A的售后退款规则加产品B的配送时效合起来预计几天到账我通常会拆分步骤第一步分别检索两个知识片段第二步让模型分别理解第三步再让模型做联合推理。而不是把两段知识一股脑塞给模型指望它自己理顺。这样的边界意识在面对越来越复杂的Agent应用时尤其重要。我自己见过不少项目前期demo做得很漂亮一到生产环境就垮掉原因不是RAG本身不行而是大家对RAG能做什么、不能做什么没有一个清晰的预期管理。回过头来看RAG这个知识获取管道是整个Agent系统里隐藏的地基。很多人学Agent喜欢研究模型调度、提示词技巧、复杂工作流编排但在实际落地中能不能稳定地从正确的地方取到正确的知识往往决定了Agent可用性的上限。这也是我把它放在系列第四篇的原因——得先让Agent有知识来源它才谈得上做判断和行动。如果你正在做Agent相关项目建议先把手头数据管道的稳定性练扎实再去追求花哨的编排技巧。
返回列表