跳到主要内容

你的 RAG 分块器是一项无人 Review 代码的数据库 Schema

· 阅读需 13 分钟
Tian Pan
Software Engineer

当检索质量回退(retrieval quality regression)第一次出现在你的值班频道(on-call channel)时,调试路径几乎总是指向一些令人意外的地方。不是嵌入模型(embedding model),不是重排序器(reranker),也不是提示词(prompt)。罪魁祸首通常是对分块器(chunker)的一行改动——比如更换了分词器、调整了边界规则或步幅(stride)——而这行代码是三个冲刺(sprint)前有人合并进预处理 notebook 的。这次修复没有触及任何生产代码。它在夜间重建了索引。而现在,所有租户的准确率都下降了四个百分点。

分块器就是数据库 Schema。你提取的每个字段、划定的每个边界、选择的每个步幅,都定义了存入向量索引的“行”的形状。修改其中任何一项,你就在改变索引的 Schema。而你系统的其他部分——检索逻辑、重排序特征、评估框架、下游提示词——都依赖于这个索引,并假设它是稳定的。但由于分块器通常存在于 notebook 或一个没人将其视为“基础设施”的小型 Python 模块中,这些改动在上线时往往只被当作配置微调,但其爆炸半径却相当于执行了一次 ALTER TABLE

这篇文章的核心观点就是严肃对待这一构想。如果分块器是 Schema,那么每一次分块逻辑的改动都是一次迁移。每一次重新嵌入都是一次索引重建。每一条边界规则都是一份契约。如果一个组织通过 notebook 的 PR 来发布分块改动,却通过另一个独立的团队进行检索评估,那么这就是治理上的缺口,而不仅仅是工作流的偏好。将分块视为简单的预处理,是导致优秀团队在悄无声息中丢失一个月检索质量,却始终无法追溯到具体提交(commit)的原因。

分块决策即 Schema 决策

梳理一下分块流水线实际确定的参数,你会发现 Schema 这一构想不仅巧妙,而且理所当然。

分块大小(Chunk size)决定了行宽。重叠(Overlap)决定了跨行存在多少冗余数据。边界规则——段落、句子、Markdown 标题、语义断句、固定 Token 数——决定了每行内容的形状。步幅(Stride)决定了行在源数据中是密集还是稀疏。元数据字段(章节标题、页码、文档 ID、时间戳、作者、敏感度)定义了过滤器和重排序器依赖的索引列。预处理转换(空格归一化、表格展平、脚注剥离)则定义了下游看到的所有内容的规范文本。

这些都不是表面文章。Chroma 在 2026 年的分块评估显示,超过 20% 的重叠会导致精确率急剧下降,而召回率几乎没有提升;Vectara 的基准测试发现,递归 512-token 切分能达到 69% 的准确率,而盲目应用语义分块(semantic chunking)则下降到 54%,因为它产生了 43-token 的碎片,太短了以至于生成器无法进行逻辑推理。分块器不仅决定了什么被索引;它还决定了检索可能恢复什么,以及模型有多少“思考空间”。一个 43-token 的片段并不是同一 Schema 下更小的分块。它是一个完全不同的 Schema。

Schema 构想还解释了最令人沮丧的 RAG 事故类型:分块器改动上线后,离线评估的检索召回率看起来没问题,但用户报告的准确率却一落千丈。检索出来的分块很干净。但这些分块无法回答任何问题。这是因为在下游消费者(生成器)的底层,Schema 发生了变化,而该生成器是针对不同的行宽进行过调优的。

每一次分块器的改动都是一次迁移

一旦你接受了分块器定义 Schema 的观点,操作上的含义就会变得异常清晰。

你无法在不重新嵌入整个语料库的情况下更改分块器。旧的向量是由旧的分块生成的;新的分块需要新的向量;边界的改变意味着旧行和新行不是一一对应的,因此你无法通过 ID 进行 diff。你需要完整的重建、双索引窗口、针对预留查询(held-out queries)的质量审计以及切换计划。生产环境中的主流模式——由 Google Cloud、Langchain 和几乎每家向量数据库供应商所记录——是蓝绿索引(blue/green indexing):在旧索引的阴影下建立新索引,在摄取期间进行双写,针对两者运行查询并对比,然后将一定比例的流量引导至新索引,随着信心增强逐步提升比例。

