乐于分享
好东西不私藏

鹅厂面试官:说说 RAG 系统里 文档切分 的策略有哪些?如何选择合适的 chunk size?

鹅厂面试官:说说 RAG 系统里 文档切分 的策略有哪些?如何选择合适的 chunk size?

今天来聊一个RAG 项目里看起来很基础,但真正做起来特别容易翻车的问题:

「RAG 系统中文档切分的策略有哪些?如何选择合适的 chunk size?」

很多人第一次做知识库,思路都非常直接:

文档读进来,按 500 字或者 512 token 一刀一刀切完,丢进向量库,然后就开始检索测试。

结果上线一跑,问题全来了:

用户问一个很具体的问题,系统明明“检索到了相关内容”,但回答总是不完整;有时候答案就在原文里,却因为被切在两个 chunk 的边界上,导致两个 chunk 都不够完整,最后大模型看了也说不明白。更尴尬的是,chunk 切太大时,检索结果又开始混入一堆噪声,明明用户只问“退款时间”,你却把“退款规则、售后政策、平台介绍”全打包召回了。

所以这题真正要回答的,不是“有哪些切分方法”,而是:

文档切分的本质,是在检索粒度和语义完整性之间找平衡。

简要回答

RAG 里的文档切分,常见思路主要有三类:固定长度切分、按语义边界切分,以及递归/混合切分。

固定长度切分最好实现,性能稳定,适合做 baseline,但最大的问题是不理解内容,很容易把一个完整段落、代码块或者问答对从中间切断。

按语义边界切分更符合内容结构,比如按标题、段落、句子、表格、代码块这些自然边界来分,能更好保留语义完整性。

递归或混合切分则更实用,它会优先按大结构切,如果超出长度限制,再继续往下按段落、句子细分,兼顾完整性和长度控制。

至于 chunk size,没有一个放之四海而皆准的数字。核心判断标准就两个:既要让检索足够精确,又要给大模型保留足够上下文。 一般可以把 200 到 500 tokens 作为经验起点,再结合文档类型、embedding 模型长度限制、问答场景和实验结果做调优。与此同时,通常还会配一个 10% 到 20% 的 overlap,避免关键信息刚好被切断在边界上。

详细解释

一、先别急着背方法,先想清楚为什么要切分

很多人讲 chunking,上来就是字符切分、语义切分、递归切分一顿罗列。但面试或者实际做项目时,更重要的是先讲清楚这个动作到底在解决什么问题。

因为 RAG 不是把整篇文档原样塞给模型,而是要先做检索。检索这一步决定了你能不能把真正相关的内容捞出来。文档如果完全不切,整章甚至整本手册作为一个向量去检索,语义会非常“稀”,用户问一个小问题时,很难精确命中。可如果切得太碎,每个 chunk 只有一两句话,虽然检索颗粒度变细了,但上下文又没了,大模型拿到之后会看不懂这句话到底在什么语境下成立。

所以 chunking 不是预处理里的“机械步骤”,它其实直接决定了召回质量的上限。

你可以把它理解成一个放大镜问题。

镜头拉得太远,什么都看得到,但没有重点; 

镜头拉得太近,细节很清楚,但又看不见全貌。

chunk size 本质上就是在调这个焦距

二、固定长度切分为什么最常见,但也最容易踩坑

固定长度切分的思路最简单:按字符数、按 token 数硬切。比如每 500 token 一个 chunk,前后再加 50 token 的 overlap。

它最大的优点是简单、稳定、工程成本低。很多框架默认就支持,比如常见的 RecursiveCharacterTextSplitter,本质上也是先尝试按结构切,不行再退化到长度控制。对于格式很规整、内容边界不强的文本,这种方法是能工作的,所以很多团队都会先拿它做 baseline。

但问题也恰恰在于它不理解内容

比如一个产品说明文档里,前半段在讲“适用条件”,后半段在讲“退款时限”,如果固定长度正好从中间切开,就很可能出现一种尴尬情况:前一个 chunk 只有条件,没有结论;后一个 chunk 只有结论,没有主语。用户问“这个订单多久能退”,检索到的内容就会像碎片一样,模型只能靠猜去补。

