Spaces:
Running on Zero
A newer version of the Gradio SDK is available: 6.22.0
title: Hybrid Vision RAG PDF Processor
emoji: π
colorFrom: indigo
colorTo: blue
sdk: gradio
sdk_version: 5.20.0
app_file: main.py
pinned: false
license: mit
Hybrid Vision RAG PDF Processor
A demo-ready Python project that extracts PDF content, analyzes charts/graphs via a Vision LLM, and answers questions with a Hybrid RAG system (Vector + Knowledge Graph + BM25).
Tech stack
- Docling β PDF content extraction (text, pages, figures)
- OpenRouter β Vision LLM for chart/graph analysis
- Groq β LLM powering the RAG pipeline
- LlamaIndex β Vector + Knowledge Graph + BM25 retrieval with RRF fusion
- Gradio β demo UI
What's new
- β
Clean layered architecture β
config,models,services,retrieval,session,web - β
Own session architecture β
Sessionentity + pluggableSessionStore+SessionManagerwith janitor - β
Pydantic configuration β all settings loaded from
.envwith validation - β
Structured logging β no more raw
print()statements - β Polished Gradio demo UI β live progress, status badge, commands, and session controls
- β Pre-flight API validation β OpenRouter + Groq are live-checked before the pipeline starts; failures pop a warning and block processing (see the API Status panel)
- β Unit tests for session lifecycle and BM25 retrieval
- β
Unified CLI β
extract,preprocess,app
Quick start
1. Install dependencies
uv sync
# or
pip install -e .
2. Configure API keys
Copy .env.example to .env and add your keys:
cp .env.example .env
OPENROUTER_API_KEY=sk-or-v1-...
GROQ_API_KEY=gsk_...
3. Launch the demo
app
# or
python -m docling_pdf_processor
Open http://localhost:7860 in your browser.
Docker
Run the demo in a container (secrets are injected at runtime β never baked into the image):
# 1. Put real keys in .env (the image does NOT see your host's OS env vars)
# OPENROUTER_API_KEY=sk-or-v1-...
# GROQ_API_KEY=gsk_...
# 2. Build & run
docker compose up --build
Then open http://localhost:8010.
Without compose:
docker build -t docling-pdf-processor .
docker run -p 8010:8010 --env-file .env docling-pdf-processor
The compose file mounts a named volume at /app/.cache/huggingface so Docling's model weights download once and persist across restarts.
β οΈ Key source caveat for containers: on your host,
GROQ_API_KEYmay live in an OS environment variable that overrides the.envplaceholder (the API Status panel shows[source: OS env $GROQ_API_KEY]). A container does not inherit host OS env vars β only what you pass via--env-file/env_file. So you must put the real Groq key in.envbeforedocker compose up, or the pre-flight validation will block processing with401 Invalid API key.
CLI usage
# Extract images + markdown from a PDF
extract --pdf path/to/doc.pdf --output ./extracted_images
# Preprocess extracted images with a Vision LLM
preprocess --quarters ./extracted_images --output graphs_description.pkl
# Launch the Gradio demo
app
Project architecture
docling_pdf_processor/
βββ config.py # Pydantic Settings from .env
βββ models.py # Shared dataclasses
βββ exceptions.py # Domain errors
βββ logging_config.py # Structured logging setup
βββ cli.py # Unified CLI
βββ pipeline.py # Session-aware PipelineOrchestrator
βββ services/
β βββ extractor.py # Docling PDF extraction
β βββ vision.py # OpenRouter Vision-LLM
β βββ rag.py # Hybrid RAG builder + query
βββ retrieval/
β βββ bm25.py # BM25 keyword retriever
β βββ hybrid_rrf.py # RRF fusion retriever
βββ session/
β βββ models.py # Session + SessionStatus
β βββ store.py # SessionStore protocol + implementations
β βββ manager.py # Lifecycle + janitor
βββ web/
βββ gradio_app.py # Demo UI
βββ components.py # UI helpers
Data flow
User uploads PDF
β
βΌ
βββββββββββββββββββββββ
β SessionManager β Creates UUID workspace in temp dir
β (own session arch) β
ββββββββββ¬βββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β PipelineOrchestratorβ Streams progress via Queue
β β’ ExtractorService β Docling β text, pages, figures
β β’ VisionService β OpenRouter β graph descriptions
β β’ RAGService β Vector + KG + BM25 indexes
ββββββββββ¬βββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Gradio Chat UI β Session-scoped memory
β !reasoning β Inspect retriever contributions
β !compare β Side-by-side retriever comparison
βββββββββββββββββββββββ
Session architecture
Every upload creates a Session with:
- A unique UUID
- An isolated workspace under
tempdir/docling_sessions/{uuid}/ - A
SessionStatuslifecycle:created β extracting β analyzing β indexing β ready - A heartbeat timestamp for stale-session cleanup
The SessionStore protocol has two built-in implementations:
InMemorySessionStoreβ default for single-process demosFileSystemSessionStoreβ persists session metadata to disk
A background janitor cleans sessions idle longer than SESSION_MAX_AGE_SECONDS (default 30 min).
Chat commands
!reasoning <query>β show how Vector / KG / BM25 contribute to the final answer!compare <query>β compare Vector, KG, BM25, and Hybrid answers side-by-side
API validation
Before the pipeline runs, the app live-checks the two external APIs it depends on:
| API | Check | Endpoint |
|---|---|---|
| OpenRouter (Vision LLM) | key + credits | GET https://openrouter.ai/api/v1/key |
| Groq (RAG LLM) | key + model actually generate (1-token chat completion) | POST https://api.groq.com/openai/v1/chat/completions |
- The API Status panel in the Process PDF tab shows each API as β
Valid / β Invalid, with a masked key prefix and the key's source (
UI field/OS env $VAR/.env) so it's clear which credential is being tested. - Click Validate APIs to re-run the checks on demand; editing any key/model field live-refreshes the panel.
- When you click Process PDF, validation runs first. If any API fails (bad key β 401, wrong model β 404, no credits β 402), a popup warning shows which API and why, and the pipeline does not start.
Note on key sources: pydantic-settings loads OS environment variables with higher priority than
.env. So if a key is set in your shell/system environment, it overrides a placeholder in.env. The source tag in the panel makes this visible β e.g.OS env $GROQ_API_KEYinstead of.env.
Validation calls are cheap and run in parallel, typically returning in ~1s.
Production notes
- Concurrency β Gradio runs with
default_concurrency_limit=1so only one pipeline processes at a time per process instance. - Multi-tenant scaling β For true multi-user production, run each session in a separate worker or process.
- Vector store β Uses LlamaIndex's in-memory
SimpleVectorStore(the previous DeepLake store is abandoned and breaks on NumPy 2 / Python 3.13). Indexes are rebuilt from the extracted files each session, which is the existing behavior, so nothing is lost β but for very large corpora consider swapping in a persisted store (FAISS, Chroma, pgvector). - API keys β Never commit
.env. It is already ignored by.gitignore. - Large PDFs β PDFs > 100 MB trigger a warning but are not blocked.
Development
Run tests
pytest
Run a specific stage from the CLI
# Extraction only
extract --pdf sample.pdf --output ./out
# Vision analysis only
preprocess --quarters ./out --output ./out/graphs.pkl --api-key $OPENROUTER_API_KEY
License
MIT