GoutamSachdev's picture
Set sdk_version 5.20.0 for ssr kwarg; drop gradio from requirements to avoid SDK pin conflict
dd3eb25
|
Raw
History Blame Contribute Delete
8.51 kB
---
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** β€” `Session` entity + pluggable `SessionStore` + `SessionManager` with janitor
- βœ… **Pydantic configuration** β€” all settings loaded from `.env` with 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
```bash
uv sync
# or
pip install -e .
```
### 2. Configure API keys
Copy `.env.example` to `.env` and add your keys:
```bash
cp .env.example .env
```
```env
OPENROUTER_API_KEY=sk-or-v1-...
GROQ_API_KEY=gsk_...
```
### 3. Launch the demo
```bash
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):
```bash
# 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:
```bash
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_KEY` may live in an OS environment variable that overrides the `.env` placeholder (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 `.env` before `docker compose up`, or the pre-flight validation will block processing with `401 Invalid API key`.
---
## CLI usage
```bash
# 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 `SessionStatus` lifecycle: `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 demos
- `FileSystemSessionStore` β€” 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_KEY` instead of `.env`.
Validation calls are cheap and run in parallel, typically returning in ~1s.
---
## Production notes
- **Concurrency** β€” Gradio runs with `default_concurrency_limit=1` so 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
```bash
pytest
```
### Run a specific stage from the CLI
```bash
# 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