PyStreamPDF
Token-efficient PDF extraction for RAG: parse PDF structure and split content into token-budget-aware chunks.
What it is
PyStreamPDF combines a Rust/PDFium-backed core for real PDF parsing with a Python layer
for semantic chunking, token-budget-aware caching, and OCR/validation. Instead of sending
an entire document to a model, it parses PDF structure and splits content into semantic,
token-budget-aware chunks so you can send only the relevant parts. It ships as
pystreampdf on PyPI and imports as pystreampdf.
Features / Highlights
- Semantic chunking —
SemanticChunkersplits text into token-aware chunks by element type rather than naive word-count splitting. - Token-budget system —
TokenBudgetConfig/BudgetRulescale allocation within a fixed 500–1000 token range using keyword/field-based multipliers. - Dual-tier cache —
PDFCache: L1 memory + L2 disk, HMAC-SHA256-signed on disk with owner-only (0600) permissions; a tampered cache file fails verification before any deserialization. - Real PDF security handling —
pystreampdf._core.PyPdfDocumentis backed by PDFium's actual encryption/permission APIs (is_encrypted(),permissions(),open_with_password()); a wrong or missing password fails closed. - Honest failure signaling — unparseable PDFs raise a real error; no fabricated placeholder content.
- OCR and validation pipelines —
ocr/andvalidation/are real implementations with dedicated test coverage. - Exact or heuristic token counts — uses
len(text) / 4by default (clearly labeled approximate); install the optionaltiktokenextra for real BPE (cl100k_base) counts. - Optional MCP/DAB connector — exposes document-processing tools over MCP, binds to
127.0.0.1only, read-only by default.
Who it's for / Use cases
- RAG pipeline authors who want to reduce the volume of text sent per query without losing the parts that matter.
- Token-cost-conscious teams that need to budget tokens per document type.
- Document-processing services that need real encryption/permission detection and a cache that fails closed on tampering.
- Agent/tool builders who want PDF extraction surfaced over MCP locally.
What it is not
- There is no
Documentclass and no.extract_relevant()/.token_savingsAPI. That class never existed in the source. The real exported API isSemanticChunker,PDFCache,TokenBudgetConfig, and the Rust-backedpystreampdf.open(). - No committed token-savings benchmark. Earlier "70% reduction" / "10-50x" claims were never backed by a checked-in benchmark and have been retracted. Actual savings depend on your documents and how narrowly you scope queries.
- Prebuilt wheels are macOS arm64 only. A source distribution ships as of 2.3.0, so
other platforms build from source, but that still requires a local Rust toolchain and a
discoverable
libpdfium(seescripts/download_pdfium.sh). - OCR/layout fixtures are generated at test time, not checked in.
tests/now renders real two-column, mildly rotated/skewed, and Times-Roman (22 pt) pages, rasterizes them with PyMuPDF at ~150 dpi, and embeds the images as text-free scanned PDFs (tests/test_scanned_and_span_fixtures.py). The OCR assertions run against real Tesseract output and skip when Tesseract is unavailable; they are not mocked. A reportlab table-with-spans fixture covers merged-cell text extraction. This is fixture coverage, not an OCR quality benchmark. - Cross-project integration is limited but real. The MCP connector imports the
canonical
BaseMCPConnectorfromstatguardian._mcp_connectorwhen StatGuardian is installed (with a local fallback for older installs), verified bytests/test_mcp_shared_base.py. No other StatGuardian, PyInferenceManager, or PyStreamMCP integration is demonstrated here. - The Rust-backed
pystreampdf.open()/load_index()silently becomesNoneif the compiled_coreextension is unavailable (e.g. a from-source install withoutmaturin develop), falling back to the pure-Python path rather than erroring. - Remote MCP exposure is bearer-token protected and supports TLS/mTLS. The
connector binds to
127.0.0.1with read-only permissions by default.allow_remote=Truerequires a non-emptyauth_token; DAB stays on loopback and a stdlib reverse proxy enforcesAuthorization: Bearer <token>plus a per-IP rate limit before forwarding. Pass bothtls_certandtls_keyto serve HTTPS directly from the proxy (stdlibssl.SSLContext, TLS 1.2+); without them traffic is plaintext HTTP. Setclient_ca(with TLS) to require and verify a client certificate (ssl.CERT_REQUIRED). Request bodies overmax_body_bytes(default 10 MiB) get a413, and the proxy writes a basic access log (pystreampdf._mcp_connector.access: method/path/status/remote, no auth headers or query strings). Rate-limit buckets are in-memory per process and reset on restart; there is no persistence or cross-worker sharing. For untrusted networks a hardened reverse proxy is still recommended. Request and response bodies are streamed in chunks rather than fully buffered; chunked request bodies are rejected with501(seeSECURITY.md).
Project status
Beta. Current version 2.3.0 (Development Status :: 4 - Beta). The full
pytest tests/ suite on this change passes locally — 600 passed / 4 skipped
(optional-dependency skips), and the last confirmed-green CI run reported cargo test —
23 passed / 0 failed across Python 3.10/3.11/3.12. See
docs/PRODUCT_VISION.md for what is real vs. partial.
Installation
pip install pystreampdf
# With exact token counting (tiktoken)
pip install "pystreampdf[tiktoken]"
Requires Python 3.9+.
Optional integrations
Every integration below is optional: the base install and the test suite run without it, and the relevant tests skip (or assert the actionable error) rather than fail when it is missing.
- PyMuPDF (
pip install pymupdf) — rasterizes scanned pages for the OCR pipeline (pystreampdf.ocr.pipeline). Without it, scanned-page OCR raises a clearImportErrorwith the install hint. - tiktoken (
pip install "pystreampdf[tiktoken]") — real BPE (cl100k_base) token counts; falls back to thelen(text) / 4heuristic. - pytest-asyncio — a dev/test extra only (
pip install "pystreampdf[dev]"), required to run thetests/test_mcp_tools.pyasync tests. - Tesseract + pytesseract + Pillow — real OCR for scanned PDFs
(
brew install tesseract,pip install pytesseract pillow). Or use the OS package manager for Tesseract; the OCR tests run against generated rasterized fixtures and skip with an explicit reason when the binary/package is missing.
Optional high-fidelity extraction (Docling)
The native Rust/PDFium path is the default. For complex layouts, tables or scanned pages, install the MIT-licensed Docling backend and use it as an extraction source while keeping PyStreamPDF's chunk and budget types:
pip install "pystreampdf[docling]"
from pystreampdf import extract_with_docling
result = extract_with_docling("report.pdf", target_tokens=500)
for chunk in result.chunks:
print(chunk.chunk_type, chunk.page_start, chunk.estimated_tokens)
Docling downloads its own layout/OCR models on first use. It is optional and is not part of the base install.
Quick start
from pystreampdf import SemanticChunker, ElementType
with open("financial_report.pdf", "rb") as f:
text = f.read().decode("latin-1", errors="ignore") # or use a real PDF text extractor first
chunker = SemanticChunker(target_tokens=500)
chunks = chunker.chunk_content(
text, element_type=ElementType.TEXT, page_start=1, page_end=1
)
print(f"Split into {len(chunks)} semantic chunks")
Usage
Budget-aware caching and chunking:
from pystreampdf import SemanticChunker, PDFCache, TokenBudgetConfig, BudgetRule, ElementType
budget_config = TokenBudgetConfig(
base_budget=800,
rules=[BudgetRule("complex", 1.1, match_fields=["filename"])]
)
cache = PDFCache(
memory_limit_mb=500,
disk_cache_dir="./pdf_cache",
token_budget_config=budget_config,
)
def extract_pdf(pdf_path):
chunks, preview, title, pages = cache.get_or_process(pdf_path, process_fn=extract_chunks)
return chunks
chunker = SemanticChunker(target_tokens=500)
chunks = chunker.chunk_content(text, element_type=ElementType.TEXT, page_start=1, page_end=1)
Exact vs. heuristic token counts. By default counts use len(text) / 4. With the
tiktoken extra installed, pystreampdf.tokenizer.is_exact() returns True and
cl100k_base is used; check pystreampdf.tokenizer.TOKENIZER_MODE to see which mode
produced a given number.
Rust-backed document API (examples/basic_parse.py) — document/page/structure
inspection, requires the compiled _core extension:
import pystreampdf
doc = pystreampdf.open("report.pdf") # None if _core isn't available
MCP connector — optional, local by default, read-only:
# see examples/mcp_pystreampdf.py
Documentation
- Product Vision — what's real and working vs. partial/unverified, verified against source
- Token Budget Multipliers — token budget guide with examples
- Architecture — component overview
- Roadmap — honest status plus near-term plans and known testing gaps
- Examples — runnable scripts: basic_parse.py (Rust-backed document API), token_budget_and_cache_example.py (
SemanticChunker/PDFCache), mcp_pystreampdf.py (MCP connector)
Run the tests with:
make test # cargo test && pytest tests/
Known issues and deferred work
Blocked / failed:
- Linux/Windows wheels, Docling/PyMuPDF benchmark.
Rejected / deferred:
- A full PDF RAG platform; Docling owns layout understanding.
- Persisting/sharing rate-limit buckets across restarts or workers (the proxy keeps them in memory by design; use an external limiter for multi-process deployments).
Full shipped/blocked/rejected detail: QUEUE_STATUS.md.
Contributing
Contributions are welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md. Security issues: see SECURITY.md for the reporting process and the cache/MCP security model.
Related projects
- PyStreamXL — stream large Excel workbooks with constant memory
- PyStreamPDF — token-efficient PDF extraction for RAG
- PyStreamMCP — context and token optimization for AI agents
- PyStreamAI — ML deployment toolkit (canary/A-B, cost tracking, ONNX)
- PyTokenCalc — LLM token counting and cost estimation
- Browse all projects
License
This project is licensed under the Apache License 2.0.
Metadata
Release files for PyStreamPDF 2.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pystreampdf-2.4.0.tar.gz | 127.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pystreampdf-2.4.0-cp312-cp312-macosx_11_0_arm64.whl | CPython 3.12 | CPython 3.12 | macOS 11.0+ ARM64 | Details |
Total release size: 1.7 MB
Release files / pystreampdf-2.4.0.tar.gz
| Download URL | pystreampdf-2.4.0.tar.gz |
|---|---|
| Size | 127.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7948c9314dacbd7fdb563f14c500465dfc90deca0c565725cc6b84da798bc252
|
|
BLAKE2b-256 checksum How to use checksums |
45ed99eaf7e32cd0714e49eacdf6593d896de70bcb3ac720c60f732ea63a7c74
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Release files / pystreampdf-2.4.0-cp312-cp312-macosx_11_0_arm64.whl
| Download URL | pystreampdf-2.4.0-cp312-cp312-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.6 MB |
| Tags | CPython 3.12 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
d6a767e1d1004f5fbeb0d080ee78d5fb02f3082f1128b44145e1ba4e9b5bf4ec
|
|
BLAKE2b-256 checksum How to use checksums |
6bf0264d02d24abeb4f8b2c52c299830c5cc86d5412e2cb1d663e73709cfdf05
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Security for PyStreamPDF 2.4.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.