Skip to content

Tree optimization for Flash (merge & expand) - #373

Merged
rejojer merged 20 commits into
mainfrom
feat/pageindex-flash
Aug 1, 2026
Merged

Tree optimization for Flash (merge & expand)#373
rejojer merged 20 commits into
mainfrom
feat/pageindex-flash

Conversation

@rejojer

@rejojer rejojer commented Jul 31, 2026

Copy link
Copy Markdown
Member

Union end_index semantics

A node's end_index now covers its whole section, subsections included (it previously stopped where the first child begins). The pages between a parent's heading and its first child stay derivable as start_index … first child's start_index. This is the convention retrieval, summaries, and the optimizer below all rely on.

Tree optimization (preview)

--optimize refines a Flash tree to minimize worst-case search cost: pages read per lookup, counting one page per routing step.

  • merge — collapses any subtree whose structure costs more to route through than its pages cost to read. Deterministic, no LLM. Removed titles are kept on the parent as key_items.
  • expand — splits a large section into the subsections printed on its pages, one lookahead call per large section (uses the summary model).
python3 run_pageindex.py --flash --optimize --pdf_path doc.pdf
python3 run_pageindex.py --flash --optimize-merge-only --pdf_path doc.pdf   # no LLM-backed expansion; summaries unchanged

The output gains an optimize key with merge/expand counts and before/after search-cost metrics. Example (222-page annual report): worst-case search cost 37 → 10 pages (normalized 0.167 → 0.045).

Recursive node summaries

Flash summaries are now composed bottom-up: leaves are summarized from their own pages; a parent's summary is composed from its children's summaries plus the pages no child covers, so it describes the whole section without re-reading the subtree. Leaves under 200 tokens use their raw text as the summary with no model call.

On the annual report this cuts summary input tokens 418K → 221K (1.9x); the saving grows with tree depth. Every raw page now enters exactly one summary prompt.

Optimization stays opt-in; without --optimize the Flash pipeline behaves as before, apart from the end_index semantics and the summary composition above.

"""page -> [heading, ...] from a per-page detection pass, or None."""
if not path or not os.path.exists(path):
return None
data = json.load(open(path))
page text. Returns the run summary; the refined tree is doc["structure"].
"""
if isinstance(doc, str):
doc = json.load(open(doc))
and not os.getenv("OPENAI_API_KEY"):
sys.exit(f"OPENAI_API_KEY is not set (expand model: {model}).")

original = json.load(open(args.structure))

refined = dict(original)
refined["structure"] = structure
json.dump(refined, open(out_path, "w"), indent=2, ensure_ascii=False)
"min_gain_ratio": args.min_gain_ratio, "model": model,
"before": result["before"], "after": result["after"],
"id_map": result["id_map"], "events": result["log"]},
open(args.log, "w"), indent=2, ensure_ascii=False)
@rejojer
rejojer force-pushed the feat/pageindex-flash branch from 00c5640 to e55f92d Compare July 31, 2026 12:11
@rejojer
rejojer merged commit 3f33a53 into main Aug 1, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant