PDF Ingestion
Three ways to feed a PDF to the model, in increasing order of preprocessing:
Native PDF input — pass the file directly. Model sees pages as images + extracted text. Best for docs under ~100 pages with meaningful layout (tables, figures, forms). Preserves structure.
Text extraction then send — pdftotext / pypdf / equivalent, then send the text. Loses layout but cheap. Fine for prose-heavy docs where tables don't matter.
Extract → chunk → summarize → send — for docs >100 pages or when you'll query the same doc many times. Preprocess once, cache the summary.
Deciding which path
| Doc shape |
Path |
| <20 pages, layout matters (report, form, invoice) |
Native |
| <20 pages, pure prose (article, memo) |
Text extraction |
| 20-100 pages, mixed |
Native, but chunk if context tight |
| >100 pages |
Extract → chunk → summarize |
| Scanned PDF (no text layer) |
OCR first (Tesseract or vision model), then treat as extracted text |
| Tables are the point |
Native — text extractors mangle tables |
| Figures/diagrams are the point |
Native + explicit "describe the figure on page N" prompt |
Native PDF — the good defaults
- Cache the PDF at a prompt-caching breakpoint (see
prompt-caching). Native PDFs are large — every uncached turn costs full input price on the whole doc.
- Ask about specific pages ("summarize section 3.2 on page 14") rather than the whole doc. The model handles targeted queries better than "summarize this 80-page report".
- Follow up with page-cited claims — "on which page does the doc say X?" — as a sanity check the model isn't hallucinating.
Extract-then-send — the traps
pdftotext reading order. Multi-column PDFs come out as interleaved lines. Use pdftotext -layout for column preservation, or pdftotext -raw for straight reading order — pick per doc, don't guess.
- Tables become word soup. If tables are load-bearing, native or per-table image extraction. Not text.
- Headers/footers repeat on every page. Strip them before sending, or the model will treat them as content.
- Footnotes drift to random positions in the extracted stream. Filter or accept the noise.
Extract → chunk → summarize (long docs)
- Chunk by section, not by token count. A section-aware split respects the doc's logic; a naive 4K-token split cuts sentences and tables.
- Summarize per section into a "map" — 1-2 paragraphs each. Keep the map short enough to fit in context whole (~2-4K tokens for a 200-page doc).
- Store the full section text alongside the map (paths in a manifest). Fetch on demand when a question needs detail beyond the summary.
- Cache the map at a prompt-caching breakpoint so multi-turn Q&A over the doc doesn't reprocess.
Red flags
- Sending a 200-page PDF native to answer one question. Extract the relevant page range first.
- Trusting the text extractor on a form or invoice. Layout carries meaning. Use native.
- OCR'ing a PDF that already has a text layer. Check
pdftotext -q file.pdf - first — if text comes out, no OCR needed.
- No page citations in output. Model can hallucinate confidently across long PDFs. Force page numbers into the response format.
- Re-uploading the same PDF every turn without caching. Cost climbs linearly; a 5-minute cache fixes it.
Loopkit-adjacent
If the PDF is a spec, extract it into PROMPT.md via spec-first — the agent should re-read prose, not re-scan the PDF, on every turn.
1---2name: pdf-ingestion3description: Get a PDF into the model without blowing the context window or losing structure. Native PDF beats OCR-then-text for most cases; extract-then-summarize beats native for very long docs.4---56# PDF Ingestion78Three ways to feed a PDF to the model, in increasing order of preprocessing:9101. **Native PDF input** — pass the file directly. Model sees pages as images + extracted text. Best for docs under ~100 pages with meaningful layout (tables, figures, forms). Preserves structure.11122. **Text extraction then send** — `pdftotext` / `pypdf` / equivalent, then send the text. Loses layout but cheap. Fine for prose-heavy docs where tables don't matter.13143. **Extract → chunk → summarize → send** — for docs >100 pages or when you'll query the same doc many times. Preprocess once, cache the summary.1516## Deciding which path1718| Doc shape | Path |19|---|---|20| <20 pages, layout matters (report, form, invoice) | Native |21| <20 pages, pure prose (article, memo) | Text extraction |22| 20-100 pages, mixed | Native, but chunk if context tight |23| >100 pages | Extract → chunk → summarize |24| Scanned PDF (no text layer) | OCR first (Tesseract or vision model), then treat as extracted text |25| Tables are the point | Native — text extractors mangle tables |26| Figures/diagrams are the point | Native + explicit "describe the figure on page N" prompt |2728## Native PDF — the good defaults2930- Cache the PDF at a prompt-caching breakpoint (see `prompt-caching`). Native PDFs are large — every uncached turn costs full input price on the whole doc.31- Ask about **specific pages** ("summarize section 3.2 on page 14") rather than the whole doc. The model handles targeted queries better than "summarize this 80-page report".32- Follow up with **page-cited claims** — "on which page does the doc say X?" — as a sanity check the model isn't hallucinating.3334## Extract-then-send — the traps3536- **`pdftotext` reading order.** Multi-column PDFs come out as interleaved lines. Use `pdftotext -layout` for column preservation, or `pdftotext -raw` for straight reading order — pick per doc, don't guess.37- **Tables become word soup.** If tables are load-bearing, native or per-table image extraction. Not text.38- **Headers/footers repeat on every page.** Strip them before sending, or the model will treat them as content.39- **Footnotes drift** to random positions in the extracted stream. Filter or accept the noise.4041## Extract → chunk → summarize (long docs)4243- Chunk by section, not by token count. A section-aware split respects the doc's logic; a naive 4K-token split cuts sentences and tables.44- Summarize per section into a "map" — 1-2 paragraphs each. Keep the map short enough to fit in context whole (~2-4K tokens for a 200-page doc).45- Store the full section text alongside the map (paths in a manifest). Fetch on demand when a question needs detail beyond the summary.46- Cache the map at a prompt-caching breakpoint so multi-turn Q&A over the doc doesn't reprocess.4748## Red flags4950- **Sending a 200-page PDF native to answer one question.** Extract the relevant page range first.51- **Trusting the text extractor on a form or invoice.** Layout carries meaning. Use native.52- **OCR'ing a PDF that already has a text layer.** Check `pdftotext -q file.pdf -` first — if text comes out, no OCR needed.53- **No page citations in output.** Model can hallucinate confidently across long PDFs. Force page numbers into the response format.54- **Re-uploading the same PDF every turn without caching.** Cost climbs linearly; a 5-minute cache fixes it.5556## Loopkit-adjacent5758If the PDF is a spec, extract it into `PROMPT.md` via `spec-first` — the agent should re-read prose, not re-scan the PDF, on every turn.