固定窗口与递归切分
本节目标
你将从最容易验证的固定窗口开始,理解步长、重叠和终止条件;再学习“优先自然边界、最后才硬切”的递归策略。重点不是记住某个库的类名,而是能验证自己的切分器没有漏文、越界或死循环。
固定窗口:先建立确定性基线
设窗口上限为 ,相邻窗口重复 个单位,则步长为:
长度 时,窗口数近似为:
例如 ,stride 为 80,起点是 0、80、160;最后一块只取到文末。若 O=S,步长为 0,循环永远不会前进,所以配置校验不是“可选优化”。
一个可运行的最小实现
下面按 Python 序列索引切分。生产环境中的 units 应来自正确 tokenizer 或解析元素,不要先把 token ID 转回字符串再盲目拼接。
from collections.abc import Sequence # 导入只读序列接口,使列表和元组都能作为输入。
from typing import TypeVar # 导入类型变量工具,保留输入元素原本的类型信息。
T = TypeVar("T") # 用 T 表示“任意一种元素类型”,例如 token、句子或解析 element。
def fixed_windows( # 定义一个返回“起点、终点、窗口内容”的确定性切分函数。
units: Sequence[T], # 待切分的单位序列;这里不假设它一定是字符串。
*, # 后面的参数必须写成关键字,避免把 size 与 overlap 的位置写反。
size: int, # 每个窗口允许容纳的最大单位数,即 hard max。
overlap: int, # 与上一个窗口重复的单位数,用于保留邻接上下文。
) -> list[tuple[int, int, list[T]]]: # 每项使用半开区间 [start, end) 并携带实际内容。
if isinstance(size, bool) or not isinstance(size, int) or size <= 0: # bool 是 int 的子类,需单独拒绝。
raise ValueError("size 必须是正整数") # 无法形成正窗口时立即失败,而不是进入循环。
if ( # 接着验证 overlap 是否既是整数又能保证步长为正。
isinstance(overlap, bool) # 同样拒绝 True/False 这类伪整数。
or not isinstance(overlap, int) # 拒绝浮点数、字符串等不精确的配置。
or not 0 <= overlap < size # overlap 等于 size 会让步长 size-overlap 变成 0。
):
raise ValueError("要求 0 <= overlap < size") # 用清晰错误提示暴露无法前进的配置。
result: list[tuple[int, int, list[T]]] = [] # 建立输出容器,按原始顺序保存所有窗口。
start = 0 # 第一个窗口从第 0 个单位开始。
while start < len(units): # 只要起点还在输入范围内,就继续生成窗口。
end = min(start + size, len(units)) # 最后一窗可短于 size,但绝不越过文末。
result.append((start, end, list(units[start:end]))) # 切出 [start,end),并复制为独立列表。
if end == len(units): # 已经覆盖到文末时,不能再生成只含 overlap 的尾窗。
break # 立即结束,保证结果没有冗余窗口。
start += size - overlap # 以正步长前进,同时留下指定数量的重叠单位。
return result # 返回全部窗口;空输入会自然得到空列表。返回 start/end 而不只返回文本,是为了保留可验证来源 span。空输入返回空列表;最后一块到达文末后立即终止,避免产生只含 overlap 的额外块。
固定窗口能保证什么,不能保证什么
它容易保证:
- 输出确定;
- hard max 可预测;
- 来源单位能全覆盖;
- overlap 数可精确计算;
- 适合作为成本与召回基线。
它不能自动保证:
- 句子、条件和代词完整;
- 标题与正文不分离;
- 表头随数据行出现;
- 函数或 JSON object 不被切断;
- 相邻块不会混入两个主题。
因此固定窗口是“比较起点”,不是默认生产答案。
递归切分的直觉
递归策略准备一组从强到弱的候选边界,例如:
- 已解析的 section/paragraph;
- 空行;
- 换行;
- 语言相关句末;
- 空格;
- 无可用边界时 hard window。
面对超长片段,先尝试较强边界。若拆出的某个子片段仍超 hard max,就只对它使用更弱边界。最后把相邻小片段按顺序合并,直到加入下一个会超过预算。
split(part, boundary_index):
if units(part) <= hard_max:
return [part]
if no weaker boundary:
return hard_windows(part)
pieces = split_by_current_boundary(part)
if boundary made no progress:
return split(part, boundary_index + 1)
recursively split oversized pieces with weaker boundaries
merge adjacent complete pieces without crossing hard_maxLangChain 当前文档把递归字符切分列为常见通用起点;这是一种实用默认值,不是它在所有语料上最优的实验证明。其默认按字符计数,候选分隔符为 ['\n\n', '\n', ' ', ''],更贴近以空格分词的语言。中文、日文或泰文应按实际文本补充句末/全角标点或零宽空格等边界,并用 fixture 验证不会破坏语义。即使把长度函数换成 tokenizer 计数,分隔符仍只定义候选边界;hard max 仍须由目标模型的实际 token 限制验收。Unstructured 则先基于解析元素组合,仅在单元素过长时继续切分。两者输入抽象不同,不能只比较类名。
合并比“切开”更容易出错
只递归拆开而不重新合并,会产生孤立标题和一句话碎块。合并器应满足:
- 保持原始顺序;
- separator 的语义不能静默消失,避免
foobar式粘连; - 同 source、revision、ACL 和允许的结构边界内才合并;
- 候选超过 hard max 时先关闭当前块;
- 单个元素本身超过 hard max 时进入兜底窗口;
- 空白片段不生成 chunk,但来源偏移仍以规范化文本为准。
若先 strip() 再计算字符偏移,位置会漂移。更稳妥的顺序是:规范化换行和 Unicode,记录单位的字符区间,再从原规范文本切片。
语言、代码与结构边界
自然语言分句并不简单:
- 中文句号、问号与引号组合需要测试;
- 英文缩写、小数和 URL 会误触发句点规则;
- 没有空格的长串仍需 hard fallback;
- emoji、组合字符和 Unicode 规范化会影响字符偏移。
代码优先使用函数、类或语法树边界;Markdown fenced code 不应被普通段落分隔符拆散;表格优先按完整行切,并把表头作为检索上下文。上述规则将在 [[Knowledge/AI Agent Engineer/docs-CN/Chunking策略/03-结构化与语义切分|结构化与语义切分]]继续展开。
必测性质
用示例测试只能证明几个例子;切分器更适合检查不变量:
| 性质 | 失败意味着什么 |
|---|---|
| 所有来源单位至少被覆盖一次 | 内容丢失 |
每块长度 <= hard max | 下游可能截断或拒绝 |
| ordinal 连续、顺序稳定 | 引用与增量更新不可预测 |
| 每次循环都前进 | 某些配置会死循环 |
| overlap 只出现在预期窗口 | 重复成本失控 |
| 不跨 source/revision/ACL | 来源或权限污染 |
| 相同输入与版本生成相同结果 | 无法缓存、比较或重放 |
| 内容哈希与实际正文一致 | 向量/引用可能来自旧文本 |
边界用例至少覆盖:空文本、一个单位、恰好 hard max、hard max+1、没有任何 separator 的长串、全空白、多语言、超长代码块和超长表格行。
常见错误与排查
- 分隔符被删掉:检查合并后相邻词是否粘连,或将 separator 归属前一片/后一片。
- 短尾巴重复两次:到达文末后没有立即
break。 - 最后若干单位漏掉:窗口数量公式或
end取值错误。 - 递归没有进展:当前 separator 根本不存在,却仍对同一输入再次递归。
- 先切字符串后猜 token span:引用偏移和 tokenizer 长度对不上。
- 单元素越界被“允许”:hard max 已不再是 hard max。
练习
- 手算 的所有半开区间
[start,end)与实际重复量。 - 给上面的
fixed_windows添加 6 个unittest:空输入、恰好一窗、尾窗、overlap、非法配置和确定性。 - 设计一个 separator 优先级,使 Markdown 标题、代码 fence、段落与句子按强到弱排列;写出代码 fence 内出现空行时的处理规则。
- 解释为何“把切出的字符串重新拼起来等于原文”在有 overlap 时不能直接成立;提出去重后的验证办法。
掌握检查
- 我会推导 stride 与窗口数量。
- 我能证明循环前进并在文末终止。
- 我把 hard max 视为不可突破的契约。
- 我知道递归策略是“强边界优先 + 超长兜底 + 小片段合并”。
- 我会检查覆盖、顺序、边界隔离、哈希和确定性。
- 我不会把一个语言的分句正则推广到全部语料。
小结与下一步
固定窗口提供可复现下限,递归切分尽量保留自然边界;真正可靠的生产输入通常还带有解析结构。下一步:[[Knowledge/AI Agent Engineer/docs-CN/Chunking策略/03-结构化与语义切分|结构化与语义切分]]。
参考资料
来源获取日期:2026-07-22。返回 [[Knowledge/AI Agent Engineer/docs-CN/Chunking策略/00-目录|Chunking 策略目录]]。