Skip to content

RAG 切分策略实战 ​

看在前面 ​

姊妹篇 RAG体系与项目定位 里说过:切分是五段工程里最影响最终质量的环节。它却最常被一行 chunk_size=500 带过。这篇单独把切分展开成决策树:

  • 四种基础策略(固定长度/递归字符/语义/父子块)
  • chunk_size 与 overlap 的权衡曲线
  • 表格与结构化文档的特殊处理
  • 评测驱动的迭代方法

配套阅读:检索片段进窗口后的位置与预算问题见 [上下文工程](../llm基础/上下文工程-Context Engineering.md)。为什么块质量决定嵌入质量,见姊妹篇 2.2 节。

一、四种策略:从机械到语义 ​

text
原文档 ──► 固定长度      按 N 字符硬切,实现一行
       ──► 递归字符切分  依次尝试 \n\n → \n → 。 → 空格 → 字符,切在最大分隔符上(LangChain RecursiveCharacterTextSplitter)
       ──► 语义切分      相邻句子嵌入相似度低于阈值处断开,块 = 语义自然段
       ──► 父子块        小块(子)用于检索命中,大块(父)用于喂给 LLM

选择顺序(决策树):
  有结构(Markdown/HTML/PDF 版式)→ 结构感知切分(按标题层级),永远优先
  无结构纯文本长文               → 递归字符切分(性价比默认解)
  质量敏感 + 预算充足             → 语义切分(每篇多花一次嵌入调用的钱)
  召回粒度与上下文粒度冲突        → 父子块(检索要准、喂给模型要全)

四种策略的本质差异一句话版:

  • 固定长度切"位置"
  • 递归字符切"标点"
  • 语义切"话题边界"
  • 父子块把"检索单位"和"生成单位"解耦

前三种回答"在哪切",父子块回答"切完拿哪块用"。子块小而准(一个向量概括一个论点,嵌入不糊)。命中后返回父块(上下文完整,模型不会拿到半句话)。

二、chunk_size 与 overlap:两个参数的权衡曲线 ​

text
召回质量
  │          ╭──── 最优区间(语料相关:256~1024 token)
  │        ╱      ╲
  │      ╱          ╲
  │    ╱              ╲
  │  ╱ 块太小:           ╲ 块太大:
  │ ╱  语义残缺             一个向量概括多个主题
  │╱   召回了没用             嵌入变糊,且浪费窗口
  └─────────────────────────────► chunk_size
  overlap 的作用:关键句骑在边界上时,两块各持有一份
  代价:token 总量 ×(1+overlap/size),10~20% 是惯例起点

调参的两头信号(从评测读,不从感觉读):

  • "该召回没召回" → 多为块过大(主题混杂嵌入糊了)或切分截断了关键定义(一半在上一块)。先减块再看 overlap;
  • "召回了但没用" → 多为块过小(语义残缺,比如只剩一句"如上所述")——加块或换父子块;
  • overlap 不是越大越安全:overlap 不小于 30% 时,同一内容多份近重复块挤占 top-k 名额。反而把别的相关块挤出去。overlap 是保险费,超过 20% 就在买重复覆盖。

一个常被忽略的实践细节:中文文档的递归切分分隔符要加中文标点(\n\n、\n、。、;、空格)。只配英文分隔符会在长中文段落里退化成固定长度切分。这是中文语料上最常见的隐性翻车点。

三、表格与结构化文档:切分的最难场景 ​

表格的危险在于任何"按行硬切"都在制造碎片:

text
错误:表头在第 3 块,数据行在第 4、5 块
  ┌ chunk3 ┐   ┌ chunk4 ┐   ┌ chunk5 ┐
  │ 指标 参考值│  │ 血糖 6.8 │   │ 尿酸 520│     ← 后两块没有表头,嵌入/生成都不知道列是什么
  └────────┘   └─────────┘   └─────────┘

