PagePilot
Standalone, provider-agnostic PDF page-indexing SDK. Layout heuristics build a nested tree index of a document; retrieval and citation-answer chat run on an injected LLM callable — PagePilot never imports a provider SDK, so it works with any model/tool (OpenAI, Gemini, Groq, Copilot, LiteLLM, …).
Vectorless, reasoning-based retrieval: the LLM navigates a table-of-contents tree the way a human reader would, then answers with exact page (and document) citations. No vector DB, no chunking required — but chunks with section paths are a first-class export for hybrid/vector pipelines.
Install
pip install pagepilot # from PyPI (0.3.0+)
# or, for development:
python -m venv .venv && .\.venv\Scripts\activate
pip install -e ".[dev]"
Quick start
from pagepilot import PagePilotClient
from pagepilot.llm import ChatCompletion
def llm(messages, *, model=None, max_tokens=None):
return my_provider.chat(messages, model=model, max_tokens=max_tokens)
client = PagePilotClient(ChatCompletion(llm, chat_model="your/model"),
summary_concurrency=1) # gentle by default
entry = client.submit_document("report.pdf") # llm-free structure, or:
# entry = client.submit_document("report.pdf", mode="skip-summaries") # never calls the LLM
doc_id = entry["doc"]["id"]
# OpenAI-shaped answer dict + cited result
answer = client.chat("What was the 2023 margin?", doc_id=doc_id)
print(answer["choices"][0]["message"]["content"])
# Typed Answer with page + document sources
typed = client.answer([{"role": "user", "content": "…"}], doc_id=doc_id)
print(typed.content, [c for c in typed.citations])
Multi-turn conversations are context-aware: page and document selection receive the earlier turns, so follow-ups ("…and the second part?") resolve correctly.
Multi-document chat
ids = [client.submit_document(d)["doc"]["id"] for d in ("a.pdf", "b.pdf")]
answer = client.chat("Compare the risk factors across these filings.",
doc_id=ids)
print([f"{s.doc_name} p.{s.page}" for s in client.answer([], doc_id=ids).sources])
The client picks relevant documents by reasoning over the library index, then retrieves pages per document and cites the correct source document.
Change detection
r1 = client.submit_document("report.pdf") # {"status": "ingested"}
r2 = client.submit_document("report.pdf") # {"status": "unchanged"} (same sha256)
r3 = client.submit_document("report.pdf", force=True) # re-index
Chunks & export (for vector pipelines)
from pagepilot import chunk_document
chunks = chunk_document(client.raw_tree(doc_id), source=doc_id)
for c in chunks[:3]:
print(c.section_path, c.page, c.content[:60])
print(client.export(doc_id, fmt="json")) # json | csv | markdown | tree
Index health (zero LLM calls)
from pagepilot import assess_library
for r in assess_library(client.storage_path):
print(r["name"], r["coverage"], r["problems"])
Or via CLI: python scripts/assess_index.py --storage <store> (exit code 1 on
failures — CI-safe).
What it does
- Detect — per-page text-layer analysis (
pagepilot.detect) - OCR — optional, via OCRmyPDF + Tesseract for scanned PDFs
(
pagepilot.ocr) - Layout extract — pure heuristics (no LLM): columns, body-font stats,
header/footer stripping, TOC recognition, heading candidates, doc title,
embedded-PDF-bookmark merge, graceful degradation to coarse page groups
(
pagepilot.layout) - Tree — node walk / validation / pruning; typed
TreeNodeview (pagepilot.tree,pagepilot.models) - Summaries — bottom-up, gentle concurrency (
pagepilot.summarize) - Library — corpus-level document selection for multi-doc chat
(
pagepilot.library) - Retrieval — one-shot page pick from the tree, history-aware
(
pagepilot.retrieve) - Chat — cited answers (page + document), OpenAI-shaped output, streaming
surface (
pagepilot.chat) - Chunk/export — hierarchy-aware chunks + JSON / CSV / Markdown / tree
serializers (
pagepilot.chunk,pagepilot.export) - Assess — zero-LLM index health reports (
pagepilot.assess) - Storage — JSON per-doc dirs under a user storage root
(
pagepilot.storage)
Examples
python examples/quickstart.py path/to/report.pdf other.pdf
python examples/export_chunks.py report.pdf out/
python examples/index_health.py path/to/report.pdf
python -m pagepilot.demo path/to.pdf # deterministic fake-LLM demo
Examples run against an injected fake LLM — no keys, no network.
Design notes
- No provider coupling — the LLM seam (
llm.py) is the only network-ward interface; providers own their retry/quota/header logic. - Cheap ingest — structure extraction is entirely heuristic; summaries are
the only per-ingest LLM cost, or zero with
mode="skip-summaries". - Honest degradation — documents that defeat heuristics fall back to page groups, never fabricated subsection structure.
- Traceable answers — every statement carries
[document p.page]citation from the actually-read pages.
Development
python -m venv .venv
.\.venv\Scripts\activate # Windows PowerShell
python -m pip install -e ".[dev]"
python -m pytest -q
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pagepilot-0.3.0.tar.gz.
File metadata
- Download URL: pagepilot-0.3.0.tar.gz
- Upload date:
- Size: 41.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
601e68eaeecb73eb2e4ab8fbcd9848de3d6db15ed75b93f43a0777166b4a0c5a
|
|
| MD5 |
fb1855c289ff1a27a264b63135618a6f
|
|
| BLAKE2b-256 |
163a48c451d38996012f44a4f8f560e941ae9fd80278268a8d150f7340b5988c
|
File details
Details for the file pagepilot-0.3.0-py3-none-any.whl.
File metadata
- Download URL: pagepilot-0.3.0-py3-none-any.whl
- Upload date:
- Size: 37.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
478a8439962e272b71c93d10729e060f1df833cbc4690d7b77a089a875d47353
|
|
| MD5 |
b90055f377f1bb2c65976bf62b0538b6
|
|
| BLAKE2b-256 |
2b4885047da9494a4ac3ebf027faa262c20e36a06f67da35653b767762a1c683
|