atrium

Parsers by format

What each parser produces, and where it gives up.

The parser turns a document into typed elements. Seven types come out of it, and they are the same for every format — which is what lets the chunker be format-agnostic.

TypeWhat it is
headingA section title, with its level and its place in the document's heading tree.
paragraphProse.
list_itemOne bullet or numbered item.
tableRows and cells, kept as a table.
codeA fenced block.
image_captionA caption, or a vision model's description of an image.
footnoteA note at the foot of a page.

PDF

pdfplumber — over pdfminer.six — with layout heuristics on top. Headings are detected from font size, weight and spacing; tables from the page's own ruling geometry; paragraphs from the rest.

The PDF library is a configuration choice, not a dependency of the architecture. atrium/pdf_backend.py defines a seam over the seven primitives the parser needs — open, text with geometry, tables, render a page, embedded images, their bytes — and ATRIUM_PDF_BACKEND selects the implementation. The default is pdfplumber, which is MIT licensed. pip install "atrium-rag[fast-pdf]" adds PyMuPDF, which is faster and AGPL-3.0; AGPL §13 reaches a service offered over a network, so it is opt-in and never a default.

Page numbers are the real page numbers — the page of the PDF.

# Vision captioning for a scanned or image-only page
VISION_ENABLED=1 VISION_MODEL=minicpm-v4.6:1b atrium serve

Where it gives up

  • Text that is baked into a figure is not text. It needs OCR, which is off by default.
  • A two-column paper is read in column order, not across the page, but the heuristic can be wrong about where a column ends when a full-width figure cuts through it.
  • Encrypted PDFs are rejected rather than guessed at.

DOCX

python-docx. Headings, paragraphs, lists and tables.

Where it gives up

  • A .docx has no pages. The page numbers in the output are derived from heading boundaries — useful, and not the same thing as paper.
  • Text boxes, headers and footers are not part of the body flow.

PPTX

python-pptx. One slide becomes one chunk, with the slide's title as its heading and the notes attached.

Where it gives up

  • The visual order of a slide is not the reading order. A slide with two columns of text comes out in shape order.
  • Charts and diagrams are not described unless a vision adapter is on.

XLSX and XLS

openpyxl for .xlsx, xlrd for the legacy .xls.

One chunk per sheet, with the header row kept with the data.

Where it gives up

  • The sheet is the unit of work, not the row. A sheet with a hundred thousand rows is one chunk, and it will be too large to embed usefully. Split it yourself, or use custom chunking.
  • Merged cells are flattened to their top-left value.

HTML

beautifulsoup4. <h1> and <h2> mark page boundaries; the text between them is the body.

Where it gives up

  • Navigation, asides and footers are not stripped. They end up in the text, and they end up in the embedding.
  • Pages are derived from headings, not from any notion of a printed page.

Markdown, plain text, CSV, JSON

Parsed directly. Markdown headings become heading elements; CSV becomes a table with its header row; JSON is walked for text.

EPUB and RTF

striprtf for RTF; EPUB is unpacked and its spine walked.

Where it gives up

  • EPUB is reflowable, so page numbers come from the spine position rather than from paper.

ODT, ODS, ODP

odfpy walks the body tree directly. Pages come from the document's outline level.

Images

JPG, PNG and WebP go to the vision adapter, which describes them.

pip install "atrium-rag[vision]"
VISION_ENABLED=1 atrium serve

Where it gives up

  • It needs a vision model. Without one, an image becomes a placeholder element rather than a description.
  • It is slow: one model call per image.

OCR

For scanned pages and image-only documents:

pip install "atrium-rag[ocr]"
OCR_ENABLED=1 OCR_PROVIDER=tesseract OCR_LANG=eng atrium serve

Two providers:

ProviderWhat it needsGood at
tesseractThe Python wrapper and the system binaryEnglish, and many other languages with extra packs
rapidocrNothing but pip — the models ship in the wheelChinese and English; mixed-script scans

The RapidOCR model reads Chinese and English only. The provider is chosen at runtime, not at install time, so switching is a config change and a restart.

Choosing a parser

There is no "best" parser here — there is the one that suits your corpus. The trade is between a layout model that reads more layouts and a heuristic one that needs no GPU, and Atrium is the second kind.

If a format is not listed here, the API still accepts it and the parser will say so in the job's error field. Add a format by extending UniversalParser — the element contract is seven types and the chunker downstream does not need to know what came out of it.

Atrium Cloud

Don't want to run it yourself? Atrium Cloud is coming.

It is not here yet — the list is what there is until it is. The library is free, MIT licensed, and yours either way.

On this page