当前位置: 首页 > news >正文

文档分块策略:切多大、怎么切、为什么

🦞 一只用 AI Agent 搭副业产线的程序员


一个真实翻车现场。

我把公司技术文档库灌进 RAG,信心满满地问:「Redis 缓存的过期策略有几种?」

它回答了一大段。前半段讲了淘汰策略,后半段扯到了数据库索引。答非所问。

我回去查检索结果——原来文档被切成每段 2000 字的大块,一块里面同时包含了缓存策略和数据库索引。向量相似度匹配上了,但返回的是一锅粥。

RAG 的质量,80% 取决于你怎么切文档。这一篇我用同一份文档,跑 3 种切分策略,用数据告诉你哪种最好。


为什么需要分块

LLM 的上下文窗口有限。即使现在有 128K 的模型,塞一整本书进去也会碰到两个问题:

  1. 成本:每轮对话把全书发一次 API,Token 烧到飞起
  2. 精度:书里 99% 的内容跟当前问题无关,LLM 会分散注意力

所以需要把文档切成小片段,每次只检索跟问题相关的几个片段。

但切太小丢失上下文,切太大混入噪音。这个「度」就是分块策略的核心。


实验设计

测试文档:一份 5000 字的技术文档(《Redis 缓存使用规范》,包含缓存策略、序列化方案、过期机制、集群配置 4 个章节)

测试问题(10 个,覆盖 4 个章节):

章节问题示例
缓存策略「Redis 淘汰策略有哪些?」
序列化「JSON 和 Protobuf 序列化怎么选?」
过期机制「惰性删除和定期删除的区别?」
集群配置「哨兵模式最少几个节点?」

策略一:固定大小切分

最简单粗暴的方案。每 N 个字切一刀,不管句子完不完整。

