cd /news/developer-tools/parsing-documents-for-air-gapped-rag… Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-73226] src=dev.to β†— pub= topic=developer-tools verified=true sentiment=↑ positive

Parsing documents for air-gapped RAG: no cloud, no JVM, no Python

A developer built DeepDoc, a single static Rust binary that parses docx, pptx, xlsx, and other born-digital formats into clean Markdown without any runtime dependencies, cloud uploads, or non-deterministic ML models. The tool is designed for air-gapped RAG pipelines where documents cannot leave the machine, offering a reproducible, deterministic extraction process.

read6 min views1 publishedJul 25, 2026

I kept running into the same unglamorous wall.

Every time I built something on top of an LLM β€” a search-over-docs feature, a RAG pipeline, an internal assistant β€” the first step was the same boring problem: "here's a folder of docx, pdf and pptx files, give me clean text." Not a hard problem. Just one that, somehow, every available tool solved by making me pay a price I didn't want to pay.

And in my case there was an extra constraint that quietly kills half the options: the documents couldn't leave the machine.

If you've done document ingestion, you know the shortlist.

Apache Tika is the honest default. It's been the de-facto standard for 15 years, it reads hundreds of formats, and it works. It's also a JVM. That means standing up a JVM, feeding it, watching it, and carrying it around inside every container β€” which feels deeply out of place in a Rust or Go service. Tika is great. The JVM tax is the part people are trying to get away from.

The Python stacks β€” unstructured

, Docling, MarkItDown β€” are powerful and popular in every RAG tutorial. But "just parse a document" turns into pip, native wheels, and down ML model weights before you get a single line of text. They're heavier and slower to cold-start than the job needs for clean, born-digital files, and the ML paths aren't deterministic. Overkill when the document already has real text in it.

Cloud parsers like LlamaParse are the easiest to start with β€” until you read the two lines of fine print. You upload your documents to someone else's server, and you pay per page. For anyone in a bank, a hospital, a law firm, or any air-gapped environment, "upload the private documents" is where the conversation ends.

None of them answered the thing I actually wanted: any document β†’ clean Markdown, one binary, on my machine, deterministically.

So I wrote it. It's called DeepDoc.

A single static Rust binary. No JVM, no Python, no model downloads, no runtime dependencies at all. Copy it into a scratch

Docker layer and go. Nothing touches the network, nothing shells out. Same input, same output, every time β€” because a RAG index you can't reproduce is a bug waiting to happen.

Here's the whole thing on a real file:

$ deepdoc report.docx

Prepared by the Platform team. This memo covers delivery and headcount for the third quarter.

## Delivery

We shipped the ingestion rewrite and cut p95 latency by 38%.

The nightly batch now runs in under nine minutes.

## Headcount

| Team     | Engineers | Open roles |
| -------- | --------- | ---------- |
| Platform | 9         | 2          |
| Search   | 6         | 1          |
| Data     | 4         | 0          |

That's a Word document β€” headings, paragraphs, and a real table β€” coming out as clean Markdown, table intact, in one command. No server, no upload, no warm-up.

Every format gets parsed into one neutral Document

model β€” headings, paragraphs, lists, tables, metadata, page markers. Then that model is serialized to Markdown, JSON or plain text by pure functions. That's the whole design, and the boringness is the point: the parser for .docx

and the parser for .pptx

disagree about almost everything, but once they're in the same shape, "turn this into Markdown" is one code path, tested once.

v0.1 covers the born-digital formats I actually hit in ingestion work: docx

, pptx

, xlsx

, odt

, ods

, odp

, epub

, html

, rtf

, csv

, md

, txt

, and text-based pdf

β€” thirteen formats in one binary. Point it at a folder and it walks the tree in parallel and mirrors the structure:

$ deepdoc ./docs --recursive -o out/
./docs/changelog.txt    β†’ out/changelog.md
./docs/meeting-notes.md β†’ out/meeting-notes.md
./docs/report.docx      β†’ out/report.md
βœ“ 3 extracted, 0 skipped

Naive chunkers split on character count and throw away structure. So a chunk about "refunds" stops knowing it lived under Billing β†’ Refunds, and you've thrown away the exact breadcrumb your embedding could have used.

DeepDoc parses structure first, then chunks on block boundaries while carrying the heading path and the byte range for every chunk:

$ deepdoc handbook.pdf --chunk 800 --format json | jq
{
  "chunks": [
    {
      "byte_range": [0, 483],
      "heading_path": ["Employee Handbook"],
      "source": "handbook.pdf",
      "text": "# Employee Handbook\n\n## Remote Work\n\nEmployees may work remotely up to three days per week..."
    },
    { "…": "1 more chunk" }
  ],
  "meta": { "source_format": "pdf", "page_count": 1, "…": "…" }
}

Every chunk knows which section it came from and where in the source it lives. That heading_path

goes straight onto the vector as metadata, and the byte_range

means you can always trace a retrieved chunk back to the exact offset in the original file.

I'll be upfront about what DeepDoc is not, because overselling a dev tool is how you lose the people you're trying to reach.

v0.1 is born-digital documents β€” files with real embedded text. It is not an OCR engine and not a complex-table-from-scans reconstructor. That's genuinely hard, it's ML territory, and tools like Docling and Marker do it well. If you hand DeepDoc a scanned PDF that's really just images, it detects that and exits with a specific code (4) telling you so β€” instead of confidently emitting garbage into your index. OCR is planned as an optional, feature-gated path later; it is deliberately not bundled into the tiny default binary.

The bet is simple: roughly 80% of the documents in a real ingestion pipeline are born-digital, and for those you don't need a GPU or a model download. You need something fast, deterministic, and local. That's the lane.

The CLI is a thin wrapper. The real entry point is the deepdoc-core

crate, so you can embed extraction directly in a Rust service β€” no subprocess, no FFI, no shelling out:

use deepdoc_core::{extract, to_markdown};

let doc = extract("handbook.pdf")?;
let md  = to_markdown(&doc);

The closest thing in the ecosystem, extractous

, is actually Tika under GraalVM native-image β€” not pure Rust, and not a small binary. DeepDoc is Rust top to bottom, and every dependency in the graph is permissively licensed on purpose, so nothing stops you from wrapping it in whatever you're building.

brew install deeplabua/tap/deepdoc
cargo install deepdoc

It's open source (MIT / Apache), v0.1, and on GitHub: https://github.com/deeplabua/deepdoc

If you're building document ingestion for a local or compliance-bound RAG system, I'd genuinely love your feedback β€” especially on which formats or edge cases to prioritize next. And if you've been fighting the JVM/Python/cloud trade-off yourself, tell me what you ended up doing; I want to know if I missed a better option.

DeepDoc is part of a small set of local-first, dependency-light tools I'm building under DeepLab β€” same idea as DeepShrink, which fits any video under a size limit from one command. Small, honest, no cloud.

── more in #developer-tools 4 stories Β· sorted by recency
── more on @deepdoc 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/parsing-documents-fo…] indexed:0 read:6min 2026-07-25 Β· β€”