这不是可选的严谨性。这是判断新分块器是变好了、变坏了,还是其形状改变大到足以让“平均水平的提升”掩盖了“在排名前五的查询类型中出现灾难性退化”的唯一方法。夜间静默的重新嵌入无法提供任何这类信号。你在三天后客户投诉时才发现回退,而那时你为了节省存储空间已经删除了旧索引。

成本和时机也很重要。重新嵌入一个包含数百万文档的语料库并非免费——计算资源、阴影索引的存储、对比期间的双重查询成本,以及审计结果的人力成本。将分块改动视为预处理的团队往往会低估这些成本,这导致了最糟糕的失败模式:一个只部署了一半的分块迁移,让旧索引和新索引以一种意想不到的方式并存,并在某人意识到关于同一政策的两份文档会根据分片返回不同的分块之前,检索质量可能已经损坏了数周。

观测性缺口:分块 Bug 在三步之外显现

将分块器(chunker)视为模式(schema)最难的部分在于,分块器 Bug 很少以分块器 Bug 的形式呈现。

考虑一个常见的故障模式:分块边界恰好落在通用规则及其例外情况之间。“退款在七个工作日内处理。”位于一个分块中。“企业账户的退款需要 CFO 批准,可能需要长达三十天。”位于下一个分块中。针对“企业退款需要多久”的查询,检索到了第一个分块,生成器自信地回答“七个工作日”,客户得到了错误的答案。这会出现在哪里?在 LLM 评估中。追踪记录会显示:检索召回率(retrieval recall)看起来没问题,生成效果看起来没问题,但与地面真值(ground truth)不匹配——可能需要更好的提示词工程(prompting)。

不。它需要让例外情况与规则存在于同一个分块中。这个 Bug 是分块器 Bug,但信号却是生成质量信号,距离原因有三层间接关系。这就是观测性缺口,它使得分块在操作上变得非常困难。你检测了检索召回率、回答忠实度(answer faithfulness)、引用正确性和延迟——而当边界规则产生语义断裂的行时,这些指标都不会报警。即使是投资于 RAG 观测栈(如 Langfuse、DeepEval、Braintrust 风格的追踪)的团队,也很少有“分块边界完整性”指标。

实际的解决办法是添加你的 RAG 管道目前尚未意识到其需要的分块器级测试:

  • 边界哈希(Boundary hashes):计算每个文档分块边界的哈希值并跟踪漂移。如果同一个文档在两次摄取之间产生了不同的边界,且上游没有变化,那么分块器中存在非确定性——而模式中的非确定性就是一个 Bug,句号。
  • 跨边界评估(Cross-boundary evals):维护一小组标记过的查询集,这些查询的答案特别跨越了可能的分块边界(带有例外的规则、多步指令、带有标题的表格)。将这组查询的召回率与整体召回率分开跟踪。
  • 分块长度分布(Chunk-length distributions):对分布的变化报警,而不仅仅是均值。如果平均 Token 数看起来很稳定,但第五百分位数从 80 个 Token 掉到了 12 个,这意味着分块器在某些地方正在产生一类新的退化分块。
  • 按源抽检(Per-source sampling):随机抽检每个新语料库摄取的分块,并现场检查结构性 Bug(如孤立的标题、被拆开的代码块、被截断的表格)。自动化目前还无法取代这里五分钟的人工检查。

这些指标都不会出现在默认的观测栈中。你必须自己构建它们,因为默认栈检测的是检索层产出的内容,而不是分块器决定的内容。

“分块器即模式”的规范

如果你接受了这个设定,工程规范也就顺理成章了。这就是你已经应用在数据库模式上的那一套规范——毫无保留地应用在定义你向量索引的工件上。

