数据截至 (上游 commit 3b55ae605435)
切块器全景
本章目标:看完能选对切块器。先讲它们共享的骨架
BaseChunker,再把 11 种策略按「复杂度阶梯」排开,最后给一张选型表。
1. 共享骨架:BaseChunker
所有切块器都继承 BaseChunker(src/chonkie/chunker/base.py:67)。子类只需实现一个抽象方法 chunk(text) -> list[Chunk](base.py:199-210),其余入口骨架由基类白送。
基类提供四种调用入口,职责各不相同:
| 入口 | 输入 | 干什么 | 源码 |
|---|---|---|---|
__call__ | 单串或列表 | 语法糖:自动分派到 chunk 或 chunk_batch | base.py:95-116 |
chunk_batch | 一批文本 | 顺序或多进程并行地逐条切 | base.py:212-233 |
achunk / achunk_batch | 同上 | 异步版,用 asyncio.to_thread 包同步实现 | base.py:235-260 |
chunk_document | Document | 切 Document 并把文档级元数据下放进每个块 | base.py:295-315 |
直觉:为什么入口要分这么多种?
因为切块器在不同场景下「输入形态」不同:
- 裸文本 → 你只想快速切一段字符串,用
__call__。 - 一批文本 → 切几千个文档,想吃满 CPU,用
chunk_batch(可开多进程)。 - Pipeline 里 → 上游传来的是
Document对象,得走chunk_document,因为它还要做一件特别的事:重切已有块。
巧妙处 1:chunk_document 的「双模式」
chunk_document 不是简单地切 document.content。它先看文档有没有已经切好的块(base.py:308-313):
# 示意,非源码(提炼 base.py:308-314 的逻辑)
if document.chunks: # 已经被别的切块器切过
results = [self.chunk(c.text) for c in document.chunks] # 对每块再切
document.chunks = self._merge_new_chunks(document.chunks, results)
else:
document.chunks = self.chunk(document.content) # 首次切整篇
这让你能链式套用多个切块器(Pipeline 里 .chunk_with("recursive").chunk_with("semantic") 就靠这个)。重点看 _merge_new_chunks(base.py:262-284):它用 dataclasses.replace 把子块的索引平移加上父块的起始偏移,保证最终每个块的 start_index/end_index 仍指向原始整篇文本的正确位置——而不是父块内部的相对位置。