funcchunkByFixedSize(textstring,sizeint)[]string{runes:=[]rune(text)// 中文字符按 rune 处理varchunks[]stringforstart:=0;start<len(runes);start+=size{end:=start+sizeifend>len(runes){end=len(runes)}chunks=append(chunks,string(runes[start:end]))}returnchunks}

设 size=500 字:

测试结果: - 平均命中率:67%(10 个问题中,正确的文档片段在 Top-3 检索结果中的次数) - 最大问题:切断了语义单元。比如「惰性删除的原理是……」和后面的代码示例被切到了两块 - 一个片段同时包含缓存策略和序列化的内容——噪音多

结论:能跑,但粗糙。适合纯流水账文档,不适合结构化技术文档。


策略二:按段落(语义切分)

按自然段落切,遇到##标题就起新块。保证每个块是完整的语义单元。

funcchunkByParagraph(textstring,minSize,maxSizeint)[]string{lines:=strings.Split(text,"\n")varchunks[]stringvarcurrent strings.Builderfor_,line:=rangelines{// 遇到 Markdown 标题就开始新块ifstrings.HasPrefix(line,"## ")&&current.Len()>minSize{chunks=append(chunks,current.String())current.Reset()}current.WriteString(line)current.WriteString("\n")// 当前块超过最大值也截断ifcurrent.Len()>=maxSize{chunks=append(chunks,current.String())current.Reset()}}ifcurrent.Len()>0{chunks=append(chunks,current.String())}returnchunks}
测试结果: - 平均命中率:82% - 每个 chunk 有明确的主题(因为按标题切了) - 问题:「集群配置」章节的一个段落有 800 字,超过了 maxSize,被硬截断了

结论:命中率明显提升。结构好的文档效果很好,长段落需要额外处理。


策略三:递归切分(带重叠窗口)

最好的策略。先按大分隔符(##),块太长再按小分隔符(\n\n),还不够再按句号切。并且块与块之间有重叠——保证边界处的上下文不丢失。

funcchunkRecursive(textstring,chunkSize,overlapint,)[]string{separators:=[]string{"\n## ","\n### ","\n\n","。",". "," "}varchunks[]stringchunks=splitRecursive(text,separators,0,chunkSize)returnchunks}funcsplitRecursive(textstring,seps[]string,depthint,sizeint,)[]string{iflen([]rune(text))<=size{return[]string{text}}sep:=seps[depth]parts:=strings.Split(text,sep)varchunks[]stringfor_,part:=rangeparts{iflen([]rune(part))<=size{chunks=append(chunks,part)}elseifdepth+1<len(seps){// 当前分隔符不行,降级到更小的分隔符chunks=append(chunks,splitRecursive(part,seps,depth+1,size)...)}else{// 所有分隔符都不行,硬截断runes:=[]rune(part)fori:=0;i<len(runes);i+=size{end:=i+sizeifend>len(runes){end=len(runes)}chunks=append(chunks,string(runes[i:end]))}}}returnchunks}
测试结果: - 平均命中率:94% - 每个 chunk 大小均匀(300-500 字),语义完整 - 重叠窗口保证了边界信息不丢失

三种策略实测对比

策略平均命中率平均 Chunk 大小噪音率索引时间
固定大小 (500字)67%502 字33%2.1s
按段落切分82%380 字18%1.8s
递归 + 重叠94%410 字6%2.4s

噪音率= 检索到的 Top-3 文档中,不包含答案的 chunk 比例。越低越好。

固定大小噪音率 33%,意味着 3 个检索结果里约有 1 个是没用的——这部分信息是在浪费 LLM 的上下文窗口。


实战建议

根据我的踩坑经验,不同文档类型用不同策略:

文档类型推荐策略Chunk 大小重叠
Markdown 技术文档递归切分300-50050-100
代码文件按函数/类切不限0
纯文本(合同、制度)按段落200-40030-50
表格数据行级单行0
对话记录按发言轮次不限1轮

一个关键细节:重叠窗口不是为了「重复存」,而是保证「搜索时不会刚好卡在边界」。你搜「惰性删除」时,解释它的句子可能在上一块的末尾,下一块的开头。有重叠,两块的向量都接近你的查询。


代码:完整的递归切分器

我把上面三种策略打包成了一个chunker包:

// chunker/chunker.gopackagechunkerimport"strings"typeStrategyintconst(FixedSize Strategy=iotaByParagraph Recursive)typeChunkerstruct{Strategy Strategy ChunkSizeint// 目标大小(字符数)Overlapint// 重叠窗口大小}funcNewChunker(s Strategy,size,overlapint)*Chunker{return&Chunker{Strategy:s,ChunkSize:size,Overlap:overlap}}func(c*Chunker)Chunk(textstring)[]string{switchc.Strategy{caseFixedSize:returnchunkByFixedSize(text,c.ChunkSize)caseByParagraph:returnchunkByParagraph(text,c.ChunkSize/2,c.ChunkSize)caseRecursive:returnchunkRecursive(text,c.ChunkSize,c.Overlap)default:return[]string{text}}}

使用:

chunker:=chunker.NewChunker(chunker.Recursive,400,50)chunks:=chunker.Chunk(markdownContent)// chunks: ["## 缓存策略\nRedis 提供...", "## 序列化\n当我们需要...", ...]

本篇核心收获

分块不是拍脑袋定一个数字。同一个文档用不同策略切,检索命中率可以从 67% 到 94%——差了 27 个百分点。这 27% 就是你 RAG 系统回答质量的天花板。

下一篇我们解决「切完了存哪」的问题——向量数据库选型。Milvus、pgvector、Chroma、Qdrant 四种方案,从部署难度、查询性能到价格,一一实测对比。

关注我,别错过。


🦞 一只用 AI Agent 搭副业产线的程序员

全平台同名:虾哥不加班
需要定制 AI 工具?来聊聊 → lob_ai

源码:GitHub - lobster-bujiaban/rag-from-scratch

http://www.jsqmd.com/news/956862/

相关文章:

  • 2026深圳收的顶奢品级爱马仕名包回收,龙头商家上门免费鉴定 - 奢侈品回收测评
  • 2026成都品牌首饰回收门店排行榜:五大领跑者揭晓 - 开心测评
  • 5分钟彻底告别Windows卡顿:Winhance终极优化指南
  • 深入STM32H7的FDCAN共享RAM:从CubeMX配置到HAL库源码的Offset计算原理
  • Arduino+EC20做物联网项目,我踩过的那些AT指令和透传的坑(附完整避坑代码)
  • MPLAB Harmony框架:嵌入式开发的一站式解决方案与实战解析
  • 2026上海黄金回收实力榜单|行业标杆连锁品牌收的顶荣登榜首 - 奢侈品回收评测
  • 搭建一个展示宣传推广类型的小程序怎么做?从内容展示到咨询承接这样搭更顺 - 维双云小凡
  • STM32H743双FDCAN实战:CubeMX里Message RAM Offset到底怎么算?附代码公式
  • 2026 武汉高端洗衣店实测榜单|金象王洗衣店领衔,13道精洗拒转包 - 科普万物
  • 从零构建固态特斯拉线圈:原理、设计与调试全指南
  • Neper多晶体建模与有限元网格划分完整教程
  • 2026年问题肌品牌加盟靠谱推荐 创业优选指南 - 谁都没有我好看
  • 深圳好玩、项目内容多全的潮玩运动馆 - 中媒介
  • 青岛香奈儿包包回收7家测评:禹竞名奢汇,价比三家最高 - 奢侈品交易观察员
  • GBase 8a数据库分布键选型提示
  • 2026 年猫咪驱虫药哪家性价比高:最新排名精选必读攻略 - 思溯深度专栏
  • 告别手动试参!用STATA循环命令批量跑ARIMA模型的心得与脚本分享
  • 从人才流失到组织升级,这本人才管理书籍值得深读
  • 采购管理:从制度设计到激励相容,构建高效供应链体系
  • 基于Arduino与Processing的超声波雷达系统设计与实现
  • 2026年问题肌品牌加盟靠谱推荐 轻资产创业优选 - 谁都没有我好看
  • 深圳企业活动场地哪家好? - 中媒介
  • 血清热销排行榜出炉,多款稳定性出众品牌成功入榜(人/驴/兔/大小鼠/鸡/新生牛/胎牛) - 品牌推荐大师1
  • 量子赛道爆发:全球最大独角兽上市,多公司排队 IPO,产业化曙光初现!
  • 医学影像三维重建分析系统技术方案
  • 基于Circuit Playground的可穿戴弹射器:从传感器到执行器的嵌入式系统实践
  • 避开STM32H7的FDCAN内存重叠坑:一份给CubeMX用户的配置检查清单
  • 2026重庆钻石回收避坑必读,虚报净度颜色再压价要小心 - 奢侈品交易观察员
  • 纯硬件太阳能自动夜灯:无LDR、无编程的晶体管控制方案