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