搭过 RAG 的人多半遇到过这种场景:文档明明导进去了,搜索也能返回几段话,但 AI 的回答就是不对。翻翻返回的内容才发现,最关键的那句话被拦腰切成了两半,一半在上一段末尾,一半在下一段开头,两段都没被命中。
问题多半出在分块上。文档入库前要切成小块再算向量,这个切块的粒度和方式,直接决定了检索质量的上限。今天把切法过一遍,每种都有明确的适用场景。
为什么分块值得认真对待
分块的初衷很朴素:嵌入模型有输入上限,再大的上下文窗口也没法把整个知识库塞进去,检索时也不可能返回整本文档。
但切得不好会出两类问题。切太碎,一句话被从上下文里孤立出来。比如「该公司的营收环比增长了 3%」,块里没说是哪家公司、哪个季度,就算被检索回来,模型也没法用。切太大,一个块里混了几件事,向量被稀释,检索时和任何问题都不够像,而且塞进提示词还费钱。
Anthropic 在 Contextual Retrieval 那篇文章里给的通用参考是:块一般不超过几百个 token。这是合理起点,不是金科玉律。
按结构切:标题层级是最好的免费信号
大多数文档自带结构,Markdown 有标题层级,PDF 有章节页眉,代码有函数边界。按这些边界切,块的语义天然完整。
LangChain 专门有按 Markdown 结构切分的 splitter,按标题逐级往下走;对代码文件,按函数和类边界切的效果远好于按字符数硬切。先看文档结构再决定切法,是我现在的固定动作,能省掉后面一半的调参。
递归切分:结构不明显时的默认选择
文档没有明显结构,就用递归字符切分:先按段落切,超了再按句子切,还超再按字符切。层层降级,尽量保住自然边界。
这是各家框架的默认选项,对通用文本的兜底效果不错。参数主要是两个:块大小和块重叠。LlamaIndex 源码里的默认值是 1024 个 token、重叠 20;LangChain 的默认块大小更保守。重叠是为了让边界附近的信息在相邻两块里各出现一次,被腰斩的句子至少有一块是完整的。
重叠不是越大越好。设得太大,存储翻倍,检索返回的块之间大量重复,多占提示词预算却不增加信息。常见做法是块大小的 10% 上下浮动,调参从这里开始。
块大小怎么定:看你的嵌入模型和问题类型
两个硬约束先摆平。
第一是嵌入模型的输入上限。bge 系列里 bge-large-zh-v1.5 的上限是 512 token,bge-m3 是 8192。块加重叠不能顶到上限,留点余量。上限小的模型配大块,后半截直接被截断,检索时等于丢了一半内容。
第二是问题的粒度。用户问的是精确事实(某个参数是多少、某天发生了什么),小块好,命中最相关的几句话。用户问的是综述性问题(这个方案的整体思路),大块好,块内自带足够上下文。两者都有的系统,可以小块入库、检索后用父块或相邻块扩展喂给模型。
两个进阶动作
分块本身只是地基,检索质量不够时,先试这两个。
第一个是上下文增强。Anthropic 的做法是入库前让模型给每个块补一句「这段话出自哪个文档、讲什么」,官方数据是检索失败率降 49%,配合重排降 67%。代价是入库时要过一遍模型,成本增加,但它解决的正是前面说的「块脱离上下文」这个病根。
第二个是重排。向量检索先用便宜的方式召回几十个候选,再用交叉编码器精排出前几条。块切得不完美时,重排经常能把真正相关的块捞回来,站内写过专门的重排入门和微调实战,这里不展开。
再给一个务实建议:分块参数的上限收益没有想象中大,别陷进去调一星期。先按结构切、1024 token 起步、10% 重叠,把检索日志打开看真实 query 命中了什么,比闭眼调参有用得多。RAG 系统的答案质量,分块是第一道闸,但不是唯一一道。