跳到正文

RAG PROJECT INTERVIEW · Q07 / 25

RAG Chunk 为什么不能只按固定字数切?

从 Token 合并逻辑出发,解释固定长度为什么会拆散条件与结论,以及怎样利用标题、段落和内容类型保住语义。

作者与复核:吴师兄内容依据:公开 RAG 教程、GitHub 教学代码与工程推导查看关联教程 →查看吴师兄公开作品 →

25 道题目Q07
本页内容14 节

30 秒先说结论

固定长度只能作为上限,不能成为唯一边界。应先按标题、段落、列表、句子、表格等自然结构形成原子单元,再在 Token 预算内合并;超长内容继续在自然边界递归切分。每个 Chunk 还要继承章节路径和原文位置,避免子段落脱离上文。

“你们的 Chunk Size 设多少?”

这是 RAG 面试里很常见的一句追问。

很多人会马上报出一个数字,再补一句设置了多少 Overlap。听起来很具体,却没有回答最关键的问题:为什么这个数字适合你的文档和用户问题?

以下用一个教学示例来说明。

一段制度原文依次写了适用条件、办理步骤和例外情况。固定长度切分刚好把“但以下情况不适用”留在前一块,把后面的例外条目放进后一块。

用户搜索某个例外条目时,系统可能召回后一块。关键词命中了,前提却不见了。模型拿到的是一句局部事实,不是完整规则。

所以,Chunk 不是每隔固定字符切一刀。

它真正要解决的是:怎样让每个被检索到的内容块足够聚焦,又能独立说清一件事。

Chunk 到底在平衡什么?

RAG 需要把长文档拆成可检索内容,因为整份文档通常不适合直接做一次检索,也不可能每次都完整塞给模型。

但块并不是越小越好。

块太小,向量主题会更集中,却容易把条件、解释和结论拆开。用户问“什么情况下可以申请”,系统可能只找回申请步骤,缺少适用对象和限制条件。

块也不是越大越好。

块太大,多个主题会被压进同一个向量。检索得到的是一段平均语义,真正相关的几句话被大量无关内容包围。随后进入模型的上下文更长,噪声也更多。

所以一个可用 Chunk 同时满足两个目标。

它要足够聚焦,让检索能分辨主题;又要足够完整,让模型不必猜测被切掉的前提。

Chunk 太小与太大的不同问题

块太小容易丢前提,块太大容易混主题。合理切分要在语义完整与检索聚焦之间取得平衡。

“512 Token”只能成为候选实验起点,不能直接当作推荐配置,更不能成为方案理由。

不同资料的自然结构差异很大。FAQ 可能一组问答就能独立成块,制度条款需要保留条件和例外,操作流程需要保留步骤顺序,表格则依赖表头与行列关系。

如果所有内容都套同一个数字,表面上实现统一,实际是把复杂度转移给检索和生成。

一条可落地的切分链路是什么?

切分不应该从整篇纯文本开始。

文档解析阶段已经拿到标题、段落、列表、表格、图片说明、代码块、页码和布局区域。切分器应当利用这些结构,而不是先把它们拍平成字符串,再尝试猜回原来的关系。

可以把完整链路分成五步。

第一步,形成原子单元。

标题、正文段落、列表、表格、图片说明和代码块先按类型整理。属于同一个段落 ID 或布局区域的碎片先合并,不能因为 PDF 换页就立即断开。

第二步,按语义顺序合并。

从同一章节内依次加入原子单元。在不超过 Token 预算时,尽量保留本来连续的说明。遇到主题切换、标题切换或内容类型变化时,可以结束当前块。

第三步,处理超长单元。

如果单个段落本身超过上限,再沿句号、分号、列表项或段内小标题递归拆分。只有自然边界仍然无法满足预算时,才使用更细的长度兜底。

第四步,补齐上下文。

正文继承文件名、标题路径和页码;表格携带理解数据所需的表头;列表保留前导句;代码块保留语言与所属小节。

第五步,执行质量检查。

检查空块、过短块、超长块、重复块、缺少标题路径的块,以及被切断的表格和列表。通过后再进入向量化和索引。

从结构化文档到可检索 Chunk 的五步链路

解析先提供结构,切分再形成原子单元、按预算合并、递归处理超长内容并补齐上下文,最后通过质量检查入库。

这条链路里,Token 预算一直存在,但它是上限,不是第一把刀。

原子单元先告诉系统哪里不该随便切,预算再告诉系统一块最多能装多少内容。这样才能兼顾结构和模型输入限制。

