
“RAG(六) 文本分割器的使用”1. 文本分割器在RAG链路中的位置与选型逻辑上个月帮一个做企业知识库问答的团队调RAG系统对方被一个诡异的问题卡了整整一周文档能导入、向量化也没报错但用户随便问一个稍微复杂点的问题返回的上下文就是文不对题。我过去看了一眼他们落库的chunk——好家伙一个400多字的自然段被硬生生切成7块每块不到60字信息碎得捡都捡不起来。换谁检索都白搭。这就是文本分割器没选对、参数没调好。很多刚上手RAG的人喜欢把精力花在选向量模型、调prompt上但我要说的是上下文切分是整个RAG地基中的地基。检索粒度不对后面embedding再好、大模型再聪明都是从垃圾堆里找黄金。文本分割器干的事情一句话就能说清把长文档切成适合向量化的短文本块。但这个“切”的动作里藏着大学问——按什么规则切、切多大、要不要重叠、重叠多少、不同文档类型怎么差异化处理每一步都能直接影响检索命中率和答案质量。当前主流的分割思路也就三类。第一类是固定长度硬切说白了就是数着字符或token一刀切。简单粗暴实现成本几乎为零任何框架里都有现成类可以直接调用。但它不关心语义边界很可能把一个完整的论点拦腰截断导致每块文本语义都不完整。第二类是递归字符分割。这类分割器维护一个分隔符优先级列表比如先尝试按段落分隔符“\n\n”切切完发现块还太大再降到“\n”切再不行就用句号、感叹号这类句子结束符切。它比硬切更懂“尊重文本结构”是我日常最常用的方案。第三类是语义分割。它会先对文本做embedding计算相邻句子的相似度然后把相似度突变的位置当作语义边界。效果确实好尤其适合叙事性强、逻辑链长的文章但计算成本高速度慢不适合实时性要求高的场景。你可能会问直接用“按自然段落切”不就行了吗不行。自然段落长短差异极大长的能到近千字向量化后信息密度过高检索的召回精度会下降短的又可能只有一句话语义信息稀疏容易被噪声淹没。所以一切都要服务于“检索粒度是否合适”这一核心目标。在后面的内容里我会把我在实际项目中用过的几种分割器逐一拆开讲包括LangChain里的几个常用类怎么用、中文场景下的参数踩坑记录、不同文档类型的处理策略以及一套我自己总结的“分割器选型清单”。这篇相当于RAG系列里关于文本块的“基建篇”建议配合向量化和检索部分一起看。2. 几种主流文本分割器的拆解与适用场景进入正题之前先交代一下我的测试环境用的是某开源向量数据库配合中等规模的通用embedding模型测试语料是几份混合内容的中文文档包括产品手册、技术方案、会议纪要和一段代码说明。下面每个分割器我都会给出实际测试时观察到的现象和适用判断而不是照着文档抄一遍API说明。2.1 固定长度分割器最快但最钝的刀固定长度分割器是最基础的一类核心逻辑就两个参数块大小chunk size和重叠长度chunk overlap。以某个常用框架的实现为例它会严格按字符数或token数切分然后用重叠部分把前一块的尾巴带到下一块的开头。举个例子如果用按字符切分的固定长度分割器chunk_size200, chunk_overlap20一段300字的文本就会被切成两块第一块是第0到200个字符第二块是从第180到第300个字符索引按压缩后的文本计算两块之间有20个字符的交叠。这个东西快是真快但问题也很明显无视语义边界。可能在句号后面切也可能在词语中间切。中文不像英文有空格天然分隔token切在“人工智能”和“人工智”中间都是可能的后者是个废块检索到它等于检索到一团乱码。长度对语义覆盖是伪等价。200个字符如果恰好是半截话跟200个字符是一个完整例子语义完整性天差地别。我给它的定位是量级极大、结构极差、只需要“有东西能查出来”的冷启动场景。比如要对一万个纯文本文件做批量预处理跑通pipeline先用它保证流程不报错后续再迭代替换成更聪明的方案。注意固定长度分割器并非一无是处。如果文档本身就是高度结构化的条目型文本比如每条日志、每条流水、每行JSON字符切分反而稳定可控因为条目之间本来就没有强上下文依赖。2.2 递归字符分割器默认首选理解优先级才能调好参递归字符分割器是LangChain里我默认推荐的首选方案。它的核心设计是维护一个有序分隔符列表按优先级从高到低尝试切分如果切出来的块还是太长就降级到低优先级分隔符再切。典型的分隔符优先级是这样的[\n\n, \n, 。, , , , , , ]这串列表的工作逻辑能这样理解先按段落之间的空行切如果整个段落还是比chunk_size大再按段内的换行切还大按句子结束符切。最后兜底是空格、单个字符。用这个方案只要参数不是太离谱切出来的块基本不会破句。实际的调用方式也很直观以LangChain为例一般是设置chunk_size和chunk_overlap两个核心参数然后调用split方法。这里有个很多人没意识到的坑分隔符列表的优先级本身就是一种文档结构假设。如果文档全是自然语言上面的列表没问题但如果是代码、表格、Markdown标题这套默认列表就不合适了。后面我会专门讲到按文档类型换分隔符的问题。再有一点实践心得chunk_overlap的作用是补一块“跨块上下文”避免某个关键信息恰好被切在边界上导致两块都缺失。它不是越大越好因为重叠会带来信息冗余增大token费用。我一般控制在chunk_size的10%到15%后面测试也验证了这个区间性价比最高。2.3 Token感知分割器跟语言模型对表用过开源embedding模型或者大模型API的人应该知道它们的输入限制计量单位往往不是字符数而是token数。一个中文汉字在常见分词器下大约对应0.6到1个token英文一个单词差不多1到1.3个token。按字符切分出来的块换算成token可能有巨大波动有时一块超长导致单条向量化失败有时长度不足又浪费token预算。Token感知分割器解决的正是这个问题。它以分词器tokenizer为标准来计量文本长度保证切出来的每块在token维度上是相对均匀的。在某个知名RAG框架里甚至可以直接把使用的embedding模型对应的tokenizer传给分割器让块的大小严格贴合后续向量化窗口。我实测过一份中文产品文档同一份文本按chunk_size200字符切平均每块大约110到130 token按chunk_size200token切每块字符数量会波动在250到400之间。后者在向量化时很少报超长错误而且检索返回的内容在语义密度上更均衡。但对中文用户来说引入tokenizer也有代价字符串长度最直观、最好控制token计数则需要多一次编码计算文档量大的时候预处理耗时能差出一倍。所以我要给个折中建议——如果文档以英文为主或者嵌入模型是主流商用模型优先考虑token感知切分如果文档是纯中文且结构简单按字符切再留足overlap反而省事得多。2.4 语义分割器技术方向正确但别神化它语义分割器是目前讨论热度最高的方向。它的思路是对文本滑窗计算相邻句子的embedding余弦相似度相似度低于某个阈值就判定为语义转折点在转折处切分。这种方案对“长文多主题”的文档效果确实惊艳。我拿一段跨越“产品介绍-部署说明-常见报错”三部分内容的文本做过对比递归字符分割器会把三个主题混在同一块里语义分割器则能在主题切换处准确下刀。检索时命中块的“主题纯度”明显更高回答生成质量也更稳。但它的局限也非常现实计算开销大。要先对完整文本逐句embedding再算相似度长文档的预处理耗时会翻数倍。阈值不好调。相似度阈值设太高会把连贯文本切得稀碎设太低不同主题又会黏在一起。这个阈值跟文本领域强相关换一批语料就得重新标定。对短文本无意义。文本总共就几百字语义分割和递归分割结果差不多还白白多了一堆向量化计算。我给它的定位是离线知识库重建、对检索精度要求极高、且有充足算力余量时的进阶选项。在线实时写入的场景暂时不建议碰它。实战补充语义分割可以不用全程替换递归分割而是“两级配合”——先用递归分割器粗切出大段再用语义分割器在每个大段内精确分块。这个组合既能控制计算量又能拿到语义边界是我目前见过最务实的折中姿势。3. 参数调优实战chunk_size与chunk_overlap的搭配密码光知道用哪个分割器还不够参数选错了工具越好越浪费。我把核心参数单独拿出来讲因为这块经验是最直接能帮到你调好效果的。3.1 测试方法与评估口径先建立一个可量化评估的习惯。我的测试流程是拿几份难度有代表性的文档分别用不同参数组合生成chunk然后对一组固定问题做检索评测记录三个口径——召回率检索出的chunk里包含答案关键词的比例、上下文纯净度命中chunk中无关内容占比的倒数和端到端回答评分把命中chunk喂给生成模型后人工对回答打分。这个测试不严谨但方向正确它逼着你从真实检索效果出发而不是只看“chunk长什么样”。3.2 chunk_size的选择逻辑我的经验是把chunk_size当成一个受语义单元影响、而非纯数字的参数来选。怎么理解你可以先统计一下这份语料里自然段落的中位数长度把chunk_size往这个中位数上靠。如果中位数是180字chunk_size设200通常会比设500更合适因为检索器看到的块长度跟作者原本的语义单元一致检索时上下文最“自然”。为什么不能一味求大块太大有两个后果一是向量语义被平均化一个块里塞了太多不同观点检索时向量被“拉偏”命中了块却找不到具体答案二是喂给生成模型时无关上下文太多模型容易被噪声带跑甚至出现幻觉。为什么也不能一味求小块太小的问题更隐蔽——语义缺失导致的召回失败。比如文档里有这么一句“本方案的最终目标是实现数据层面的自动对齐”如果前面“数据层面”的定义被切到上一块、“自动对齐”的展开在下一块检索“对齐机制”时这两块的向量表示都不完整命中率会直线下降。针对中文通用文档我的默认起点是chunk_size300~500字符chunk_overlap40~80字符。基于这个起点如果文档本身段落较长、逻辑链较长往高处调如果文档是问答格式、条例条目式往低处压到200字符左右。注意这里是字符数不是token数如果你用token感知切分按比例换算即可。3.3 chunk_overlap的实战公式overlap的直觉理解特别像看外国电影时字幕的“上屏时间重叠”新旧字幕重叠的那几秒是为了让你在旧字幕消失前完成对新字幕的扫读。同理overlap存在的意义是让相邻chunk共享边界信息减少关键内容横跨两块带来的检索损耗。直接给一个可复制的公式overlap min(chunk_size * 0.15, 200字符)再放宽一点可以到chunk_size的20%但我建议不要超过25%。超过这个比例向量库里的冗余会显著增加——同一段信息出现在多个块里检索去重困难query可能同时命中高度雷同的多个块不仅浪费token还会让生成模型认为“这段信息很重要”而过度关注。有个反直觉的现象值得一提在部分测试语料里chunk_size500, overlap50的组合反而比chunk_size200, overlap80检索得分更高。原因在于该语料以长段技术说明为主200字符的块天然切断了论点的前因后果而overlap救不回来被切断的逻辑关系。这说明overlap是“补救”而非“根治”参数根子上的块大小选错overlap调得再花也有限度。3.4 不同场景的参数速查表我根据几个常见场景整理了一张参数起始建议表可以直接作为第一版配置用后续再根据实测微调场景推荐分割器chunk_sizechunk_overlap说明通用文章/技术博客递归字符分割器400字符60字符结构明确语义连贯均衡选择问答/条文/FAQ库固定长度或递归200字符30字符单条完整适合高精度短命中长汇报/论文摘要库语义分割器 递归600字符100字符主题切换明显需保留上下文代码仓库/接口文档按注释和函数块分以函数/类为单位头注释保留必须结合代码结构而非纯长度多模态PDF图文按版块切OCR后合并500字符80字符需结合版面分析预处理关键提醒任何一副参数都只是起点换一批语料、换一个embedding模型最优参数可能整体漂移。保存一份参数基线每次改动只动一个变量别“三管齐下”地调否则你永远不知道是哪一项起了作用。4. 不同文档形态的分割器实战策略上面讲的都是“通用文档”的混沌状态。实际项目里的文档形态千差万别同为“文本”代码、表格、Markdown、PDF扫描版的分割策略根本不是一套逻辑。这里我按文档类型给出我实际跑过的处理方案。4.1 代码与接口文档按结构切割拒绝纯长度切分做AI辅助代码问答的人应该深有体会拿纯长度切分代码几乎等于自杀。一个函数可能刚好被拦腰切断函数签名在上块、实现体在下块检索“某函数的参数含义”时签名块太短没用实现块缺了签名也没法完整回答。我处理代码类文档的标准做法是按语义结构分块为主、长度控制为辅。先按代码的AST或缩进层级找到函数、类的边界以每个函数/类为一个基础语义块如果某个函数过长比如超过800行再按函数内部的逻辑段落、注释块切分保留文件开头的import区域和每个函数前面的docstring/注释作为独立小块供“这个模块干什么用”类问题命中。有的框架内置了按代码分行的splitter可以直接按“函数起点 连续行 函数终点”提取。如果你用的是通用递归分割器至少也要把分隔符列表换成代码相关的token比如把\n\n换成\ndef、\nclass这类。代码文档的overlap策略也跟自然语言不同。自然语言靠句意衔接代码靠作用域和注释衔接。我一般把下一个函数开头的一两行docstring和上一块末尾压在一起这样跨块检索时至少能带上函数签名信息。4.2 Markdown与结构化文本善用标题层级的天然边界Markdown文档是结构红利最明显的类型。标题分级#、##、###本身就是作者规划好的语义图谱照着标题层级切分比任何启发式规则都可靠。我的做法是先按二级标题##作为大块边界检查每个大块是否超出chunk_size上限超出的再按三级标题###或列表边界切分最后才落到句子切分。这样切出来的chunk有个额外好处每个块天然携带标题上下文。假设把“## 安装部署”下的内容切成了多个块每一块的开头都可以拼上父级标题前缀检索时query“安装报错怎么处理”更容易命中且模型能感知到这是部署环节的问题。实际操作中还有点细节容易漏Markdown里经常有表格、图片引用、脚注。图片引用对纯文本embedding是无效信息可以过滤表格按行切分后语义不完整建议把整个表作为一块除非表格极大超过chunk_size上限表头和一组行怎么分组也需要人工程序判断没有万能规则。4.3 扫描版PDF与图文混排分割前先解决OCR和版面还原扫描版PDF是个绕不开的坑。很多团队做到“PDF文档导入RAG”时直接用解析库把每页文本抽出来再切分结果整页都变成稠密的一坨标题、正文、页眉页脚混在一起切出来的块惨不忍睹。我处理这类文档的思路是分割器永远不是第一顺位先解决版面还原。用OCR工具把扫描页转成带坐标的文本根据坐标和字体大小还原版面结构区分标题、正文、页脚按版面区块标题所属正文区域重组文本重组后的文本再走“Markdown式标题切分”流程。这样虽然处理链路长了但分块质量能产生质变。我拿一份带复杂表格的扫描版产品手册做过对比直接文本抽取后向量化检索命中率不到50%版面还原后按区域重组再切分同一批问题的命中率能到80%以上。多出来的精力全花在“让分割器面对真结构”上了。4.4 会议纪要、聊天记录类流水文本时间戳与对话者维度流水型文本会议纪要、工单记录、聊天导出有自身的结构线索——时间、发言人、话题转折。直接切字符经常会在一小块里塞两个不相干话题。我实际操作的最小可行方案是先按发言人或时间戳把流水切成“会话块”再对每个会话块内部做句子级切分。如果某段对话本身很短就直接整段作为一个小块。举一个真实场景某团队把售后工单记录接进知识库每次客户报障客服回复是一整段对话。如果按字符切一次报障的记录会被拆成两三块检索“客户换货流程”时返回的往往是描述问题的半截记录而不是处理方案。按会话切分后每一块天然包含“问题描述处理动作结论”召回和回答完整度都上来了。关于长度与结构的再强调对任何文档类型分割的优先级永远先是“结构边界”其次才是“长度控制”。结构边界是作者留给你的语义地标长度控制只是为了让地标块不超过向量模型的载荷。5. 调参与评测过程中的经典问题与排障速查最后这块我把日常项目里踩过、也帮别人排查过的典型问题做个速查表。每一条背后都有不止一次的实际调试经历。现象最常见原因排查与解法检索返回的chunk看似相关但回答仍靠猜chunk跨语义边界关键论据被切开调大chunk_size或换递归分割器检查命中块是否包含完整逻辑链检索几乎什么都查不到召回为零chunk过碎query的关键词分布在不同块减小分割粒度损失增大chunk_size和overlap同一问题返回多块高度雷同的内容overlap比例过高或分块粒度失衡降低overlap到15%以下检查是否因为固定长度切分导致整体性内容重复向量化过程中频繁报长度超限按字符切分时token数波动超限换token感知分割器按模型窗口比例设定chunk_size中文文本被切成残词语义崩坏分隔符列表不含中文标点在分隔符优先级中加入“。”、“”、“”等中文结束符结构化文档Markdown/代码/PDF处理效果差未按文档类型选择分割策略先做结构还原再按标题/函数/表格边界切分语义分割速度慢到不可用对每一条入库文本都跑完整embedding对短文本跳过语义分割对长文档先粗切再精切两级处理换了embedding模型后效果变差分割参数与旧的模型维度/窗口不匹配重新统计平均token数与窗口占比再调整chunk_size5.1 一例调试实录“能查到但答不对”的完整排查年初帮某团队定位过一个典型问题知识库基于某网络设备手册构建用户问“设备指示灯的含义”系统返回了文档内容却给出模棱两可的回答。我把流程拆开看先看检索结果——命中的chunk确实提到了“指示灯”但不完整只有一段类似“指示灯状态说明见下表”的引导语。真正的表格内容被切到了下一个块。问题根源在于chunk_size150设得太小作者在文档里用一段引导语一个表格讲完整主题150字符把引导语和表格拆散了。把chunk_size调到400分隔符列表里加入表格识别逻辑让表格和引导语留在同一块里。重新测试检索命中块直接包含“绿灯常亮表示设备运行正常”的结果文本端到端回答评分明显上升。这个案例价值在于它提醒你在调参时先盯住一个失败case把“检索→生成”全链路看一遍找到真正的断点而不是盲目在参数网格里试。5.2 关于“自动调参”的实话市面上已经有号称“自动选择最优分割参数”的工具和库。我用过一些结论是它们能帮你从“纯拍脑袋”进化到“有根据的猜测”但离“自动优化”还差得远。原因在于分割参数的质量严重依赖下游评测指标而下游评测指标又跟用户真实问题分布强相关——没有一份标准的评测集任何自动调参都只是过拟合一个代理目标。真正靠谱的做法是积累一批针对你自己业务的query-答案对做成回归评测集每次改分割策略都跑一遍防止“修好一个case弄坏三个case”。5.3 一些写在最后的经验文本分割器这类模块太不起眼了很多人宁可花一周调prompt也不愿花半天看chunk长什么样。但我在多个项目里反复验证过一件事一个文档结构清晰、分割合理、边界信息完整的chunk库往往比换一个更强的embedding或加一段更复杂的prompt带来更明显的效果提升。有条件的团队我建议建立这样一个pipeline导入文档→格式检测→结构还原→分割参数按文档类型配置→小批量抽样质量检查→全量切分→向量化入库→用回归问题集做评测。每一步都不复杂但串起来你得到的就不只是一个“能跑的RAG系统”而是一个“能解释为什么好、也知道怎么改更好”的知识库底座。关于文本分割的实操层我能分享的经验差不多就这些了。下次再遇到RAG效果不好先别急着怀疑大模型不够聪明回去看看落库的那些chunk多半会有意外发现。