Spaces:
Running
A newer version of the Gradio SDK is available: 6.20.0
Technical Architecture β Document Extraction Agent
1. Guiding principle
One reusable core; thin replaceable edges. The core is a pure-ish function:
process_document(path) -> ExtractionResult
Everything that triggers it (folder watcher, web upload) and everything it depends on (the model backend) sits behind interfaces so the core never knows which entry point invoked it or which model produced the fields. This is what keeps the project portable across local and cloud, and it is the main thing that makes the design read as engineering rather than prompting.
2. System overview
βββββββββββββββββββββββββββββββ
inbox/ (watcher) ββββββΆ β
β CORE PIPELINE ββββββΆ SQLite + CSV
web upload (demo) βββββΆ parse β extract β validate β (batch mode)
β β score β route β
β ββββββΆ review/ (batch)
βββββββββββββββββ¬ββββββββββββββ
β
Model Backend (interface)
βββ GeminiBackend (free API, multimodal)
βββ OllamaBackend (local, offline)
In batch mode the pipeline persists and moves files. In demo mode the same pipeline returns a result that is rendered and then discarded.
3. Core pipeline stages
- Intake & type detection. Identify modality from extension/MIME:
native_pdf,image(scan or photo). This determines the parse path. - Text/representation acquisition.
native_pdf: parse with Docling to get text + layout. Optionally also keep the page image for backends that prefer vision.image: two supported strategies, backend-dependent β (a) vision-direct: pass the image to a multimodal backend (Gemini), no separate OCR; (b) ocr-then-text: run OCR (e.g. PaddleOCR/Tesseract) to text, then pass text to a text-only backend (Ollama small models).
- Extraction. Call
backend.extract(document_payload, schema)and get back a structured object plus whatever confidence signal the backend exposes. Structured output is enforced (JSON schema / grammar), not parsed out of free text. - Validation. Run the rule set (types, formats, required fields, arithmetic cross-checks). Produce a per-field validation status and a list of failures.
- Confidence scoring. Combine model signal, validation outcome, and required-field completeness into a single document-level score.
- Routing. Compare score to threshold and apply hard-fail overrides
(a failed critical cross-check forces review regardless of score). Emit an
ExtractionResultwithdecision β {accept, review}.
4. Components and responsibilities
- Watcher / runner (
ingest/): detects new files, calls the core, performs file moves and persistence side-effects in batch mode. Owns retry and isolation so one document never halts the loop. - Parser (
parsing/): modality detection and text/layout acquisition. Wraps Docling and the OCR option behind a singleacquire(path) -> Payload. - Backend interface (
backends/base.py): definesextract(). Concrete adapters:gemini.py,ollama.py. Selected by config via a factory. - Schema & models (
schema/): the Pydantic models that define the output contract and are used to enforce structured output and to validate types. - Validation (
validation/): pure functions over the parsed object β validation report. No I/O. - Confidence & routing (
routing/): pure functions β score and decision. - Persistence (
store/): SQLite writer + CSV exporter. Batch mode only. - Web demo (
web/): a Gradio app that uploads one file, calls the core, and renders the result. No persistence. - Eval (
eval/): runs the core over a labelled dataset and computes metrics.
5. Model backend abstraction
class ExtractionBackend(Protocol):
name: str
def extract(self, payload: DocumentPayload, schema: type[BaseModel]) -> BackendResult: ...
# BackendResult: { data: dict, field_confidence: dict | None, raw: Any }
- GeminiBackend. Calls the Gemini free tier. Multimodal: accepts the page image directly for scans/photos and text for native PDFs. Requests schema-constrained JSON output. Used by the cloud demo (CPU host can't run a local model) and available locally.
- OllamaBackend. Calls a local Ollama server (e.g. a 3Bβ7B model). Text-in only, so images must go through the OCR path first. Uses grammar/JSON-schema constrained decoding for reliable structure. Used for offline, private, and no-quota local runs.
Backend rule: never hardcode a single remote model name in logic β model identifiers are config, because free catalogs change without notice. Treat a missing/renamed model as a recoverable config error.
6. Dual entry points
- Watcher (autonomous). A long-running process using a filesystem watcher
(or a poll loop for portability). On a new file:
process_document(), then persist + move. This is the "runs on its own" capability. - Web demo (URL). A Gradio interface with a single upload control. On
upload:
process_document(), render fields + confidence + validation + decision, then discard. Carries an explicit "synthetic/public documents only" notice (see NFR-2).
Both are ~50β100 lines of glue. All real logic lives in the core.
7. Data flow for one document
file ββΆ detect modality ββΆ acquire payload (Docling | OCR | raw image)
ββΆ backend.extract(payload, schema) ββΆ raw structured data
ββΆ validate(data) ββΆ validation report
ββΆ score(data, report, model_signal) ββΆ confidence
ββΆ route(confidence, report) ββΆ {accept | review}
ββΆ [batch] persist + move file / [demo] render + discard
8. Confidence and routing logic
Document confidence blends three inputs:
- Model signal β backend-exposed token/field confidence where available; otherwise treated as neutral.
- Validation β a hard-failed critical cross-check (e.g. totals don't
reconcile) forces
reviewregardless of score. Soft failures reduce score. - Completeness β missing required fields reduce score.
Routing:
if any(critical_hard_failures): decision = review
elif confidence >= THRESHOLD: decision = accept
else: decision = review
THRESHOLD is a single tunable constant. The evaluation harness exists
precisely to set it (see data spec β optimize auto-accept precision on
critical fields, accept the resulting recall, and report both).
9. Error handling
- Per-document
try/exceptin the runner; failures log full context and route toreview/with a reason. The loop continues. - Backend calls are wrapped with bounded retries and timeouts; exhausted retries route to review, they do not crash.
- Idempotency: a content hash prevents reprocessing the same file twice across restarts.
10. Technology choices (and why)
- Python β ecosystem fit for parsing/ML.
- Docling β open-source, free, structured PDF/scan parsing with layout and tables, agent-framework friendly.
- Pydantic β the output contract doubles as a validation and structured-output schema.
- Gradio β minimal code to a public demo UI; native to Hugging Face Spaces.
- SQLite + CSV β zero-infra local persistence and a portable export.
- Gemini free tier + local Ollama β two free inference paths covering cloud-demo and offline/private use behind one interface.
11. Deployment topology
- Local / batch: watcher + core + (Gemini or Ollama) + SQLite/CSV on the operator's machine. Fully free; Ollama gives no quotas and full privacy.
- Cloud / demo: Gradio app + core + Gemini backend on Hugging Face Spaces (free, CPU-only, sleeps when idle). No local model in the cloud; no persistence. Public URL for the portfolio.
See the setup doc for concrete structure, configuration, and deployment steps.