实现时还要把“边界判断”和“长度计算”拆开。边界判断负责给每个原子单元标记章节、内容类型、是否可继续合并;长度计算只回答加入下一个单元后是否超过预算。两者混在一个循环里,常见后果是为了满足长度,悄悄跨过标题、表格或列表边界,最后很难解释某一块为什么这样生成。

下面是一段只表达控制流程的教学伪代码。它没有绑定任何解析框架,也没有给出可直接照搬的阈值:

for unit in parsed_units:
    if changes_semantic_scope(current, unit):
        emit(current)
        current = new_chunk(unit)
    elif token_count(current, unit) <= max_tokens:
        current.append(unit)
    else:
        emit(current)
        current = new_chunk_or_split(unit)

emit(current)

真正落地时,changes_semantic_scope 至少要读标题路径、内容类型和父结构。new_chunk_or_split 也不能只调用固定长度切分:普通长段落可以沿句子递归处理,表格、代码和图片说明则应进入各自的降级策略。这样一条 Chunk 才能从结果反查到具体规则,而不是只能看到一个无法解释的长度。

还要固定规范化步骤的版本。换行合并、页眉去除、句子分隔规则只要发生变化,边界和 ID 就可能一起变化。若没有记录解析版本与切分版本,同一份文档重跑后出现不同 Chunk 时,团队无法判断是原文变化、解析变化还是参数变化。

标题应该单独切一块吗?

标题处理很容易走向两个极端。

一种做法是把每个标题单独做成很短的 Chunk。问题是标题通常只有几个词,缺少能直接回答问题的正文,还会产生大量相似候选。

另一种做法是完全丢掉标题,只保留正文。子段落里如果大量使用“本规则”“上述情况”“该材料”之类表达,脱离标题后就不知道主题是什么。

更稳妥的方式,是把标题路径作为结构字段保存。

例如某段正文可以继承“申请流程、材料要求、特殊情形”这样的层级路径。检索返回时,系统既能展示正文,也能告诉用户它属于哪一节。

是否把标题文字拼进用于 Embedding 的内容,则要通过评测决定。

标题明确、正文依赖代词时,加入标题可能补充语义。标题高度重复、正文已经自包含时,反复拼接同一标题可能让不同 Chunk 变得更像。

所以“保存标题路径”和“把标题写进向量文本”是两个决定。前者服务结构与追溯,后者影响检索表示,不能混成一个开关。

表格、列表和代码为什么不能按普通正文切?

表格的最小语义单位不是一句话,而是表头、行名、列名和单元格之间的关系。

小表格如果能在预算内完整保留,可以作为一个整体。大表格超过上限时,可以按行组或有意义的分区拆分,但每个块都要继承表头,并标记自己属于哪张表以及哪一部分。

只把数据行复制出来,虽然节省 Token,却让数值失去含义。

列表的问题在于前导句。

原文可能写“以下情况不予受理”,后面跟着若干列表项。如果每个列表项单独成块,检索命中“材料不完整”时,模型可能看不到“不予受理”这个共同结论。

处理时应优先把前导句和列表项放在一起。列表太长需要拆分,也要让每一块保留前导语义。

代码依赖函数、参数、分支和返回值的完整关系。

从函数中间切开,后半块可能只剩一个条件分支,看不出输入和目的。切分器至少要知道代码块边界,超长时再按函数、类或明确代码结构处理,而不是按普通句号拆分。

图片说明同样需要和对应图片或图表建立关系。如果只保存“如下图所示”,却没有图片内容与标题,这个 Chunk 几乎没有检索价值。

Overlap 能解决什么,又不能解决什么?

Overlap 的作用,是让边界附近的信息至少在一个 Chunk 里保持完整。

假设前一块在一句话中间结束,后一块从半句话开始。让后一块继承前一个完整句子,可以避免两边都缺少语义。

但 Overlap 不是万能修复。

它不能把已经错误交叉的双栏重新排好,不能给丢失的表头补回列名,也不能把被 OCR 漏掉的文字重新创造出来。

固定重叠还会带来副作用。

同一段内容被复制到相邻多个 Chunk,增加存储和上下文消耗。检索时,这些近重复块可能一起排在前面,挤掉真正互补的证据。

因此,Overlap 更适合对齐句子或段落边界。

当前一块确实在同一语义单元中结束时,后一块继承必要句子。边界本来就是章节切换时,不应为了凑固定长度复制上一节内容。

Size 与 Overlap 的职责边界

Size 控制一块内容的预算,Overlap 保护边界附近的必要上下文。阅读顺序、表格结构和 OCR 缺失必须在更早环节处理。

这也是为什么 “Chunk Size 加 Overlap” 不能概括完整切分方案。

