Docs / Guides · updated

How do I summarize long PDF reports locally with an LLM?

Extract the text from the PDF on your own machine and keep the page numbers, pull out the hard facts (dates, amounts, classes, defect counts) with plain rules, then give a local model small, page-tagged chunks and ask for a fixed JSON schema: key points, risks, recommendations, deadlines. The model writes the summary; your code checks it against the source. If the model runs on your hardware, the document never leaves it.

The problem with "paste the PDF into a chatbot"

Long reports (inspection reports, audits, diagnostics, technical assessments) break the naive approach in predictable ways:

  • Confidentiality. A hosted service sees the full document, including names, addresses and figures.
  • Length. A 60-page report may not fit in a model's context, and a model that silently drops the end will miss the deadline on page 58.
  • Invented or lost facts. Free-form summaries round numbers, merge dates or skip the one critical defect.
  • No structure. Each run returns a differently shaped answer, so you cannot feed it to another tool or compare two reports.

The fix is to treat the model as the last step of a pipeline, not the whole pipeline.

A local pipeline you can build without buying anything

1. Extract text and keep page boundaries

Use a local extractor such as pdftotext (poppler-utils) or the pypdf library, and store the result as JSON with one entry per page. Page numbers let you cite where a fact came from and let you spot extraction failures: a page with almost no text is often a scanned image, which needs OCR before anything else can read it.

2. Clean and classify

Strip repeated headers and footers, rejoin hyphenated words, and normalize the encoding. Then decide what kind of report it is with a simple keyword score: count domain terms for each type and pick the highest. Knowing the type lets you use a prompt and a fact list written for that type. If no type scores, say so instead of guessing.

3. Extract facts with rules before the model sees anything

Run deterministic patterns over the full text:

  • dates (dd/mm/yyyy, yyyy-mm-dd)
  • ratings and classes (an energy label A to G, a pass or fail result)
  • counts of flagged items (critical, major, minor)
  • keywords that mark abnormal values

This list is cheap, reproducible and complete over the whole document. It becomes the checklist the final summary must cover.

4. Chunk by structure, then map and reduce

  • Split on headings or page ranges first; fall back to a character budget only inside long sections.
  • Leave room in the context for the prompt and the answer, not just the chunk.
  • Tag every chunk with its page range.
  • Map: ask the model for the same JSON schema on each chunk.
  • Reduce: merge the chunk results in code, remove duplicates, and sort risks by severity. Ask the model for a final short synthesis only from the merged JSON, not from the raw text again.

5. Ask for a fixed schema and validate it

Use a schema such as: report type, key points, risks (each with a severity), recommendations, deadlines (each with a date or delay), and a short synthesis. Validate before you accept it: every key present, risks in severity order, and every deadline date also present in your rule-extracted list. A date the model produced that does not appear in the source is a reason for human review, not a fact.

6. Keep a human on decisions

A summary helps someone read faster. For medical, legal or safety reports, the person responsible still reads the source before acting.

How the packs implement this

Report Summarizer Pack (v2) is a summarization pipeline with stages for ingesting, parsing, extracting, synthesizing, formatting and delivering. It ships prompts and keyword detection for three report types: medical, controle_technique (vehicle roadworthiness inspection) and dpe (energy performance diagnosis). Its output always has six keys: report_type, points_cles (key points), risques (risks with a severity of critique, eleve, moyen or faible), recommandations, echeances (deadlines) and synthese, with risks sorted by decreasing severity. Output is Markdown or JSON (--format md|json).

agent.py reads a .pdf directly when pypdf is installed; otherwise it asks you to convert first. Its docs advise about 50,000 characters per call and splitting the report beyond that. From its REPORT_TYPES.md:

pdftotext rapport.pdf - | python agent.py --type dpe -

Without any model, it runs an offline extraction: dates by regex, the DPE energy class and consumption, vehicle inspection defects counted by severity, and abnormal-value keywords in medical reports. Setting PACK_LLM=off turns off the coding-agent CLI; with no ANTHROPIC_API_KEY set either, the pack stays in this deterministic mode.

Report Summarizer Pack (v1) has the same command line and output schema. For example:

python agent.py --type controle_technique --format json ct.txt

PDF Creator & Editor Pack handles both ends of the pipeline. extract turns a PDF into JSON with source, total_pages, a pages list of page_num and text, and document metadata. create turns Markdown, JSON or text (such as your finished brief) back into a PDF, and modify can merge files, add a page, stamp a watermark or set metadata.

python pdf_agent.py extract --in document.pdf --out document.json
python pdf_agent.py create --in data.json --out data.pdf --title "Rapport"

Agent Knowledge Base Pack ($4) is a place to keep what you have summarized. It ingests a directory of Markdown, text or JSON files into SQLite, skips content it has already seen by SHA-256 hash, groups entries by topic and lets you query them:

python3 agent.py ingest --source data/runbooks --type markdown
python3 agent.py query --topic deployment

Honest limits

  • Where the model runs. In the summarizer packs, the model step goes through a local Ollama server, a connected coding-agent CLI (Claude Code, Kimi Code or Codex) or, if ANTHROPIC_API_KEY is set, the Anthropic API. The CLIs and the API are hosted services. If documents must stay on your machine, set PACK_LLM=ollama: the summarizer then uses your local model and never falls back to the hosted API, even if a key is set. PACK_LLM=off gives the deterministic extraction only.
  • Offline mode extracts; it does not write. It returns at most the first three dates it finds, labels, counts and flags, plus a one-line count, not a prose summary.
  • Three report types, in French. Detection keywords and output keys are French. Other documents are typed inconnu and summarized generically.
  • No built-in chunking or OCR. Long reports must be split by you, and scanned PDFs need OCR before extraction returns any text.
  • Dependencies. The PDF pack installs fpdf2 and pypdf with pip at launch unless you pass --no-install; extract needs pypdf.
  • Not a search engine. The knowledge base pack states it does not replace a large-scale vector search engine.
  • Human review. For medical reports, the offline summary itself recommends consulting the practitioner for interpretation.

Where to start

  1. Run pdftotext on one real report and read the output. If pages come back empty, solve OCR first.
  2. Write the date and keyword rules for your report type and compare their output with what you would underline by hand.
  3. Add a local model last, on page-tagged chunks, with a fixed schema and the validation checks above.

If you want the extraction and schema already written, start with PDF Creator & Editor Pack and Report Summarizer Pack ($3 each, one-time). See all packs at /packs or the quickstart.

Packs used in this guide

Browse all packs by category →