1. 分块为什么成了RAG项目的胜负手
前两年做RAG项目,我一直以为检索效果不好是Embedding模型的锅,换了个更强的向量模型,结果提升微乎其微。后来认真排查才发现,真正卡住我的,是文本分块方案。
分块(Chunking)在RAG链路里排在第一个,却是最容易被人忽略的环节。文档进来之后,先切成一个个独立的小块,再做Embedding入库。检索时用户的问题也向量化,然后去向量库里找相似块,最后把这些相似块拼起来丢给大模型生成答案。你会发现,如果这一刀切得不对,后面无论Embedding多强、Rerank多准、大模型多聪明,都是白搭——检索回来的内容本身就不对,生成结果怎么可能对。
一个很典型的例子:你把一篇制度文件按固定200个字切块,正好把一个完整条款从中间截断,下半句存到了下一个块里。用户问“这个费用能报销吗”,检索命中的块只包含上半句“报销标准如下:无”,那大模型就只能一本正经地告诉你“不能报销”。这种问题,换什么模型都救不回来。
这篇文章我是写给正在做RAG实战的人看的。不管你是用LangChain、LlamaIndex这类框架,还是自己手写RAG管道,又或者正在搞知识库问答、企业文档助手、政务制度问答,分块方案的选型和调优都是你绕不开的环节。我会把目前实践中真正常用的七种分块技术方案逐一拆开来讲,讲清楚它们的原理、适用场景、参数怎么调、坑在哪里,最后再给一份组合使用的实战建议。
这七种方案没有绝对的优劣之分,它们各自适合不同的文档类型、检索粒度和算力预算。看完之后,你应该能对着自己的业务场景直接做出选择,而不是再到处翻文档查“到底用哪种”。
2. 七种分块技术方案全景对比
先把七种方案亮出来,后面再逐一展开。为了方便记忆,我把它们分成两大类:一类是“规则驱动型”,靠字符数、分隔符、文档结构来切;另一类是“语义驱动型”,靠Embedding相似度甚至大模型理解来切。
规则驱动型方案包括固定长度分块、递归字符分块、文档结构感知分块、句子级滑动窗口分块。语义驱动型方案包括父子分块、语义分块、上下文增强分块(也有人叫Agentic分块)。在实际项目中,我见过很多团队把其中两到三种混着用,形成多路召回,效果往往比单一方案好不少。
先看全景对比表,这样对整体有个概念:
| 方案 | 核心切分依据 | 典型场景 | 计算成本 | 检索粒度 | 典型配置 |
|---|---|---|---|---|---|
| 固定长度分块 | 字符数/token数 | 日志、对话流、纯文本 | 极低 | 粗 | chunk_size=512, overlap=50 |
| 递归字符分块 | 分隔符优先级 | 通用知识库、新闻、说明文档 | 低 | 中 | 按段落优先,再按句子 |
| 文档结构感知 | 标题/章节/标签 | 制度文件、产品手册、HTML/Markdown | 中 | 中细 | 按H1/H2/H3切,保留标题 |
| 句子级滑动窗口 | 句子边界+窗口 | FAQ、短问答、句子语义较强的文本 | 低 | 极细 | window=3~5句 |
| 父子分块 | 子块检索、父块输出 | 需要完整上下文的知识问答 | 中 | 细检索+粗输出 | 子块256字符,父块整节 |
| 语义分块 | Embedding相似度断点 | 长文、讲义、逻辑段落明显的文本 | 较高 | 动态 | 候选句+相似度突降断点 |
| 上下文增强分块 | LLM生成上下文摘要 | 检索精度要求高的私有知识库 | 高 | 自定义 | 每个块附加来源、主题、关联上下文 |
这里我要先说一句大实话:别指望选一个方案就一劳永逸。我做过一个政务知识库项目,制度类文档适合结构感知分块,而通知公告类文档又更适合递归字符分块,最后我是在同一套系统里对不同数据源使用了不同的分块方案,才把检索命中率拉到可用水平。这也是为什么我把“多路召回和混合分块”单独留了一章来讲的原因。
3. 规则驱动型分块:基础方案到结构感知方案
3.1 固定长度分块:最朴素,也最容易踩坑
固定长度分块就是按固定的字符数或token数硬切,比如每512个字符切一块。这是最直观的方案,很多刚接触RAG的人第一版代码都是这么写的。
实现逻辑很简单:
def fixed_size_chunk(text, chunk_size=512, overlap=50): chunks = [] start = 0 while start < len(text): end = start + chunk_size chunks.append(text[start:end]) start = end - overlap return chunks注意这里有两个关键参数:chunk_size控制每块长度,overlap控制相邻块之间的重叠字数。overlap的作用是避免一句话正好被截在边界上,通过让相邻块共享一部分文本,把语义断点的影响降到最低。我见过有人设置overlap=0,结果检索效果明显变差——因为被截断的关键信息永远找不到完整上下文。
固定长度分块的优点就一个字:快。不管什么文档进来,处理时间基本可以预估,Embedding调用成本也稳定。缺点是切出来的块几乎必然存在语义割裂,一个完整段落可能被劈成两半,一个列表项可能缺了后半截。
这个方案真正适合的场景是那种没有明显结构、内容密度均匀的文本。比如聊天记录、日志文件、法律条文这种短句多且相对独立的文本,切成固定长度反而不容易破坏什么关键语义。至于长篇的论述型文档、制度文件、技术手册,我强烈不建议直接用固定长度,哪怕加了overlap,也只是把问题从“边界截断”变成“边界截断的概率降低”,并不能根治。
3.2 递归字符分块:日常项目的主力方案
如果你去翻LangChain的默认文本分块器,大概率会遇到RecursiveCharacterTextSplitter,这就是递归字符分块。它比固定长度聪明的地方在于:不是硬按长度切,而是按照一组分隔符的优先级来切。
它的执行逻辑是这样的:先按段落分隔符(比如两个换行)尝试切分,如果切出来的块还是太大,再按句子分隔符(句号、问号、感叹号)切,要是还大,就再按逗号、分号、空格等切。整个过程可以理解成“先礼后兵”——尽量保留语义完整的段落和句子,实在不行才退到字符级。
separators = ["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] def recursive_split(text, max_len): if len(text) <= max_len: return [text] for sep in separators: parts = text.split(sep) if len(parts) > 1: result = [] buffer = "" for part in parts: if len(buffer) + len(part) <= max_len: buffer += part + sep else: result.extend(recursive_split(buffer, max_len)) buffer = part + sep if buffer: result.extend(recursive_split(buffer, max_len)) return result return [text[:max_len]](上面的实现只是示意,生产环境建议直接用成熟框架的方法,别重复造轮子。)
为什么说它是主力方案?因为绝大多数知识库文档都是“段落+句子+短语”的结构,递归字符分块能最大程度地保证切出来的块落在自然语义边界上,同时实现成本又很低,不需要额外的大模型调用,也不需要复杂的依赖。我在通用知识库项目里,第一版基本都是先用它跑通全链路,拿到基线指标再说。
参数上,主要关注chunk_size和overlap。chunk_size设多大,跟你的Embedding模型和最终生成模型都有关系。以常见的文本Embedding模型为例,512到1024个字符都是相对安全的区间;如果文档本身段落很长,可以把上限稍微放宽一点,但别超过1500字符——太长的块会让向量表示的语义变模糊,检索精度反而下降。
3.3 文档结构感知分块:政务、制度、产品手册的最优解
这个方案的核心思路是:既然源文档本来就有标题、章节、列表这些结构,那就顺着结构来切。比起靠分隔符猜语义边界,直接读取Markdown的标题层级、HTML的标签、PDF的目录结构,切出来的块天然和文档的逻辑单元对齐。
我做过一个政务知识库项目,里面全是政策文件、制度汇编、办事指南。这种文档的特点是有非常清晰的层级——章、节、条、款、项。如果拿递归字符分块去切,遇到一条很长的条款,一样会被拦腰截断;但如果识别出“第X条”这个结构边界,把它当作一个切分点,那么每个块就是一个完整条款,检索命中率会质变。
在实际实现里,常见的做法是这样:
- Markdown/HTML文档:按H1、H2、H3标签逐级嵌套切分,同时把标题文本保留到每个子块前面作为前缀。
- PDF文档:先用文档解析工具抽取目录大纲和标题位置,再根据标题位置确定每个章节的范围。
- Word/PDF制度文件:把“第X条”“(X)”“一、二、三”等结构化关键词当作切分点。
这里有一个非常容易被忽略的细节:切完块之后,一定要把块的“结构上下文”保留下来,而不是只存正文。什么意思?比如一个块的内容是“第四十二条规定……”,你最好把“第三章 财务管理”这个标题也拼到块头部再一起Embedding。这样用户问“报销流程”的时候,向量检索才能感知到“这个块属于财务章节”,匹配精度会明显提升。
结构感知分块的缺点是处理成本高一些,因为要先解析文档结构,不同格式的文档要分别写解析逻辑。而且很多历史扫描PDF根本没有结构信息,这时候就只能退而求其次用递归字符分块。但对制度、手册、标准、规范这类结构化程度很高的文档,这个方案值得投入。
3.4 句子级滑动窗口分块:检索片段的最小化尝试
句子级分块的思想是:把每个句子当成独立的小块进行Embedding,检索的时候找出最相关的那一两个句子,再把它周围的上下文句子补上一起送给大模型。
这种“切小块检索、补窗口输出”的模式,特别适合处理短问答场景。比如FAQ类知识库,用户问“离职了公积金怎么提取”,答案其实就一两句话,你如果返回一个512字符的大块,里面可能有一半内容是不相关的;但如果按句子切,检索回来的就是精准的那一句,再带上前后两三句作为缓冲,效果会好很多。
我举一个常用实现思路:先用NLP工具或者简单规则把文本拆成句子,然后以当前句为中心,向前向后各取N个句子合并成一个检索单元。这里的N就是窗口大小,一般取2到3比较合理。窗口越大,上下文越完整,但噪声也可能越多;窗口太小,语义可能缺胳膊少腿。
原始文本:A句。B句。C句。D句。E句。F句。 窗口大小=2: 索引1 -> A句。B句。C句。 索引2 -> B句。C句。D句。 索引3 -> C句。D句。E句。这种方案在LangChain里也有对应的Splitter,核心参数是window_size和step。需要提醒的是,句子的划分质量直接决定效果。中文还好一些,句号、问号、感叹号基本能覆盖绝大多数情况;英文要注意缩写点、小数点和引用格式,否则会把一句话切成好几截。
句子级分块不适合那种一个知识点需要跨多个段落展开的复杂文档。用户问的是需要综合多处论据才能回答的问题时,单句窗口往往信息量不够。这时候我建议你往后看父子分块,它才是为这类场景设计的。
4. 语义驱动型分块:让模型参与理解
规则驱动型方案无论怎么切,本质上是靠“表面的符号特征”来猜语义边界。那么有没有一种方式,直接让模型判断“哪些句子放在一起是语义上完整的”?这就是语义驱动型分块做的事。它们通常需要更多的计算资源,但换来的往往是更符合人类阅读习惯的检索单元。
4.1 父子分块:兼顾检索精度和上下文完整性
父子分块在业界的普及度越来越高,它的核心思路是:建两套索引层级。子块是细粒度的短块,负责和用户的查询做精确匹配;父块是粗粒度的长块,负责在大模型生成时提供完整上下文。
打个比方,这就像查字典。你通过词条索引(子块)快速定位到某个词在第几页,但真正阅读的时候,你看的是整个词条的完整解释(父块)。这样既避免了短查询在大文本块里“淹没”,又避免了只给一句话导致生成时缺上下文。
具体实现流程是这样的:
- 先把文档按较大粒度切成长段,这些是父块,通常是1000到2000字符,对应一个章节或一个完整主题。
- 再在父块内部按较小粒度切出子块,通常是200到500字符。
- Embedding只对子块做。
- 检索时用子块去匹配用户查询,命中子块后,把所属的父块整体返回给大模型。
子块负责“定位准”,父块负责“上下文全”。这个方案尤其适合那些“答案需要覆盖多个子观点才能说清楚”的知识问答场景,比如规章制度里的处罚条款、审计流程的多步骤说明。
我在一个企业知识库项目里是这么配的:子块用固定长度切,256字符加30字符重叠;父块按段落或章节切,最多1000字符。为什么子块不用递归字符切?因为子块本身很细,递归切分反而容易把相对完整的短句合并得比较随机,固定长度加上适度重叠在这个粒度下表现很稳定。
要提醒的是,父子分块会让索引结构变复杂,检索的时候需要维护“子块ID到父块ID”的映射关系;同时因为返回的是父块,可能会超出大模型的上下文窗口,所以一般还要配合截断策略,或者只取父块中与子块位置相邻的一小段文本。
4.2 语义分块:用向量相似度找断点
语义分块,英文叫Semantic Chunking,思路是让Embedding模型自己判断句子的语义转折。先把文本切成一串候选句子,然后逐句做向量化,计算相邻句子向量的余弦相似度。当相似度出现明显下降时,说明语义发生了转折,这里就是一个合适的分块断点。
听起来很巧妙,但实现起来有几个细节要处理。候选句怎么切、相似度差多少算“明显下降”、最小块长度怎么限制,这些都是需要试出来的。
我梳理了一个基本的实现流程:
1. 将文本按句子切分为候选句列表 S = [s1, s2, ..., sn] 2. 对每个句子做Embedding,得到向量列表 V = [v1, v2, ..., vn] 3. 计算相邻句子的余弦相似度 sim(v1,v2), sim(v2,v3), ... 4. 计算相似度的均值 mu 和标准差 sigma 5. 当相邻相似度 < mu - k*sigma(k通常取1~2)时,判定该处为断点 6. 把所有断点拼起来,得到语义分块结果为什么用“均值减k倍标准差”而不是固定阈值?因为不同文档的全局相似度分布差异很大。制度文件前后句子高度相关,classical的句子可能频繁转折。固定阈值很难一套通吃,用统计量来标定“相对于这篇文档的突变程度”会更稳妥。
语义分块的优点很明显:它不依赖显式结构,对没有标题、排版混乱的长文本也有不错的处理效果。比如论文全文转过来的纯文本、课程讲稿、访谈记录,这些文档用递归字符分块切出来的块,经常出现“前面还在讲概念,后面突然跳到案例”的问题,语义分块能大大缓解。
缺点也很实在:计算成本高。每句话都要过一遍Embedding,长文档的向量化调用量会比普通方案高一个数量级;而且断点判定对超参数比较敏感,需要有一套带标注的测试集来调。我的建议是,如果你的文档规模不大(几千篇以内)、文本质量偏高,值得用语义分块;如果是千万级大规模语料,成本往往hold不住,还是退回结构感知方案更划算。
4.3 上下文增强分块:让大模型给每个块写“自我介绍”
这个方案是最近在RAG实战社群和Agentic RAG语境里频繁被提到的一种做法。我最早是在Anthropic提出的一篇关于Contextual Retrieval的技术内容里看到完整思路的,核心思想很简单:单独一个文本块在向量化的时候,缺少全局上下文,导致检索时语义匹配不上;那么在向量化之前,先让LLM给每个块写一段包含来源、主题、相关背景的上下文描述,把这段描述和原块拼在一起再做Embedding。
比如,一个制度文件中有个块写的是“全年不得超过十万元”。这句话单独看非常模糊,什么不得超过十万元?如果Embedding只看到这句话,用户问“年度预算上限”,很可能匹配不到。但如果你让LLM生成一段上下文说明:“本块选自XX公司财务报销管理制度,第三章关于年度差旅费用预算的规定,前文规定了报销审批流程,此处明确部门年度差旅费用预算总额上限为十万元”,拼接之后做Embedding,检索命中率会有非常显著的提升。
这个方案的实现逻辑更像Agentic——每个块的处理不再是单纯的字符串切割,而是一次独立的“大模型理解+改写”调用。所以有人把它归到Agentic RAG的范畴。具体流程是:
1. 先用任意规则方案切出基础块 2. 对每个块,构造提示词:请根据整个文档上下文,为以下文本生成一段30~50字的说明 包括来源章节、主题、关联背景等 3. LLM返回上下文描述 4. 将“上下文描述 + 原始块”拼接,作为最终Embedding的输入 5. 入库时同时保存描述、原始块、所在文档ID我实际测试下来,这个方案对检索精度的提升是最直接的,尤其适合那种单个块看起来“字面意思不完整”的场景。但它也是最烧钱的:每个块都要调用一次LLM,小规模文档还好,到了几十万块的规模,成本和时间需要认真评估。
还有一种折中做法值得推荐:只让LLM为“检索命中率低”的部分——比如较短的块、字面歧义大的块——做上下文增强,普通的段落直接跳过。这样能把成本控制在一个比较现实的范围。
5. 分块参数调优与多路召回实践
5.1 关键参数怎么定:chunk_size、overlap、断点阈值
很多人在分块上栽跟头,不是因为不知道有哪些方案,而是不知道参数该怎么调。这里我给出一个比较务实的调参次序,你可以直接照着试。
先定chunk_size。chunk_size不是越大越好,也不是越小越好。它跟三个因素强相关:Embedding模型的最大输入长度、大模型的上下文窗口、文档内容本身的粒度。经验做法是先看Embedding模型支持多少token。比如输入上限是512 token的模型,你在中文场景下大约对应700到1000个汉字,那么chunk_size取500到600个汉字会比较稳,留出富余量给后续可能拼接的上下文描述。
再调overlap。overlap的典型值在chunk_size的10%到20%之间。512字符的块,overlap取50到80比较常见。overlap太大会导致相邻块大量重复,检索时同一个段落被多次返回,浪费上下文空间;太小则起不到防止截断的作用。
最后是语义分块的断点阈值。前面提过用mu - k*sigma的方法,k的取值需要在你的验证集上调。我的建议是先取1.5作为基线,如果切出来的块数量太多、平均长度太短,就往大调(比如2.0);如果块的语义内部依然有明显跳跃,就往小调。
这里要强调一个很容易被忽视的原则:调参的依据永远是“你的验证集”,不是直觉。建一套至少一两百条业务问题的评测集,人工标注好标准答案和参考文档段落,然后用不同的参数组合跑检索,对比召回率、命中位置、生成答案的准确率,用数据说话。
5.2 多路召回:同一份文档用不同分块方案并行
多路召回在搜索领域是老生常谈,但在RAG里结合分块来做,效果常常出人意料。核心做法是:对同一份文档同时用两到三种分块方案建多套索引,查询时分别检索,然后把多路结果合并、去重、重排。
为什么这么做有效?因为不同分块方案的“信息视野”不一样。固定长度分块虽然容易切断句子,但它的块边界稳定,检索时不容易漏掉长段落中的局部内容;结构感知分块对章节把握准,但在细节匹配上可能不够细。多路召回本质上是用多个视角去看同一份文档,降低单一分块方案的系统性偏差。
常见的组合我试过这几组:
| 组合 | 适用场景 | 效果说明 |
|---|---|---|
| 递归字符分块 + 句子级窗口 | 通用文档、FAQ | 兼顾段落级语义和句子级精确定位 |
| 结构感知分块 + 父子分块 | 制度文件、手册 | 章节上下文完整,条款细粒度可命中 |
| 固定长度分块 + 语义分块 | 长文、论文、讲义 | 全局检索不遗漏,语义边界更准确 |
多路结果合并的时候,一个简单有效的策略是RRF(Reciprocal Rank Fusion),它不依赖分数绝对值,而是把每条结果在各自路内的排名取倒数再求和,排名越靠前贡献越大。这个方式对“各路分数尺度不一致”的问题非常鲁棒,实现也就十几行代码。
# RRF简易实现 def rrf_fusion(result_lists, k=60): scores = {} for results in result_lists: for rank, doc_id in enumerate(results): scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1) return sorted(scores.items(), key=lambda x: x[1], reverse=True)这里要注意,多路召回会带来检索延迟和存储成本上升。每增加一路索引,就需要多一遍Embedding调用和检索查询。不建议一开始就上三路以上,先两路并联观察收益,如果召回率提升不明显,说明瓶颈不在分块,可能要去看看Embedding模型或Rerank层面的问题。
5.3 一个企业知识库的混合分块实际配置
拿我之前做过的一个企业制度知识库来举例。这套系统里的文档类型大致有三类:规章制度、操作手册、公告通知。一开始我统一用递归字符分块,结果是制度类文档的条款经常被切断,操作手册里的步骤列表被拆得七零八落,公告通知反而还行。后来我按文档类型做了分路处理:
- 规章制度:走结构感知分块。先用正则识别“第X条”“第X章”,以条为基本块,保留章节标题作为块前缀。子块控制在400字符。
- 操作手册:走父子分块。父块是每个操作步骤的大节,子块是单个步骤或步骤中的说明文字。子块200到300字符。
- 公告通知:走递归字符分块,chunk_size=600,overlap=60。
这样改完,评测集上的Top-5命中率从54%提到了79%,效果非常直观。所以你看,实际项目里的分块方案不是一道单选题,而是一套需要根据数据源定制的策略组合。
5.4 与Rerank的配合:分块决定了Rerank的天花板
最后补一句关于Rerank的话。很多项目上了Rerank之后发现效果提升不大,原因往往就是分块太粗,Top-20里压根没有正确答案,Rerank再怎么排也排不出来。Rerank的作用是把“基本相关的候选”排到前面,而不是凭空变出相关片段。所以,分块方案的目标应该调整为“提供高质量、高覆盖率的候选集”,在此基础上Rerank才能最大化发挥价值。
实操中的先后顺序应该是:先用分块方案提高Top-20的召回率,再靠Rerank提高Top-5的准确率。如果直接上来堆Rerank,花钱多,收益却有限。这也是为什么我在项目里一直强调,分块多花点时间,后面整个链路都会轻松很多。
6. 常见问题与排查技巧实录
写项目复盘的时候,我把这两年遇到的典型问题整理成了一份速查表,分享出来,希望能帮你少走弯路。
6.1 检索召回的内容前言不搭后语
现象:用户问了一个具体问题,召回的块文本里明明包含了关键词,但句子不完整,语义残缺。
原因:大概率是固定长度分块把句子截断了,或者chunk_size太小,块内容不够表达一个完整语义。
排查步骤:
- 先打开召回结果里那几个块,肉眼检查是不是存在半句话。
- 是的话,换成递归字符分块,并把chunk_size适当调大。
- 确认overlap是否在合理区间,如果没有重叠,加上10%到20%的重叠。
6.2 召回结果太短,大模型无法组织完整答案
现象:召回的内容是一个孤零零的句子,信息量不够支撑回答。
原因:句子级分块或子块切得过于细,缺少上下文。
排查步骤:
- 看当前是不是句子级窗口分块,窗口大小是否只有1或2。加大窗口到3到5句。
- 换成父子分块,子块负责定位,父块负责提供完整段落。
- 如果用的是父子分块,检查返回逻辑是否正确返回了父块。我见过有人把子块直接送去生成了,导致上下文缺失。
6.3 长文档的中间段落永远搜不到
现象:文档开头和结尾的块经常被命中,中间部分几乎从不出现。
原因:很多Embedding模型对处于长文档中部的内容,在向量空间中区分度不高,容易被两端的强语义片段压制;也有可能是分块时中间部分的标题或上下文信息丢失。
排查步骤:
- 给中间块补充结构前缀,把所属章节标题拼到块头再做Embedding。
- 检查是否用了整体文档Embedding再切块的做法(这会让所有块共享全局向量,中间信息被稀释),改成逐块独立Embedding。
- 适当缩小chunk_size,让中间部分的局部语义更聚焦。
6.4 Embedding调用量暴涨,成本失控
现象:换了语义分块或上下文增强分块之后,账单飙升。
原因:逐句向量化和逐块LLM调用都属于高成本方案,如果没有做成本规划,很容易失控。
排查步骤:
- 确认是否所有文档都适合用高成本方案。很多短文本用递归字符分块就够了,不需要逐句Embedding。
- 上下文增强分块可以只对“字面歧义大”的块启用。先用普通方案跑一遍,筛出检索命中率低的块,再对这部分做二次处理。
- 可以考虑用批量调用、缓存相同或相似块结果来降低成本。
6.5 分块参数怎么调才靠谱:回归测试是底线
最后一个心得,也是我踩坑最重的一条:分块方案和参数的改动,一定要用回归测试来验收,不要凭感觉。很多RAG项目改了一轮分块参数,看起来问答效果好了,但换个测试集又打回原形,就是因为没有建立稳定评测集。
你可以这样建一个最小的评测流程:
- 准备100到200条有代表性的业务问题,每条标注好对应的参考文档ID和标准答案。
- 跑一遍“分块 -> 检索 -> Rerank -> 生成”全链路,记录Top-5命中率和最终答案正确率。
- 修改分块方案或参数后,重跑同一套评测,对比指标变化。
注意不要只记录“答案对不对”,还要记录“正确答案是否出现在召回候选中”。如果命中了但生成不好,问题大多在提示词或大模型;如果压根没召回,那才真正需要回去改分块。
我个人在项目中的经验是,建立这套评测流程后,分块方案的迭代就从一个“靠玄学”的过程变成了一个纯工程过程,后续不管是换Embedding模型、调chunk_size,还是加多路召回,都有了明确的判断依据。
最后再分享一个小技巧:动手写代码之前,把一个项目的文档类型清单和典型查询问题列出来,对着这两个列表去选分块方案,比任何技术文章的推荐都靠谱。毕竟分块砍下去的那一刀,最终还是为了解决你业务里的具体问题。