Skip to main content

multixtract

Vendor-neutral document extraction for search & RAG. Pull text, tables, and images out of PDFs, Word, PowerPoint, and Excel/CSV files, let any vision model describe the images, chunk everything into bite-size pieces, embed them, and store the result anywhere.

The core is tiny (just Pillow + ImageHash). Every format parser and every cloud SDK is an optional extra — install only what you need and plug in OpenAI, Azure OpenAI, a local model, Azure Blob, S3, or local disk.

Install

pip install multixtract                 # core only — framework + image filters

Format extractors

Install the formats you need (each lazy-loads its parser; calling an extractor without its extra raises a clear pip install hint):

Extra Formats Pulls in
[pdf] .pdf PyMuPDF, pdfplumber
[docx] .docx (+ legacy .doc*) python-docx
[pptx] .pptx (+ legacy .ppt*) python-pptx
[xlsx] .xlsx, .xlsm, .csv openpyxl
[imaging] decode .wdp / JPEG-XR images embedded in pptx/xlsx imagecodecs
pip install "multixtract[pdf]"                 # just PDFs
pip install "multixtract[pdf,docx,pptx,xlsx]"  # all document formats

* Legacy .doc / .ppt are converted via a system LibreOffice install (headless) then parsed natively (.doc→docx, .ppt→pptx). EMF/WMF/SVG vector images also require LibreOffice.

Providers

Extra Adds
[openai] OpenAI vision & embeddings
[azure] Azure OpenAI + Azure Blob Storage
[qwen2vl] Qwen2.5-VL — recommended local vision model (leads 7B class on DocVQA/ChartQA; GPU 16–24 GB recommended)
[smolvlm] SmolVLM 2.2B — CPU-friendly local vision model, better accuracy than Moondream
[llama] Llama 3.2 Vision — strong free alternative (11B; GPU 16 GB recommended)
[all] all formats + imaging + all providers
pip install "multixtract[openai]"     # + OpenAI vision & embeddings
pip install "multixtract[azure]"      # + Azure OpenAI & Azure Blob Storage
pip install "multixtract[qwen2vl]"    # + Qwen2.5-VL local vision (GPU recommended)
pip install "multixtract[smolvlm]"    # + SmolVLM 2.2B local vision (CPU-friendly)
pip install "multixtract[all]"        # everything

Quick start

from multixtract import Pipeline
from multixtract.providers import OpenAIVisionModel, OpenAIEmbedder
from multixtract.providers.storage import LocalDiskStore

pipeline = Pipeline(
    vision=OpenAIVisionModel(api_key="sk-...", model="gpt-4o"),
    embedder=OpenAIEmbedder(api_key="sk-...", model="text-embedding-3-large", dim=1024),
    store=LocalDiskStore("./output_folder"),
)

result = pipeline.process("report.pdf")   # also .docx / .pptx / .xlsx / .csv
print(result.document)   # {metadata, pgs:[{txt, tables, imgs:[...]}]}
print(result.chunks)     # [{chunk_id, chunk_type, content, embedding, ...}]

Recipes — use only the parts you need

Extraction, vision (OCR/description), chunking, and embedding are fully decoupled. Call only the steps you want — no Pipeline required.

Extract only — no chunking, no embedding

from multixtract import extract_document          # needs multixtract[pdf]

document, images = extract_document("report.pdf")  # .docx / .pptx / .xlsx / .csv too

for page in document["pgs"]:
    print(f"--- page {page['pg_num']} ---")
    print(page["txt"])                 # plain text
    for table in page["tables"]:       # each table is a list of row-lists
        print(table)

# `images` = filtered, de-duplicated images ready for analysis.
# NOTE: no vision model was called — these are raw image bytes + metadata.
for img in images:
    print(img["image_id"], img["page_number"], img["width"], "x", img["height"])

No API keys, no cloud SDKs, no chunk_document — just text, tables, and the filtered image bytes.

Extract + chunk, but don't embed

from multixtract import extract_document, chunk_document

document, _ = extract_document("timetable.pdf")
chunks = chunk_document(document, base_name="timetable")  # each chunk has embedding=None

OCR images with a vision model — no embedding

OCR text comes from a VisionModel (e.g. GPT-4o vision), which also returns a caption and a longer description. Run it directly on the filtered images and skip the embedder/chunker entirely.

from multixtract import extract_document
from multixtract.providers import OpenAIVisionModel    # needs multixtract[openai]

