抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

做 RAG 时,第一个容易陷进去的参数是 chunk_size。500 字还是 1000 字、overlap 取 50 还是 100,大家常凭几个问答效果争论。这个讨论缺少一个前提:我们希望每个 chunk 保住什么证据。

我不会先从字数出发,而是整理真实问题,为每个问题标注“最小充分证据”。切块策略能让这些证据稳定被召回,条件和结论没有被拆散,同时不带进大段无关内容,才算有效。

用问题验证切块,而不是先猜 chunk_size

先标最小充分证据

问题“任务失败后默认重试几次”需要的证据,不只是“重试 3 次”这一句。若文档上一段写着“离线同步任务”,同一行右侧表头写着“生产环境默认值”,两者都是答案成立的条件。只标答案短语,会奖励一个容易断章取义的切块器。

我给每个问题保存:问题文本、目标 source/version、必要 span、适用条件、允许的等价证据和不可接受的干扰证据。span 可以跨相邻段落,但要尽量小,避免把整页都标成正确答案。

这份标注首先用于测 retrieval。固定 embedding、索引参数和 top-k,分别跑不同切块策略,统计充分证据 recall、条件完整率和噪声 token。生成模型先不参与,否则它可能凭已有知识答对,掩盖检索根本没有拿到依据。

DPR 与 RAG 的工作都把 retrieval 与 generation 明确分成可讨论的部分。工程评测也应沿用这个边界:先问“证据有没有被拿到”,再问“模型有没有用对证据”。整体答案正确率无法定位 chunker 的问题。

结构边界通常比字符边界可靠

技术文档自带标题、段落、列表、表格、代码块和引用关系。它们比每 N 个字符切一刀更接近作者表达的知识单元。我先解析文档结构,再在过长节点内部二次拆分。

标题需要作为祖先上下文附在子块上,但不用在每个 chunk 重复整条目录。可以保存 section_path 结构字段,embedding 文本只加入最必要的父标题。这样检索“生产环境超时”时知道当前段属于哪个模块,又不会让大量相同标题主导相似度。

列表要判断项目是否共享前置句。下面有十个配置项时,单个列表项可独立索引,但要附上引导句和字段含义。定义列表则尽量把术语与解释放在一起。纯字符切分最容易把 key 留在上一块、value 留在下一块。

代码块与配置块不按自然语言句子拆。过长代码以函数、类或配置段为单位,附文件路径、语言和版本。用户搜错误栈时需要精确 token,代码索引可以同时建立关键词通道,不必指望 embedding 记住每个类名。

表格需要重建行列语义

把 Markdown 或 HTML 表格直接转成连续文本,常会丢列头与合并单元格。用户问“高优先级任务默认并发是多少”,检索命中的可能只有一行 高 | 20 | 5,模型无法知道 20 是并发还是超时。

我会把每行重建成带表名和列名的记录:

1
2
3
4
5
表:任务资源默认值
环境:生产
优先级:高
最大并发:20
失败重试:5

小表可以整表作为父块,每行作为子块;检索先命中行,再把父表头和相邻必要行带入上下文。大表则按业务键分组,不能整张塞入。合并单元格的值向下填充,但保留原始坐标,答案引用仍能回到原表。

表格有脚注时,脚注与对应列/行建立引用关系。很多限制条件藏在表下的一句话里,只召回数据行会得到错误结论。最小充分证据标注能直接暴露这类切分缺陷。

父子块比盲目 overlap 更可控

固定 overlap 能缓解边界截断,但也制造大量近重复 chunk。top-k 被同一段的五个重叠版本占满,其他来源进不了上下文;更新时还要判断哪些重叠块受影响。

父子块更容易解释。子块用于精确召回,父块保存完整小节;命中后按问题复杂度扩展父块或相邻兄弟。扩展必须有 token 上限和去重,不能命中一句就把整篇文档搬进来。

邻接扩展也要看结构。代词“该配置”需要上一段,结论后的例外条件可能在下一段。可以在 parser 阶段建立 previous/next/parent,重排后只为高分候选选择性扩展。比所有 chunk 固定重叠 20% 更节省上下文,也更容易审计来源。

长文档还需要跨章节关系。术语定义在开头,操作步骤在后面,二者不适合合并成一个巨块。把术语作为实体或 glossary relation 关联到步骤 chunk,查询时通过结构化关系补充定义。

切块版本必须能灰度和回滚

切块结果不是一次性离线产物。parser 修复了表格,chunker 调整了父子规则,embedding 模型也可能升级。若所有向量写进同一 collection 且没有版本,线上效果变化后无法知道是哪一步导致。

每个 chunk 保存 source_versionparser_versionchunker_versionembedding_model 与内容哈希。索引发布生成独立 index_version。同一批问题在旧、新索引离线回放,通过后灰度部分查询,最后切换 alias;回退只需切回旧 alias。

增量更新按 source version 做。文档某小节变化,只重建受影响结构节点及关系,不必全库重算;已删除片段先 tombstone,在检索层立即不可见,再异步清理物理向量。这样新旧版本不会一起回答。

评测报告除了 recall@k,还要显示每个问题缺了哪个条件、带入多少重复 token、命中几个不同来源。平均分提高可能掩盖关键制度类问题退化,重要问题应设单独门槛。

生成阶段要验证引用覆盖

检索拿到充分证据后,生成仍可能只引用其中一半。答案中的每个关键结论要能映射到具体 source span;适用条件不能从最终文本里消失。多片段组合时,检查它们是否属于兼容的版本和环境。

若问题需要两条证据而只召回一条,系统应指出缺口,不让模型补全。若同一配置在两个有效文档中冲突,返回冲突与 owner,而不是用相似度高低替代治理规则。

我会把错误反馈回切块层:没有召回、召回不完整、噪声过多、表头丢失、代码标识不命中、引用定位失败。每类问题的修复方式不同。统一改大 chunk_size,通常会改善一种错误,又扩大上下文噪声和成本。

文档切块没有全局最佳数字。可用的方法是从真实问题和充分证据出发,让结构解析、召回与生成分别接受检验。这样每次参数调整都有可比较结果,RAG 也从“试几个数字看感觉”变成可回归的工程系统。

参考论文与资料