处理代码文档时这个问题更明显。一段函数说明,如果参数解释和返回值示例被切到两个 chunk 里,实际召回的时候,你可能只能命中一半。模型看到后,不是答不全,就是直接编。

所以固定长度切分可以用,但你一定得知道,它适合的是先快速跑通系统,不适合直接当成最终方案。

三、真正更实用的,是按语义边界切分

语义边界切分的核心思想很朴素:尽量让每个 chunk 成为一个完整的信息单元。

最常见的做法,是按文档天然结构来切。比如普通说明文档先按标题和段落切,Markdown 文档优先按标题层级切,FAQ 文档则更适合按“一问一答”切,代码文档要尽量保证一个完整代码块不要被拆开,表格则最好整表保留,而不是把行和列拆散。

这类切分的优势在于,召回回来的内容通常更“像人话”。 它不是一段被腰斩的文本,而是一小块能独立表达意思的内容。 这样检索更稳,生成也更稳。

举个例子。如果你的知识库是售后问答,里面有一条:

“退款申请提交后,通常会在 3 个工作日内完成审核,节假日顺延。”

这种内容最适合按问答对或者按句子级信息单元切出来。因为用户大概率问的就是“退款多久能审核完”,这时候一个小而完整的 chunk,比一个混着“退款条件、退款入口、退款时效”三种信息的大 chunk 更容易被精准召回。

但语义切分也不是没有代价。它实现起来比固定长度复杂,而且遇到结构很差的原始文本,效果会明显依赖前面的文档解析质量。标题识别不准、表格结构丢了、段落顺序乱了,后面的语义切分自然也会跟着出问题。

四、项目里最常用的,其实是递归或混合切分

推荐最实用的思路 递归切分

它不是一上来就按固定长度硬砍,而是先尝试保留大的语义单元。比如先按章节切,如果某个章节太长,再往下按段落切;段落还是太长,就继续按句子切;实在不行,再退到按字符或 token 限制截断。

这套策略的好处就在于它很“工程化”。它不是追求理论上最优,而是尽量让大多数 chunk 既完整,又不会失控地过大。

比如一份 API 文档,理想情况下,一个接口的说明就应该是一个完整 chunk。可有些接口说明太长,里面既有参数定义,也有请求示例、响应示例、错误码解释。这时候如果整个接口说明硬塞成一个 chunk,向量表示会变得太混杂;但如果完全按长度硬切,又会把接口定义拆烂。

递归切分就比较合适:先把接口说明当成整体,如果超过 size,再拆成“参数说明”“请求示例”“响应示例”“错误码”几个子块,同时保留接口名作为元信息。这样检索精度和上下文完整度都会更平衡。

所以从实战角度看,最好的策略往往不是纯固定长度,也不是纯语义切分,而是语义优先、长度兜底。

五、chunk overlap 不是可选项,而是补边界损失的手段

很多人只关注 chunk size,却忽略了 overlap。其实它非常关键。

因为再好的切分策略,也几乎不可能百分之百避免边界损失。现实文档里,关键信息跨 chunk 是常态。一个句子的前半段在上一个 chunk,后半段在下一个 chunk,这种情况太常见了。

overlap 的意义,就是让相邻 chunk 有一部分重复区域,相当于给边界加缓冲带。比如 chunk size 是 500,overlap 是 50,那么 chunk2 会带上 chunk1 末尾的那 50 token。这样如果一句关键信息刚好卡在边缘,它至少有更大概率完整落在某一个 chunk 里,不至于被切得两边都不完整。

但 overlap 也不是越大越好。太大了会带来两个问题:一是存储量增加,二是检索结果容易返回大量重复内容,最后把上下文窗口浪费在重复片段上。一般经验上,10% 到 20% 是比较稳的起点,后面再根据 badcase 调。

六、chunk size 到底怎么选?先记住一个核心判断

很多人最想听的,是一个标准答案,比如“512 最好”或者“768 更合理”。但实话是,没有这种银弹。

chunk size 的选择,本质上要看三个东西:文档类型、查询粒度、模型约束。

如果你的文档偏 FAQ、客服问答、短政策说明,用户通常问得很细,那 chunk 就应该偏小。因为用户要命中的,往往就是某一句话、某个条件、某个时效。chunk 太大,只会把很多无关内容一起编码进去,稀释真正相关的信息。

