Chunking 与文档切分策略

为什么切分直接决定检索质量

切分(Chunking)是 RAG(检索增强生成)流水线的第一步,也是最容易被低估的一步。当用户提出一个问题时,检索系统能否找到正确的上下文,完全取决于切分后每个 chunk 是否恰好包含回答所需的信息。如果切分策略不当,即使 embedding 模型再强大、检索算法再精准,也无从找回正确答案。

切分策略的影响体现在三个核心维度:

  • 召回精度:chunk 太小会丢失跨段落的关键信息,导致检索无法覆盖答案;chunk 太大则会引入过多噪声,稀释关键信息在向量空间中的显著性。
  • 上下文窗口利用率:切分决定了模型每次只能看到 chunk 内容的窗口大小。切分不合理会导致窗口要么装不满(浪费 token 成本),要么装不下(被迫截断)。
  • 语义一致性:同一主题的内容应该落在同一个 chunk 内,不同主题的内容应该被隔开。如果切分破坏了语义边界,检索回来的内容会牛头不对马嘴。

因此,切分不是”把文档切成小块”的技术活,而是直接影响最终回答质量的策略决策。


Chunk 太大和太小分别会带来什么问题

Chunk 太大

当 chunk 超过合理规模时,主要面临以下问题:

1. 向量稀释(Vector Dilution) 一个 chunk 包含的语义主题越多,其在向量空间中的位置就越”中庸”。Embedding 模型会将 chunk 压缩为一个固定维度的向量,如果 chunk 同时包含”A公司的财报”和”B公司的财报”,检索”苹果的营收增长”时,这个 chunk 既匹配 A 又匹配 B,在余弦相似度排序中可能排不到最前面。关键信息被稀释在大量无关内容中。

2. 上下文窗口浪费 假设模型上下文窗口为 128k tokens,而一个 chunk 被切成 50k tokens。回答一个问题可能只需要 5k tokens 的相关上下文,剩余 45k tokens 的噪声不仅浪费了窗口空间,还会被模型当作相关内容来理解,增加推理负担和幻觉风险。

3. 检索粒度过粗 大 chunk 对应的检索粒度是”段落级”甚至”章节级”,但用户的问题往往是具体的。当用户问”ResNet 的残差连接为什么能缓解梯度消失”时,返回整篇深度学习论文的 chunk 远不如只返回残差连接那一段来得精准。

Chunk 太小

1. 语义不完整 如果把一句话切成独立的 chunk,embedding 模型难以捕捉跨句子的因果关系和指代含义。例如,“Transformer 提出了自注意力机制”和”它可以并行处理序列”这两个句子分开后,单独检索前者会丢失”它”指代的是什么。

2. 上下文碎片化 RAG 的一个常见问题是回答缺乏连贯性。当 chunk 太小、检索回来的多个 chunk 之间缺乏衔接时,模型生成的回答会出现跳跃和不一致。

3. 标题和元数据被剥离 小切分容易把标题、表格说明、公式注释等元信息丢掉,而这些往往是最重要的检索信号。缺少上下文的 chunk 就像没有标题的新闻段落——你知道它讲了什么,但不知道它是关于什么的。

理想 chunk 大小的经验值

应用场景推荐 chunk 大小备注
通用文本(文章、报告)500–1000 tokens含 token 计算,一般 300–800 中文字符
代码片段(函数/类级)200–500 tokens保持函数签名和完整逻辑
学术论文(按节)1000–2000 tokens可包含标题+摘要+正文的一节
知识库问答300–600 tokens保证一个 chunk 包含完整问答对
对话式检索200–400 tokens短小精悍,减少噪声干扰

具体数值需要根据文档的平均句子长度、主题密度以及下游模型的窗口大小来调优。一个实用的原则是:chunk 应该包含足以回答大多数中等复杂度问题的信息,但又不包含太多跨主题的内容。


Overlap 的作用与设计

相邻 chunk 之间保留重叠区域(overlap)是处理信息边界丢失的常用手段。