面试官更想听到的是,你先识别了什么结构,哪些内容被视为原子单元,哪些边界允许断开,Overlap 又只在哪些情况下启用。

前后 Chunk 关系还要不要保存?

即使边界合理,也会遇到单个问题需要相邻证据的情况。

例如检索命中的块以“但以下情况除外”开头。它本身可能包含完整例外列表,却没有说明前面讨论的是什么规则。此时,与其把所有 Chunk 都做成很大,不如保存前后关系,在命中后按需要扩展上下文。

每个 Chunk 可以记录前一个和后一个内容块的稳定标识,同时保留章节路径和原文位置。系统发现候选缺少前导句、引用对象或续表信息时,再读取相邻块进行补充。

这种扩展也不能无条件执行。

如果前后块属于不同章节,直接拼接可能混入另一主题。如果每次命中都把前后各拉一块,实际效果接近把 Size 隐性放大,还会增加重复内容。

因此,扩展前至少要检查章节是否一致、内容类型是否允许连接,以及当前候选到底缺少什么。列表块可以向前寻找前导句,续表块可以寻找表头,普通自包含段落则不必扩展。

这里要区分两件事。

Overlap 是入库时复制一部分边界内容,所有查询都会承担它的存储与重复成本。前后关系是检索后按需读取,没有需要时不增加候选正文。

两者都能补上下文,但使用时机不同。边界处长期都需要同一句话,可以考虑自然边界 Overlap;只有少数查询需要额外解释,更适合按关系动态扩展。

四个坏样本,分别说明了什么?

以下四个坏样本均为教学场景,只用于说明排查方法,不代表真实项目结论。

条件与结论被拆开(教学示例)

前一块写适用对象,后一块写办理结论。用户问题只命中结论词,召回结果缺少适用条件。

先看原子单元是否定义过细,再看段落和列表前导句是否被保留。盲目增加固定 Overlap 可能暂时补回某个例子,却无法保证其他边界也合理。

一个块里混入多个主题(教学示例)

同一块同时包含申请、审核、收费和例外规则。用户只问收费,候选却带回大量无关内容。

先检查标题切换和自然段边界,再看 Size 上限是否过大。不能只通过调低检索门槛解决,因为根因是表示本身不聚焦。

表格数据有值却没有表头(教学示例)

正确数值可能被召回,模型却不知道它对应费用、期限还是数量。

检查表格是否作为结构单元处理,长表格拆分后是否为每个块继承表头,以及表格标题和页码是否保留。

前几条结果几乎一模一样(教学示例)

这通常与固定重叠或重复入库有关。

先查看相邻 Chunk 的重复比例和来源位置,再判断是 Overlap 过大,还是同一内容被不同版本写入。不能仅靠 Rerank 掩盖重复,因为候选空间已经被同类片段占满。

坏样本比一个平均分更能说明切分质量。它告诉你是块太小、块太大、结构丢失,还是重复过多。

参数应该怎样用实验选?

这道题只保留实验原则:准备带原文证据位置的问题,固定向量模型、检索、候选数和重排,只替换结构切分方案。查看正确证据是否完整、标题与条件是否继承,以及近重复片段是否挤占候选,不能只看最终答案。

问题集不能只挑“答案正好落在一个自然段里”的简单题。至少要覆盖四类边界:答案跨两个段落,结论依赖前置条件,长列表中的单个条目,以及表格或代码等非普通正文。每条问题同时标记最小必要证据范围,评测时才知道系统召回的是完整证据,还是只碰巧命中几个关键词。

对照实验也要保持可复现。为每种切分方案保存规则版本、长度分布、Chunk 数量和相邻重复比例;查询侧固定索引数据、Embedding 模型、过滤条件、Top-N 与重排配置。若一边改切分,一边换模型或调整候选数,最终即使指标上升,也无法把变化归因到语义边界。

结果应分三层读。第一层是候选覆盖:正确证据有没有进入前 K 条。第二层是证据可用性:被找回的块是否带齐条件、表头或步骤顺序。第三层是生成代价:拼接后用了多少块、多少 Token,近重复是否挤占上下文。只看最终答案会把生成模型的猜测能力混进切分评估,也会掩盖“答案对了但证据残缺”的风险。

上线前可以把两种方案并行写入隔离索引,用同一批脱敏问题回放。先比较离线结果,再对失败样本逐条看原文边界。确认新方案没有让重要类型退化后,才切换读取版本。这个流程不需要虚构一个普遍适用的提升比例,但能让面试官听到完整的验证闭环。

用同一批问题选择 Chunk 参数

固定检索与模型配置,只比较不同切分方案。结果同时观察证据排名、拼接块数、重复比例和上下文 Token。

