When the first page of a document exceeds the per-part token average, page_list_to_group_text() emits an empty string as its first chunk. Both callers pass that chunk to a model call as if it were a slice of the document.
Reproduction
from pageindex.page_index_classic import page_list_to_group_text
print(page_list_to_group_text(["PAGE-A", "PAGE-B", "PAGE-C"],
[60000, 500, 500], max_tokens=20000))
# ['', 'PAGE-A', 'PAGE-APAGE-B', 'PAGE-BPAGE-C']
# ^^ empty
Root cause
page_index_classic.py:530-537 — at i == 0 the accumulator is still empty, so the boundary test reduces to token_lengths[0] > average_tokens_per_part and flushes before anything has been added:
if current_token_count + page_tokens > average_tokens_per_part:
subsets.append(''.join(current_subset)) # current_subset is [] when i == 0
Only the first page can trigger it — later splits re-seed the accumulator with the overlap page. [60000, 500, 500] reproduces; [100, 60000, 100] does not.
Consequence
process_no_toc() (:686) — generate_toc_init() seeds the whole table of contents from an empty prompt. The empty chunk has no <physical_index_N> markers, so _validate_chunk_physical_indices() nullifies everything it returns, and that seed is carried into every generate_toc_continue() call (:698). No page content is lost (page 1 arrives via the overlap in chunk 1) and the bogus entries are filtered at :1221 — the cost is a wasted call and a contaminated seed.
process_toc_no_page_numbers() (:738) — this loop does not skip chunk 0. add_page_number_to_toc() is asked to assign page numbers from an empty document, and a mismatched entry count raises ValueError at :742, aborting the run.
Scope
average_tokens_per_part is bounded below by max_tokens / 2, so the first page must exceed ~15k tokens (~60 KB) — a wide table, a dense appendix, a badly paginated export. Not the common case, and call site 1 fails silently, which is likely why it has gone unnoticed. Also reachable per-node: process_large_node_recursively() (:1174) re-runs process_no_toc over one oversized node, so "first page" there is the node's.
When the first page of a document exceeds the per-part token average,
page_list_to_group_text()emits an empty string as its first chunk. Both callers pass that chunk to a model call as if it were a slice of the document.Reproduction
Root cause
page_index_classic.py:530-537— ati == 0the accumulator is still empty, so the boundary test reduces totoken_lengths[0] > average_tokens_per_partand flushes before anything has been added:Only the first page can trigger it — later splits re-seed the accumulator with the overlap page.
[60000, 500, 500]reproduces;[100, 60000, 100]does not.Consequence
process_no_toc()(:686) —generate_toc_init()seeds the whole table of contents from an empty prompt. The empty chunk has no<physical_index_N>markers, so_validate_chunk_physical_indices()nullifies everything it returns, and that seed is carried into everygenerate_toc_continue()call (:698). No page content is lost (page 1 arrives via the overlap in chunk 1) and the bogus entries are filtered at:1221— the cost is a wasted call and a contaminated seed.process_toc_no_page_numbers()(:738) — this loop does not skip chunk 0.add_page_number_to_toc()is asked to assign page numbers from an empty document, and a mismatched entry count raisesValueErrorat:742, aborting the run.Scope
average_tokens_per_partis bounded below bymax_tokens / 2, so the first page must exceed ~15k tokens (~60 KB) — a wide table, a dense appendix, a badly paginated export. Not the common case, and call site 1 fails silently, which is likely why it has gone unnoticed. Also reachable per-node:process_large_node_recursively()(:1174) re-runsprocess_no_tocover one oversized node, so "first page" there is the node's.