Small-to-Big:小块检索与上下文扩展
Small-to-Big 的思路是:用较小的文本单元检索,命中后取回更大范围的原文,交给模型阅读。 检索单元与最终放进上下文的单元可以不同。
父子块检索(Parent Document Retrieval)和句子窗口(Sentence Window)是这种思路的两种实现。前者根据预先保存的父子关系扩展,后者根据句子位置扩展。
为什么要把检索和阅读分开
分块时常遇到一个矛盾:小块的主题更集中,便于定位具体内容,但可能缺少解释它所需的上下文;大块能保留完整段落,却可能把多个主题压进同一个向量,让某条细节难以被检索到。
例如,差旅制度的“提交期限”小节包含三句话:
① 员工应在行程结束后 30 个自然日内提交报销申请。
② 如遇系统故障,应在期限内登记工单。
③ 工单处理期间暂停计算提交期限。用户问“系统坏了,出差回来超过 30 天还能提交吗?”如果检索只返回①,模型会缺少暂停计时的规则;如果只返回③,又可能漏掉“在期限内登记工单”这个前提。
把这个小节整体作为一个 chunk 可以解决眼前的问题。但在篇幅较长的小节中,还可能有发票种类、审批流程等其他内容;直接用整个小节检索,又可能让提交期限这项细节被其他主题稀释。
Small-to-Big 允许分别选择两种粒度:
建库:较完整的原文 → 切成小片段 → 小片段进入检索索引
↘ 保存回到原文的位置或关系
查询:问题 → 命中小片段 → 根据关系扩展原文 → 模型阅读并回答它改变的是检索结果的返回方式。小片段帮助找到位置,扩展出的原文补齐条件、解释和例外。小块是否更容易命中仍取决于内容和模型,不能无限缩小到失去语义的几个词。
父子块:通过父块 ID 取回完整内容
父子块检索先确定较大的父块,再把每个父块切成较小的子块。父块可以是一条完整规定、一个章节,也可以是一篇较短的文档。
建库时保存两部分数据
沿用前面的制度,可以把整个“提交期限”小节保存为父块 P7,再切出两个子块:
父块 P7:提交期限
① 员工应在行程结束后 30 个自然日内提交报销申请。
② 如遇系统故障,应在期限内登记工单。
③ 工单处理期间暂停计算提交期限。
子块 C71:①
子块 C72:②③子块生成 embedding 并写入向量索引。每条子块记录同时保存 parent_id,用于回查父块。父块原文保存在可按 ID 读取的文档存储中;在这一基本方案里,父块本身不必生成向量。
| 存储位置 | 保存什么 |
|---|---|
| 子块索引 | child_id、子块向量、parent_id,以及必要的来源信息 |
| 父块存储 | parent_id、父块完整原文、文档版本与原文位置 |
两部分数据可以存放在同一个数据库,也可以由不同存储承担。检索器需要能从子块 ID 找到所属父块的正文。
查询时先找子块,再读父块
假设问题命中了 C72,返回过程是:
用户问题
→ 向量检索命中 C72
→ 读取 C72.parent_id,得到 P7
→ 从父块存储读取 P7 的原文
→ 将 P7 中的①②③一起交给模型模型现在可以同时看到正常期限、工单登记条件和暂停计时规则。它可以说明哪些时间暂停计入期限;是否确实超期,还需要用户提供行程结束时间、工单登记和处理时间等信息。
如果一次检索同时命中 C71 和 C72,它们都指向 P7,最终只需返回一份父块。这里的去重依据是父块 ID,不需要让模型判断两个片段是否属于同一小节。
父块的边界决定扩展效果。如果整本手册都被设为一个父块,命中一句话就会带回整本手册。因此,父块通常应覆盖一个相对完整的主题;过长章节仍可以划分成多个父块。
Sentence Window:通过句子位置取回邻近内容
Sentence Window 不要求先建立较大的父块。它将文档切成句子,以句子作为检索单元,并保留句子所属文档和顺序。命中一句话后,取回其前后一定范围内的句子。
前面的三句话可以分别作为索引记录:
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 数据工程。