首页 / 资讯中心 / 文章详情

RAG效果提升的六处分水岭:从文档切分到评估体系的工程实践

RAG效果提升的六处分水岭:从文档切分到评估体系的工程实践 ★ FEATURED ARTICLE
1. 那条流水线为什么谁都能搭但谁都用不好RAG 这个词现在确实被说烂了。打开任何一个技术社区搜一下 RAG你能看到几百篇标题几乎一模一样的文章加载文档、切分、向量化、存库、检索、拼 Prompt、丢给大模型。七步走完Demo 跑通截图发出来评论区一片“学到了”。但如果你真的拿这套东西去跑一个稍微正经点的业务场景比如企业内部的制度问答、技术文档检索、客服知识库你会发现一个很尴尬的事实Demo 里那套流水线上线之后基本没法用。问题出在哪不是 RAG 这个思路不行而是大多数人把 RAG 当成了一条固定的流水线来理解。流水线的意思是输入进去经过几个固定工位输出出来。你只要每个工位买对设备装好传送带它就能跑。但 RAG 不是这样的东西。RAG 更像是一个需要根据原料、产品、客户反馈不断调整配方的生产过程。文档怎么切、检索怎么召回、召回之后怎么排序、排序之后怎么组装上下文、组装之后怎么让模型用——每一个环节都有大量的决策点而这些决策点之间是互相影响的。我见过太多团队在 RAG 上踩的坑本质上都是同一个原因他们把注意力全放在了“用哪个向量库”“用哪个 Embedding 模型”“用哪个框架”这些选型问题上却忽略了真正决定 RAG 效果的那几个分水岭。选型当然重要但选型是入门级问题。真正拉开差距的是下面这六处。这六处每一处都对应着一个具体的工程决策而且每一处的决策逻辑都不是“哪个更先进就用哪个”而是“你的场景需要什么”。这篇文章不打算再给你讲一遍 RAG 的基础流程那种内容到处都是。我想做的是把这六处分水岭拆开讲清楚每一处到底在分什么、为什么大多数人的默认做法会出问题、以及在实际项目里应该怎么判断和取舍。如果你正在做 RAG 相关的项目或者准备做这六处值得你花时间想清楚。2. 第一处分水岭文档切分不是切菜是在重建语义边界2.1 固定长度切分为何几乎总是错的几乎所有 RAG 教程的第一步都是“把文档按固定字数切分”。500 字一段或者 1000 字一段加个 overlap 防止切断句子。这个做法在 Demo 里没问题因为 Demo 的文档通常很短、结构很简单。但真实场景里的文档是什么样的技术手册里有嵌套的章节和小节合同里有条款和子条款产品文档里有表格和代码块FAQ 里有问答对。你拿一把固定长度的刀去切这些东西切出来的片段在语义上往往是残缺的。举个具体的例子。假设你有一份产品配置文档里面有一段话“当系统检测到内存占用超过 80% 时会自动触发垃圾回收。如果垃圾回收后内存仍然高于 70%则开始拒绝新的请求。”如果你按固定长度切分恰好把“如果垃圾回收后内存仍然高于 70%”这句话切到了下一个片段那么第一个片段就变成了一个不完整的条件描述第二个片段则失去了上下文。检索的时候用户问“内存高的时候系统会怎样”第一个片段被召回了但模型看到的是一个没有后续的规则回答就会不完整甚至错误。这不是极端情况这是常态。固定长度切分的问题在于它假设文档的语义边界和字符边界是对齐的但事实几乎从来不是这样。2.2 结构感知切分的实际做法正确的做法是结构感知切分。什么意思就是先理解文档的结构再根据结构来决定切分点。Markdown 文档按标题层级切HTML 按 DOM 结构切PDF 先做版面分析再按段落和表格切代码文件按函数和类切。切出来的每个片段应该是一个语义上自洽的单元而不是一个被字符数截断的碎片。具体操作上我通常会用这样的策略首先用文档解析工具把原始文档转成结构化格式比如 Markdown 或者带层级信息的 JSON。然后按照标题层级做第一层切分每个最小标题单元作为一个候选片段。如果某个单元太长超过了 Embedding 模型的最佳输入长度再在这个单元内部按段落做第二层切分。如果某个单元太短比如只有一句话就把它和相邻的同类单元合并。这样切出来的片段每个都是一个完整的语义单元检索的时候命中率会高很多。这里有一个容易被忽略的细节切分的时候要保留层级路径。什么意思就是每个片段除了自身的文本内容还要带上它在文档中的位置信息比如“产品手册 配置管理 内存配置 垃圾回收策略”。这个路径信息在检索和组装上下文的时候非常有用后面会讲到。2.3 切分粒度与检索精度的权衡切分粒度是另一个需要仔细考虑的问题。切得太粗一个片段里包含多个主题检索的时候容易召回不相关的信息而且会浪费上下文窗口。切得太细一个片段只包含一两句话虽然精度高了但模型可能缺乏足够的上下文来理解这句话的含义。我的经验是切分粒度应该和你的查询粒度匹配。如果你的用户查询通常是“XX 功能的配置方法是什么”这种中等粒度的问题那片段就应该是一个完整的功能说明单元大概几百字。如果你的用户查询是“XX 参数的默认值是多少”这种细粒度的问题那片段可以更小但最好还是保留所在的段落作为上下文。实际操作中我建议做一次切分粒度的评估。拿一批真实的用户查询分别用不同粒度的切分方案跑检索看召回率和准确率的变化。这个评估不需要很复杂几十条查询就能看出明显的趋势。很多人跳过这一步直接拍脑袋定一个 chunk size结果上线之后发现检索效果不好又回头调浪费的时间远比做一次评估多。3. 第二处分水岭检索不是一次向量搜索就完事3.1 纯向量检索的盲区大多数 RAG 流水线里的检索环节就是一句话把查询向量化然后在向量库里做相似度搜索取 Top-K。这个做法的问题在于向量检索擅长的是语义相似但它不擅长精确匹配。用户问“ERR-4032 错误码是什么意思”向量检索可能会召回一堆关于错误处理的通用文档但真正包含 ERR-4032 这个具体错误码的文档反而可能排在后面。因为从语义上讲“错误码”和“错误处理”确实很相似但用户要的是那个具体的码。这就是纯向量检索的盲区它对精确的术语、编号、名称不敏感。而在企业知识库场景里大量的查询恰恰是包含这类精确信息的。产品型号、错误码、API 名称、配置项名称这些东西用向量检索往往效果不好。3.2 混合检索的工程实现解决方案是混合检索也就是把向量检索和关键词检索结合起来。关键词检索可以用 BM25 或者全文索引来实现它对精确匹配非常有效。把两路检索的结果合并再做一个重排序通常能显著提升召回质量。具体实现上我通常会用这样的架构查询进来之后同时走两路。一路做向量化然后查向量库另一路做分词然后查全文索引。两路各取 Top-20 左右然后用一个重排序模型或者简单的分数融合策略合并成一个 Top-10 的列表。分数融合可以用加权求和向量相似度和 BM25 分数各占一定权重权重根据你的场景调。如果精确匹配更重要BM25 权重大一些如果语义理解更重要向量权重大一些。这里有一个实操细节BM25 的分词策略对中文场景特别重要。中文不像英文有天然的空格分隔分词质量直接影响关键词检索的效果。我一般会用 jieba 或者 HanLP 做分词同时把一些领域专有名词加到自定义词典里避免被切碎。比如“垃圾回收”如果被切成“垃圾”和“回收”检索效果就会打折扣。3.3 重排序模型到底值不值得上重排序是另一个经常被讨论的点。简单说就是在检索召回一批候选片段之后用一个更精细的模型对它们重新打分排序。常用的重排序模型有 Cohere Rerank、BGE Reranker 等。这个东西值不值得上我的答案是如果你的检索召回质量直接影响到最终回答质量而且你有一定的延迟预算那值得。重排序的价值在于它可以用比向量检索更精细的方式来判断查询和片段的相关性。向量检索是双塔结构查询和文档分别编码交互发生在最后一步信息损失比较大。重排序模型通常是交叉编码器查询和文档一起输入能做更细粒度的交互判断更准。实测下来加一层重排序通常能把 Top-3 的准确率提升 10 到 20 个百分点。但重排序也有代价。它增加了延迟因为要对每个候选片段做一次模型推理。如果候选有 20 个每个推理 50 毫秒那就是 1 秒的额外延迟。所以要不要上重排序取决于你的延迟预算和精度要求。如果延迟敏感可以考虑用轻量级的重排序模型或者只对 Top-10 做重排序而不是 Top-20。4. 第三处分水岭上下文组装决定了模型能看到什么4.1 拼接 Top-K 片段的隐患检索完成之后下一步是把召回的片段组装成上下文塞进 Prompt 里让模型回答。大多数流水线在这里的做法是把 Top-K 个片段按顺序拼在一起加个分隔符然后前面加一句“根据以下内容回答问题”。这个做法看起来没问题但实际上有很多隐患。第一个隐患是信息冗余。Top-K 个片段里可能有大量重复内容因为相关文档往往会在不同位置重复同样的信息。这些重复内容不仅浪费上下文窗口还可能让模型产生困惑因为它不知道哪个版本是权威的。第二个隐患是信息冲突。不同片段可能包含矛盾的信息比如旧版本文档和新版本文档对同一个配置的描述不同。如果直接把冲突信息拼在一起模型可能会给出一个模棱两可的回答或者随机选一个。第三个隐患是上下文顺序。模型对上下文中的位置是有偏好的通常开头和结尾的信息更容易被注意到中间的信息容易被忽略。如果你把最重要的片段放在中间模型可能反而没用好。4.2 上下文压缩与去重的实操解决这些问题的方法之一是上下文压缩。具体做法是在组装上下文之前先对召回的片段做一轮处理去掉重复内容合并相似片段保留最相关的部分。可以用一个小的语言模型来做这件事也可以用基于规则的策略。我通常的做法是先用相似度去重把内容重叠度超过阈值的片段合并保留信息量最大的那个版本。然后按相关性分数排序把最相关的放在最前面和最后面次相关的放中间。如果总长度超过上下文窗口的限制就按分数从低到高截断优先保留高分片段。还有一个技巧是给每个片段加上来源标注。比如“来源产品手册 v2.3第 4 章”。这样模型在回答的时候可以引用来源用户也能验证信息的可靠性。这个做法在企业场景里特别有用因为用户往往需要知道答案的出处。4.3 让模型学会说“我不知道”上下文组装的另一个关键点是要让模型知道什么时候该说“我不知道”。如果召回的片段里没有足够的信息来回答问题模型应该明确说“根据现有资料无法回答”而不是硬编一个答案。这个行为需要通过 Prompt 来引导。我通常会在 Prompt 里加这样的指令“如果以下内容不足以回答问题请直接说明无法回答不要编造信息。”同时在组装上下文的时候如果检索的置信度很低比如最高分低于某个阈值可以在 Prompt 里加一句提示“注意以下内容的匹配度较低请谨慎使用。”这样模型会更倾向于保守回答。这个做法看起来简单但在实际项目里非常关键。企业用户对错误答案的容忍度远低于对“不知道”的容忍度。一个编造的答案可能导致严重的决策失误而一个“不知道”至少不会误导人。5. 第四处分水岭Agentic RAG 把单次检索变成多步推理5.1 单次检索为何搞不定复杂问题前面讲的都是单次检索的优化。但有些问题单次检索根本搞不定。比如用户问“我们系统里哪些配置项会影响垃圾回收的行为这些配置项的默认值分别是什么修改它们有什么风险”这个问题需要多个步骤先找到垃圾回收相关的配置项然后查每个配置项的默认值再查每个配置项的风险说明。单次检索只能召回一批相关片段但模型需要在这些片段之间做推理和整合而且可能需要多轮检索才能收集齐所有信息。这就是 Agentic RAG 要解决的问题。Agentic RAG 的核心思想是把 RAG 从一个单次检索-生成的流程变成一个多步的、有规划的、可以调用工具的智能体流程。智能体可以先分析问题决定需要哪些信息然后一步步检索、验证、整合最后给出答案。5.2 用 LangGraph 搭建多步检索流程实现 Agentic RAG 的一个常用工具是 LangGraph。LangGraph 是 LangChain 生态里的一个库专门用来构建有状态的、多步骤的智能体流程。它的核心概念是图节点代表一个操作边代表操作之间的流转。你可以定义一个图里面有检索节点、推理节点、工具调用节点然后根据条件决定下一步走哪个节点。举个具体的例子。假设我们要搭建一个多步检索的 RAG 流程。可以这样设计图第一个节点是“分析问题”用一个语言模型判断这个问题需要哪些类型的信息。第二个节点是“生成检索计划”把问题拆成几个子查询。第三个节点是“执行检索”对每个子查询分别检索。第四个节点是“评估结果”判断检索到的信息是否足够回答问题。如果不够回到第二个节点重新规划如果够了进入第五个节点“生成回答”。这个流程比单次检索复杂得多但它能处理的问题类型也丰富得多。对于简单的事实性问题它可能一步就搞定了对于复杂的多跳问题它可以反复检索和推理直到收集齐所有需要的信息。5.3 工具调用让 RAG 真正“下地干活”Agentic RAG 的另一个重要能力是工具调用。检索只是获取信息的一种方式有些问题需要调用其他工具才能解决。比如用户问“上个月我们的 API 调用量是多少”这个问题需要查数据库而不是查文档。如果 RAG 系统只能检索文档它就回答不了这个问题。但如果它有一个“查询数据库”的工具就可以先检索文档了解数据库的结构和查询方式然后调用数据库工具获取实际数据最后整合成回答。LangGraph 对工具调用的支持很自然。你可以把每个工具定义成一个节点然后在图里根据条件决定是否调用。工具的输入输出可以用结构化格式定义这样模型能清楚地知道每个工具需要什么参数、返回什么结果。这里有一个实操经验工具的描述非常重要。模型是根据工具的描述来决定是否调用以及怎么调用的。如果描述写得含糊模型可能在不该调用的时候调用或者传错参数。我通常会把工具描述写得非常具体包括它适合解决什么问题、需要什么输入、返回什么格式的结果。这看起来是小事但对工具调用的准确率影响很大。6. 第五处分水岭GraphRAG 处理的是关系不是文本6.1 向量检索搞不定多跳关系有些问题的难点不在于找到相关文本而在于理解文本之间的关系。比如用户问“A 系统的故障会不会影响 B 服务”这个问题需要知道 A 系统和 B 服务之间的依赖关系。如果文档里分别描述了 A 系统和 B 服务但没有明确说它们之间的关系向量检索就无能为力了因为它只能找到分别提到 A 和 B 的片段但无法推断出它们之间的关系。这就是 GraphRAG 要解决的问题。GraphRAG 的核心思想是从文档中抽取实体和关系构建一个知识图谱然后基于图谱做检索和推理。当用户问一个涉及多跳关系的问题时系统可以在图谱上做路径查询找到实体之间的关联路径从而回答问题。6.2 实体抽取与图谱构建的实际难点GraphRAG 听起来很美好但实际做起来有几个难点。第一个难点是实体抽取。从非结构化文本里抽取实体和关系需要用到信息抽取模型或者大语言模型。抽取的质量直接决定了图谱的质量。如果抽取不准图谱里就会有很多噪声检索效果反而更差。第二个难点是实体消歧。同一个实体在不同文档里可能有不同的名称比如“垃圾回收”“GC”“garbage collection”指的是同一个东西。如果不做消歧图谱里就会有多个节点表示同一个实体关系就断了。消歧可以用规则做也可以用模型做但都需要领域知识。第三个难点是图谱的维护。文档更新了图谱也要更新。如果文档频繁变动图谱的维护成本会很高。所以 GraphRAG 更适合文档相对稳定的场景比如法规、标准、技术手册。6.3 GraphRAG 和向量 RAG 的混合架构在实际项目里我通常不会纯用 GraphRAG而是把它和向量 RAG 结合起来。具体做法是先用向量检索召回一批相关片段然后从这些片段里识别出关键实体再在知识图谱里查询这些实体的关系和属性把图谱查询的结果作为补充上下文一起给模型。这样既能利用向量检索的语义匹配能力又能利用图谱的关系推理能力。这种混合架构的实现复杂度比纯向量 RAG 高不少但它能处理的问题类型也更丰富。如果你的场景里有大量的关系型查询比如“哪些服务依赖于这个组件”“这个变更会影响哪些下游系统”那 GraphRAG 值得投入。7. 第六处分水岭评估体系决定了你能不能持续优化7.1 没有评估就没有优化前面讲了五处分水岭但如果没有第六处前面五处你都做对了也不知道自己做对了。这就是评估体系。RAG 系统的一个特点是它的效果很难用单一指标衡量。检索召回率、回答准确率、回答完整性、延迟、成本这些指标之间往往有 trade-off。你需要一个评估体系来帮你判断每次改动到底是变好了还是变差了。大多数团队在 RAG 评估上的做法是找几个人问几个问题看看回答得怎么样。这个做法的问题在于它不可复现、不可量化、不可持续。今天觉得回答不错明天换个问题可能就崩了。而且人的判断标准不一致同一个人在不同时间段的判断也可能不同。7.2 构建 RAG 评估集的实操方法构建一个可用的 RAG 评估集需要做几件事。第一收集一批真实的用户查询。这些查询应该覆盖你的主要场景包括简单的事实性问题、复杂的多跳问题、需要精确匹配的问题、需要关系推理的问题。数量不用很多几十到几百条就够但要有代表性。第二为每个查询标注标准答案和相关的文档片段。标准答案可以是人工写的也可以是从现有文档里摘的。相关文档片段的标注很重要因为它能帮你评估检索环节的效果而不仅仅是最终回答的效果。第三定义评估指标。检索环节可以用召回率、准确率、MRR 等指标。生成环节可以用答案准确率、完整性、忠实度等指标。忠实度指的是回答是否完全基于检索到的内容没有编造。这个指标在企业场景里特别重要。第四建立自动化评估流程。每次改动之后自动跑一遍评估集生成指标报告。这样你就能清楚地看到每次改动的影响。如果某个改动提升了准确率但降低了忠实度你就需要判断这个 trade-off 是否值得。7.3 在线反馈与离线评估的闭环离线评估集是基础但它不能覆盖所有情况。真实用户的查询分布可能和你的评估集不一样而且用户的行为会随着时间变化。所以还需要在线反馈机制。比如在回答下面加一个“这个回答有帮助吗”的按钮收集用户的反馈。这些反馈可以用来发现新的问题类型补充到评估集里形成闭环。我通常会把在线反馈和离线评估结合起来用。离线评估用来做快速迭代每次改动跑一遍看指标变化。在线反馈用来做长期监控发现离线评估没覆盖到的问题。两者结合才能持续优化 RAG 系统的效果。8. 这六处之外还有一些零散但重要的经验上面六处分水岭是主线但实际做 RAG 项目的时候还有一些零散的经验值得分享。关于 Embedding 模型的选择我的建议是不要盲目追求排行榜上的最高分。排行榜上的评估集和你的场景可能差别很大。更好的做法是拿你自己的评估集跑几个候选模型看实际效果。另外Embedding 模型的维度、推理速度、成本也是要考虑的因素。有时候一个中等效果但速度快的模型比一个效果好但慢的模型更适合你的场景。关于向量库的选择如果数据量不大几万到几十万条用 FAISS 或者 Chroma 就够了部署简单性能也够。如果数据量到了百万级以上或者需要分布式部署再考虑 Milvus、Qdrant 这些。不要一上来就上重型武器维护成本很高。关于 Prompt 的设计一个常见的错误是把 Prompt 写得太长太复杂。模型对长 Prompt 的遵循能力是有限的太长的 Prompt 反而容易让模型忽略关键指令。我通常会把 Prompt 控制在几百字以内把最重要的指令放在最前面和最后面。关于缓存RAG 系统里有很多可以缓存的地方。Embedding 结果可以缓存检索结果可以缓存甚至最终回答也可以缓存。对于重复查询比较多的场景缓存能显著降低延迟和成本。但要注意缓存的失效策略文档更新之后相关的缓存要及时清理。关于多语言场景如果你的文档和查询涉及多种语言Embedding 模型的选择会更关键。有些模型在多语言上表现好有些只擅长英文。另外检索的时候可能需要做跨语言检索这又是另一个复杂话题了。最后说一点关于 RAG 和微调的关系。经常有人问RAG 和微调哪个更好。我的看法是它们解决的是不同的问题。RAG 解决的是知识获取的问题让模型能访问外部知识。微调解决的是行为对齐的问题让模型按照特定的风格或格式输出。两者不冲突可以结合使用。但在大多数场景下先做好 RAG 的收益比先做微调大得多因为 RAG 的迭代成本低效果提升也直接。我在实际项目里的体会是RAG 的效果提升不是线性的而是阶梯式的。你可能花了很多时间调切分、调检索效果提升不明显但突然某一处做对了整体效果就上了一个台阶。这六处分水岭每一处都可能是那个让你上台阶的点。关键是不要只盯着流水线要理解每一处背后的原理和取舍然后根据你的场景做判断。
阅读完成 · 觉得有帮助?
咨询建站