分块与解析
分块是 RAG 的地基。地基没打好,Embedding 模型再好、LLM 再强,效果都打折。分块的本质不是“把长文本切短”,而是切出可回答单元:每个块主题明确、自包含、被召回后能直接支撑回答。
一个真实的对照实验:某项目对同一份临床文档切分,固定大小切分完整准确率 13%,自适应分块 50%。文档相同,LLM 相同,只有切分方式变了。
解析:数据进管道的第一站
各种格式先解析成纯文本:PDF(要处理表格、扫描件 OCR)、HTML(去标签)、Word、Markdown。三个高频坑:
- 表格不能切。 表格一旦被切坏,损失的是字段关系。“A 款 500000 5000”这种流水账和保留列关系的表格,语义价值不是一个量级。表格要整表保留为原子单元,必要时每个子块保留表头和行列对应关系
- 图片和扫描件。 先 OCR 或生成图片描述,文字内容提取出来后和图片一起作为块内容
- 代码按函数和类切。 递归切分器会在函数体中间切断,把文档字符串和函数签名分开。基于 AST 的切分尊重语法边界
分块策略
| 策略 | 做法 | 定位 |
|---|---|---|
| 固定大小 | 按 token 数切,加 overlap | 基线方案,不理解语义边界 |
| 递归字符 | 按分隔符优先级逐级切:段落→句子→字符 | 最通用的生产默认 |
| 结构感知 | 按标题层级、章节、列表边界切 | 利用文档天然语义边界 |
| 语义分块 | 相邻句子向量相似度下降处设边界 | 尊重主题过渡 |
| 父子块 | 小块检索、大块返回 | 精度和上下文兼顾 |
递归字符分块是实际项目最常用的:按一组分隔符依次尝试,先按段落切,段落太大按句子切,句子太大按字符切。参数是块大小上限和 overlap。
语义分块有个悖论。 基于相似度的边界检测倾向产生很小的块(实测平均 43 token),检索召回率最高,但小块很少包含生成正确答案所需的上下文。你从错误的段落里检索到了正确的句子,但它已经被剥离了论证。高检索召回和低生成准确并存,所以评估不能只看检索指标。
父子块(Parent-Child)是更稳的折中。 用细粒度小块做向量匹配保证召回精度,命中后返回其所属的父块(章节级大块)给 LLM,同时拿到精确定位和完整上下文。LangChain 的 ParentDocumentRetriever 就是这个思路。
三种真实失败模式
- 指代断裂。 文档第 1.2 节定义“本公司”,后面整篇用“该实体”。从第 7 节切出的块包含“该实体不应……“但不含指代对象,LLM 只能捏造
- 定义与规则分离。 术语只定义一次,后面反复引用。检索到应用术语的块但没有定义,模型必须猜
- 边界污染。 切分边界把上一章结尾和下一章开头拼在一起,块内部自相矛盾
解法是把分块当成内容类型感知的:表格、代码、混合布局 PDF 各走专门的解析器。
Overlap 与块大小
Overlap 解决的是块与块断开后丢失的连续性。太小衔接不够,太大带来重复信息、存储检索开销、重排阶段被相似重复块干扰。经验值 10% 到 20%,且尽量贴着完整句子和语义边界切,不从字符中间截一刀。
块大小没有万能值,但有三条经验规则:
- 通用场景 300 到 500 token 起步,FAQ 问答 100 到 200,长文档总结 500 到 1000
- 块大小不能超过 Embedding 模型的最大输入长度(bge-large-zh 约 512 token,text-embedding-3 支持 8191),要留余量
- 必须用自己的数据集实验验证。Reddit 上有开发者实测 256 token 的小块精确度优于 384,但那只代表他的数据集
另一个稳健默认:512 token 递归切分加 10-25% overlap,胜在结构安全性,入库成本为零,索引大小可预测。
面试追问
- 固定长度切分有什么问题? 不理解标题、表格、列表、跨页段落这些自然结构,容易把完整可回答单元切碎。条款在“以下情况除外”处被截断,免责内容跑进下一个块
- Overlap 设多少? 没有万能数字,经验值 10-20%,关键看是否保住连续性且没有把重复信息推得过多。overlap 越大越好的说法是误区
- 表格怎么处理? 整表保留为原子单元,不切分。必须拆时每个子块保留表头和行列对应关系
- 为什么语义分块召回高但生成差? 相似度边界产生过度碎片化的小块(平均 43 token),检索精准但缺少生成所需的上下文。评估不能只看检索指标,要看端到端答案和忠实度
- 块大小怎么定? 按场景定初始值、受嵌入模型窗口约束、用评估集实测调优。没有最优固定值,只有适合你数据集的参数