如果你的文档偏技术手册、法律条款、长流程说明,很多答案依赖上下文链条,那 chunk 就不能太小。因为小到一定程度之后,召回虽然精确,但模型拿到的只是局部碎片,根本拼不出完整含义。

所以你会发现,chunk 太小的问题是缺上下文,chunk 太大的问题是引噪声。

这也是为什么很多场景下,200 到 500 tokens 会被当成一个经验起点。这个区间既不会大到把多个主题硬塞进同一个向量,也不会小到只剩下一句孤立的话。当然,技术文档和代码场景往往可以再大一些,FAQ 和短问答则可以再小一些。

七、选 size 不能拍脑袋,得靠实验

真正靠谱的调参方式,从来不是“别人说 512 好用,我也用 512”。而是拿你自己的数据去验证。

比较务实的做法,是先准备一小批真实问题,比如 20 到 50 个典型 query,手工标出每个问题的正确答案大概落在原文哪个位置。然后用不同的 chunk size 和 overlap 组合去跑检索,观察两个结果:

一个是 Top-K 召回情况。也就是前几条召回里,有没有把正确答案所在 chunk 找回来。另一个是 最终答案质量。把这些召回结果喂给大模型之后,回答到底是更完整了,还是更模糊了。

这两个指标要一起看。因为有些配置检索命中率看起来不错,但生成答案还是差,往往说明 chunk 虽然召回到了,却缺足够上下文。反过来,有些配置生成质量还行,但检索排序不稳定,说明 chunk 太大,语义表示被稀释了。

实际调优时,一个非常常见的现象就是: 你把 chunk 从 512 调到 768,复杂问题的回答变完整了,但简单问题的排序开始变差;或者你保持 512 不变,只把 overlap 从 50 提到 100,跨边界问题明显改善了。

这些都不是理论能直接推出来的,只能通过实验和 badcase 一点点逼近最优点。

八、不同文档类型,切分策略本来就不该一样

这点在项目里特别重要。很多人做 RAG,喜欢全库统一一个 splitter、一套参数,图省事。但只要文档类型一杂,效果通常就会崩。

比如代码文档,最重要的是代码块完整性,不能把一个函数示例从中间切断。 比如对话记录,常常要按轮次切,因为问题、追问、澄清、回答之间是强上下文关系。 比如研究论文,摘要、方法、实验、结论天然就是不同层次的信息块,很多时候按章节切比按长度切合理得多。 再比如商品文档,规格参数最好单独成块,不要和品牌故事、营销文案混在一起,否则用户问“电池容量是多少”时,向量相似度会被别的信息干扰。

所以切分策略不是通用模板题,而是强依赖数据分布的。好的切分,不是“最先进”,而是“最适合你这批文档”。

九、一个面试里很好用的总结方式

如果面试官最后追问:“那你实际会怎么选?” 你可以这样回答:

我不会一上来追求复杂策略,而是先用一个稳妥 baseline 跑通,比如语义优先的递归切分,chunk size 从 300 到 500 tokens 起步,overlap 设成 10% 到 15%。然后根据文档类型单独处理不可拆内容,比如代码块、表格、问答对。接着用一批真实 query 做离线评测,看 Top-K 召回和最终答案质量,再根据 badcase 判断是 chunk 太大引入噪声,还是 chunk 太小丢了上下文,最后再做针对性调整。

这套回答会比单纯说“我一般用 512”强很多。因为它体现出来的不是记忆参数,而是你真的知道怎么把这件事落地。

写在最后

文档切分这件事,特别像很多 RAG 里的工程问题:看着不起眼,真正决定效果的时候却非常致命。

你以为问题出在 embedding、出在 rerank、出在大模型不够强,最后一查,根因可能只是最开始那一刀切错了。

所以这题最重要的一句话,我建议你记住:

chunking 不是简单的“把文档切开”,而是在尽量保留语义完整性的前提下,为检索创造合适的粒度。

理解了这句话,你再去看固定长度、语义切分、递归切分、overlap、chunk size 这些概念,就不会只停留在“背方法”的层面,而是真的明白它们各自在解决什么问题。

点个关注,学习更多大模型知识!