语义分块:超越固定长度的智能切块策略——从句子边界到语义边界的进化

固定长度切块的原罪:切断语义的代价

一个被腰斩的段落

切块(chunking)是 RAG 管线的第一步,却是被讨论最少、拍脑袋最多的环节。「512 token 一块、重叠 50」是无数系统的默认起点。固定长度切块的问题不在于它偶尔出错,而在于它的错误是系统性的:它对内容毫无感知。一个论证段落被从中间切开,结论留在下一块;一个转折句被切断,「虽然……但是」的前半句和后半句分属两个 chunk;一个定义术语紧跟着的解释被拆散。检索器召回的是半截论证,生成模型拿着半截话猜全貌。

我们在自己的技术文档库上做过一个统计:按 512 token 固定切块,抽样检查发现约三分之一的切块边界落在句子中间,接近两成的 chunk 包含「悬空指代」——chunk 里出现「它」「该方案」「如上所述」这类指代,而先行词在相邻的其他 chunk 里。这两类 chunk 即使被检索命中,也回答不好问题;更隐蔽的代价是它们的嵌入向量本身质量就不高——一段以「但是」开头的文字,语义表示是破碎的,检索召回率从源头受损。

为什么滑窗重叠救不了

固定切块的标准补丁是重叠窗口(overlap):相邻块共享一部分文本,指望被切断的语义至少在其中一块里完整。重叠能缓解「边界切断」的症状,但代价立刻显现:索引体积膨胀、同一内容重复召回(去重又引入新逻辑)、以及它治不了悬空指代——先行词依然不在块里。更根本的是,重叠窗口仍然是内容盲的:它在句子中间切断的概率和固定切块一样高,只是切断处的文本出现了两次。

结论:滑窗重叠是止痛药,不是治疗。真正的解法是让切块边界对齐语义边界——块与块的分界,应该是话题转换的地方。

flowchart LR
    DOC["文档"] --> S["句子切分"]
    S --> E["逐句嵌入"]
    E --> SIM["相邻句相似度序列"]
    SIM --> BP{"低于断点阈值?"}
    BP -- "是:话题转换" --> CUT["在此切块"]
    BP -- "否:话题延续" --> S
    CUT --> C["语义完整的 chunk"]
    C --> IDX["进检索索引"]

语义边界的三种判定方法

句子边界 + 嵌入相似度断点

目前工程上最主流的方法:先把文档切成句子,用嵌入模型给每个句子算向量,然后计算相邻句子的相似度序列,在相似度骤降的位置切块——相似度骤降意味着话题转换。LlamaIndex 的 SemanticSplitterNodeParser 是这个方法的标准化实现:滑动窗口内的句子组相似度,与前后窗口比较,差异超过阈值(breakpoint percentile)即切分。

from sentence_transformers import SentenceTransformer
import numpy as np

model = SentenceTransformer("paraphrase-multilingual-mpnet-base-v2")

def semantic_chunks(sentences: list[str], percentile: int = 90) -> list[str]:
    """相邻句子嵌入相似度骤降处切分。"""
    emb = model.encode(sentences, normalize_embeddings=True)
    sim = [np.dot(emb[i], emb[i+1]) for i in range(len(emb)-1)]
    threshold = np.percentile(sim, percentile)      # 骤降阈值
    chunks, buf = [], [sentences[0]]
    for s, sc in zip(sentences[1:], sim):
        if sc < threshold:                          # 话题转换点
            chunks.append(" ".join(buf)); buf = []
        buf.append(s)
    chunks.append(" ".join(buf))
    return chunks

这个方法的可调参数只有 percentile,直觉清晰:取值越高,只有「急剧」的话题转换才触发切块,块更大;取值低,轻微的语义波动也会切块,块更碎。我们在企业文档库上扫过 50 到 95 的区间,60 到 75 之间检索质量最稳,过高的阈值(90+)切块行为退化成接近「按标题分段」,过低的阈值产生大量单句碎块。

成本是这个方法的软肋:每个句子过一次嵌入。一份文档多出几十次嵌入调用,全库就是百万级调用。缓解手段有二:句子嵌入用小模型(嵌入这一步不需要生成模型级别的智力);对相似度序列做缓存与增量计算(追加内容只算新句子)。

话题分割:TextTiling 的现代回声

按相似度找语义边界不是新想法。TextTiling(Hearst,1997)在深度学习之前就用词频重叠的滑动窗口做话题分割——相邻窗口共享词汇多就是同一话题,共享词骤减就是话题边界。现代的语义分块本质上是 TextTiling 的嵌入版:把词频重叠换成语义向量相似度,把启发式阈值换成统计分位数。理解这层渊源有一个实用价值:TextTiling 时代积累的评估经验(分割边界与人工标注话题边界的 Pk 指标、窗口大小对粒度的影响)今天依然适用。

嵌入版之外,基于 LLM 的话题分割也在成熟:让模型逐句判断「这句话是否开启了新话题」。判断准确率更高,但成本高出一个数量级,我们只在规则和嵌入都拿不准的边缘场景(比如访谈实录这类无格式口语语料)用 LLM 复核。

命题级分块:Dense X 的粒度革命

