ARTICLE DETAIL

资讯详情

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

RAG系统调优的六大分水岭:从切块到评估的工程实践

RAG系统调优的六大分水岭:从切块到评估的工程实践 RAG 这个词现在确实被说烂了。打开任何一个技术社区搜RAG 实战能刷出几百篇教你用 LangChain 加个向量库、把文档切一切塞进去、接个 LLM 就完事的教程。我去年帮三个团队做过 RAG 系统的调优每次接手前对方都说我们已经有 RAG 了结果一看检索召回率不到 40%答非所问是常态用户问一个跨文档的对比问题直接歇菜。问题出在哪不是 RAG 这个范式不行是绝大多数人只搭了那条最粗的流水线——切块、嵌入、向量检索、拼 prompt——然后就以为完事了。真正决定一个 RAG 系统能不能用的从来不是这条流水线本身而是藏在它背后、大多数人根本没意识到的几个分水岭。这篇文章我想把这六处分水岭掰开揉碎讲清楚它们分别是什么、为什么它们才是决定成败的关键、以及每一处你具体该怎么落地。不管你是刚准备搭第一个 RAG 知识库还是手里已经有一个跑得半死不活的系统想抢救这六处都值得你对照着过一遍。我会尽量用大白话加实际案例来讲涉及参数和操作的地方给出可复现的细节让你看完能直接动手改。1. 分水岭一切块策略决定了检索的天花板1.1 为什么固定长度切块是原罪几乎所有人搭 RAG 的第一步都是RecursiveCharacterTextSplitterchunk_size 设个 500 或 1000overlap 设个 50 或 100然后就开始跑。这个做法不是错是太粗糙。固定长度切块最大的问题是它完全无视文本的语义边界。一个完整的论证可能被从中间劈开前半段进了 chunk A后半段进了 chunk B检索的时候你只能召回其中一半LLM 拿到的是残缺的上下文回答自然缺胳膊少腿。我见过最离谱的案例是一个法律合同问答系统一份合同里的违约责任条款被切成了三段用户问违约了要赔多少系统召回了第一段讲违约定义和第三段讲赔偿上限偏偏漏掉了中间那段讲具体赔偿比例。结果回答里说合同约定了赔偿上限但未明确比例实际上原文写得清清楚楚。这就是切块把语义切碎了导致的。固定长度切块的另一个隐性代价是它让 embedding 的质量大打折扣。一个 chunk 里如果混了两个不相关的主题它的向量表示就是两个主题的平均既不偏向 A 也不偏向 B检索时对 A 和 B 的查询都匹配不好。你可以把 embedding 想象成给一段文字拍一张语义照片如果这段文字本身是拼凑的照片就是糊的。1.2 语义切块与结构感知切块怎么选正确的做法是让切块跟着文本的语义结构走。具体分两个层次结构感知切块适用于有明确格式的文档比如 Markdown、HTML、代码文件。Markdown 就按标题层级切一个二级标题下的内容作为一个 chunk如果太长再按三级标题细分。代码就按函数或类切。这种切法能保证每个 chunk 是一个完整的语义单元。LangChain 里的MarkdownHeaderTextSplitter就是干这个的用起来很简单from langchain.text_splitter import MarkdownHeaderTextSplitter headers_to_split_on [ (#, Header 1), (##, Header 2), (###, Header 3), ] splitter MarkdownHeaderTextSplitter(headers_to_split_onheaders_to_split_on) chunks splitter.split_text(markdown_doc)语义切块适用于没有明显结构的纯文本比如会议纪要、访谈记录。做法是先按句子切分然后计算相邻句子的 embedding 相似度相似度骤降的地方就是语义边界。这个思路在semantic-text-splitter这类库里有实现。代价是要多跑一遍 embedding慢一些但对长文档质量提升明显。我的经验是优先用结构感知切块没有结构再用语义切块实在不行才退回固定长度但要把 chunk_size 调大一些800-1200overlap 给足100-200尽量减少语义被切断的概率。1.3 父子块与小块检索大块生成还有一个进阶技巧值得单独说小块检索、大块生成。具体做法是把文档切成两层小的子块200-300 字用来做 embedding 和检索因为它语义聚焦、匹配精准检索命中后返回它所属的父块1000-2000 字给 LLM 做上下文。这样既保证了检索精度又保证了生成时有足够的上下文。这个模式在 LlamaIndex 里叫SentenceWindowNodeParser或HierarchicalNodeParser实现起来不复杂但效果提升很明显。我实测过一个技术文档问答场景从固定切块换成父子块之后答案完整度人工评估从 62% 提到了 81%。原因很简单小块让检索更准大块让生成更全。提示父子块的父块大小不要超过 LLM 上下文窗口的 1/4否则几个块一拼就爆了。一般父块控制在 1500 字以内比较稳妥。2. 分水岭二检索质量不是向量库能救的2.1 纯向量检索的三个死穴很多人以为选个好点的向量库比如 Milvus、Qdrant、Weaviate检索质量就上去了。这是个误解。向量库只是存储和检索的引擎它不负责理解你的查询意图。纯向量检索有三个绕不过去的死穴第一对精确匹配无能为力。用户问GPT-4 的上下文窗口是多少向量检索可能召回一堆讲大模型上下文的泛泛内容但就是漏掉那个明确写着128K的段落。因为向量相似度衡量的是语义相近不是关键词命中。第二对否定和限定词不敏感。不含糖的饮料和含糖的饮料在向量空间里可能非常接近因为大部分词是一样的。用户问哪些方案不支持分布式向量检索很可能召回一堆讲支持分布式的文档。第三对长尾查询效果差。用户问一个非常具体、训练数据里少见的问题query 的 embedding 可能落在向量空间的稀疏区域召回质量断崖式下跌。2.2 混合检索BM25 加向量的正确配比解法是混合检索把 BM25关键词检索和向量检索的结果融合。BM25 负责精确匹配和关键词命中向量负责语义泛化两者互补。融合的常用算法是 RRFReciprocal Rank Fusion它不需要调权重直接把两路结果的排名做倒数加权def rrf_fusion(vector_results, bm25_results, k60): scores {} for rank, doc in enumerate(vector_results): scores[doc.id] scores.get(doc.id, 0) 1 / (k rank 1) for rank, doc in enumerate(bm25_results): scores[doc.id] scores.get(doc.id, 0) 1 / (k rank 1) return sorted(scores.items(), keylambda x: x[1], reverseTrue)RRF 的好处是不用调参k 取 60 是论文里的经验值实测下来很稳。如果你想要更精细的控制可以用加权融合给向量和 BM25 各一个权重但这个权重得在你的数据上试出来没有通用值。我一般建议如果你的知识库是技术文档、法律条文、产品手册这类术语密集的内容BM25 的权重给高一些0.5-0.6如果是客服对话、经验分享这类口语化内容向量权重给高一些0.6-0.7。2.3 Rerank 模型把粗排结果精修一遍混合检索解决的是召回问题但召回回来的 top-20 里真正相关的可能只有 5 个而且排序不一定对。这时候需要Rerank 模型做精排。Rerank 模型比如 BGE-Reranker、Cohere Rerank的工作方式是把 query 和每个候选文档拼在一起用一个 cross-encoder 算相关性分数然后重新排序。它比向量检索准得多因为 cross-encoder 能同时看到 query 和文档的全部内容做的是真正的交互式匹配而不是各自算 embedding 再比距离。代价是慢所以只能用在召回后的少量候选上一般 20-50 个。标准流程是混合检索召回 top-50 → Rerank 精排取 top-5 → 送给 LLM 生成。我实测过一个场景加了 Rerank 之后top-5 的命中率从 58% 提到了 84%提升非常显著。Rerank 模型建议用 BGE-Reranker-v2-m3中文效果好本地部署也方便。注意Rerank 不是万能的。如果召回阶段压根没把正确文档捞回来Rerank 也救不了。所以混合检索和 Rerank 是配套的不能只做一个。3. 分水岭三Contextual Retrieval 让每个块自带背景3.1 孤立块的信息缺失问题这是 Anthropic 提出的一个思路我觉得是这两年 RAG 领域最实用的改进之一。问题背景是这样的你把文档切成块之后每个块是孤立的。比如一个块写着该方案的成本比上一版降低了 30%但上一版是什么、该方案指哪个块里没说。检索命中这个块之后LLM 拿到这句话也是一头雾水。传统解法是加大 overlap让相邻块有重叠内容但这治标不治本而且浪费存储。Contextual Retrieval 的思路是在把每个块存入向量库之前先用 LLM 给这个块生成一段简短的背景说明然后把背景说明拼在块前面一起做 embedding。比如上面那个块LLM 生成的背景可能是本段出自 XX 产品 2024 年 Q2 的成本优化报告讨论的是方案 B 相对于方案 A 的成本变化。 拼上之后这个块的 embedding 就带上了完整的语境检索时更容易被正确命中LLM 拿到后也更容易理解。3.2 用 LLM 给块补背景的具体做法实现起来不复杂核心是一个 promptcontext_prompt 以下是一份文档的全文或摘要 document {document} /document 以下是文档中的一个片段 chunk {chunk} /chunk 请用一到两句话说明这个片段在文档中的背景和位置帮助读者理解这个片段在讲什么。只输出背景说明不要重复片段内容。然后对每个块跑一遍把生成的背景拼在块前面。代价是要多跑一遍 LLM成本上去了但效果提升明显。Anthropic 的测试数据显示加了 Contextual Retrieval 之后检索失败率降低了 35%如果再叠加 Rerank能降低 49%。我的实操建议是不要对全库都做优先对孤立块比例高的文档做。怎么判断简单粗暴的办法是随机抽 20 个块人工看有多少块脱离上下文就读不懂。如果超过 30%就值得做 Contextual Retrieval。另外背景说明不要超过 100 字太长了会稀释块本身的语义。3.3 成本与收益的平衡点Contextual Retrieval 的成本主要在 LLM 调用上。假设你有 10 万个块每个块生成背景要 500 token 输入加 100 token 输出用便宜点的模型比如 Haiku 级别总成本大概几十美元。一次性投入之后增量更新时只对新块做成本可控。收益方面除了检索准确率提升还有一个隐性好处它让 chunk 变得自解释即使检索错了LLM 拿到一个带背景的块也更容易判断这个块是否相关从而在生成时主动忽略不相关内容。这一点在 Agentic RAG 场景下尤其重要因为 Agent 需要自己判断检索结果的质量。4. 分水岭四GraphRAG 解决的是关系型问题4.1 什么时候向量检索彻底失效有一类问题向量检索从原理上就解决不了需要跨多个文档、多个实体做关系推理的问题。比如公司 A 的 CEO 和公司 B 的 CTO 之前在哪家公司共事过这种问题答案分散在多个文档里需要先找到 A 的 CEO 是谁、B 的 CTO 是谁再查两人的履历找交集。向量检索只能召回看起来相关的块但它没有关系的概念无法做这种多跳推理。这就是 GraphRAG 要解决的问题。它的核心思路是从文档里抽取实体和关系构建一个知识图谱然后基于图谱做检索和推理。检索时不是找相似的块而是找相关的实体和它们之间的关系路径。4.2 实体抽取与图谱构建的工程细节GraphRAG 的落地分三步第一步实体和关系抽取。用 LLM 从每个块里抽实体人、公司、产品、事件和关系任职于、投资了、合作过。这一步的 prompt 设计很关键要明确告诉 LLM 抽什么类型的实体、什么类型的关系。微软的 GraphRAG 项目里有现成的 prompt 可以参考。第二步图谱构建和社区检测。把抽出来的实体和关系建成图然后用 Leiden 算法做社区检测把关系紧密的实体聚成社区。每个社区生成一个摘要这个摘要在做全局性问题比如这个领域的主要玩家有哪些时特别有用。第三步图检索。查询时先定位到相关实体然后沿关系边扩展把相关的实体、关系、社区摘要一起送给 LLM。4.3 GraphRAG 的适用边界与成本陷阱GraphRAG 不是银弹它的成本和复杂度都比向量 RAG 高一个量级。实体抽取要跑一遍全量 LLM图谱构建和社区检测也有计算开销。而且它对抽取质量非常敏感抽错了实体或关系整个图谱就是歪的。我的判断标准是如果你的查询里超过 20% 是关系型、多跳型问题才值得上 GraphRAG。如果大部分查询还是XX 是什么XX 怎么做这种单跳问题向量 RAG 加混合检索就够了上 GraphRAG 是杀鸡用牛刀。另外GraphRAG 和向量 RAG 不是二选一可以混合。常见做法是先用向量检索召回相关块再从这些块里定位实体然后沿图谱扩展。这样既利用了向量的召回能力又利用了图谱的关系推理能力。5. 分水岭五Agentic RAG 把检索变成决策5.1 从检索一次到检索多次传统 RAG 是检索一次生成一次的线性流程。但真实问题往往需要多轮检索先查 A根据 A 的结果决定下一步查 B再根据 B 的结果决定要不要查 C。这种边查边想的模式就是 Agentic RAG。举个例子用户问我们公司去年在华东区的销售额是多少和华南区比怎么样传统 RAG 可能一次检索召回一堆销售数据但 LLM 要自己从里面挑出华东和华南的数字做对比容易出错。Agentic RAG 的做法是Agent 先规划——我需要华东数据和华南数据然后分别检索拿到数据后判断是否完整不完整就再检索最后做对比生成答案。5.2 查询改写与子问题拆解Agentic RAG 的第一个能力是查询改写。用户的原始 query 往往不适合直接检索需要改写成更适合检索的形式。比如那个新出的模型怎么样这种指代不清的 queryAgent 要先结合对话历史改写成XX 模型2024 年发布的性能评价。第二个能力是子问题拆解。复杂问题拆成多个子问题分别检索最后综合。这个在 LlamaIndex 的SubQuestionQueryEngine里有实现。拆解的好处是每个子问题都聚焦检索精度高而且可以并行检索提速。5.3 自我反思与检索结果校验Agentic RAG 最关键的能力是自我反思拿到检索结果后Agent 要判断这些结果是否足以回答问题。如果不够要能识别缺什么然后发起新的检索。这个能力靠的是给 Agent 一个明确的判断 promptreflection_prompt 用户问题{question} 已检索到的信息{retrieved_context} 请判断这些信息是否足以完整回答用户问题 如果足以回答输出 SUFFICIENT。 如果不足输出 INSUFFICIENT并说明还缺什么信息。这个反思循环可以跑多轮直到信息足够或达到最大轮数。实测下来加了反思循环之后复杂问题的回答完整度提升很明显但延迟也上去了。所以要根据场景权衡对延迟敏感的场景比如实时客服反思循环限制在 1-2 轮对质量敏感的场景比如研究报告生成可以放宽到 3-5 轮。提示Agentic RAG 的调试比传统 RAG 难得多因为流程是非线性的。建议在每一步都打日志记录 Agent 的决策、检索的 query、召回的结果方便回溯问题。6. 分水岭六评估体系决定你能不能持续优化6.1 没有评估就没有优化这是最容易被忽视、但最重要的一处。我见过太多团队RAG 系统上线后用户反馈答得不好但具体哪里不好、是检索问题还是生成问题、改了什么有没有变好完全说不清。没有评估体系优化就是盲人摸象。RAG 的评估要分两层检索层和生成层。检索层看召回率、命中率、MRR平均倒数排名生成层看答案的忠实度faithfulness答案是否基于检索内容、相关性relevance、完整度completeness。6.2 检索层与生成层的关键指标检索层的核心指标是RecallK正确文档出现在 top-K 里的比例。这个指标直接决定了生成层的上限。如果 Recall5 只有 60%那生成层再强也只能在 60% 的问题上答对。我一般建议 Recall5 至少做到 85% 以上才值得去优化生成层。生成层的核心指标是Faithfulness答案里的每一句话是否都能在检索内容里找到依据。这个指标衡量的是幻觉程度。可以用 LLM 做裁判来评估把答案和检索内容一起给 LLM让它判断答案是否有依据。faithfulness_prompt 检索内容{context} 生成的答案{answer} 请判断答案中的每一句话是否都能在检索内容中找到依据 输出格式{{faithful: true/false, unsupported_claims: [...]}}6.3 用 RAGAS 搭建自动化评估流水线手动评估不现实要用工具。RAGAS是目前最成熟的 RAG 评估框架它内置了 faithfulness、answer_relevancy、context_precision、context_recall 等指标输入是问题、答案、检索上下文、标准答案可选输出是各项分数。搭建评估流水线的关键是测试集。测试集要覆盖你的真实查询分布简单事实查询、多跳推理查询、否定查询、模糊查询各占一定比例。测试集不用大50-100 条就能反映问题但一定要有代表性。我一般建议从真实用户日志里采样然后人工标注标准答案。有了评估流水线之后每次改动换切块策略、加 Rerank、调 prompt都跑一遍评估看指标变化。这样才能知道改动是有效还是无效避免拍脑袋优化。注意评估指标不是越高越好要结合业务场景看。比如客服场景faithfulness 比 completeness 重要宁可答得少也不能答错而研究场景completeness 更重要宁可多给信息让用户自己判断。7. 六处之外那些容易被忽略的工程细节7.1 元数据过滤被低估的检索加速器前面讲的六处都是核心分水岭但还有几个工程细节做了能让系统更稳。第一个是元数据过滤。每个块除了文本内容还应该带上元数据来源文档、创建时间、文档类型、权限标签等。检索时可以先按元数据过滤再在过滤后的子集里做向量检索。这样既提速又提准。比如用户问2024 年的销售政策你可以先按year2024过滤再检索避免召回 2023 年的旧政策。权限标签则能保证用户只能检索到自己有权限的文档这在企业场景下是刚需。7.2 增量更新与版本管理第二个是增量更新。知识库不是静态的文档会新增、修改、删除。如果每次更新都全量重建索引成本高且不可持续。正确做法是维护文档到块的映射文档更新时只重建这个文档对应的块删除时只删对应的块。向量库一般支持按 ID 删除所以关键是维护好 ID 映射。版本管理也很重要。有时候新版本反而效果差要能快速回滚。建议每次索引更新都打一个版本号保留最近几个版本出问题能切回去。7.3 缓存与降级策略第三个是缓存。相同或相似的 query 会重复出现缓存检索结果和生成结果能大幅降低延迟和成本。缓存 key 可以用 query 的 embedding 做近似匹配相似度超过阈值就命中缓存。降级策略则是当检索服务或 LLM 服务不可用时要有兜底方案比如返回缓存结果、返回暂时无法回答而不是报错。这些工程细节不性感但决定了系统能不能在生产环境稳定跑下去。我见过太多 demo 很惊艳、一上生产就崩的 RAG 系统问题往往就出在这些地方。8. 我的实操路线建议如果你现在要从零搭一个 RAG 系统或者要抢救一个现有系统我建议按这个顺序推进第一阶段把基础打牢。切块用结构感知或语义切块检索用混合检索BM25 加向量加 Rerank 精排。这三件事做完系统就能达到能用的水平。别急着上 GraphRAG 或 Agentic RAG基础不牢上层越复杂越乱。第二阶段补语境和评估。对孤立块多的文档做 Contextual Retrieval同时搭建 RAGAS 评估流水线。有了评估你才知道下一步该优化哪里。第三阶段按需上高级能力。关系型问题多就上 GraphRAG多跳问题多就上 Agentic RAG。但一定要有评估数据支撑别为了技术而技术。最后分享一个我踩过的坑不要一次性把所有优化都堆上去。我曾经在一个项目里同时改了切块、加了 Rerank、换了 embedding 模型结果指标反而降了但根本不知道是哪个改动导致的。后来学乖了每次只改一个变量跑评估确认有效再改下一个。RAG 优化是个细活急不得。这套东西我在三个不同规模的团队里都落地过从几万文档的小知识库到百万级文档的企业系统核心逻辑是一样的分水岭不在流水线本身而在流水线之外的这些决策点上。把这几处想清楚、做到位你的 RAG 就能甩开那些烂大街的系统一大截。
返回列表