Skip to content

Small-to-Big:小块检索与上下文扩展 ​

Small-to-Big 的思路是:用较小的文本单元检索,命中后取回更大范围的原文,交给模型阅读。 检索单元与最终放进上下文的单元可以不同。

父子块检索(Parent Document Retrieval)和句子窗口(Sentence Window)是这种思路的两种实现。前者根据预先保存的父子关系扩展,后者根据句子位置扩展。

为什么要把检索和阅读分开 ​

分块时常遇到一个矛盾:小块的主题更集中,便于定位具体内容,但可能缺少解释它所需的上下文;大块能保留完整段落,却可能把多个主题压进同一个向量,让某条细节难以被检索到。

例如,差旅制度的“提交期限”小节包含三句话:

text
① 员工应在行程结束后 30 个自然日内提交报销申请。
② 如遇系统故障,应在期限内登记工单。
③ 工单处理期间暂停计算提交期限。

用户问“系统坏了,出差回来超过 30 天还能提交吗?”如果检索只返回①,模型会缺少暂停计时的规则;如果只返回③,又可能漏掉“在期限内登记工单”这个前提。

把这个小节整体作为一个 chunk 可以解决眼前的问题。但在篇幅较长的小节中,还可能有发票种类、审批流程等其他内容;直接用整个小节检索,又可能让提交期限这项细节被其他主题稀释。

Small-to-Big 允许分别选择两种粒度:

text
建库:较完整的原文 → 切成小片段 → 小片段进入检索索引
                             ↘ 保存回到原文的位置或关系

查询:问题 → 命中小片段 → 根据关系扩展原文 → 模型阅读并回答

它改变的是检索结果的返回方式。小片段帮助找到位置,扩展出的原文补齐条件、解释和例外。小块是否更容易命中仍取决于内容和模型,不能无限缩小到失去语义的几个词。

父子块:通过父块 ID 取回完整内容 ​

父子块检索先确定较大的父块,再把每个父块切成较小的子块。父块可以是一条完整规定、一个章节,也可以是一篇较短的文档。

建库时保存两部分数据 ​

沿用前面的制度,可以把整个“提交期限”小节保存为父块 P7,再切出两个子块:

text
父块 P7:提交期限
  ① 员工应在行程结束后 30 个自然日内提交报销申请。
  ② 如遇系统故障,应在期限内登记工单。
  ③ 工单处理期间暂停计算提交期限。

子块 C71:①
子块 C72:②③

子块生成 embedding 并写入向量索引。每条子块记录同时保存 parent_id,用于回查父块。父块原文保存在可按 ID 读取的文档存储中;在这一基本方案里,父块本身不必生成向量。

存储位置保存什么
子块索引child_id、子块向量、parent_id,以及必要的来源信息
父块存储parent_id、父块完整原文、文档版本与原文位置

两部分数据可以存放在同一个数据库,也可以由不同存储承担。检索器需要能从子块 ID 找到所属父块的正文。

查询时先找子块,再读父块 ​

假设问题命中了 C72,返回过程是:

text
用户问题
  → 向量检索命中 C72
  → 读取 C72.parent_id,得到 P7
  → 从父块存储读取 P7 的原文
  → 将 P7 中的①②③一起交给模型

模型现在可以同时看到正常期限、工单登记条件和暂停计时规则。它可以说明哪些时间暂停计入期限;是否确实超期,还需要用户提供行程结束时间、工单登记和处理时间等信息。

如果一次检索同时命中 C71 和 C72,它们都指向 P7,最终只需返回一份父块。这里的去重依据是父块 ID,不需要让模型判断两个片段是否属于同一小节。

父块的边界决定扩展效果。如果整本手册都被设为一个父块,命中一句话就会带回整本手册。因此,父块通常应覆盖一个相对完整的主题;过长章节仍可以划分成多个父块。

Sentence Window:通过句子位置取回邻近内容 ​

Sentence Window 不要求先建立较大的父块。它将文档切成句子,以句子作为检索单元,并保留句子所属文档和顺序。命中一句话后,取回其前后一定范围内的句子。

前面的三句话可以分别作为索引记录:

text
S1:员工应在行程结束后 30 个自然日内提交报销申请。
S2:如遇系统故障,应在期限内登记工单。
S3:工单处理期间暂停计算提交期限。

如果命中 S2,并设置每侧扩展一句,返回给模型的是 S1 + S2 + S3。检索时比较的是 S2 的向量,生成答案时阅读的是整个窗口。

实现时,可以在建库时为每句预存窗口文本,也可以只保存 document_id 和句子序号,查询时再读取邻句。无论采用哪种方式,都应保持同一文档版本内的正确句序。

窗口以位置为依据,所以适合解释信息就在附近的正文。它也可能跨过小节边界,带回不相关内容;可以根据标题、段落等结构限制扩展范围。若多个命中产生重叠窗口,例如 [10, 14] 和 [12, 16],可合并为 [10, 16],避免重复放入上下文。

两种扩展方式怎样选择 ​

对比项父子块检索Sentence Window
检索单元子块,通常包含一句或几段相关文本句子或极短片段
扩展依据预先保存的父子关系命中位置前后的邻接关系
返回范围预定义的父块以命中句为中心的窗口
更适合的文档条款、章节等层级清楚的文档上下文通常在相邻句中的连续正文
主要风险父块过大,带回多个无关主题窗口过窄漏条件,过宽又跨入其他主题

若已有清楚的章节结构,可以先考虑父子块;若文档缺少稳定层级,但句序可靠、解释集中在附近,可以考虑 Sentence Window。两者也可以结合,例如命中句子后扩展到所属段落,并限制在同一条款内。

它们与增加 chunk overlap 的区别在于扩展发生的时间。Overlap 在建库时让相邻块包含重复文本,重复部分也会参与编码;Small-to-Big 则允许只用小单元匹配,命中后再补回更多原文。

扩展能补齐哪些信息 ​

这类方法适合处理“已经找到相关位置,但返回内容不完整”的问题。如果例外条款写在另一章,父块或邻句窗口可能都覆盖不到,需要沿文档中的引用继续检索;如果正确位置根本没有命中,扩大返回范围也无从补救。

扩展还应受上下文长度、文档版本和访问权限约束。子块可见不代表整个父块都可见;读取父块或窗口时,应确认新增内容属于用户可访问的同一版本资料。具体权限和索引更新机制见企业 RAG 数据工程。

参考资料 ​

持续学习,持续实践。