正确姿势(按表格类型分治):
  小表(< 阈值行)  → 整表一个块,不切
  大表              → 行级拆分 + 每块重复表头(Markdown 表格每块都是完整表)
  宽表/交叉表       → 行转文本:每行渲染成"指标X:值Y(参考Z)【偏高】"的自然句
                     —— ai-service report_interpret._render 正是这条路:行式清单
  表格摘要          → 表上方加一段 LLM 生成的表格概述,与表格同块(帮助嵌入"看懂"表)

结构化文档的通用规则:结构边界是天然切分线(Markdown 按标题层级、HTML 按标签、PDF 按版式)。并把标题路径写进块内容(# 二级标题 > 三级标题 前缀)。块脱离文档后仍然自带"我在讲什么"的上下文。嵌入质量和引用溯源同时受益。metadata(source/页码/层级路径)在切分时上钩,检索过滤与引用全靠它。切完补不回来(姊妹篇 2.1 的纪律在本篇同样成立)。

四、评测驱动迭代:切分不是一次定终身 ​

切分参数没有理论最优,只有语料上的实证最优——所以迭代方法比初始参数重要:

text
1. 建金标准集:50 个问题 × 每题标注"应命中的原文段落"(宁小勿糙)
2. 基线跑分:固定嵌入模型与 top-k,只变切分参数,测召回率@k / MRR
   ┌────────────┬──────┬────────┬──────────┬────────┐
   │ 切分方案     │ size │ overlap│ 召回率@5  │ MRR@10 │
   ├────────────┼──────┼────────┼──────────┼────────┤
   │ 固定长度     │ 512  │ 0      │ 62%      │ 0.41   │
   │ 递归+中文标点│ 512  │ 64     │ 78%      │ 0.55   │
   │ 结构感知+表整│ 800  │ 100    │ 86%      │ 0.63   │
   └────────────┴──────┴────────┴──────────┴────────┘
3. 归因:逐个看 miss 案例——"没召回"查切分与嵌入,"召回没排前"查排序(rerank 层问题,不是切分问题)
4. 一次只动一个变量;金标准随语料演进滚动补 case

两条纪律直接决定迭代效率:

  • 评测时嵌入模型、top-k、检索器全部冻结,否则切分的贡献被稀释到无法归因。
  • miss 案例人工归因是唯一可靠的信号源——数字告诉你差多少,案例告诉你差在哪。边界截断/表格碎片/主题混杂三种病的药方完全不同。

五、常见语料的落地配方 ​

把前四节压成四张速查卡(按语料形态直接套):

语料形态推荐切法关键细节
Markdown 文档(手册/笔记)标题层级切(H2 为父块、H3/H4 为子块)标题路径前缀进块内容;代码块原子不切
PDF 合同/报告版式感知 + 表格分治表格行转文本;页眉页脚剔除;扫描件先 OCR
代码库文件/函数级切 + 依赖符号补全函数与其 docstring 同块;import 依赖摘要进块
对话/访谈记录说话人轮次 + 语义切分组合轮次是天然边界;长发言内部再语义切

每个配方的公共尾巴:metadata 上钩(source/层级/时间)→ 金标准集上跑基线 → miss 归因微调。切分没有"配置一次终身有效",只有"语料 + 评测"驱动的持续调优。这也是第一节把策略写成决策树而不是推荐参数的原因:参数会过时,决策依据不会。

上线前的切分 checklist(六条全过再发布):

text
[ ] 语料形态已识别(结构/无结构/表格占比),策略按决策树选择而非默认参数
[ ] 中文分隔符已配置(递归切分防退化)
[ ] 表格按分治规则处理(整块/复读表头/行转文本),抽样检查无碎片表头
[ ] metadata(source/标题路径/时间)随块入库,引用溯源可用
[ ] 金标准集召回率基线已记录,miss 案例归因归档
[ ] 切分参数版本化,重建索引的触发条件与回滚方案已写明

追问链 ​

面试官:父子块和普通小块 + 多取邻居(相邻块拼接)有什么区别?

邻居拼接假设"相关内容物理相邻",命中第 5 块取第 4、6 块。对线性文档有效,对"定义在第 1 章、示例在第 5 章"的结构失效。父子块的父子关系是切分时显式建立的结构映射(子块任意分布都映射回同一个父块)。检索精度与上下文完整性解耦得更干净。代价是要维护两层索引与映射关系。

面试官:语义切分效果好多少?值得每次都用吗?

对无结构长文本(访谈记录、合同条款)提升明显。但它把切分成本从"纯正则"变成"每篇一次全量嵌入调用"。且阈值(相邻句相似度断点)本身要调。工程默认:结构感知 + 递归字符覆盖 80% 场景,语义切分留给质量敏感且无结构的语料。先问语料形态再谈策略,比"越高级越好"的答案加分。

面试官:chunk_size 对召回和生成的伤害为什么不对称?

召回端:块决定嵌入的语义密度,太大糊太小碎,两边都伤。生成端:模型拿到的是整块(或父块),只要命中,稍大的块顶多多花 token,信息是超集。所以召回质量对 chunk_size 更敏感。先按召回指标调,再在生成端用"取父块/截断"控制窗口开销,两端分开优化。

面试官:一个块里混了代码和中文说明,怎么处理?

代码块(``` 围栏)当作原子单元不切断,且代码块与它的说明文字同块。拆开后代码没有语义、说明没有依据,两头都检索不到。嵌入模型选代码感知的(bge-m3 这类多语言多模态能力)。这与表格分治是同一个原则:切分尊重内容的原子性,宁大勿碎。

面试官:切分调优一般什么时候做?上线后还能改吗?

能改但贵:切分变了 = 全量重建索引(嵌入重算)。所以节奏是:上线前用金标准集把策略与参数定型(避免带着 62% 召回上线)。上线后靠 miss 案例回流驱动小步迭代,语料大改版时顺势重调。切分策略的变更等同索引 schema 变更,要走版本化——这也是"评测集是基础设施"的又一理由。

面试官:chunk_size 与嵌入模型的 max_seq_len 什么关系?

块必须塞得进嵌入模型的输入上限(常见 512 token),这是 chunk_size 的硬顶。超过部分要么被截断(静默丢内容,召回莫名缺失的第一嫌疑)。要么用长文本嵌入模型(bge-m3 支持 8k)换成本。另一个方向的关系:块也不该远小于模型"擅长概括的长度"。现代嵌入模型在 200~500 token 上训练得最充分,太短的块向量质量同样打折。选 chunk_size 前先看嵌入模型说明书,这个顺序很多人是反的。

面试官视角小结 ​

  1. 决策树:有结构走结构感知(永远优先),无结构走递归字符(默认解)。质量敏感上语义切分,粒度冲突上父子块。先看语料形态再选策略。
  2. 两参数:chunk_size 权衡曲线两头是"召回没用的碎块"和"嵌入发糊的杂块"。256~1024 token 起步。overlap 是边界保险(10~20%),过 30% 在买重复覆盖挤占 top-k。
  3. 中文陷阱:递归切分必须配中文标点分隔符,否则退化成固定长度;
  4. 表格分治:小表整块、大表行拆 + 复读表头、宽表行转文本。ai-service _render 的行式清单是现成范式。标题路径写进块内容,metadata 切分时上钩。
  5. 评测驱动:金标准集(50 题宁小勿糙)→ 冻结他变量跑基线。miss 案例人工归因(截断/碎片/混杂三病三药)→ 一次一个变量。切分变更 = 索引 schema 变更,要版本化。
  6. 硬约束:chunk_size ≤ 嵌入模型 max_seq_len(截断是静默丢内容),且别远小于模型擅长长度。
    • 四类语料的落地配方(Markdown 层级/PDF 表格分治/代码函数级/对话轮次)可直接套用。

2026-10 新增(Round1 补缺)。