PageIndex 研究
- 短文档直接合并
- 切分文档(切分阈值)
python
- num_tokens / expected_parts_num = 「理想均分」时每块的大小;
- max_tokens = 「硬上限」;
- 阈值取两者的中点。
expected_parts_num = math.ceil(num_tokens / max_tokens) # 至少要切几块
average_tokens_per_part = math.ceil(((num_tokens / expected_parts_num) + max_tokens) / 2)
- 贪心累加+overlap:章节边界不可能一定对应切分阈值(块边界),每相邻两组共享 1 页,边界同时出现在「上一组的尾部」和「下一组的头部」
python
for i, (page_content, page_tokens) in enumerate(zip(page_contents, token_lengths)):
if current_token_count + page_tokens > average_tokens_per_part: # 加这页会超阈值
subsets.append(''.join(current_subset)) # ① 先把当前组存下来
overlap_start = max(i - overlap_page, 0) # ② 新组从「前一页」开始
current_subset = page_contents[overlap_start:i] # (默认回退1页=overlap)
current_token_count = sum(token_lengths[overlap_start:i])
current_subset.append(page_content) # ③ 再把当前页加入新组
current_token_count += page_tokens
关键在 overlap_page=1:每相邻两组共享 1 页。边界页同时出现在「上一组的尾部」和「下一组的头部」。
为什么必须 overlap?
因为章节边界不可能正好对齐我们强加的「块边界」。 一个节标题可能出现在第 X 页底部(恰是第 N 组最后一页),而正文流到第 N+1 组。没有 overlap 时:
- 第 N 组的 LLM 看到标题但内容被截断,无法判定该节「起始页」;
- 第 N+1 组的 LLM 只看到无标题的正文,没法锚定
physical_index。
overlap 1 页后,边界页两组都可见 → 跨边界的节能被一致地定位。在两个调用场景里这正是关键:
process_no_toc:第一块用 generate_toc_init 建树,后续块用generate_toc_continue续写树(:586-589)。overlap 让续写处能正确衔接。process_toc_no_page_numbers:树已知,逐块用add_page_number_to_toc定位每节起始页(:611-612)。overlap 保证边界上的节不丢、不重。
大节点递归细分(处理「检索单元过大」的节点),触发条件:页数+Token(避免了过度碎片化)。如:
- 避免页多但内容稀疏(附录等,细分会得到近乎空的叶子,无意义)
- Token密,但页少。本身就是紧凑单元,不必拆分