为什么需要 Overlap

文本的语义边界并不总是整齐的。一句话的结尾可能引用了下一句的概念,一个段落的结论依赖于前一段的论证。如果严格按边界切分,这些跨边界的依赖关系就会被切断。Overlap 通过在相邻 chunk 之间引入冗余来缓解这个问题。

假设 chunk 大小为 500 tokens,overlap 为 100 tokens:

Chunk 1: [0, 500)      →  tokens 0–499
Chunk 2: [400, 900)    →  tokens 400–899(重叠100 tokens)
Chunk 3: [800, 1300)   →  tokens 800–1299(重叠100 tokens)

这样即使语义边界落在 token 400 附近,检索时至少有一个 chunk 包含了完整的上下文。

Overlap 的代价

  • 存储和计算成本线性增长:overlap 比例为 20% 时,存储增加约 20%,embedding 计算量也同步增加。
  • 检索冗余:可能把高度相似的多个 chunk 都检回来,增加去重负担。
  • 并非越多越好:过大的 overlap 会导致 chunk 之间缺乏差异性,检索结果重复率高。

Overlap 设计建议

场景Overlap 比例说明
语义边界清晰(按段落切分)10–15%防止边界信息丢失
语义边界模糊(按句子切分)20–30%需要更多冗余来桥接语义
长文档且主题连贯固定 overlap tokens(如 50–100)而不是按比例
代码(函数级切分)不推荐 overlap函数边界本身是语义边界

切分粒度对召回率、精确率、上下文一致性的影响

切分粒度是召回率(Recall)和精确率(Precision)之间权衡的核心变量。

粒度越粗(chunk 越大)

  • 召回率提升:相关内容的概率增加,因为大 chunk 更容易包含正确答案的完整上下文。
  • 精确率下降:噪声内容增多,检索结果中不相关的比例上升。

粒度越细(chunk 越小)

  • 精确率提升:chunk 内容集中,检索结果噪声少。
  • 召回率下降:正确答案被切分到多个 chunk,可能漏掉关键信息。

上下文一致性(指检索回来的多个 chunk 在叙事上是否连贯):

  • 粒度不一时会导致不一致。例如一段是”ResNet 解决了退化问题”,另一段是”VGG 使用了小卷积核”——两段完全不相关,但可能因为都提到”卷积神经网络”而被同时检索回来。
  • 同一主题的内容应该尽量在同一 chunk 内,通过按标题层级或语义切分来保证。

实际工程中往往采用分层切分策略:用较大的 chunk(如整个章节)做初次检索,再用较小的 chunk(如段落级)做重排序或精匹配。


常见切分方式

固定长度切分(Fixed-Length Chunking)

最简单的策略,按 token 数量或字符数量硬性切分。

优点:实现简单、计算确定性好、对所有文档统一处理。

缺点:完全无视语义边界,很可能把一句话从中间截断,或者把两个不相关的短段落合并到一个 chunk。

适用场景:文档结构单一、内容均质、对召回精度要求不高的粗筛阶段。

按段落切分(Paragraph Chunking)

以段落(通常以换行或空行)为自然边界进行切分。

优点:保留了段落的语义完整性,embedding 能较好地捕捉段落主题。

缺点:段落长短不一,短段落单独成 chunk 时内容过少,长段落可能包含多个主题。

适用场景:新闻、博客、报告等以段落为主要叙事单位的文档。

按标题层级切分(Hierarchical Heading Chunking)

根据文档的层级结构(h1/h2/h3 标题)来决定切分边界。

优点:天然保留了文档的逻辑结构,检索时可以同时利用标题做上下文理解。

缺点:依赖文档有清晰的标题结构,网页或笔记类文档适用,但扫描版 PDF 或纯文本需要先做结构解析。

适用场景:技术文档、教程、wiki、Markdown 笔记等结构化文档。

语义切分(Semantic Chunking)

通过 embedding 模型计算相邻段落/句子之间的语义相似度,当相似度低于阈值时在当前位置切分。