语义分块的另一个方向不是「在哪里切」,而是「切多细」。Dense X Retrieval(Chen 等,2023)提出用命题(proposition)作为检索单元:命题是独立成立的最小语义单元——每个命题自带完整的主谓宾和指代消解,不依赖上下文即可理解。原文「它于 2023 年发布并迅速成为社区热点」被分解成「X 模型于 2023 年发布」「X 模型迅速成为社区热点」两个命题,指代「它」在分解时被替换成实体名。

论文的核心发现对我们触动很大:命题粒度显著提升检索召回率,但会伤害下游生成。原因很直观——命题太细,单条命题的信息量不够生成模型回答问题,需要召回很多条命题拼上下文;而且命题抹掉了原文的修辞结构和论证流。我们的落地方式是折中的「命题索引、段落供给」:检索索引用命题(召回粒度细、指代干净),命中后回溯到命题所属的原段落进上下文。这和表格篇里「索引用三元组、回答用整表」是同一个设计哲学:检索粒度和生成粒度解耦。

Late Chunking:先整篇嵌入、再切块

传统流程都是「先切块、再对每块做嵌入」,Late Chunking(Jina AI,2024)把这个顺序倒了过来:先对整篇文档做长上下文嵌入,让每个 token 的向量都带着全文语境,然后再沿 token 边界切块——块向量由块内 token 向量平均而来。效果是每个 chunk 的向量天然包含跨块上下文:第二段里「该方案」的向量,已经吸收了第一段里方案名称的信息。

这个想法的正确性可以由两个独立证据支撑。其一,2024 年前后上下文长度 8K 起步的嵌入模型已经普及(Jina v3、各家的多语言长文本模型),整篇过一遍嵌入在算力上完全可行;其二,上下文化的 token 表示让短块的向量质量接近「在完整文档语境下人工写摘要」的向量质量。我们对照测试的结果:Late Chunking 在长文档检索基准上比先切后嵌有明显优势,优势最大的正是悬空指代密集的文档(会议纪要、访谈实录)。

它的适用边界也明确:需要文档作为整体可用(超长文档要先分段再过嵌入,跨段语境又丢了);嵌入模型必须输出 token 级表示(池化到句向量的模型用不了);切块边界要在嵌入之后才能定,意味着索引流程和切块流程耦合更紧。我们的结论是把它当成工具箱里的选项:指代密集的语料优先用,格式规整、自包含性强的语料(手册、条款)收益有限。

分块策略的评测:别拍脑袋

用检索指标反推切块

分块策略最大的工程误区是没有评测直接上线。切块质量没有金标准直接可打,但它有完美的代理指标:切块的目的是被检索命中并支撑回答,那就用检索和问答指标反推。最小可行评测集只需要五十到一百条「问题 + 标准答案所在段落」,跑三组数字:答案所在 chunk 的 Top-K 命中率(检索层)、命中 chunk 的信息完整度抽样(切块层)、端到端回答准确率(问答层)。换一种切块策略,三组数字重跑一遍,高下立现。

两条实战结论:其一,不同文档类型的最优策略不同——技术手册(结构自包含)对切块策略不敏感,叙事性文档(指代密集)对语义分块极其敏感,按文档类型分别评测、分别配置,比找一个全局最优解现实得多;其二,切块大小和语义边界是两个正交的旋钮,语义边界决定「在哪里切」,目标块大小决定「切多大」——先定边界再按大小合并相邻块(小的语义段合并、过大的语义段内部再切),比单独调任何一个旋钮都有效。

工程实践与代价

最后盘一盘成本与工程约束。语义分块的三类开销要算在账上:嵌入调用开销(句子级嵌入,全库成本上升数倍不等)、索引体积开销(语义块长度不均,块数通常比固定切块多一到两成)、管线复杂度开销(断点检测的参数、缓存与增量逻辑)。对应的回报是检索命中率和端到端准确率的提升——我们在两个真实知识库上的对照,语义分块加 Late Chunking 的组合,检索命中率提升在十个百分点上下,端到端准确率提升五到八个点。这笔账在不同语料上差异很大,所以结论永远是:先建评测集,再动手改切块。

一条容易被忽略的工程建议:切块策略要可灰度、可回滚。切块变了,索引必须全量重建,这是一个高风险操作。切块参数连同版本号写进每条索引记录,查询层可以按流量比例切流到新旧索引,出问题一键回滚。把切块当成「需要版本管理的配置」而不是「一次性写死的代码」,是分块工程化的分水岭。

小结

固定长度切块的原罪是内容盲:系统性切断论证、产生悬空指代,滑窗重叠治标不治本。语义分块的三条进化路线各有适用场景:句子边界 + 嵌入断点(工程主流,成本低、参数少)、命题级分块(检索粒度的极限优化,要配合「命题索引、段落供给」才能扬长避短)、Late Chunking(顺序倒转带来的上下文化向量,指代密集语料的利器)。

方法之外,两件事决定成败:用检索指标反推切块质量的评测闭环,以及切块配置的版本化与灰度机制。切块是 RAG 里「改一行代码、动全库索引」的环节,评测先行、灰度发布,是它工程化的两个底线。

← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?