至于 Size 和 Overlap 到底设多少、怎样做参数矩阵和版本化上线,继续看 Chunk Size 和 Overlap 怎么选。这里的主问题始终是“应该在哪里切”,不是背一组长度数字。

切分参数被追问时,回到证据边界

这些追问不要从某个固定 Token 数开始答。先判断一条证据的自然边界,再说明长度上限、重叠和特殊结构分别怎样兜底。

为什么不能直接用 512 Token?

可以把它当实验起点,但不能当通用结论。文档结构、问题粒度、向量模型输入和上下文预算不同,合适上限也会不同。更重要的是先确定语义边界,数字只负责限制最大长度。

Overlap 越大,语义是不是越完整?

不一定。更大重叠会复制更多内容,增加存储、上下文和近重复召回。Overlap 只应补必要的边界语义,并尽量对齐完整句子或段落。

标题要不要拼进每个 Chunk?

标题路径应当保存在结构字段里。是否加入 Embedding 文本要通过评测判断。正文依赖标题时可能有帮助,标题高度重复时则可能降低不同块的区分度。

单个表格已经超过上限怎么办?

不能因为“表格不可拆”就无限突破预算。可以按有意义的行组或分区切分,但每一块都要保留表头、表格标识和分段关系,确保数据仍能解释。

切分后召回率提高,就说明方案更好吗?

还不够。候选可能因为块更多和重复更多而提高覆盖,同时 Precision 下降、上下文变长。还要看证据排名、近重复、拼接块数、总 Token 和具体坏样本。

新文档类型进来,要不要全库重新调参?

先按类型观察。新文档可能只需要新增一种结构策略,不必改变所有旧内容。确认新类型的失败模式后,再决定局部重建还是全局调整。

面试官如果继续问“怎样定位一条坏 Chunk”,可以从结果向前倒查:先用引用定位原文页和区域,再读取 Chunk 的标题路径、原子单元列表、切分规则版本与前后块;确认原子单元是否已经缺失,再判断错误发生在解析、边界合并还是检索阶段。若解析结果本身就丢了列表前导句,调 Size 没有意义;若原子单元完整但合并跨了章节,才应修改切分规则。

如果问“如何监控线上质量”,不要回答只看平均 Chunk 长度。更有用的是分文档类型观察过短、超长、空块和近重复比例,记录必须跨块才能回答的问题,以及引用被用户打开后定位失败的样本。分布异常负责发现系统性变化,真实坏样本负责说明变化是否伤害答案,二者缺一不可。

把 Chunk 设计讲成一条证据链

收尾时沿着“结构原子—边界合并—来源继承—固定问题验证”组织,而不是报一个看似精确的通用数字:

Chunk Size 没有跨项目通用数字。我的思路是先利用解析结果中的标题、段落、列表、表格和代码结构形成原子单元,再在 Token 上限内按语义顺序合并。单个单元超长时,优先沿句子、列表项或段内小标题递归切分,长度切分只作为最后兜底。

每个 Chunk 会继承标题路径、页码和原文位置。表格拆分后保留表头,列表保留前导句。Overlap 只用于保护边界附近的必要上下文,不能修复阅读顺序错误、表格结构丢失或 OCR 漏字,也不能无限增大,否则会造成重复召回和上下文浪费。

参数选择上,我会固定向量模型、检索和重排配置,用同一批问题与正确证据比较不同切分方案,观察前 K 条是否找回证据、一个答案需要几个块、近重复比例和上下文 Token。持续运行时按文档类型监控长度分布与坏样本,新类型出现时先局部定位,再决定是否重建。

面试官最终会用四个问题检查这套设计:关键证据有没有被切断,超长内容怎样退化,Overlap 是否制造了重复候选,以及同一批问题能否复现参数选择。做过切分优化,就带上本人处理的文档类型、真实异常样本、候选参数和评测结果;没有做过的实验,只说验证方案。

INTERVIEW FOLLOW-UPS

面试官会怎样继续追问?

这些追问会继续检查方案取舍、验证方法和项目事实。
  1. Q01

    Chunk 太小和太大分别会伤害哪一段链路?

  2. Q02

    一个段落超过上限时,你按什么顺序递归切?

  3. Q03

    标题是单独成块,还是复制到正文块里?

  4. Q04

    你怎样用检索问题证明切分方案有效?

PROJECT CHECK

换成你的项目,这几件事能否说清楚?

  • 画出解析单元到 Chunk 的合并流程
  • 准备一个被固定长度切坏的样本
  • 说明超长段落的递归边界
  • 用同一评测集比较不同切分版本
用自己的项目经历练一次