vision = OpenAIVisionModel(api_key="sk-...", model="gpt-4o")

document, images = extract_document("scanned.pdf")      # needs multixtract[pdf]
for img in images:
    result = vision.analyze(
        image_bytes=img["image_bytes"],
        ext=img["ext"],
        width=img["width"],
        height=img["height"],
    )
    print(img["image_id"], "| OCR:", result.ocr_text)
    print("            caption:", result.caption)
    print("            description:", result.description)

On Azure OpenAI, swap in the Azure provider (multixtract[azure]) and pass your endpoint + deployment. Keep secrets out of code — inject them via environment variables or a secrets manager:

from multixtract.providers import AzureOpenAIVisionModel

vision = AzureOpenAIVisionModel(
    endpoint="https://<resource>.openai.azure.com",
    api_key=AZURE_OPENAI_KEY,          # injected, never hard-coded
    deployment="gpt-4o",
)
# vision.analyze(...) exactly as above

Bring your own OCR — fully offline (no cloud)

A VisionModel is just any object with an analyze() method (structural typing — no subclassing or cloud SDK needed). Here's a zero-cloud one backed by Tesseract (pip install pytesseract, plus a system tesseract binary):

import io
import pytesseract
from PIL import Image
from multixtract import extract_document
from multixtract.interfaces import VisionResult

class TesseractVisionModel:
    """Offline OCR-only VisionModel — no network, no API key."""
    def analyze(self, image_bytes, ext="png", width=0, height=0) -> VisionResult:
        try:
            text = pytesseract.image_to_string(Image.open(io.BytesIO(image_bytes)))
        except Exception:
            return VisionResult()          # never break the caller
        return VisionResult(ocr_text=text.strip())

vision = TesseractVisionModel()
document, images = extract_document("scanned.pdf")     # needs multixtract[pdf]
for img in images:
    print(img["image_id"], "| OCR:", vision.analyze(img["image_bytes"], img["ext"]).ocr_text)

Because it satisfies the same VisionModel interface as the cloud providers, you can also drop it straight into the full pipeline — Pipeline(vision=TesseractVisionModel(), embedder=..., store=...) — for offline OCR end-to-end.

Local vision models — offline, no API key

Three local model options are available. All return the same VisionResult structure and work as drop-in replacements for the cloud providers.

Qwen2.5-VL (recommended)

Best accuracy for document images — leads the 7B class on DocVQA, ChartQA, TextVQA, and OCR benchmarks as of 2025. Requires a GPU with 16–24 GB VRAM for BF16; use the 3B variant or load_in_4bit=True for smaller cards.

pip install "multixtract[qwen2vl]"
from multixtract import extract_document
from multixtract.providers import Qwen2VLVisionModel

# Default: 7B. Use "Qwen/Qwen2.5-VL-3B-Instruct" for lower VRAM.
vision = Qwen2VLVisionModel()
document, images = extract_document("report.pdf")
for img in images:
    r = vision.analyze(img["image_bytes"], ext=img["ext"])
    print(r.caption, "|", r.description)

# Drop into the full pipeline:
Pipeline(vision=Qwen2VLVisionModel(), embedder=my_embedder, store=my_store).process("report.pdf")

SmolVLM 2.2B (CPU-friendly)

At 2.2B parameters, SmolVLM runs on CPU without impractical wait times and delivers meaningfully better DocVQA and ChartQA accuracy than Moondream2. No trust_remote_code required. Use it when a GPU is unavailable.

pip install "multixtract[smolvlm]"
from multixtract.providers import SmolVLMVisionModel

vision = SmolVLMVisionModel()       # ~4 GB download on first use
vision = SmolVLMVisionModel("HuggingFaceTB/SmolVLM-500M-Instruct")  # 500M for extreme constraints
document, images = extract_document("report.pdf")
for img in images:
    r = vision.analyze(img["image_bytes"], ext=img["ext"])
    print(r.caption, "|", r.ocr_text)

Llama 3.2 Vision

Strong free alternative, especially for users already in the Meta/Llama ecosystem. Requires ≥16 GB VRAM for the 11B model.

from multixtract.providers import Llama32VisionModel   # pip install "multixtract[llama]"

vision = Llama32VisionModel()                                         # 11B default
vision = Llama32VisionModel("meta-llama/Llama-3.2-90B-Vision-Instruct")  # 90B, highest accuracy
vision = Llama32VisionModel(load_in_4bit=True)                        # 4-bit, needs bitsandbytes

