ARTICLE DETAIL

资讯详情

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

RAG系统优化六处分水岭:从流水线到工程化实践

RAG系统优化六处分水岭:从流水线到工程化实践 1. 那条被做烂的流水线到底烂在了哪里先把话说直白点RAG 这套东西之所以让人觉得“烂大街”不是因为它的思路过时了而是因为绝大多数人做出来的东西本质上就是一条**“切块—向量化—检索—拼进提示词”**的流水线。这条流水线在 Demo 阶段看着挺唬人一旦丢进真实业务里问题就全冒出来了。我见过太多团队拿着一个 PDF 解析库、一个向量数据库、一个开源 embedding 模型三天搭出一个“知识问答”然后兴冲冲地拿去给业务方演示。演示的时候问的都是“公司年假多少天”这种问题答得挺准。可一旦业务方开始问“如果员工在试用期内离职年假怎么折算和转正后离职有什么区别”系统立刻开始胡言乱语——要么只捞到半句话要么把两个不同条款的片段拼在一起生成一个看似合理但完全错误的答案。这就是那条流水线的第一个死穴它把“检索”当成了“找相似文本”而不是“找能回答问题的证据”。向量相似度高不代表这段文本真的能支撑答案。很多时候最相似的片段恰恰是最没用的因为它只是措辞接近逻辑上根本不相关。第二个死穴是切块策略的粗暴。大部分教程教你按固定字数切比如 512 个 token 一块重叠 50 个 token。这种做法在结构规整的文档里勉强能用但遇到表格、多级标题、跨页段落、代码块直接就把语义切碎了。你想想一个“适用范围”条款被切成了两半前半段在块 A后半段在块 B检索的时候只捞到块 A模型自然只能瞎猜。第三个死穴是对“知识”的理解太浅。很多团队以为把文档塞进向量库就叫“知识库”了其实那只是一个“文本片段仓库”。真正的知识库需要处理实体、关系、层级、时序、条件依赖。比如“A 条款在 B 条件下不适用除非 C 同时满足”这种逻辑关系向量检索根本表达不了。所以我说烂大街的只是那条流水线不是 RAG 本身。真正的分水岭在于你有没有意识到RAG 的核心不是“检索增强生成”而是“如何让模型在正确的时间拿到正确的证据并且知道这些证据之间的关系”。接下来我要拆的六处就是我认为真正拉开差距的地方。2. 第一处分水岭从“相似片段”到“上下文检索”2.1 为什么相似度检索经常捞回一堆废话先做个思想实验。假设你的知识库里有一句话“员工在试用期内提出离职需提前三日通知且不享受当年度年假折算。”现在用户问“试用期离职有年假吗”你用 embedding 去算相似度最可能捞回来的片段是什么很可能是另一段讲“年假折算规则”的文本因为“年假”这个词出现了多次向量空间里距离更近。但真正能回答问题的是那句“不享受当年度年假折算”。问题出在哪儿embedding 模型衡量的是整体语义相似度而不是“这段文本是否包含回答问题的关键信息”。一段文本只要主题相关哪怕它完全不包含答案也可能获得高分。更糟糕的是很多 embedding 模型对否定句、条件句、转折句的处理很弱。“不享受”和“享受”在向量空间里可能非常接近因为上下文词都一样。这就是为什么你需要Contextual Retrieval。这个词听起来玄乎其实核心思想很简单在检索之前先给每个文本块补上它所在的上下文信息让检索单元从“孤立片段”变成“带背景的片段”。2.2 Contextual Retrieval 的实操做法具体怎么做我拿一个真实场景举例。假设你有一份员工手册原始结构是这样的第三章 休假制度 3.1 年假 员工入职满一年后享受年假。 3.2 试用期规定 试用期内员工不享受年假折算。如果你按固定长度切块很可能把“3.2 试用期规定”和“试用期内员工不享受年假折算”切成两块或者把“3.1 年假”的结尾和“3.2”的开头混在一起。检索时用户问“试用期有年假吗”系统可能只捞到“员工入职满一年后享受年假”然后模型回答“有年假”完全错误。Contextual Retrieval 的做法是在切块之后用一个小模型给每个块生成一段“上下文前缀”比如本段来自《员工手册》第三章“休假制度”第 3.2 节“试用期规定”。该节专门说明试用期员工的年假政策与 3.1 节的正式员工年假规则形成对比。然后把这段前缀和原始块拼在一起再做 embedding。这样检索时“试用期”和“年假”的语义关联就被强化了而且“不享受”这个关键否定词也不会被淹没。我实测下来这种做法在条款类、制度类文档上召回准确率能提升 20% 到 35%。代价是前期要多跑一遍小模型生成前缀但这是一次性成本值得。2.3 一个容易踩的坑前缀不能瞎编这里有个坑我必须提醒你上下文前缀必须基于原文结构生成不能让它自由发挥。我见过有人直接让模型“总结这段的上下文”结果模型把“试用期不享受年假”总结成了“试用期年假政策”把否定词丢了。正确的做法是从文档的标题层级、章节编号、前后段落里抽取结构化信息拼成前缀而不是让模型自由生成。你可以用这样的模板def build_context_prefix(doc_title, chapter, section, prev_section_title, next_section_title): return f本段来自《{doc_title}》{chapter} {section}。上一节是“{prev_section_title}”下一节是“{next_section_title}”。这样生成的前缀是确定性的、可追溯的不会引入幻觉。3. 第二处分水岭知识库不是文本仓库结构知识库和 RAG 知识库要分开用3.1 一个被问烂了的问题RAG 知识库能存图片吗这个问题在热词里出现了我直接给结论能存但“存”和“用”是两码事。你可以把图片的 OCR 文本、图片描述、图片向量都塞进向量库但如果你指望 RAG 系统能像人一样“看懂”一张流程图然后回答流程问题那基本不现实。我做过一个实验把一张包含 20 个节点的审批流程图丢进 RAG用多模态模型生成描述再切块入库。用户问“如果金额超过 5 万需要经过哪几个节点”系统捞回来的描述是“流程图展示了从申请到审批的完整流程包含多个节点”。这有用吗没用。因为描述丢失了节点之间的顺序、条件、分支关系。这就是我要说的核心区分RAG 知识库适合存“非结构化文本”结构知识库适合存“实体、关系、规则、流程”。两者不是替代关系而是互补关系。3.2 结构知识库到底长什么样结构知识库的典型形态是知识图谱或者规则引擎。还是拿审批流程举例结构知识库里存的不是一段描述而是这样的三元组主体关系客体条件申请触发初审金额 ≤ 1 万申请触发复审1 万 金额 ≤ 5 万申请触发终审金额 5 万复审前置初审无终审前置复审无用户问“金额 8 万走什么流程”系统可以直接在图上做路径查询返回“初审 → 复审 → 终审”。这种查询是确定性的、可解释的不会像向量检索那样捞回一堆无关片段。3.3 什么时候用 RAG 知识库什么时候用结构知识库我的经验判断标准很简单如果问题的答案是一段话、一个解释、一个定义用 RAG 知识库。比如“年假政策是怎么规定的”“这个 API 的返回值是什么意思”。如果问题的答案是一个路径、一个集合、一个条件判断用结构知识库。比如“这个流程经过哪些节点”“哪些条款适用于试用期员工”“A 和 B 之间有什么关系”。如果问题同时需要两者那就做混合检索先用结构知识库定位到相关实体和关系再用 RAG 知识库捞取这些实体的详细描述最后拼在一起给模型。我见过最成功的案例是一个法律咨询系统。它用知识图谱存法条之间的引用关系、修订历史、适用范围用 RAG 库存法条的全文和司法解释。用户问“某行为在 2020 年修订后是否还构成违约”系统先在图上找到相关法条和修订节点再捞取修订前后的全文最后生成对比回答。这种效果纯向量检索根本做不到。4. 第三处分水岭GraphRAG 不是万能药但它解决了向量检索的盲区4.1 向量检索的盲区多跳问题和全局问题向量检索最擅长的是“局部相似”最不擅长的是“多跳推理”和“全局总结”。举个例子你的知识库里有这样几条信息A 公司的 CEO 是张三。张三毕业于某大学。某大学的校长是李四。李四发表过一篇关于新能源的论文。用户问“A 公司 CEO 的母校校长发表过什么论文”这是一个典型的多跳问题。向量检索会分别捞回“张三”“某大学”“李四”“新能源论文”这几个片段但它无法自动把它们串成一条推理链。模型拿到这些片段后可能能猜出答案但很容易漏掉某一跳或者把不同实体的信息混淆。GraphRAG 的思路就是先把文档里的实体和关系抽出来建成图然后在图上做多跳查询把查询到的子图作为上下文喂给模型。这样模型拿到的不是一堆孤立片段而是一张有结构的关系网。4.2 GraphRAG 的落地成本与取舍但我要泼一盆冷水GraphRAG 不是免费的午餐。它的成本主要在三个地方第一实体抽取的准确率。你用模型从文档里抽实体和关系抽得不准图就是脏的后续查询全错。我试过用开源模型抽法律文档实体识别还行但关系抽取经常把“适用”和“不适用”搞反需要大量人工校验。第二图的维护成本。文档更新了图也要更新。如果文档量大、更新频繁图的增量维护是个麻烦事。第三查询的复杂度。图上做多跳查询需要写 Cypher 或者 Gremlin 之类的查询语言对开发者的要求比写向量检索高得多。所以我的建议是不要一上来就上 GraphRAG。先用向量检索 Contextual Retrieval 跑通如果发现多跳问题和全局问题确实是瓶颈再考虑引入图。而且引入图的时候不要试图把整个知识库都图化只把那些关系密集、查询频繁的部分图化。比如法律系统只图化法条引用关系医疗系统只图化症状-疾病-药物关系其他部分继续用向量检索。4.3 一个折中方案Ontology RAG热词里有个词叫Ontology RAG我觉得这个方向很务实。它的做法是不建完整的知识图谱而是先定义一个轻量级的本体Ontology比如“文档类型”“章节”“条款”“实体类型”这些概念然后用这个本体来指导切块和检索。举个例子你定义本体文档 → 章节 → 条款 → 条件 → 动作。切块的时候按这个层级切每个块都带上它的本体路径。检索的时候先根据问题判断它问的是哪个层级的本体然后只在该层级检索。这样既保留了结构信息又不用维护复杂的图数据库。我实测下来Ontology RAG 在制度类、规范类文档上效果很好成本也比 GraphRAG 低得多。你可以把它理解为“Contextual Retrieval 的升级版”——不只是加一段上下文前缀而是给每个块打上结构化的本体标签。5. 第四处分水岭Agentic RAG 的核心不是“加个 Agent”而是“让检索有策略”5.1 普通 RAG 和 Agentic RAG 的本质区别普通 RAG 的流程是线性的用户提问 → 检索 → 生成。Agentic RAG 的流程是循环的用户提问 → 判断问题类型 → 选择检索策略 → 检索 → 评估证据是否充分 → 不充分则换策略再检索 → 充分则生成。这个区别听起来简单但实际效果差距巨大。我拿一个真实案例对比用户问“我们公司去年发布的那个关于远程办公的规定现在还有效吗如果有效每周最多可以远程几天”普通 RAG 的做法直接把问题向量化捞回“远程办公规定”的片段生成回答。它很可能捞到旧版规定因为旧版和新版在向量空间里距离很近。而且它不会去查“是否还有效”这个时效性问题。Agentic RAG 的做法先把问题拆成三个子问题——“远程办公规定有哪些版本”“最新版本是什么时候发布的”“最新版本对远程天数的规定是什么”。然后分别检索最后综合。如果发现有两个版本它会去查版本之间的修订关系确认哪个是现行有效的。5.2 用 LangGraph 搭建 Agentic RAG 的关键节点LangGraph 是目前做 Agentic RAG 比较顺手的框架它的核心思想是把 RAG 流程建模成一张状态图每个节点是一个操作边是条件跳转。我分享一下我常用的节点设计节点名称作用跳转条件classify判断问题类型事实型/比较型/时效型/多跳型根据类型跳转到不同检索节点retrieve_vector向量检索总是执行retrieve_graph图检索仅多跳型问题执行retrieve_structured结构知识库查询仅时效型、条件型问题执行evaluate评估证据是否充分不充分则跳回 classify 换策略generate生成回答证据充分时执行这个图的关键在于evaluate 节点。它需要判断捞回来的证据能不能回答问题如果不能是缺什么是缺时效信息还是缺多跳关系还是缺具体条款然后根据缺失类型跳回不同的检索节点。我踩过的一个坑是evaluate 节点如果只用模型判断“够不够”模型很容易说“够了”然后生成一个错误答案。后来我改成让模型输出“还缺什么信息”比如“还缺 2023 年修订后的版本号”然后根据这个缺失信息去定向检索。这样准确率高了很多。5.3 Agentic RAG 的代价延迟和成本必须承认Agentic RAG 的延迟比普通 RAG 高不少。普通 RAG 一次检索一次生成可能 2 秒出结果。Agentic RAG 可能要循环三四次每次都要调模型延迟可能到 8 到 10 秒。所以我的建议是不要所有问题都走 Agentic 流程。先用一个轻量级分类器判断问题复杂度简单问题走普通 RAG复杂问题才走 Agentic。这样既能保证体验又能控制成本。6. 第五处分水岭切块策略决定了检索的上限6.1 固定长度切块为什么是灾难我再强调一遍固定长度切块是 RAG 系统最大的隐形杀手。它的问题不是“切得不好”而是“切得没有意义”。512 个 token 一块这个数字是怎么来的没人知道。它和文档的语义结构没有任何关系。我做过一个对比实验同一份技术文档三种切块方式切块方式召回率答案准确率固定 512 token62%48%按段落切78%65%按语义结构切标题段落列表89%82%差距非常明显。按语义结构切的意思是尊重文档本身的层级。一个二级标题下的内容如果不超过 800 token就整块保留如果超过再按三级标题或段落切。列表项尽量不拆开表格单独成块代码块单独成块。6.2 父子块策略小块检索大块生成另一个我常用的策略是父子块。具体做法是把文档切成大块比如按章节再在大块内部切成小块比如按段落。检索的时候用小块去匹配因为小块语义更集中匹配更准但生成的时候把小块所属的大块一起喂给模型这样模型有足够的上下文。这个策略解决了一个经典矛盾小块检索准但上下文少大块上下文多但检索不准。父子块把两者的优点结合起来了。实现上你可以在向量库里存小块的向量但每个小块都带一个parent_id指向它所属的大块。检索到小块后根据parent_id把大块捞出来一起拼进提示词。6.3 一个容易被忽略的细节块之间的重叠很多教程说“块之间要重叠 10% 到 20%”但没说清楚重叠什么。我的经验是重叠的内容应该是“承上启下”的句子而不是随便截一段。比如一个段落结尾是“综上所述试用期员工不享受年假”下一段开头是“但以下情况除外”这两句必须重叠否则检索时很容易丢掉“但以下情况除外”这个关键转折。你可以用这样的规则切块时如果当前块的结尾是一个不完整的句子或者下一块的开头是一个转折词、条件词就强制把这两句重叠进去。7. 第六处分水岭评估体系才是持续迭代的引擎7.1 没有评估你根本不知道系统在变好还是变坏我见过太多团队RAG 系统上线后靠用户反馈来判断好坏。用户说“答得不对”就去调一下提示词用户说“还行”就继续用。这种做法完全是盲人摸象。你需要一套离线评估体系。具体来说至少要有三样东西第一标注数据集。从真实用户问题里抽 100 到 200 个人工标注每个问题的标准答案和应该检索到的证据片段。这个数据集是你的“黄金标准”。第二检索评估指标。最常用的是 RecallK 和 MRR。RecallK 衡量“正确答案是否在前 K 个检索结果里”MRR 衡量“正确答案排在第几位”。这两个指标能告诉你检索模块有没有问题。第三生成评估指标。可以用模型来打分比如“答案是否忠实于检索到的证据”“答案是否完整”“答案是否包含了错误信息”。我常用的是 ** faithfulness ** 和 ** answer relevance ** 两个维度。7.2 一个真实的迭代案例我拿一个实际项目举例。最初系统上线后用户反馈“回答太笼统”。我们跑了一遍评估发现 Recall5 只有 55%也就是说一半的问题正确答案根本没被检索到。进一步分析发现问题出在切块上——很多关键信息被切碎了。我们改成按语义结构切块后Recall5 提升到 78%。但用户还是说“有时候答非所问”。再跑生成评估发现 faithfulness 只有 0.6模型经常在证据不足的情况下强行生成。我们加了“证据不足时明确说不知道”的提示词faithfulness 提升到 0.85。最后用户满意度明显上升。整个过程如果没有评估数据我们根本不知道先改哪里、改完有没有效果。7.3 评估不是一次性的要持续跑我的做法是每次修改切块策略、检索策略、提示词都跑一遍评估集。如果指标下降立刻回滚。如果指标上升再上线给部分用户试用收集真实反馈。这套流程听起来麻烦但它是唯一能让你持续迭代、不靠运气的方法。RAG 系统不是搭完就完事的它是一个需要长期维护和优化的工程。8. 我在实际项目里踩过的三个坑第一个坑过度依赖向量检索。早期我做的系统所有问题都走向量检索结果遇到“比较型”问题就歇菜。比如“A 方案和 B 方案有什么区别”向量检索会分别捞回 A 和 B 的片段但模型拿到后经常把两者的特点搞混。后来我加了结构化的对比检索先定位到 A 和 B 的实体再分别捞取它们的属性最后拼成对比表给模型效果才好起来。第二个坑忽略文档的时效性。很多文档有多个版本向量库里全存了检索时新旧版本混在一起。用户问“现行规定是什么”系统可能捞回旧版。后来我在每个块上加了version和effective_date字段检索时先按时间过滤再按相似度排序问题才解决。第三个坑提示词里塞太多证据。我一开始觉得“证据越多越好”把前 10 个检索结果全塞进提示词。结果模型被无关信息干扰反而答不准。后来改成只塞前 3 到 5 个最相关的并且明确标注每个证据的来源和可信度准确率反而提升了。9. 如果你现在要动手我的建议顺序别一上来就搞 GraphRAG 或者 Agentic RAG。我的建议是按这个顺序来先把切块做对。按文档的语义结构切别用固定长度。这一步能解决 50% 的问题。加上 Contextual Retrieval。给每个块补上下文前缀提升召回准确率。建立评估集。没有评估后面所有优化都是瞎猜。再考虑混合检索。向量检索 结构知识库查询解决条件型和时效型问题。最后才上 Agentic RAG 或 GraphRAG。这两者成本高只在前面几步都做完、确实遇到瓶颈时才引入。RAG 这条流水线本身没有错错的是把它当成终点。真正的分水岭在于你有没有把“检索”当成一个需要策略、需要评估、需要持续迭代的工程问题。做到这一点你的 RAG 就不会烂大街。
返回列表