03. Chunk 应该怎么切?为什么切分策略会直接影响效果?

整理 Chunk 应该怎么切?为什么切分策略会直接影响效果? 的核心概念、工程要点与面试回答。

简单回答

Chunk 切分是 RAG 效果的基础环节。切太大,一个 Chunk 里混杂多个主题,检索精度下降;切太小,丢失上下文,模型无法理解片段含义。常见策略包括固定长度切分、按段落/句子切分、基于文档结构切分(利用标题层级)、以及语义切分。实际项目中通常需要根据文档类型做定制化,并加上 overlap 防止信息丢失。

详细解释

为什么 Chunk 切分这么重要

很多人做 RAG 时把精力全放在选模型、调 Prompt 上,但实际上 Chunk 切分的质量对最终效果的影响可能比模型选择更大。原因很直接:Embedding 模型是以 Chunk 为单位做编码的,如果一个 Chunk 本身内容混乱、主题不聚焦,它对应的向量也会是一个"模糊"的语义表示,检索时就很难精准匹配。

举个具体例子。假设一个文档有连续两个段落,第一段讲"产品 A 的定价策略",第二段讲"产品 B 的退货政策"。如果这两段被切进了同一个 Chunk,当用户问"产品 A 怎么定价的",这个 Chunk 可能会被检索到,但里面混杂的退货政策内容会干扰模型的回答。反过来如果把一个完整的段落切成了两半,前半段说"根据最新政策",后半段说"报销上限为 5000 元"——两半单独拿出来都缺乏完整信息。

常见的切分策略

固定长度切分是最简单粗暴的方式,按 token 数或字符数切,比如每 512 个 token 切一次,前后留 50 个 token 的 overlap。优点是实现简单、Chunk 大小统一方便管理,缺点是完全不顾语义边界,经常把一句话或一个段落切断。适合对效果要求不高的快速原型,或者文档本身结构很松散(比如聊天记录)的场景。

按段落或句子切分稍微好一些,至少尊重了自然语言的基本单位。按段落切在文档格式规整的时候效果不错,但有些文档的段落本身就很长(一个段落几千字),这时候就需要在段落内部再做二次切分。按句子切粒度太细,通常需要合并相邻句子到目标长度。

基于文档结构切分是更推荐的方式。如果文档有清晰的标题层级(比如 Markdown 的 H1/H2/H3,或 Word 文档的大纲结构),就按章节来切。每个章节作为一个 Chunk,如果章节太长再在章节内部按段落或语义切分。这种方式能最大程度保证每个 Chunk 的主题聚焦。很多框架(LangChain、LlamaIndex)都提供了 MarkdownHeaderTextSplitter 这类工具。

语义切分(Semantic Chunking)是近年来比较热的方向。思路是用 Embedding 模型计算相邻句子之间的语义相似度,当相似度突然下降时,说明话题发生了转换,就在这里切断。这种方式能自适应地找到语义边界,但计算成本更高(需要对每个句子做 Embedding),而且依赖 Embedding 模型本身的质量。

关于 Chunk 大小的选择

Chunk 大小没有万能的最优值,需要根据场景调整。一般来说 256 ~ 1024 个 token 是比较常见的范围。FAQ 类的文档可能 100 ~ 300 token 就够了,一问一答本身就是一个完整的语义单元。长篇技术文档可能需要 500~1000 token 来保证上下文完整。法律合同、学术论文这类信息密度很高的文档,可能需要更大的 Chunk。

Embedding 模型本身也有最大输入长度限制,比如早期的 text-embedding-ada-002 最大 8192 token,但如果输入太长,编码质量会下降(信息被"压缩"进一个固定维度的向量,太长的文本难以充分表示)。所以即使模型支持长输入,Chunk 也不宜过大。

Overlap 的作用

Overlap 是指相邻两个 Chunk 之间共享一部分文本。比如 Chunk 大小 512 token,overlap 64 token,那么第一个 Chunk 是 token 1 - 512,第二个 Chunk 是 token 449 - 960。Overlap 的目的是防止关键信息刚好落在切分边界上被截断。尤其是当一个因果关系或论证跨段落时,overlap 能保证至少有一个 Chunk 包含完整的信息。代价是索引体积会增大。

进阶:Parent-Child Chunk 策略

一种在实践中很有效的策略是分层切分。先把文档切成较大的 Parent Chunk(比如 1024 token),再把 Parent Chunk 切成较小的 Child Chunk(比如 256 token)。检索时用 Child Chunk 做匹配(粒度细、精度高),但返回给模型的是对应的 Parent Chunk(上下文完整)。这样兼顾了检索精度和上下文完整性。LlamaIndex 里的 SentenceWindowRetriever 就是类似的思路。

实际项目中的经验

真正做项目时,Chunk 切分策略往往不是"选一种用到底",而是根据文档类型做差异化处理。比如结构化的产品文档用标题层级切分,非结构化的客服对话记录用固定长度加 overlap,FAQ 数据直接按问答对切分。预处理阶段还可以对 Chunk 做"增强"——比如给每个 Chunk 加上它在文档中的上下文信息(属于哪个章节、前后 Chunk 的摘要等),作为元数据存储,帮助后续检索。

面试时可以这样答

Chunk 切分直接决定了检索的粒度和质量。切太大,一个 Chunk 里主题混杂,向量表示模糊,检索精度下降;切太小,上下文丢失,模型拿到一个残缺的片段没法正确理解。

常见策略有几种。固定长度切分最简单但效果一般,按段落或句子切分尊重语言边界但对文档格式有要求,基于标题层级切分能保证主题聚焦,语义切分通过计算句间相似度自动找切分点但成本更高。

实际项目中我一般会根据文档类型做差异化。结构化文档优先用标题层级切,FAQ 按问答对切,非结构化文本用固定长度加 overlap。Chunk 大小通常在 256 到 1024 token 之间调,overlap 设 10%~20%。还有一种实践中效果不错的做法是 Parent-Child 分层切分——用小 Chunk 检索,返回大 Chunk 给模型,兼顾精度和上下文。

Chunk 切分这个环节说起来不复杂,但在实际项目中花的调试时间往往比想象的多。很多时候效果不好,回头一看根因就在切分上。

常见追问

  1. 你实际项目中 Chunk 大小是怎么确定的?有没有做过对比实验?
  2. 语义切分具体怎么做?有什么开源工具?
  3. 如果文档是表格或图片混排的,Chunk 怎么切?