优点:切分边界由语义内容驱动,而非人为设定的规则。能够处理跨段落的长距离依赖。

缺点:需要额外的 embedding 计算和相似度聚类,pipeline 复杂度提升;阈值选择需要调参。

适用场景:需要高质量切分的生产环境,尤其是文档主题多样、段落长度不均的情况。

滑动窗口切分(Sliding Window Chunking)

以固定步长(step)滑动窗口生成 chunk,窗口大小和 step 的差值即为 overlap。

优点:保证相邻 chunk 之间的上下文连贯,通过 overlap 覆盖边界。

缺点:chunk 之间可能高度相似,导致检索结果冗余;计算成本比固定切分高。

适用场景:长文本且语义连贯的文档,如技术书籍、长篇报告、论文全文。


结构化文档与非结构化文档的切分差异

非结构化文档

指没有预定义格式的纯文本,如小说、新闻、聊天记录。这类文档的切分策略相对简单,主要依赖:

  • 段落边界(空行检测)
  • 句子边界(NLTK/spaCy 分句)
  • 固定 token 长度(适合 embedding 模型的最佳输入长度)

非结构化文档的核心挑战是缺乏显式的语义边界提示,因此语义切分和滑动窗口是更合适的选择。

结构化文档

指具有明确格式的文档,典型类型包括:

文档类型结构特征推荐切分策略
Markdown / 笔记标题层级、代码块、列表按标题层级切分,保留标题作为 chunk 元数据
HTML / 网页DOM 树结构、标题标签按标签层级切分,保留 h1–h3 标题
PDF(扫描)视觉布局、栏、段落先做布局分析(表格检测、栏检测),再按区域切分
JSON / API 文档嵌套结构、字段定义按对象或 endpoint 切分
表格数据行/列结构按行或按单元格组切分,保留列名作为元数据
代码仓库文件、类、函数按文件或函数级切分,保留文件名和行号

结构化文档切分时,元数据(标题、来源、层级)必须作为 chunk 的附属信息保存,而不是丢弃。这些信息在检索时是关键的上下文信号,在回答生成时可以帮助模型理解 chunk 的来源和主题。


代码、论文、网页、笔记分别适合什么切分策略

代码

代码的语义边界非常明确——一个函数或一个类应该尽量保持完整,不应该被截断。

  • 切分单位:函数级(Python def、Java method)是首选,不推荐按行数切分。
  • 元数据:文件名、函数签名、所在行号、语言类型。
  • 特殊处理:长函数(超过 500 tokens)可以考虑按内部逻辑块再切,但要在 chunk 元数据中记录这是哪个函数的一部分。
  • 工具选择:推荐使用 tree-sitter 等代码解析工具,而非简单的文本分句。

学术论文

论文有标准的结构(摘要、引言、方法、实验、结论),每个部分讨论的主题相对独立。

  • 粗切分:按一级标题(Section)切分,chunk 大小约 1000–2000 tokens。
  • 精切分:对于关键章节(如 Related Work、Experiment),可以按段落再切。
  • 元数据:论文标题、作者、发表于哪个会议/期刊、章节标题。
  • 特殊处理:参考文献列表单独处理(如果需要检索);图表说明单独成 chunk;公式尽量保留 LaTeX 源码而非截图。

网页

网页的 DOM 树结构是天然的切分依据,但需要清理大量导航栏、广告、页脚等无关内容。

  • 切分前:必须清洗文档,去掉导航、侧边栏、广告、版权信息。
  • 切分单位:按 article、section 或主要内容区块切分。
  • 元数据:页面标题、URL、域名、主要标题(h1/h2)。
  • 特殊处理:多语言页面需要先检测语言,同一语言的内容才放在一起;表格和列表要作为独立 chunk 提取。

笔记(Obsidian / Notion 类)