为分块器设定版本。 每次分块器的更改都会获得一个版本号,作为元数据存储在每个分块中,并包含在索引名称本身中。你应该能够询问索引中的任何一行“你是哪个版本的分块器生成的”,并且你应该能够同时运行两个版本而不会产生混淆。这也意味着你的检索代码和评估工具会锁定一个分块器版本,就像应用代码锁定迁移版本号一样。

编写迁移计划。 在合并分块器更改之前,写下:更改的原因、预期的质量影响、证明它的评估、重新嵌入的成本、推出计划(影子索引、双读窗口、切换标准)以及回滚程序。这就是成熟数据团队中模式迁移 PR 的样子。这也是成熟 RAG 团队中分块器 PR 应有的样子。

维护双索引窗口。 在切换时,让两个索引同时运行一段可衡量的周期——而不仅仅是冒烟测试。将一小部分查询路由到这两个索引,比较检索重叠度和答案一致性,只有当指标达到你的阈值时才扩大规模。在旧索引承载 100% 流量并经过至少一个完整的业务周期之前,保持旧索引处于热备状态以便回滚。

固定元数据契约。 如果过滤器和重排序器(rerankers)使用了分块元数据字段,那么这些字段就是模式的一部分。在分块器输出中将 "section" 重命名为 "heading" 而不更新下游过滤器,会无声地破坏任何依赖该过滤器的查询。像对待关系模式中的列名一样对待元数据字段名和类型——重命名需要向后兼容的双写期。

将真相源与表示形式分离。 保持原始语料库永久化,而向量索引是派生出来的。这是让这份清单上所有其他规范成为可能的唯一架构决策。如果你无法通过文档化的程序从源头重建索引,你就没有模式迁移方案,你只有一道单向门。

让情况变得更糟的组织裂缝

最后一个故障模式不是技术性的,而是组织性的。

在许多团队中,拥有分块器的人(数据工程、摄取团队、平台 ML)并不是拥有检索评估的人(应用 ML、产品工程、交付面向用户功能的团队)。分块器团队优化的是吞吐量、摄取可靠性和上游源覆盖范围。评估团队优化的是固定查询集上的端到端质量。这两者不是同一个优化目标,当它们发生分歧时,两个团队都无法掌握全貌。

症状是:某个分块器更改上线了,因为它将摄取吞吐量提高了 15%,并且“检索指标在我们的基准测试中看起来没问题”。然后面向用户的质量下降了,因为评估团队的基准测试没有涵盖新分块器破坏的跨边界查询类。分块器团队不知道该基准测试的存在。评估团队不知道分块器已经更改。根因分析需要一周时间,因为每个团队都首先从排除自己那一层开始。

这里的解决方法枯燥且有效:让分块器和端到端检索评估共享一个负责人,或者至少共享一个 CI 门槛。如果分块器 PR 导致检索评估退化,无论哪个团队编写了这两个工件,该 PR 都应该无法通过 CI。在那个门槛建立之前,你只能依靠善意和 Slack 消息来捕捉系统本可以自动捕捉到的退化。

“预处理”是你打下的最昂贵的标签

最深层的错误在于语言表述。将分块器(chunker)称为“预处理”,会使其显得微不足道、处于上游,且不值得生产环境运维人员关注。这种标签为那些 Notebook 级别的代码、无版本管理的部署,以及分块器与其所服务的系统之间的所有权分离提供了借口。

但分块器并不是预处理。它是系统中决定什么可以被检索、模型可以用什么来思考,以及每一个下游指标形状的关键部分。它就是 Schema。围绕 Schema 的规范——版本控制、迁移、双读、发布计划、所有权——正是你的分块器所需要的规范。这并非因为 RAG 是一个特殊领域,而是因为索引化的派生数据一直都需要这种规范,你没有任何理由仅仅因为它是用 Python 而不是 SQL 编写的就对分块器网开一面。

那些在下一次事故复盘中不需要讨论这些问题的团队,是已经将分块器视为基础设施、锁定了其版本、编写了迁移计划,并将其接入了管控整个检索技术栈的 CI 流程的团队。搭建这些只需要一周时间。但它能防止数月莫名其妙的回归问题。其回报是显而易见的。

References:Let's stay in touch and Follow me for more thoughts and updates