做RAG落地快两年,我越来越觉得分块策略是最值得先花时间打磨的环节。之前在一个知识库问答项目里,我连续两周都在跟“为什么模型回答总是漏关键信息”较劲。换更大的模型没效果,换更复杂的提示词也只是稍微好一点,后来做了一次索引深度体检才发现,问题出在最不起眼的分块上——所有文档都按5000个字符硬切,很多长句被劈成两半,检索到的是语义残缺的碎片,模型当然答不准。这篇文章就从我踩过的那些坑出发,聊聊RAG分块策略的底层逻辑、常见方法、参数调优和落地陷阱,适合正在做知识库问答、文档检索、私有语料分析的同学参考。
1. 为什么我把分块视作RAG系统的胜负手
在RAG的整个链路里,分块往往看起来最“不起眼”,但当你深入调优时,它就变成最值得先花时间的点。RAG的过程可以简单理解为:文档先被切成块,每一块编码成向量,检索阶段把用户问题转成向量后去向量库里找最相似的一批块,最后把召回结果塞进语言模型的上下文里生成答案。这个“块”是什么样,直接决定了后面每一步的上限。
我常打一个比方:如果你要把一本手册交给一个人去回答具体问题,你是让他看整个手册、看某一章、还是看某一段?RAG也一样。用户的问题通常只对应文档中的一小段信息。分块过小,关键信息残缺;分块过大,那一小段信息被淹没在大量无关文字里,检索相似度就会被稀释。所以分块策略才是检索精度的关键变量之一。
1.1 检索粒度决定答案质量
块是检索的基本单位。向量相似度计算的是“查询向量”和“块向量”之间的距离。如果块内有太多不相关内容,即使正确答案在里面,整个块的向量表示也会被稀释,导致排序下降。
举例:某政策文档中,适用对象写完后讨论了流程,如果整个文档被切成一个大块包含这两个主题,用户问“适用对象是什么”,这个块的向量就会被流程部分的向量拉偏,可能排到很后面,而召回前几的块可能是别的主题。如果按条款或自然段切,问题对应的段落就能靠前。
这里涉及一个关键概念:信息密度。一个好的分块策略应该是让每个块尽量是一个语义紧凑、相对自洽的信息单元。用俗话说,一块能回答一个问题。我在实际项目中看到过太多例子——检索结果前五位全都不相关,把块改小之后,原来藏在大块中间的答案自动浮到第一位。这种变化往往和模型大小无关,纯粹是分块边界导致的。
1.2 分块并不是一个独立变量
很多人把分块当成单纯的文本预处理,其实它和后面的每个环节都强耦合。
- 嵌入模型:多数嵌入模型对输入长度敏感。超过一定长度,向量会更趋向于整体主题而不是具体细节;太短的块又缺上下文。
- 上下文窗口:语言模型的上下文窗口决定了最终能容纳几个块。块越大,能放进窗口的块越少,跨多个来源的信息整合能力也越弱。
- 检索方式:如果是纯向量检索,块语义要自洽;如果还会用关键词或混合检索,块的边界最好尊重关键词密度。
- 下游任务:问答、摘要、代码生成对粒度的要求完全不同。问答需要精确定位,摘要需要宏观上下文。
所以分块策略不是一个“切就完事”的环节,而是一个需要在多个约束下求解的中间层。理解了这一点,下面聊各种方法时你才会知道为什么有些场景用固定大小完全够用,有些场景必须上语义分块。
2. 分块粒度与边界:核心变量的交互逻辑
分块看似只是设定一个长度然后切,但真正决定效果的是三个变量:块大小、重叠窗口、边界切在哪里。这三个变量互相纠缠,单独调任何一个都可能改变另两个的最优值。
2.1 块大小:信息完整性与检索敏感度的博弈
块大小直接影响两个东西:检索的命中率和生成阶段的信息完整性。
块小的时候,每个块的主题聚焦,检索容易在高相似度区域命中。但太小了,比如一句话一个块,很多关键上下文(比如指代词、前置条件)会丢失。块大,语义完整但检索敏感度下降,因为向量是整块的压缩表示,块越大噪音越多。
实际操作中,我发现可以遵循一条经验曲线:把块大小从200字符逐渐调到2000字符,用测试集去测召回率,多数情况下会看到一个“倒U型”曲线,中间某个区间效果最好。但那个“最好区间”不是固定的,它和语料风格、嵌入模型、查询方式都有关。不要轻信网上“最优token数320”之类的结论,那些数字只对特定语料有效。
2.2 重叠窗口:防止截断的代价
重叠分块是指在相邻两个块之间保留一部分重复文本,避免关键信息恰好落在切分边缘被丢掉。举例,一个段落被切成两块,交界处正好有一个长从句的主语,如果不重叠,前一块会缺少后半句宾语,后一块缺少主语,两块的语义都不完整。设置15%左右的重叠通常能缓解。
但重叠不是越多越好。我第一次做项目时,为了让信息“不漏”,直接把重叠率设到50%,结果索引体积几乎翻倍,检索耗时长了不少,而且重叠部分产生大量重复向量,会让召回结果高度冗余——查询命中的前五个块里有三块内容几乎一样,既浪费上下文窗口,也没有带来新信息。后来我把重叠率定在10%-20%之间,才在召回与开销之间找到平衡。
2.3 语义边界:在完整逻辑单元处切断
与固定长度相比,更合理的方式是在语义边界处切分:段落结束、句子结束、章节标题变化时。这样每个块尽量是被作者组织好的一个意思单元。
文档中的结构信息都是分块线索:空行、标题层级、列表项、表格行。利用这些线索做分块的收益很明显:语义完整、可读性好,也方便在块上附加元数据(比如章节标题)。这个方向也是很多分块工具默认路径的核心逻辑:先按大结构切,再按小结构切,实在不够才按字符数硬切。
3. 主流分块方法的适用场景与实现细节
分块方法没有绝对的好坏,只有适合不适合。下面这张表是我常用的选型参考。
| 方法 | 核心思路 | 适用场景 | 需要注意 |
|---|---|---|---|
| 固定大小分块 | 按字符或token数硬切 | 日志、编号无关的连续文本 | 容易切破句子,不适合长文档 |
| 段落/句子分块 | 按换行、标点切 | 新闻、说明书等结构较好的文本 | 段落过长时仍需要二次拆分 |
| 递归分块 | 按分隔符优先级降级切 | 大多数通用长文本 | 参数需要结合语料调整 |
| 结构化分块 | 按标题/表格/列表等层级结构切 | Markdown、HTML、技术文档 | 需要解析器支持结构信息 |
| 语义分块 | 用向量相似度找边界 | 主题变化明显的文章 | 计算成本高,需要先逐句嵌入 |
3.1 固定大小分块:快但容易切碎语义
这个最简单,遍历文本,每N个字符切一刀。对英文可以直接按空格切,对中文没有天然空格,可能还要配合分词器。优点是实现快、行为可预测,适合处理日志、大文件等没有明确段落边界的文本。缺点也很明显:它不关心语义边界,把句子和段落拦腰截断。
如果你是刚开始做RAG,可以用固定大小快速打通链路,但生产环境推荐至少升级到按段落和句子切。我通常写一个递归函数来模拟这种分级切法:优先按段落分隔符,如果块仍然太大,再按句末标点切,最后才按逗号等短标点兜底。这种思路本质上就是递归分块。
SEPARATORS = ["\n\n", "\n", "。", "!", "?", ";", ","] def cut_text(text, max_len): if len(text) <= max_len: return [text] if text.strip() else [] for sep in SEPARATORS: parts = text.split(sep) if len(parts) < 2: continue result = [] for part in parts: result.extend(cut_text(part, max_len)) return result return [text]这段代码把切分逻辑压缩得很短:每次从一个分隔符开始,如果当前分隔符能把文本分成多个部分,就递归地对每个部分继续切分,直到长度不超限。如果某个超长文本没有任何分隔符可用(比如一段超长的URL),最后会原样返回,生产环境需要再补一个按字符硬切的兜底分支。
3.2 结构优先的递归分块:让文档层级帮你切
很多人一开始会想“递归分块”是不是很玄,其实原理很简单:设定一个最大长度,然后依次按“段落 -> 行 -> 句号 -> 逗号”这些分隔符去切。每切完一层,如果某一部分还是太大,继续用更细的分隔符切。这也是我推荐的通用分块起点。相比固定大小,它大大降低了切破句子的概率。
如果你要处理的是Markdown或HTML,则可以更进一步:先抽取标题层级,把正文按标题组织成父子内容块。查询时先匹配子块,再把父块或兄弟块带回上下文,效果往往很好。这个策略叫父子分块,本质上是让“检索粒度小、上下文粒度大”,既保精度,又保完整信息。
我在跑一个技术文档问答系统时,用纯递归分块最多只能把Top10召回率做到63%,后来给每个子块追加所属章节标题和父块摘要,召回率直接跳到78%。不是算法多高级,而是检索时多了一路结构信息的强信号。
3.3 语义分块:用嵌入相似度找边界
语义分块是不依赖标点和结构,而是把文本切成句子后逐句获取向量表示,然后比较相邻句子的余弦相似度。相似度低的地方意味着主题可能切换,就在那里切断。这样切出来的块,边界往往很“自然”。
优点是可以处理没有明显结构的文本,例如访谈记录和散文。缺点也很现实:你需要对全文逐句做嵌入运算,成本比普通分块高;相似度阈值也不稳定,同一套阈值在不同文档上可能表现差异很大。在实际项目里,我一般把语义分块当作“锦上添花”,而不是默认起手式。除非文本主题跳跃明显,并且普通递归分块验证过效果确实不行,才上语义分块。
3.4 特殊文档的分块:表格、代码、混合排版
并不是所有文档都是“连续的散文”。表格、代码、日志和富文本都有自己的特殊结构。
表格如果按普通文本切,很容易把表头、单元格、行号切散,导致检索时拿到一堆数字而不知道含义。更好的做法是每行打上表头上下文,比如把“列名:值”这样的键值对作为块。代码则要保留注释和函数签名,按函数或代码块切分,而不是按行数硬切。更复杂的混合排版(PDF、PPT)还要先恢复阅读顺序,否则分块再成熟也没用。
4. 块大小和重叠率怎么定:实验设计与踩坑
这块是最需要动手的环节。很多项目上线后效果不好,复盘时发现根本没有做过实验,全是拍脑袋定的参数。我的建议是:把分块调优当成一个正式实验来做,而不是随手改几个数字。
4.1 构造评测集,别靠感觉调参数
分块的参数必须靠评测。我会先准备一个几十到几百条的评测集,每条包含:一个典型用户问题、这个问题的标准答案所对应的原文段落位置。然后跑一遍分块+检索,看系统能不能在返回结果的前几位里召回对应段落。
常用指标包括:Top1/Top5/Top10召回率、答案命中率。这样可以直接对比不同分块参数。没有评测集,调参就是在碰运气。我曾经在一个测试集只有20条的项目里调了好几天参数,后来扩到200条才发现之前的最优组合其实只是偶然表现好。
4.2 一次参数调优的完整过程
我用模拟项目X的经历说明:某法律政策库,文档有数百篇,原先用固定800字符切分,重叠0,Top5召回率只有54%。问题集中在条款被切断、标题丢失。
第一次调整:把块长度降到300字符,召回率到了68%,但是答案生成时缺少前置条件,准确率反而下降。第二次:引入20%重叠,同时按条款优先切,Top5召回率上到81%,生成准确率也提升了。第三次:试过400字符、500字符、无重叠等多个组合,发现最优是350字符+15%重叠,Top5召回率约86%。
这个数字不是普适结论,只是说明调优是一个过程。关键是要有测试集,每次只改一个变量,记录清楚。否则你会发现自己调了一下午,根本说不清是哪个改动起了作用。
4.3 指标怎么看:召回率、排名位置、生成质量
我通常会同时看三组信号:
- 召回率:标准答案对应的原文是否出现在Top N里。这直接反映检索系统有没有找对地方。
- 排名位置:如果查询命中位置从第20提升到第3,说明分块质量改善明显。
- 生成质量:最后回答有没有引用关键点,有没有胡说。有时候检索很好,但生成依然差,那就不是分块问题,而是提示词或上下文编排问题。
还要警惕一种情况:把块切得很碎后,召回率很高,但返回的块无法覆盖完整逻辑,让模型无中生有。这时候要检查生成答案中有没有出现原文没有的细节,如果频繁出现,就得适当增大块尺寸或引入父块。
5. 落地RAG时最容易忽略的分块陷阱
分块策略的坑往往不在分块本身,而在语料和系统的各种边界条件。我在这里整理三个最常见也最隐蔽的坑。
5.1 中文语料分块:标点、词边界与长句
中文RAG项目里,固定按字符数切分最容易翻车。中文没有空格,词边界不明显,如果按N个字符硬切,很可能把一个完整词语从中间切开,比如“机器学习”被拆成“机器/学习”,虽然向量模型能部分理解,但语义多少会受损。
我建议中文分块至少用“句子级”切分,以句号、问号、感叹号为一级边界,段落换行为更高优先级边界。对于特别长的句子(例如合同里的长条款),再按逗号或分号降级切。同时可以结合标点的重要性设置分隔符列表,先试大单位再逐级缩小。这个思路就是递归分块在中文里的具体化。
5.2 结构化信息的丢失:标题、表格、引用
常见坑:正文被切成块,但它的标题层级没有带入。比如用户问“关于社保缴费比例,第三十二条怎么说”,检索系统来了一个块,里面全是正文条款,但因为没有标题前缀,它不知道自己属于哪一条,检索时就容易抓错块。
解决方法是元数据注入:在分块时把所属章节标题、块编号、来源路径一起存进索引,甚至把章节标题作为文本前缀拼到块内容里。这样查询“第几条”或者某个章节关键词时,检索能多一层有效信号。对于表格,我前面提到过,最好把表头信息和行数据组合成键值对。对于引用关系,比如“见上表”“如表2所示”,要保留原上下文或链接信息,否则分块后这些指代词彻底失去指向。
5.3 索引膨胀和更新成本:别让冗余拖垮系统
分块参数的另一个隐性代价是存储和更新。重叠率越高、块越碎,写入向量库的向量条数就越多。假设一个100万字的语料库,按500字符切是2000个块,按200字符切加50%重叠就变成约6000个块,索引体积涨了三倍。检索耗时会变长,增量更新也会更频繁。
所以生产环境要控制块数量级。不要单纯追求“颗粒细=召回高”,要结合系统成本来选。我的做法是:先用150字符、250字符、350字符、500字符四档跑评测,选出效果可接受且索引体积不过大的那个组合,同时把重叠率控制在20%以内。如果业务对实时性要求高,还要评估向量库的写入吞吐是否能跟上。
6. 从一次失败案例看分块策略的迭代过程
前面讲了很多原则,这里用一个具体的失败案例把整个过程串起来。案例背景是模拟项目X,一个技术文档问答系统,语料是从某社区论坛导出来的技术问答帖。
6.1 问题现象:检索命中错误块
最开始我用的是固定长度分块,没有重叠。用户问“连接超时应该检查哪些服务”,系统总是答成“日志采集器配置说明”。我打开向量库一看,命中的块是帖子里一大段包含多个主题的纯文本,“连接超时”的句子恰恰在块的末尾,整块向量被前面的日志配置信息带偏了,所以排名反而靠后。
这个现象非常典型:不是向量模型出了问题,而是块的内容不够聚焦。一个块塞进了太多主题,查询向量的细粒度信号被稀释。我当时才真正理解,为什么分块边界比分块长度更关键。
6.2 迭代方案与量化对比
第一阶段:把固定长度改成按“段落+句子”递归分块,块长300字符,重叠15%,Top10召回率从41%升到63%。第二阶段:针对论坛帖,把“问题描述”和“解决方案”作为两个独立语义块,并保留原帖标题作为元数据前缀。Top10召回率到了78%,回答开始能命中真正的排障步骤。第三阶段:加入父块召回机制——检索子块命中后,把包含它的完整帖文段落一并送给模型,回答完整度明显改善。
| 方案 | Top5召回率 | Top10召回率 | 生成答案人工评分 |
|---|---|---|---|
| 固定800字符无重叠 | 23% | 41% | 2.1 |
| 400字符无重叠 | 31% | 52% | 2.6 |
| 300字符递归+15%重叠 | 44% | 63% | 3.2 |
| 300字符递归+结构元数据 | 58% | 78% | 3.8 |
| 增加父块上下文 | 61% | 82% | 4.1 |
这些数字是模拟的,但模式很常见:结构化信息带来的提升往往比单纯调块长更明显。我后来把这个经验复制到好几个项目里,规律大体一致。
6.3 这次踩坑换来的经验清单
- 使用真实评测集,否则你不知道改的是变好还是变差。
- 不要迷信某个固定大小,结合语料结构先做几组实验。
- 优先保留文档本身的结构信号:标题、段落、列表。
- 分块要同时考虑检索和生成:块太小,生成会缺上下文;块太大,检索会抓不准。
- 任何新增语料类型,都要重新验证分块策略。
我现在的流程是,接入任何新语料先抽一二十篇做分块体检,确认边界、标题、表格这些信息是否被保留,再进索引。分块这个环节看着基础,但返工成本最高。希望这篇聊到的思路能让你少走一些弯路。