Skip to main content

kaos-pdf

Part of Kelvin Agentic OS (KAOS) — open agentic infrastructure for legal work, built by 273 Ventures. See the full KAOS package map for the rest of the stack.

PyPI - Version Python License CI

kaos-pdf is the PDF-extraction layer of KAOS — it turns a PDF byte stream into a typed kaos-content ContentDocument AST with provenance (page numbers, bounding boxes, extraction confidence) on every node, plus a small set of read-only MCP tools for agentic workflows. The engine is pypdfium2 (Apache-2.0) and all PDFium calls are serialised through a global lock so the library is safe to call from threaded executors. The primary parse path (parse_pdf, extract_outline, extract_metadata, render APIs) returns AST nodes, typed dataclasses, or KaosImage values with provenance. extract_page_text(path, page_number) and its MCP counterpart kaos-pdf-extract-page-text are an intentional escape hatch that returns raw page text for lightweight context-free page inspection; use the AST path when provenance, structure, or downstream redaction / verification is required.

The base install is intentionally small: three runtime dependencies (kaos-content[images,layout,markdown], kaos-core, pypdfium2) and no compiled native code beyond the PDFium wheel. Heavier capabilities are opt-in extras: [ocr] adds pytesseract for scanned pages (and requires a system tesseract binary), [tables] adds pdfplumber (MIT, pure Python — no Java, no GPU) for borderless and multi-line tables, and [nlp] adds kaos-nlp-core for BM25 sentence-level search. VLM page programs (describe / classify / OCR-via-VLM) live in kaos-llm-core[vision] — they were moved out of kaos-pdf to keep the extraction → LLM dependency direction one-directional. We do not and will not depend on AGPL or GPL libraries (this rules out Surya for OCR and camelot-lattice / Tabula for tables).

Install

uv add "kaos-pdf>=0.1.0"
# or
pip install "kaos-pdf>=0.1.0"

# OCR for scanned PDFs (requires system tesseract binary)
uv add 'kaos-pdf[ocr]>=0.1.0'

# Structured table extraction via pdfplumber
uv add 'kaos-pdf[tables]>=0.1.0'

# BM25 sentence-level search via kaos-nlp-core
uv add 'kaos-pdf[nlp]>=0.1.0'

kaos-pdf requires Python 3.13 or newer (3.14 is supported). The package is pure Python — the only native code is the PDFium wheel shipped by pypdfium2, which has prebuilt wheels for Linux, macOS, and Windows on x86_64 and arm64.

Quick start

Extract a PDF into the document AST, render a page, and search for a term:

from kaos_pdf import (
    extract_pdf,
    get_pdf_metadata,
    get_pdf_outline,
    render_page,
    search_document,
)

# Parse the whole document into a kaos-content ContentDocument
doc = extract_pdf("contract.pdf")
print(len(doc.body), "top-level blocks")

# Typed metadata (PdfMetadata dataclass; sparse to_dict() for JSON)
meta = get_pdf_metadata("contract.pdf")
print(meta.page_count, meta.title, meta.author)

# Outline / bookmarks (list[PdfOutlineEntry], also typed)
for entry in get_pdf_outline("contract.pdf"):
    print("  " * entry.level, entry.title, "p", entry.page)

# Render the first page as a 300-DPI PIL image (returned as KaosImage)
image = render_page("contract.pdf", page_number=0, dpi=300)
image.pil.save("page-1.png")

# AST-grounded search — paragraph-level by default
hits = search_document(doc, "indemnification", top_k=5)
for hit in hits.results:
    print(f"score={hit.score:.2f} :: {hit.text[:80]}")

Every node in the returned ContentDocument carries a Provenance (source path, 1-based page, bounding box, extractor name, confidence) so downstream consumers — citation verifiers, redaction tooling, labelers — can ground answers back to the original PDF.

Concepts

The package is a thin, typed surface over pypdfium2. The most important entries:

