数据截至 (上游 commit 3b55ae605435)
语义切块与 LLM 切块
本章讲两个「聪明但贵」的切块器。它们不靠文本表面的分隔符,而是靠语义信号找边界:一个用嵌入相似度,一个直接问 LLM。
3.1 SemanticChunker:在相似度低谷处切
它要解决的小问题
递归切块只看符号(段落、句号),看不懂意思。一篇文章可能连续两段讲「哲学」、第三段突然转「菜谱」,我们想正好在话题转折处切一刀。怎么知道「话题变了」?看相邻文本的嵌入相似度——相似度突然变低的地方,就是语义边界。
思路 / 直觉
传统语义切块用「相似度低于某阈值就切」。Chonkie 做得更细:它把相似度序列当成一条曲线,找曲线的局部低谷(local minima)做切点,而且先用滤波器把曲线平滑掉噪声毛刺(src/chonkie/chunker/semantic.py:1-6 的模块说明)。
相似度
高 │ ●─●─● ●─●
│ ╲ ╱
低 │ ●──────● ← 这个低谷 = 语义边界,在这切
└──────────────────────► 句子序号
原理演示
# 示意,非源码(提炼 semantic.py 的 chunk 流程)
sentences = split_into_sentences(text) # 1. 先切句
sims = [similarity(window_emb[i], sent_emb[i]) # 2. 算「窗口 vs 下一句」相似度
for i in range(len(sentences) - W)]
smoothed = savitzky_golay(sims) # 3. 平滑曲线
split_points = local_minima(smoothed) # 4. 找低谷做切点
groups = group_by(sentences, split_points) # 5. 按切点分组成块
真实实现:四步
第一步,切句并嵌入。 _get_similarity(semantic.py:220-228)算的是「窗口嵌入」与「单句嵌入」的相似度。窗口嵌入 _get_window_embeddings(semantic.py:213-218)把连续 similarity_window 个句子拼起来整体嵌入——比把单句嵌入平均更准:
# 真实源码 semantic.py:215-218
for i in range(len(sentences) - self.similarity_window):
paragraphs.append("".join([s.text for s in sentences[i : i + self.similarity_window]]))
return self.embedding_model.embed_batch(paragraphs)
第二步,找低谷(下沉 Rust)。 _get_split_indices(semantic.py:230-269)把 Savitzky-Golay 平滑 + 找局部极小值的数值活儿全交给 chonkie_core:
# 真实源码 semantic.py:244-261(节选)
minima_indices, minima_values = chonkie_core.find_local_minima_interpolated(
similarities, window_size=self.filter_window,
poly_order=self.filter_polyorder, tolerance=self.filter_tolerance)
filtered_indices, _ = chonkie_core.filter_split_indices(
minima_indices, minima_values, self.threshold, self.min_sentences_per_chunk)
两个 Rust 函数分工(inferred,据签名与上下文):find_local_minima_interpolated 平滑曲线并定位极小值;filter_split_indices 按相似度阈值(百分位)和「每块最少句数」过滤掉太密/太浅的切点。数据点不足一个滤波窗口时直接返回空(不切,semantic.py:239-241)。
第三步,分组并控大小。 按切点把句子分组(_group_sentences,semantic.py:365-396),再用 _split_groups(semantic.py:398-433)把超过 chunk_size 的组贪心二次切——语义边界优先,但硬上限不破。
第四步,可选的跳跃合并。 skip_window > 0 时,_skip_and_merge(semantic.py:311-363)会尝试把非相邻但语义相似的组合并(比如「A 话题—B 插叙—A 话题」把两段 A 并起来)。默认关闭。
关键细节 / 坑
- 默认嵌入模型是
minishlab/potion-base-32M(semantic.py:36),一个轻量静态嵌入,符合 Chonkie「轻」的取向;tokenizer 直接取嵌入模型自带的(semantic.py:95)。 - 句子太少直接整篇成一块:句数 ≤
similarity_window时不做相似度分析(semantic.py:467-481)。 threshold必须严格在 (0,1) 开区间(semantic.py:76-77),它在这里当百分位过滤用,不是简单的相似度截断。