个人笔记往往结构松散,但会使用 # 标题、[[双链]]- 列表等标记。

  • 切分单位:按最高级标题(h1)切分为独立的 chunk,如果一个 h1 下内容过长(超过 2000 tokens),再按 h2 或自然段落细分。
  • 元数据:笔记标题、创建日期、标签(#tag)、反向链接(backlinks)。
  • 特殊处理:双链 [[笔记名]] 指向的内容应在切分时解析为文本,而不是保留为链接格式,否则 embedding 时会丢失语义。
  • 上下文问题:笔记类文档的上下文往往需要跨笔记检索(如 [[相关笔记]] 的内容应该作为本笔记 chunk 的补充上下文),这种情况建议在切分后将相关笔记的链接内容也并入 chunk。

工程流程

第一步:文档清洗(Pre-processing)

在切分之前,必须对原始文档做清洗,否则会把大量噪声带入 chunk:

通用清洗步骤

  1. 编码检测与统一:转换为 UTF-8,处理乱码。
  2. 去重与标准化:去除重复的页眉页脚、水印、版权声明(对每页都出现的固定文本做差分检测)。
  3. 结构解析:提取标题层级、段落、列表、表格、代码块。
  4. 去除无关内容:导航栏、广告、追踪脚本、HTML 标签(除非保留结构信息)。
  5. 格式化处理:将 LaTeX、Markdown、HTML 实体转换为可读文本;保留表格的行列结构(可用 CSV 或 JSON 表示)。
  6. 语言检测:多语言文档按语言分段,同一语言的内容放在一起。

文档类型特定的清洗

文档类型特殊清洗步骤
PDF布局分析(两栏/三栏检测)、表格检测、公式检测、页眉页脚去除
网页DOM 净化(去掉 nav/aside/footer/ad)、富文本转 plain text
扫描件OCR、版面恢复
代码格式化(统一缩进)、注释保留(可选)、依赖分析

第二步:定义 Chunk 元数据

每个 chunk 生成后,应该附带元数据字段。元数据不仅是检索的辅助信号,也是回答时模型理解上下文来源的依据。

推荐元数据字段

字段名说明是否必须
source文档来源(文件名/URL)必须
titlechunk 所在文档的标题必须
headingchunk 对应的章节标题推荐
chunk_index当前 chunk 在文档中的序号推荐
total_chunks文档被切分的总 chunk 数推荐
language内容语言推荐
document_type文档类型(pdf/web/markdown 等)推荐
created_at文档创建时间可选
authors作者(论文类文档)可选
page_numbers来源页码(PDF)可选
tags从标题或内容中提取的关键词标签可选
backlinks反向链接(笔记类文档)可选

第三步:控制 Chunk 长度与重叠

_chunk 长度_和_overlap_是两个核心超参数,建议按以下步骤调优:

  1. 确定基线:先按经验值设定 chunk 大小(如 512 tokens),不做 overlap。
  2. 评估召回效果:用一批代表性的查询(Query)测试,看检索回来的 chunk 是否包含正确答案。
  3. 分析失败案例
    • 如果正确答案被截断 → 减小 chunk 大小 或增大 overlap。
    • 如果检索结果噪声太多 → 增大 chunk 之间的差异性(用语义切分替代固定切分)。
    • 如果跨 chunk 的信息丢失 → 增大 overlap。
  4. 设定最终参数:记录每次调参后的 Precision@K 和 Recall@K(K 通常取 3 或 5),选择最优组合。

第四步:抽样检查(Spot Check)

切分完成后,不能完全依赖自动评估,必须做人工抽样检查。建议流程:

  1. 随机抽样:从每个文档类型中随机抽取 5–10 个 chunk,检查语义是否完整。
  2. 边界检查:重点检查 chunk 边界附近的内容,看是否有被截断的句子或被拆散的列表。
  3. 元数据检查:确认元数据中的标题、页码、章节信息是否与 chunk 内容一致。
  4. 检索模拟:用几个典型查询手动检索,确认返回的 chunk 是否真的包含答案。
  5. 记录问题:建立切分质量评分表,定期回顾和迭代切分策略。

切分策略选择表

文档类型结构化程度内容特征推荐切分策略Chunk 大小参考必要元数据
新闻/博客段落叙事连贯段落切分 + 语义合并500–800 tokens标题、发布日期、作者
技术文档/教程层级结构清晰按标题层级切分800–1500 tokens标题、章节路径、层级
API 文档结构化定义按 endpoint/对象切分200–500 tokensAPI 路径、方法名、版本
学术论文章节主题独立按 section 粗切 + 段落精切1000–2000 tokens标题、作者、会议/期刊
代码仓库函数/类边界明确按函数/类切分200–500 tokens文件名、函数签名、语言
知识库问答QA 对应关系强按 QA 对切分300–600 tokens问题类型、来源系统
网页变化大内容混杂DOM 净化 + 按内容区块切分500–1000 tokensURL、页面标题、主要标题
个人笔记主题分散、双链引用按 h1 切分 + 反向链接注入500–1500 tokens笔记标题、标签、双链
表格数据行列结构按行或行列组切分取决于表格宽度列名、表头、来源
合同/法律文本条款编号清晰按条款/章节切分500–1500 tokens条款编号、合同编号、日期

Chunk 设计注意事项清单

语义完整性

  • Chunk 不应截断完整的句子或列表项
  • 段落级主题应该独立成 chunk,不与其他主题混合
  • 函数/类/方法的签名和实现不应被分到不同 chunk

元数据完整性

  • 每个 chunk 必须包含 source(来源)字段
  • 标题结构信息应作为元数据保留,不应丢弃
  • 对于跨 chunk 的内容,应在元数据中标记前向/后向引用关系

长度控制

  • Chunk 长度应控制在目标 embedding 模型的最佳输入范围内(通常 256–1024 tokens)
  • 长度分布的方差不应过大,避免过短 chunk(< 50 tokens)过多
  • 过长的 chunk 应考虑二次切分,同时保留原始 chunk 索引

噪声控制

  • 切分前应去除页眉页脚、水印、版权声明等重复内容
  • 导航栏、广告、追踪脚本等无关内容应在清洗阶段去除
  • 代码注释是否保留应基于具体场景决定(注释多的代码可以考虑去掉注释后切分)

Overlap 设计

  • 语义边界不清晰的文档应使用 overlap(建议 15–25%)
  • 语义边界清晰的文档(如按标题切分)可以不使用 overlap
  • Overlap 不应过大,否则检索结果重复率高

上下文一致性

  • 同一主题的段落应尽量在同一 chunk 内
  • 检索回来的多个 chunk 应该有某种程度的叙事连贯性
  • 可以通过在 chunk 末尾添加”前情提要”或”上文提到”来增强上下文衔接

检索评估

  • 建立评估集(包含 Query、相关文档、预期答案)
  • 用 Precision@K 和 Recall@K 评估切分策略的效果
  • 定期做人工 spot check,记录常见失败模式并迭代优化

切分与检索效果的深层联系

回到最初的观点:切分直接决定检索质量。为什么?

检索的本质是在向量空间中找到与 Query 最相似的 chunk。如果 chunk 的语义边界合理,每个 chunk 就是一个”信息原子”——既包含完整的知识点,又不混入其他知识点。检索时找到正确的 chunk,就等于找到了正确的知识点,回答质量就有了保证。

反之,如果 chunk 切分混乱——把不相关的内容捏在一起,把相关的内容拆得七零八落——无论检索算法多么精准,都无法弥补切分策略的缺陷。这就是为什么在实际 RAG 项目中,调优切分策略往往比调优检索算法带来的收益更大。

最后,切分策略不是一次设定终身使用的。随着文档库的变化(新增文档类型、文档主题漂移)、用户查询模式的改变(如从宽泛问题变成具体问题),切分策略都需要重新评估和调整。建立一套切分质量的监控机制,定期抽检和迭代,是保持检索系统长期效果的关键。