Concept What it is
extract_pdf(path, *, pages=None, ocr="never", tables="geometric", extract_images=False, image_src_builder=...) Primary entry point. Returns a ContentDocument. pages selects 0-based indices; ocr is "never" / "auto" / "always"; tables is "geometric" / "engine" / "disabled"; image_src_builder lets callers control the image URI policy (default inlines as data: URLs).
extract_pdf_bytes(data, ...) / extract_pdf_with_tables(path, ...) Bytes-input variant and the sidecar form that returns (ContentDocument, TabularDocument) when you want tables out of the body.
render_page(path, page_number, *, dpi=300, grayscale=False) Renders a single page (0-based) to a KaosImage (PIL + DPI + provenance).
extract_page_text(path, page_number) / get_page_count(path) Lightweight per-page text + page-count helpers that skip full AST construction.
PdfMetadata / PdfOutlineEntry @dataclass(frozen=True, slots=True) result types returned by get_pdf_metadata() and get_pdf_outline(). Sparse to_dict() (None fields omitted) preserves the historical wire format. page_count lives on PdfMetadata directly — no extra get_page_count() call needed.
classify_document(path) / classify_page(path, page_number) Lightweight document/page-type heuristics (e.g. text, scanned, mixed).
search_document(doc, query, *, top_k=10, level="paragraph") Re-exported from kaos-content. AST-grounded ranked search returning SearchResults with total_matches / has_more for pagination. level="sentence" requires the [nlp] extra.
join_hyphenated_lines=True / hyphen_resolver=None / HyphenResolver / hyphen_join_decision Hyphenated words broken across lines. PDFium replaces the hyphen that ends such a line with the marker U+0002 (older builds: U+FFFE). With the default join_hyphenated_lines=True, extract_pdf and extract_page_text merge a line ending in that marker (or in U+00AD, or in the ¬ that OCR text layers use) with the next line when it continues the word: "Repre" + "sentatives" becomes one paragraph reading "Representatives", and its bbox is the union of both lines. PDFium marks real compound hyphens the same way. So the default decision, hyphen_join_decision, uses the packaged word list and needs no corpus data. It drops the hyphen only when the joined form is a dictionary word. It keeps - for two-word compounds ("postal-service", "day-to-day"). When it is unsure, it keeps both - and the line break. Pass your own hyphen_resolver(left, right) (for example one backed by corpus counts) to override that decision. With False, every marker renders as -. Either way no C0 control character other than tab, newline and carriage return reaches the output. Runs of \x01 / \x02 leader glyphs render as ., and unmapped controls are dropped and logged at DEBUG.
reading_order="auto" / ReadingOrder Line order within a page. "auto" (the default) keeps PDFium's text lines in content-stream order, as alea-preprocess / KL3M extraction did, when that order reads plausibly top-down and column by column. Otherwise it falls back to "geometric". "stream" always uses PDFium's order. "geometric" is the pre-0.1.9 column-detection-plus-sort order, which interleaves columns when a gutter goes undetected. Line text comes from PDFium's per-character data, not bounded-rectangle queries, so glyphs on overlapping text runs are not doubled. Words of one OCR line stay together even when their baselines differ by a few points.
OCRMode / OCREngine / TesseractEngine OCR pluggability. OCRMode is the extract_pdf(ocr=...) setting; OCREngine is the engine ABC; TesseractEngine is the Apache-2.0 default (install with [ocr] + system tesseract). OCR paragraphs carry Provenance.confidence so verifiers can weight them.
TableMode / TableEngine / ExtractedTable / TableResult Table pluggability. pdfplumber is the MIT default behind [tables]. Extracted tables become TabularDocument with typed columns and live in the body with Provenance.extractor = "kaos-pdf/tables/{engine}".
ParsePDFTool, GetPageTextTool, RenderPageTool, PDFMetadataTool, SearchDocumentTool, GetOutlineTool, ClassifyPageTool The seven KaosTool subclasses exposed over MCP as kaos-pdf-extract-parse, -extract-page-text, -render-page, -metadata, -search-document, -get-outline, -classify-page. All seven are readOnly, idempotent, non-destructive, non-open-world. Register with register_pdf_tools(runtime).
Errors (KaosPdfError, PdfNotFoundError, PdfExtractionError, PdfRenderError) Dedicated exception hierarchy. MCP tools translate these into ToolResult.create_error() with the documented three-part recovery hint (what / how to fix / alternative tool).

CLI

kaos-pdf ships two entry-point scripts. Every structured command on the admin CLI supports --json for machine-readable output piped to other agents:

kaos-pdf --help                                     # admin CLI
kaos-pdf-serve --help                               # MCP server

kaos-pdf info contract.pdf --json                   # metadata + page count + classification
kaos-pdf outline contract.pdf --json                # PDF bookmarks (falls back to detected headings)
kaos-pdf page contract.pdf 3 --json                 # plain text from a single page (1-based)
kaos-pdf extract contract.pdf -f markdown -p 1-5    # full AST → markdown / text / json / html
kaos-pdf render contract.pdf 1 --dpi 300 -o p1.png  # render a page as PNG
kaos-pdf classify contract.pdf --page 1 --json      # document- or page-level type
kaos-pdf search contract.pdf "indemnification" -k 5 # AST-grounded ranked search

kaos-pdf-serve                                      # stdio (Claude Code / Desktop)
kaos-pdf-serve --http --port 8000                   # streamable HTTP

The admin CLI uses 1-based page numbers (consistent with how the file opens in any PDF viewer) and translates internally to the 0-based indices the Python API uses. kaos-pdf-serve exposes the seven MCP tools listed in Concepts above.

Compatibility & status

