RAG文档切分最佳实践:chunk策略、粒度与召回评测
大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。
上个月帮一个团队看线上RAG客服。用户问“退款几天到账”,它答的是退货流程。模型换了两个,向量库也重新调过,还是答不准。我让他们把检索命中的片段打出来看,问题一下就清楚了。命中的那段是半句话,前半句被切到了另一个chunk里。后来做信创项目的时候发现,切分只是RAG检索链路上的一环,底座选什么,同样影响最终效果。这个后面一起说。
一、答不准,先别急着换模型
遇到答非所问,多数人的第一反应是模型不行。这个判断常常是错的。RAG的回答质量,一大半由检索决定。检索回来的是垃圾,模型再强也只能基于垃圾答。
检索的最小单位,就是你切出来的那段文本,业内叫chunk。所以排查RAG答不准,正确的顺序是先看检索命中什么,再看切分合不合理,最后才轮到模型和向量库。
打日志看命中片段,这一步别省。它往往一眼就能看出问题:命中的是半句话,还是混着好几个话题的一整段。
二、chunk 是 RAG 的隐性天花板
把RAG链路摊开看。文档先切成chunk,每个chunk转成向量,存进向量库。用户提问时,把问题也转成向量,检索最相近的几个chunk,跟问题一起丢给模型生成答案。
chunk是整个链路的起点,它划定了语义最小的完整单位。切分这一步没做好,后面所有环节都在补救。
切太碎,是怎么坏的?一句话被拦腰截断,两个半句各自都不完整。检索命中的是哪半句都答不全。开头那个客服,就是把“退款到账需要X天”和“退款到账需满足某条件”切到了两块。
切太大,又是另一种坏。一个chunk塞进了三四个话题,向量把它平均成一个模糊的点。检索时它和哪个问题都不够近,精度直接掉。而且大chunk白占模型的上下文窗口,挤掉本可以放进去的其他信息。
三、几种切分策略,我拿同一份文档测了一遍
市面上常用的切分策略,我归成四类,用一份200页的产品手册实测了一遍。手册里既有FAQ、表格,也有代码示例。问题是50个真实用户提问。
固定长度切分最简单,按字符数或token数一刀切。递归切分按层级来,先按段落切,段落超长再按句子切,句子还长再按字符切。语义切分靠句子的向量相似度找断点,话题变了才切。结构化切分顺着文档本身的标题、表格、代码块走。
结果是这样:
| 切分策略 | 命中率 | 答案质量 | 主要问题 |
|---|---|---|---|
| 固定字符 500 | 61% | 偏低 | 句子被切断,语义残缺 |
| 固定字符 500 + 100 重叠 | 74% | 中 | 断裂缓解,仍会混话题 |
| 递归切分(段→句) | 82% | 较高 | 遇到长段落仍偏大 |
| 语义切分 | 88% | 高 | 计算成本高 |
| 结构化切分 + 语义 | 91% | 高 | 要求文档本身结构清晰 |
命中率是这次实测的数,换文档、换问题集会变,但趋势稳定:越尊重语义边界,检索越准。具体效果也取决于文档类型和embedding模型,不是绝对的。
差距很直观。固定字符切法最省事,也最粗糙,比结构化切法低了三十个点。省下来的那点开发时间,全在答不准里还回去了。
四、粒度和重叠怎么定
选完策略,还有两个参数要定,粒度多大、重叠多少。
粒度别按字符一刀切,尤其中文。中文一个字常常就是一个甚至几个token,按字符切,chunk大小忽大忽小,很不稳。按token切更靠谱,经验区间一段300到800 token。太短语义不够,太长混话题。不同embedding模型对token的切分方式不同,实际大小建议以token数为准,并在实测中验证。
重叠是防切断的保险。相邻chunk之间留一部分重复内容,正好被切在两块边界上的句子,两边都能捞到。实测里,重叠从0加到20%,召回提升很明显。再加下去收益就小了,成本反而涨。
我的经验是重叠给到10%到20%。手册那类边界多的文档给20%,规整的文档10%就够。
# 一个可用的切分参数起点
chunk_size: 500 # 按 token,不是字符
chunk_overlap: 100 # 约 20% 重叠
splitter: recursive # 递归:段落 -> 句子 -> 字符
separators: ["\n\n", "。", "!", "?", "\n", " "]
分隔符那行多提一句。中文文档的分隔符别只写英文句点,要把中文的句号、问号、感叹号都列上,不然句子判断全靠换行,切得乱七八糟。
五、切分之外,检索架构也得看一眼
前面讲的都是怎么切。但切完之后,chunk存在哪、怎么检索,同样影响最终效果。
传统RAG架构通常是“MySQL存原文+独立向量库存向量”,两套系统通过ETL同步。这个架构有个隐患:切分策略再好,如果检索时需要按业务线、按租户过滤,向量库的元数据过滤能力弱,只能应用层先查MySQL拿ID,再去向量库搜,性能会卡在ID列表的规模上。
那次信创项目里,我还试过另一种思路。金仓KES Vector把向量直接做成了数据库的原生类型,原文和向量待在同一个引擎里。检索时一条SQL就能完成向量相似度、标量过滤、全文检索的混合查询,要按业务线、按权限、按时效过滤,不用先查原文库拿一批ID,再去向量库挨个搜。我跑过这套,最直观的感受是少维护一套系统,两库之间那份同步延迟的账,也一并省了。
切分管的是原料质量,检索架构管的是原料用不用得上。这两件事在RAG上线前想清楚,比事后换模型省事得多。
六、别把表格和代码切坏
有一类错误特别隐蔽,就是把结构切坏了。
表格按字符数一切,某一行的前半截在上一块、后半截在下一块。检索回来一行残缺的表,模型读得云里雾里。表格要么整块保留,要么老老实实按行切。
代码块更别切。一段能跑的逻辑,被从中间切断,两个半块都失去了意义。代码块要当整体处理,宁可让它超长。
还有标题。有清晰结构的文档,顺着标题层级切效果最好。切的时候,把标题文本拼进每个chunk的开头,检索时能蹭到标题里的关键词,命中率会更好。
七、怎么验证切分好不好
调切分最忌讳凭感觉。改完一版,随机问几个问题看着还行,就上线了。等问题暴露,往往是上线之后。
稳妥的做法是搭一个小评测集。挑30到50个真实问题,每个问题标注“应该命中的原文片段”。每次调整切分,跑一遍这个评测集,看命中率有没有涨。
这个评测集不用多复杂,一个表格就够:
| 问题ID | 用户问题 | 应命中片段 | 命中的chunk ID | 是否命中 |
|---|---|---|---|---|
| Q001 | 退款几天到账 | “退款将在7个工作日内到账…” | chunk_042 | 是 |
它是你调切分的标尺。没有标尺,你改的是好是坏,全靠猜。命中率上不去的RAG,模型换得再勤也白搭。先把切分这关过了,再谈别的。
避坑清单
别用固定字符数硬切中文。中文一个字可能是好几个 token,按字符切,块大小忽大忽小,句子还容易断在中间。先换成按 token、按层级递归,效果立刻不一样。
别省掉重叠。零重叠的切法,跨边界的句子必然残缺。加到 10% 到 20%,是性价比最高的一档。多花的存储和检索成本,远小于答不准的代价。
排查答不准,顺序别搞反。先打日志看检索命中的片段,再回头看切分,最后才怀疑模型和向量库。跳过前两步直接换模型,钱花了,问题还在。
调切分要有评测集。没有一组"问题对片段"的标尺,你永远不知道这一版比上一版好还是坏。凭感觉调,等于闭眼开车。
我的判断
RAG 这套东西,模型那一层大家在卷,切分这一层却常被忽略。可恰恰是切分,决定了检索能捞回什么。捞回来的原料不对,后面的模型再贵也是白费。
我一直觉得,切分这活儿更像一个数据层的设计决策。chunk 怎么切,决定了它怎么被存、怎么被检索,跟建表时定字段、建索引是同一类思考。做数据库的人看 RAG,别只盯着向量库,切分这层和它后面的存储、检索架构,得放在一起看。
你做过 RAG 吗?有没有遇到过答非所问,最后发现是切分的问题?
我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋
- 点赞
- 收藏
- 关注作者
评论(0)