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 切分这个环节说起来不复杂,但在实际项目中花的调试时间往往比想象的多。很多时候效果不好,回头一看根因就在切分上。
常见追问
- 你实际项目中 Chunk 大小是怎么确定的?有没有做过对比实验?
- 语义切分具体怎么做?有什么开源工具?
- 如果文档是表格或图片混排的,Chunk 怎么切?