Aspect
Python 3.13, 3.14
OS Linux, macOS, Windows (pure-Python wheel; the only native code is the PDFium wheel shipped by pypdfium2)
Maturity 0.1.0 GA. The public API is documented in kaos_pdf.__all__.
Stability policy Pre-1.0: minor bumps may change behaviour. Every change is documented in CHANGELOG.md. The MCP tool surface (kaos-pdf-* names) and the KAOS_PDF_* environment-variable namespace are public API and follow the same policy.
Test coverage 340 unit tests plus a small integration tier hitting the MCP wire end-to-end. Bounded unit gate (pytest tests/unit -q --no-cov) finishes in ~35s.
Type checker Validated with ty, Astral's Python type checker.

Documentation

Per-package reference: see in-tree docstrings and CHANGELOG.md.

Cross-cutting KAOS guides (agentic patterns, persona presets, settings policy, citations, MCP data flow, migration to 0.1.0 GA) live in kaos-modules/docs/guides/.

Companion packages

kaos-pdf is one of the packages in the Kelvin Agentic OS. The broader stack:

Package Layer What it does
kaos-core Core Foundational runtime, MCP-native types, registries, execution engine, VFS
kaos-content Core Typed document AST: Block/Inline, provenance, views
kaos-mcp Bridge FastMCP server, kaos management CLI, MCP resource templates
kaos-pdf Extraction PDF → AST with provenance
kaos-web Extraction Web extraction, browser automation, search, domain intelligence
kaos-office Extraction DOCX / PPTX / XLSX readers + writers to AST
kaos-tabular Extraction DuckDB-powered SQL analytics
kaos-source Data Government + financial data connectors (Federal Register, eCFR, EDGAR, GovInfo, PACER, GLEIF)
kaos-llm-client LLM Multi-provider LLM transport
kaos-llm-core LLM Typed LLM programming (Signatures, Programs, Optimizers)
kaos-nlp-core Primitives (Rust) High-performance NLP primitives
kaos-nlp-transformers ML Dense embeddings + retrieval
kaos-graph Primitives (Rust) Graph algorithms + RDF/SPARQL
kaos-ml-core Primitives (Rust) Classical ML on the document AST
kaos-citations Legal Legal citation extraction, resolution, verification
kaos-agents Agentic Agent runtime, memory, recipes
kaos-reference Sample Reference module for module authors

Packages depend on kaos-core; everything else is opt-in. Mix and match the ones you need.

Development

git clone https://github.com/273v/kaos-pdf
cd kaos-pdf
uv sync --group dev

Install pre-commit hooks (recommended — they run the same checks as CI on every commit, scoped to staged files):

uvx pre-commit install
uvx pre-commit run --all-files     # one-time full sweep

Manual QA commands (the same set CI runs):

uv run ruff format --check kaos_pdf tests
uv run ruff check kaos_pdf tests
uv run ty check kaos_pdf tests
uv run pytest tests/unit -q --no-cov

Build from source

uv build
uv pip install dist/*.whl
python -c "import kaos_pdf; print(kaos_pdf.__version__)"  # smoke import

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md for setup, quality gates, pull request expectations, and engineering standards. By contributing you agree to follow the project conduct expectations and certify the Developer Certificate of Origin v1.1 — sign every commit with git commit -s. Please open an issue before starting on a non-trivial change so we can align on scope.

Security

For security issues, please do not file a public issue. Report privately via GitHub Private Vulnerability Reporting or email security@273ventures.com. See SECURITY.md for the full disclosure policy.

License

Apache License 2.0 — see LICENSE and NOTICE.

Copyright 2026 273 Ventures LLC. Built for kelvin.legal.

Metadata

Release files for kaos-pdf 0.1.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kaos-pdf 0.1.9
File Size Uploaded Provenance Actions
kaos_pdf-0.1.9.tar.gz 310.8 kB With provenance Details for kaos_pdf-0.1.9.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for kaos-pdf 0.1.9
File Interpreter ABI Platform Actions
kaos_pdf-0.1.9-py3-none-any.whl Python 3 none any Details for kaos_pdf-0.1.9-py3-none-any.whl

Total release size: 618.1 kB

Release files / kaos_pdf-0.1.9.tar.gz

Download URL kaos_pdf-0.1.9.tar.gz
Size 310.8 kB
Tags Source
SHA-256 checksum
How to use checksums
2dfb93f2b2776c069ba4566d088d72548d19ecaf940a20b6272023fa5887db7e
BLAKE2b-256 checksum
How to use checksums
d83e4278d7b14d73077c909fb9eebe9a5b5195b37d906f4a2289183cfa5918df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Release files / kaos_pdf-0.1.9-py3-none-any.whl

Download URL kaos_pdf-0.1.9-py3-none-any.whl
Size 307.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f4190227693a7a791c78f9f59054fd507496359f6a5ceb89c85ce111314e95d6
BLAKE2b-256 checksum
How to use checksums
1fec2d30314ebb2d00e95f6f21bab757a422c3929ed243be3d0faebe528ffe3d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Security for kaos-pdf 0.1.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for kaos_pdf-0.1.9.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page