两年前我第一次用大模型做文档问答时,干了一件特别蠢的事:把一个三十多页的PDF完整丢给模型,让它“总结主要内容”。结果自然不用说——在长文本的处理上直接翻车,输出前言不搭后语。后来我才意识到,市面上大多数教程都在教你怎么写Prompt、怎么调参数,却很少有人认真讲一个最基础但也最关键的操作:文本分块(Chunking)。
文本分块是连接“原始资料”和“大模型能力”之间的桥梁。在检索增强生成(RAG)、知识库问答、长文档分析这些场景里,文本分块的质量几乎决定了整个系统的上限。如果你把文档比作一本书,分块就是给书做目录和索引——目录做得好,想找什么内容一翻就到;目录做得烂,整本书就等于白买了。这篇内容就是写给刚接触大模型应用开发的同学看的,我会把文本分块的原理、常见方法、参数调优和踩坑经验一次性讲透,全程手把手,直接照着做就行。
1. 为什么“如何把文本喂给模型”是你入行的第一道坎
先别急着看代码,我们从最底层的逻辑开始捋。很多人以为文本分块是个简单的“切字符串”问题,实际上它是整个大模型应用里最容易被低估的工程环节。
1.1 大模型的上下文限制决定了你“一次能吃多少”
大模型本身是有注意力窗口限制的。通俗点说,模型一次性能“看到”的文字量是固定的,不同模型各有不同,少则几千个Token,多则十几万Token。Token不是“字”,它约等于单词或子词片段,英文一个词大概1.3个Token,中文一个字大概1.5到2个Token。
这个限制意味着一条铁律:你不可能把整本《三体》一次塞给模型让它总结全貌,就像你不能要求一个人一口气读完整套百科全书然后复述所有细节一样。上下文窗口越大,模型内部计算量和延迟也越大,成本更是直线上升。所以现实的做法是:把长文本拆成合适大小的小块,需要时只把最相关的那几块喂给模型。
1.2 文本分块在RAG架构里的位置:它决定检索的上限
RAG的流程可以简化成“索引—检索—生成”三步。索引阶段要把文档切块、向量化、存入向量数据库;检索阶段要找出和用户问题最相似的若干块;生成阶段才把块交给大模型回答。你会发现,如果分块做得不好,后面的一切都是在垃圾上建高楼——块太小,语义被切断,检索召回一堆残片;块太大,冗余信息过多,检索精度下降,又费Token。
我用一个例子说明这个问题。假设有一段话:“某公司2023年营收增长20%,主要得益于海外市场扩张和产品线拓宽。”如果你按固定字数300字去切,恰好把“海外市场扩张”这个关键信息甩到了上一个块的结尾,而块的下半截全是其他内容,那用户问“某公司营收增长的原因是什么”时,检索系统可能根本匹配不到这一段。分块不是一个简单机械的“剪切动作”,它是一个需要兼顾语义边界、结构边界和检索命中率的系统工程。
1.3 小白最容易犯的三种分块错误
我见过太多刚入门的同学在分块这一步栽跟头,典型错误可以归结为三类:
第一,不分块直接丢进去。这是最极端也最普遍的问题,结果就是超出上下文限制、模型输出乱码或者干脆报错。
第二,盲目追求“块越小越好”。有些教程说块小就检索准,实际上块太小会导致上下文信息不完整,模型回答时缺乏必要的背景,反而答非所问。
第三,所有文档用一套参数一刀切。代码文档、法律合同、聊天记录、Markdown笔记,它们的结构特性完全不同,用同一套分块逻辑处理就是在强行削足适履。
在进入具体方法之前,我心里先立一个框架:分块这件事,从来没有“唯一最优解”,只有“在特定场景下最合适的方案”。所以下面这几种方法,与其说是上下级关系,不如说是一套由浅入深的工具箱,你按场景挑着用就行。
2. 触发我啃下这块骨头的一次真实翻车经历
在没有完全理解分块之前,我吃了不少苦头。这里坦诚分享一下我是怎么踩坑、怎么一点点把问题定位到“分块”这个环节上来的,希望看得人少走弯路。
2.1 现象:问答系统总是答非所问,且时好时坏
当时我在某个公司内部搭建一个合同文档问答系统。第一次测试的时候,我用的是最原始的思路——把每份合同按段落拆开,丢给向量模型转成向量,用户提问的时候做余弦相似度检索,取出最相似的三个块让大模型回答。
表面上看流程完整,实际表现完全不靠谱。用户问“违约金比例是多少”,系统经常返回一段跟“合同期限”有关的内容。更让人崩溃的是,同一个问题上午问和下午问,答案还不一样——后来发现是向量数据库里存的内容不一样了,因为白天我又灌了一批新文档进去。
那段时间我每天都在调Prompt参数、换向量模型,忙活了一周毫无改善。后来偶然一次把检索回来的原文块打印出来看,才发现了问题所在:很多块的内容压根就是残破的,块和块之间的话题早就被切断成碎片了。
2.2 定位:问题根本不在Prompt,而在“切坏了”
我打印了几个具体案例。有一份合同里写:“本合同自双方签字盖章之日起生效,有效期三年。期满后如双方均未提出书面异议,则自动续期一年,续期次数不限。任何一方拟提前终止本合同的,应提前三十日书面通知对方。”
这段内容其实是一个完整的“有效期与终止”语义单元,但因为我当时用的是按1000字符硬切的办法,这段恰好被劈成两半。前半块只有“本合同自双方签字盖章之日起生效,有效期三年”,后半块是“期满后如双方均未提出书面异议……”,两块内容单独拿出来都是残血信息。用户如果问“合同会自动续期吗”,按理说“续期”这个词在两个块里都出现,但它语义上的主语和条件却被拆散了,检索模型很难把碎片拼回去。
那一刻我才彻底明白一个朴素的道理:文本块不是“纸片”,它是信息的容器。容器的边界划得对不对,比容器本身有多大更重要。从那之后,我把分块从“顺手做一下”的杂活,提升到了整个系统设计的核心环节来研究。
2.3 复盘:为什么很多开源项目看起来能跑,换个数据就崩
顺带说一句,很多人喜欢直接抄开源项目里的分块代码,以为别人验证过就没问题。但实际上,开源示例往往针对特定数据集做过多轮调参,比如代码专用、英文新闻专用或者微信聊天记录专用,你手里的中文合同、行业研究报告、技术手册未必适用。从那次翻车我总结出一条原则:关于分块,永远以你自己数据的实际表现为准,别人报告里的“最佳参数”只能当起点,不能当结论。因为你不知道对方的场景是否相似,更不知道对方有没有像我现在这样去打印每一块内容、逐条检查切分边界是否破坏了语义。
3. 上手实操:先记住文本结构,再来谈分块方法
分块方法的本质,是“借力文本自身的结构”。文本不是杂乱无章的字符流,它天然携带很多边界信号——章节标题、段落换行、句号问号感叹号、Markdown的井号层级——这些信号就是你的分块依据。
3.1 分块的底层单位:从字符到Token,再到语义单元
我们需要澄清几个概念:字符(Character)、Token、句、段、语义单元(Semantic Unit)。
字符是最底层,肉眼可见但最没信息含量;Token是模型真正读取的单位,分块长度通常以Token计算更科学;句是一句话,以句号、问号、感叹号结束;段是多个句子的组合,通常由换行符区分;语义单元则是由一个完整主题统领的内容块,可能是多个段落,也可能只有一句。
日常开发中,很多人默认用“字符数”或“字数”作为分块单位,这在快速验证时够用,但要追求系统稳定性,我更推荐以Token为单位设计分块逻辑。因为大模型的上下文窗口是按Token算的,向量模型对输入长度也有限制,以Token为单位能让你更精确地控制计算量。
这里有个小换算经验:英文文本大致按1词≈1.3Token估算,中文文本大致按1字≈1.5到2Token估算。所以如果你打算每个块控制大约600Token,中文文本就大致对应400字左右,英文大致对应450词左右。不同分词器差异比较大,建议用模型自带的分词器实际数一遍。
3.2 理解几种主流的分块边界信号,以及它们的优先级
文本结构里的边界信号,大致可以分四个层级,按可靠程度从高到低排:
第一层级是文档结构。很多文档自带明确的章节标题和小标题,比如“第一章”“2.3节”“结论”等。这些结构性标识是天然的语义边界,优先级最高——优先按它们分块,可以最大程度保住语义完整。
第二层级是段落边界。一个段落通常围绕一个中心意思展开,段落末尾或开始往往是话题切换点。如果文档没有章节信息,按段落分是次优选择。
第三层级是句子边界。句号、问号、感叹号、分号,是明确的语言边界信号。与段落相比,句子更细粒度,适合需要精细检索的场景。
第四层级是语义边界。这是最难捕捉的,往往需要借助向量模型或分类模型来识别文本在语义上的“跳跃点”。比如某一段在讲产品功能,下一段突然讲团队介绍,你能感觉到这不是写法问题,是话题切换了。
3.3 为什么“按字数硬切”是大多数问题系统的万恶之源
偏偏市面上最流行的分块实现,就是按字符数硬切。原因也很现实:实现简单,一行代码解决,跑得快。但这种做法根本不顾文本语义,纯粹是把长文本当成火腿肠切片。
举个例子,一个长段落里按1500字硬切,很可能第1499字是“虽然”,第1500字是“但是”的左半部分,下一块开头就是“但是,我们不能忽视……”——单独看第二块,“但是”后面的转折内容没头没尾;单独看第一块,“虽然”后面没有下文。检索系统拿这样的残块去做匹配,结果能好才怪。
有人可能会问,那我加个Overlap(重叠)不就好了?前后两块各留一点重复内容,让关键信息至少在两块里都出现。这个思路本身没问题,但它只是“补救”而非“根治”——它保证信息不会被切断,却没办法保证切断处上下文仍然是连贯的。就像一块断裂的木地板,你用胶水粘上能走路,但它已经不是一块完整的木板了。真正要想问题少,得从源头优化分块边界,而不是事后靠重叠去兜底。
4. 四套主力分块方法:原理、代码与适用场景全拆解
到这节为止,我把分块方法按从简单到进阶的顺序逐个讲。你会看到每种方法的适用边界,以及我会附上可运行的Python代码和调用逻辑,方便大家直接在自己的数据上实验。
4.1 固定窗口分块:什么时候够用,什么时候必须放弃
固定窗口分块(Fixed-Size Chunking)是所有方法里最简单的。它的逻辑是:确定一个固定的块大小(比如256个Token),然后从头到尾按这个大小切,每两个相邻块之间可以设置一个重叠区,保证关键信息不容易被切断。
Python里用LangChain的话,实现相当简单。以CharacterTextSplitter为例,指定chunk_size和chunk_overlap两个参数就能跑通:
from langchain_text_splitters import CharacterTextSplitter text = "这里是你的长文本内容,可以是一篇几万字的合同、报告或者技术文档。" splitter = CharacterTextSplitter( separator="\n\n", # 先按段落换行来切分 chunk_size=500, chunk_overlap=50, length_function=len, ) chunks = splitter.split_text(text) for i, chunk in enumerate(chunks): print(f"第{i+1}段:{chunk}")注意,这个办法里真正起作用的是separator(分隔符)和chunk_size之间的配合。LangChain会先按separator把文本拆成片段,再尽可能把若干片段拼成不超过chunk_size的块,并在超出时从separator处主动截断。
什么时候固定窗口够用?我判断标准有三条:文本结构很弱(比如纯聊天记录、日志信息);对检索精度要求不高,只想快速做一轮Demo;文本量极大且彼此关联不紧密。
什么时候必须放弃?文本有明确的章节、小节结构时,千万别用固定窗口硬切。比如技术手册、法律文书、学术论文,这些文档的语义边界都在标题和段落里,固定窗口会把章节边界完全打乱。另外,当你在实验里发现很多块的首尾都是“半个词”“半句话”时,你就要知道你正在用固定窗口切一个结构分明的文本,此时就该换方法。
4.2 按结构分块:Markdown、HTML、LaTeX、代码文件都这么切
针对有结构的文档,按结构分块是远比固定窗口更明智的选择。它的核心思路是:用文档自身的标记语法来划分边界。你给文档做过大纲吗?按结构分块相当于把每一级大纲当成分块的边界。
在LangChain里,针对不同类型的文档,已经封装好了对应的Splitter。以Markdown为例:
from langchain_text_splitters import MarkdownHeaderTextSplitter markdown_text = """ # 第一章 项目背景 ## 1.1 市场分析 本段落展示市场分析的具体内容... ## 1.2 竞品对比 这里说明主要竞品的特点... # 第二章 技术方案 ## 2.1 系统架构 描述系统架构的关键设计... ## 2.2 模块划分 说明模块之间的依赖关系... """ headers_to_split_on = [ ("#", "标题1"), ("##", "标题2"), ] splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on) chunks = splitter.split_text(markdown_text) for chunk in chunks: print(chunk.metadata) print(chunk.page_content)这段代码跑完之后,每个块的内容会自动带上它的祖先标题,metadata里会有“标题1”“标题2”这两层信息。这个特性在RAG里特别有价值——你可以把块本身的元数据作为过滤条件,将来用户问“2.1节的内容”时,检索系统能直接限定到系统架构这一节去搜。
HTML也有对应方案(HTMLHeaderTextSplitter),它会解析标签层级并保留结构;LaTeX文档有RecursiveCharacterTextSplitter配合段落信息做粗分。
这种方法的优缺点非常鲜明。优点是完全尊重文档天然语义,块和块之间内容主题清晰、检索准确率高;缺点是依赖文档本身存在明确结构,如果文档压根没有标题、或者标题命名混乱,那就无从下手。另外,结构化分块得到的一些块可能特别长(比如某章内容洋洋洒洒五千字),超出上下文窗口限制,这时候后续可能需要再套一层递归分块做二次细化。
4.3 递归分块:目前最均衡、最通用的方案
递归分块(Recursive Character Text Splitter)是日常开发里我推荐大家优先试用的方案。它的核心思想是:用一个分隔符列表,按优先级从高到低依次尝试分隔文本——先按段落分隔符(如两个换行)切分,如果切出来的块仍然太大,再用下一个分隔符(如单个换行)继续切,接着是句号,最后是逗号甚至单个字符。
听起来是不是有点像“从最大的篮子开始装水果,装不下了就换小篮子”?其实正是这个逻辑。LangChain的实现如下:
from langchain_text_splitters import RecursiveCharacterTextSplitter text = "你的长文本内容,结构可能复杂多变,既有段落又有列表还有代码。" splitter = RecursiveCharacterTextSplitter( chunk_size=400, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", ";", ",", ""], ) chunks = splitter.split_text(text) for i, chunk in enumerate(chunks): print(f"第{i+1}块({len(chunk)}字):{chunk}")注意这里的separators顺序很重要。它决定了切分时优先用什么边界。我倾向于把段落层级放最前,句子次之,词最后——这样能最大程度保证语义完整。相反,如果你把逗号放前面,句子就会碎得很彻底,语义完整性会大打折扣。
递归分块为什么均衡?因为它既照顾到了文本结构的多样性,又不需要你为每一种文档专门写代码。你用一套相对通用的分隔符规则,就能覆盖大部分常规文档。实测下来,在合同、技术文档、论文、帮助文档这些混合结构的数据集上,递归分块的表现都相对稳定。
它的缺点主要是:对文本结构的利用不够“聪明”,它并不理解“标题1”和“标题2”的层级关系,只是一个优先级队列。所以如果遇到章节结构极强的文档,递归分块虽然能保语义,但可能丢失标题层级信息——这一点和Markdown结构化分块还是有差距。
4.4 语义分块:向量模型兜底,适合无结构的长文本
固定窗口按字符切,结构分块靠标记,递归分块靠分隔符——那如果文本完全没有这些显式结构呢?比如一段连续的长幅个人叙述、一个没有换行的聊天记录文件,这时候要保住语义边界,就得靠语义分块(Semantic Chunking)。
语义分块的核心思路是:把文本切成若干候选片段,然后计算相邻片段的语义相似度,相似度突然下降的地方,就是话题切换的位置,把块就在这里断开。LangChain里有一个SemanticChunker实现,需要配合一个Embedding模型来用。下面以OpenAI的Embedding接口为例:
from langchain_experimental.text_splitter import SemanticChunker from langchain_openai import OpenAIEmbeddings # 先初始化嵌入模型,用于计算语义相似度 embedding_model = OpenAIEmbeddings(model="text-embedding-3-small") # 语义分块器支持按分位数阈值控制切分粒度 text_splitter = SemanticChunker( embeddings=embedding_model, breakpoint_threshold_type="percentile", # 还可以选 standard_deviation / interquartile ) text = "这是一段很长但没有显式结构的连续文本...(此处省略若干字)" chunks = text_splitter.split_text(text) for i, chunk in enumerate(chunks): print(f"第{i+1}块:{chunk}")这个方案看起来很“AI”,但一定注意两点。第一,它依赖Embedding质量,如果你的向量模型本身对领域文本不敏感,分出来的块可能还不如递归分块合理。第二,Embedding需要逐个片段做向量化计算,处理大量文本时耗时和成本都明显高于前三种方法,不适合做超大规模离线索引。
我的建议是:语义分块用在“后处理”环节,而不是“唯一分块方案”。比如先用递归分块把文本切成较粗的段落,再对跨段落的相似度做聚类合并,这样既控制成本,又能获得一定语义边界的提升。直接全套上语义分块,前期开发成本高,后期维护也麻烦。
5. 参数调优的实战经验:chunk_size、chunk_overlap和编码器选型
现在你已经会跑通四种分块方法了,下一步是调参。参数调优没有银弹,但有相对普适的调参顺序和判断标准,我按实操经验拆开讲。
5.1 chunk_size的选择逻辑:小到能命中文档,大到能撑起上下文
先说块大小。块大小的下限由“语义完整性”决定——一个块至少要能完整表达一层意思,比如一个段落的长度;块大小的上限由两块因素限制:一是向量模型对文本长度的限制(比如有些模型单段不能超过512个Token),二是大模型生成时对输入上下文的预算。
常规的起步建议是:英文文档从300到600Token开始试;中文文档从300到600字开始试;如果你用递归分块,实际切出来的块大小平均会比设定值略小,因为它在边界处截断了。
判断块大小是否合适的标准,不是“别人用400我也用400”,而是:
- 检索召回率是否稳定;
- 每个块内部是否“自包含”,即不看上下文也能看懂;
- 是否存在大量块之间高度重复、内容雷同。
有一个简单做法:把切好的块打印出来,自己从头到尾读一遍,如果连续十块里有一块让你觉得“这段话没头没尾”,就说明块偏小或者分隔符优先级不对。
5.2 chunk_overlap:它不是遮羞布,而是信息桥
Overlap参数用来解决一个实际问题:一个完整语义单元恰好横跨两个块的切分线,导致检索时只能召回到半截信息。给每个块加上一段和前一块的尾部重叠,能让关键信息至少完整出现在某一个块内。
但Overlap不是越大越好。重叠过大的直接后果是:相邻块的重复内容变多,向量化之后它们的向量相似度自然很高,检索时同一个文档的好几个块都会被召回,反而挤占了其他文档的配额。更麻烦的是,如果多个块都只能提供半截信息,最终召回集的质量被拉低。
我的经验参考:常规场景把overlap控制在chunk_size的10%到20%之间;语义要求高的场景可以放宽到25%到30%;对检索去重要求高的场景,建议不要超过10%。另外,把Overlap部分的文本打上特殊标记(比如加一个不可见符号),是一种高级技巧,但常规项目不值得这样做。
5.3 嵌入模型(Embedding)对分块效果的影响,一个常被忽略的变量
分块之外,嵌入模型同样主导着检索效果。分块切得好,等价于“文章段落分明”;嵌入模型选得好,等价于“阅读理解能力强”——后者能把“意思相近的句子”映射到相近的向量空间。
嵌入模型的选择有几个实用建议:选择与你的生成模型来自同一生态或同一公司往往兼容性更好;领域文本与模型训练语料差异越大,越要关注领域适配性;中文场景要优先选择在中文数据上表现好的模型,切不可直接拿英文模型硬跑中文数据。
我自己测试过两种嵌入模型在同一份中文合同上的检索差异,一个模型在两个语义相似但用词完全不同的块上(比如“违约金”和“赔偿金”)可以正确关联,另一个模型则完全区分不了,直接导致检索切换到错误块。所以当你发现分块参数已经调了很久效果仍然不佳时,先想一想是不是嵌入模型对文本的理解本身出了问题。
5.4 一次完整的分块调参实验设计
为了让大家少走弯路,我把调参实验的标准流程写下来:
第一步,选一小批有代表性的文档(不同章节结构、不同话题混合),数量控制在二十份以内,方便人工查验。 第二步,用一套初始参数跑通分块,打印所有块的编号、长度和文本预览。 第三步,人工抽查三十个块,记录“语义不完整的块”数量和“块间高度重复”的数量。 第四步,调节分块方法或参数(优先改分隔符优先级,其次改chunk_size和overlap)。 第五步,固定分块结果,测试几组有代表性的Query,观察检索召回的相关块是否包含正确答案。 第六步,记录每次实验的参数组合和效果评分,形成一张简单的实验记录表。
这里也整理一张参数参考表,方便刚入手的同学对照自己的数据规模做初始配置:
| 文本类型 | 推荐分块方法 | 初始chunk_size | 初始overlap | 备注 |
|---|---|---|---|---|
| 技术报告、论文 | 按Markdown结构分块 | 500-800 Token | 10%-15% | 保持章节语义 |
| 合同、法律文书 | 递归分块 | 400-600 Token | 15%-20% | 避免切断条款 |
| 聊天记录、日志 | 固定窗口分块 | 200-400 Token | 10% | 弱结构场景 |
| 无结构长文本 | 语义分块 | 依模型限制 | 可设0 | 成本较高 |
| 混合文档 | 递归分块 | 400 Token | 15% | 最通用起点 |
5.5 分块效果怎么量化?别只靠“感觉还行”
很多团队在分块这一步全靠肉眼“感觉还行”,这是不太负责的。我建议至少设计三个定量指标:
第一个是边界完整率,人工标注一批块的边界是否切断语义,统计“无效块”占比,目标是控制在5%以内。
第二个是检索命中率,设定一组标准问答对,看正确答案所在块能否被系统检索召回在前三名,目标通常要到80%以上。
第三个是答案忠实度,把检索回来的块交给大模型生成答案,用人工或规则判断回答是否忠于原文、有无幻觉。
这三个指标不用做得很重,每轮实验跑一次,把数字记录下来。等积累到几十组数据之后,你对“什么参数最适合自己的数据”就会形成直觉,而不是永远在猜。
6. 分块之外的工程细节:元数据、后处理与增量更新的经验
分块绝不是“切完就完事”。那只是万里长征第一步,后面还有一堆工程细节,处理不好照样白搭。这一节我讲三个相对隐蔽但又影响深远的问题。
6.1 给每个块写“身份证”:Metadata的重要性
如果你在RAG系统里只把文本块存进向量库,却不管来源信息,那将来一定会后悔。想象一下用户问“本季度营收多少”,系统召回了一个相关的块,但你根本不知道这个块来自哪份文档,不知道它属于第几章,甚至不知道是哪一年的报表,生成的答案就完全不可信。
所以从分块阶段就要养成习惯:给每个块打上元数据标签,至少包括:来源文档标识、章节路径、页码或序号、创建时间、块在原始文档中的起止位置。这么做有两个立竿见影的好处:一是将来可以用元数据做检索过滤,大大提升准确性;二是可以做引用溯源,系统回答完问题后能给出“依据来自哪一段哪一页”,这在企业级应用里几乎是刚需。
6.2 分块切割的后处理:合并、去重和边界修正
有些分块器切出来的块长短不齐,短的只有十几个字,这样的块既浪费向量空间又影响检索效果。常见的后处理方式有两种:短的相邻块合并成一个大于最小长度阈值的块;重叠过多的块做去重,避免同一信息在向量库里重复出现。
另外你还要留意“标题漂移”问题——一个章节标题被切到前一个块的末尾,导致标题信息丢失。解决的办法是,在分块时优先把标题信息单独提取出来,合并进其后面正文的第一个块,并清理掉标题所在旧块的尾巴。这一步虽然繁琐,但对检索准确率影响相当大。
6.3 增量更新与动态分块:别每次全量重跑
最后提醒一句:真实业务里文档是不断新增和修改的,不可能每次都全量重新分块、重新向量化。合理的做法是设计增量更新流程,只对新文档和新修改的部分做分块与向量化,并且把“已更新的文档块”从旧索引中剔除。这一步看似简单,实际坑很多——比如同一份文档有新旧两个版本,向量库里如果同时存在,检索时就会出现优先级混乱。
我建议在文档入库阶段就确立版本管理意识,为每个文档设置统一的版本号或更新时间字段,查询时默认按版本过滤。等后期数据量上来,你才会庆幸当初这套设计救了你无数次。
7. 从“能跑通”到“靠得住”:分块选型路线图和我的个人体会
前面讲了很多细节,这里做一个收束性的梳理。分块的方法那么多,对新手来说怎么选,最容易出现的还是选择困难症——从零开始做的时候,分块方法选哪个?参数怎么定?流程卡在哪一步?我给出一个实际可行的路线参考,并把我的个人经验沉淀在这里。
7.1 新手从零起步的推荐路线
第一步,先拿递归分块跑通全流程。线性结构文本、混合内容的文档,递归分块是最稳妥的起点。用默认参数(chunk_size=400,overlap=50)跑一遍,打印出所有块,手工看二十块左右。
第二步,根据“看完的感觉”调第一轮参数,如果块太碎,增大chunk_size;如果块之间内容重复明显,降低overlap;如果语义边界被切断,调整separators把段落和句子优先级往前提。
第三步,等基本块结构稳定后,再考虑要不要换结构化分块。如果你的文档有清晰的Markdown或HTML结构,改成对应的结构化Splitter,用章节标题作为边界,效果提升往往很明显。
第四步,当你对结果质量有更高要求时,才去研究语义分块。它不是第一选择,而是“递归结构性已到瓶颈”时的进阶手段。
7.2 我踩过最深的坑:参数优化排错顺序
调试分块效果时,很容易陷入“参数无限循环”的死胡同。我的排错顺序是从底层往表层推的:
先检查数据清洗是否到位(有没有脏符号、编码错误、HTML残留),再检查分隔符优先级是否合理,然后检查chunk_size和overlap配合,最后才检查嵌入模型和检索式。这个顺序的依据是,底层问题不解决,调上层参数永远只会适得其反。
另外还有一条经验:分块调参一定是“小步快跑”,每次只动一个变量,记录效果,再动下一个。如果一次性改了五个参数,效果变好变坏你都没法归因原因,后面排错完全靠猜。
7.3 分块没有“标准答案”,但可以有“默认起点”
开头我就说过,分块没有唯一最优解。同样的文档,面向问答系统可能用小块更灵活,面向摘要生成可能用大块更合适。哪怕同样是问答系统,不同嵌入选型、不同检索算法,对块大小的偏好也都不一样。所以别人的“最佳参数”拿到你手里未必是最佳,你真正需要做的是建立自己的实验习惯和判断基准。
如果你现在正准备接大模型应用开发,不妨从今天开始就把分块当成一个正式模块来设计——先定义清楚数据形态,再选择分块方法,然后跑参数实验,最后在检索效果上验收。相信我,这套流程跑通一次之后,你会发现很多所谓“玄学”的问题,根源其实都在分块这一步。我自己已经在这条路上摔过不少跟头,也希望这篇内容能帮你少踩几个坑,尤其是让你记住理解数据、尊重结构、用实验说话,远比抄什么“万金油参数”靠谱得多。