Architecture

 file → extract (text/tables/images) → filter images → vision describe
      → chunk (text/table/image) → embed → store (JSON)

The right extractor is chosen by file extension via a registry; the pipeline talks only to three interfaces — it never imports a vendor directly:

Interface Job Built-in implementations
VisionModel image → caption + OCR + description OpenAIVisionModel, AzureOpenAIVisionModel, Llama32VisionModel
Embedder text → vector OpenAIEmbedder, AzureOpenAIEmbedder
BlobStore save bytes/JSON LocalDiskStore, AzureBlobStore

Write your own by implementing the same methods (e.g. a local vision model, a sentence-transformers embedder, or an S3 store). Add a new format by implementing DocumentExtractor and calling register_extractor.

Features

  • Multi-format: PDF, Word, PowerPoint, Excel/CSV (+ legacy .doc/.ppt via LibreOffice)
  • Cross-page image deduplication via xref tracking
  • Image filters: solid-color / tiny-icon / dimension / reference-logo (perceptual hash)
  • Sliding-window text chunking (~500 tokens, ~50 overlap) at sentence boundaries
  • Tables serialized to Markdown; images embedded once and reused
  • Parallel vision calls, batched embeddings

Development

pip install -e ".[dev,pdf,docx,pptx,xlsx]"
pytest
ruff check src tests

Troubleshooting

LibreOffice not found / vector images skipped EMF, WMF, and SVG images embedded in PPTX/XLSX are converted via LibreOffice. Install it system-wide (apt install libreoffice / brew install libreoffice / libreoffice.org) and ensure soffice is on PATH. Without it, vector images are silently skipped; other image types are unaffected.

.doc / .ppt legacy files not extracted Legacy binary formats require LibreOffice for conversion to DOCX/PPTX before extraction. The same soffice dependency applies.

transformers / torch import errors or CUDA failures Local vision models (Qwen2.5-VL, Llama 3.2 Vision, SmolVLM) require a compatible torch + CUDA environment. Confirm with:

import torch; print(torch.cuda.is_available(), torch.version.cuda)

If CUDA is unavailable, SmolVLM ([smolvlm]) is the recommended model that runs on CPU at practical speeds. Qwen2.5-VL and Llama 3.2 Vision require a GPU with ≥16 GB VRAM in BF16; use load_in_4bit=True for smaller cards.

pip install multixtract[qwen2vl] takes a long time torch is a large package (~2 GB). Pull a GPU-specific wheel with:

pip install "multixtract[qwen2vl]" --extra-index-url https://download.pytorch.org/whl/cu121

Replace cu121 with your CUDA version (cu118, cu124, etc.).

Azure DefaultAzureCredential fails locally DefaultAzureCredential tries several auth paths in order. For local dev the easiest is az login (Azure CLI). For managed identity in production, ensure the compute resource has an assigned identity and the necessary role on the target resource.

PyMuPDF / pdfplumber version conflicts If you see ImportError from fitz, ensure PyMuPDF>=1.23 is installed. pdfplumber and PyMuPDF can coexist; both are required for the [pdf] extra.

Acknowledgements

Built with assistance from Claude (Anthropic) for code review, bug analysis, and quality improvements.

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

multixtract-0.1.1.tar.gz (52.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

multixtract-0.1.1-py3-none-any.whl (73.7 kB view details)

Uploaded Python 3

File details

Details for the file multixtract-0.1.1.tar.gz.

File metadata

  • Download URL: multixtract-0.1.1.tar.gz
  • Upload date:
  • Size: 52.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for multixtract-0.1.1.tar.gz
Algorithm Hash digest
SHA256 46ef50fa6a64f5bb6356a5da7550e7ca1d02229ed4cda23cd2edf6177ecc3ba4
MD5 689cf5972d7e93bd6b95fc72ff82c82c
BLAKE2b-256 d452fdb1fdf8d263bae7726dba4af6f1941b8e63d1be3dd791ada2c85bc2ce59

See more details on using hashes here.

File details

Details for the file multixtract-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: multixtract-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 73.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for multixtract-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3ad0f580212a71c7d0e19c6e5350163aec0fd03230152fe6ff9045a65bfb9d0c
MD5 aac0d3106269e82f902f5d0fd28bfd31
BLAKE2b-256 558202aad64e0f395641d08f3a892e0a1a28a62744d27b2c6ed5caaa533ca3cc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page