shreerangss's picture
Deploy feature commit 2c5f2eb to Hugging Face Space
935c8f7
|
Raw
History Blame Contribute Delete
13.7 kB
---
title: Intelligent Document Query Engine
emoji: 📄
colorFrom: blue
colorTo: green
sdk: docker
app_port: 7860
pinned: false
---
# Intelligent Document Query Engine - Full-Stack RAG Document QA
Live demo: https://shreerangss-intelligent-document-query-engine.hf.space/
GitHub repo: https://github.com/Shreerang4/Intelligent-Document-Query-Engine-RAG
## Overview
Intelligent Document Query Engine is a full-stack PDF RAG application with an evaluated retrieval pipeline and persistent document/query history. It combines a React/Vite frontend with a FastAPI backend that ingests PDFs from a URL or upload, chunks extracted text, retrieves relevant evidence, reranks it, asks Groq to generate source-grounded answers with page/chunk citations and claim verification, and stores history in managed MySQL.
The Hugging Face live demo is updated with persistence enabled. The app defaults to E5-small-v2 for retrieval quality. MiniLM remains selectable through `EMBEDDING_MODEL_NAME` for fallback/baseline comparison.
## Features
- React/Vite UI for PDF URL ingestion and PDF upload.
- Persistent document and query history backed by managed MySQL/Aiven.
- FastAPI API with bearer-token protection for query endpoints.
- PDF validation, download/upload handling, and PyMuPDF text extraction.
- Page-aware chunking with 500-character chunks and 50-character overlap.
- Configurable embeddings through `EMBEDDING_MODEL_NAME`.
- E5-small-v2 default: `intfloat/e5-small-v2` with correct `passage:` and `query:` prefixes.
- MiniLM fallback/baseline: `all-MiniLM-L6-v2`.
- In-memory FAISS vector search with embedding-aware RAM cache keys.
- Persistent upload chunks and float32 E5 embeddings in MySQL, allowing FAISS
reconstruction without re-embedding after a restart.
- Optional upload `request_id` recovery for committed responses.
- BM25 and E5+BM25 hybrid retrieval experiments behind `RETRIEVAL_MODE`.
- CrossEncoder reranking, defaulting to `cross-encoder/ms-marco-TinyBERT-L-2-v2`.
- Groq LLM answer generation, defaulting to `llama-3.1-8b-instant`.
- Source-grounded responses with page number, chunk id, and excerpts.
- Claim extraction and verification against retrieved evidence.
- Persistence-ready user ownership columns using the current `local-dev-user` placeholder until OAuth is added.
- Retrieval evaluation harness with benchmark reports and targeted probes.
## Architecture
`React UI -> FastAPI API -> RAM/MySQL artifact lookup -> PDF extraction on full miss -> chunking -> embeddings -> FAISS/BM25 retrieval experiments -> CrossEncoder reranking -> Groq LLM -> source-grounded answers + claim verification -> atomic MySQL persistence`
```mermaid
flowchart LR
A[React UI] --> B[FastAPI API]
B --> C{PDF input}
C -->|Upload| D[UploadFile bytes]
C -->|URL| E[httpx PDF download]
D --> F[PyMuPDF extraction]
E --> F
F --> G[Text cleanup and chunking]
G --> H[Configurable embeddings]
H --> I[FAISS retrieval]
G --> J[BM25 lexical retrieval]
I --> K[CrossEncoder reranking]
J --> K
K --> L[Groq LLM answer generation]
L --> M[Claim verification]
M --> N[Source-grounded answer with excerpts]
N --> O[MySQL history tables]
```
## Retrieval Evaluation
The retrieval pipeline was evaluated on a fixed financial-document benchmark:
- 33 labeled questions across Infosys, HDFC Bank, and Bajaj Finance annual reports.
- Question types: lexical, paraphrase, conceptual, and distractor.
- Metrics: Recall@3, Recall@5, MRR, needs_review count, retrieval latency, and ingestion/indexing time.
- Evaluation mode: retrieval-only, no LLM answer generation.
Final retrieval metrics:
| Configuration | R@3 | R@5 | MRR | needs_review | p50 | p95 | ingest/index time |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| MiniLM | 54.2% | 62.5% | 0.474 | 7 | 56 ms | 144 ms | 230.8s |
| E5-small-v2 | 58.3% | 70.8% | 0.496 | 4 | 73 ms | 156 ms | 534.0s |
| E5+BM25 hybrid | 58.3% | 70.8% | 0.504 | 5 | 216 ms | 527 ms | 510.0s |
Decision summary:
- E5-small-v2 improves retrieval quality over the MiniLM baseline, raising R@5 from 62.5% to 70.8% and reducing needs_review from 7 to 4.
- E5-small-v2 is especially helpful on paraphrase and conceptual questions.
- E5+BM25 hybrid rescued one exact table case but did not improve R@5, increased needs_review from 4 to 5, and tripled p50 retrieval latency, so it remains a documented ablation rather than the default.
- Larger embedding candidates were rejected for this environment: GTE was slower and worse than E5, and Qwen3-0.6B CPU ingestion was impractically slow.
- Remaining misses are documented limitations around table extraction, candidate-pool size, reranker ordering, and benchmark hit criteria.
See [docs/retrieval_evaluation.md](docs/retrieval_evaluation.md) for the detailed evaluation summary.
## Configuration
Backend variables referenced by the code:
| Variable | Required | Default | Purpose |
| --- | --- | --- | --- |
| `API_TOKEN` | Yes | none | Bearer token required by query, history, and database health endpoints. |
| `GROQ_API_KEY` | Yes | none | Used by the Groq SDK for answer generation and claim verification. |
| `DATABASE_URL` | Production | `sqlite:///./rag_persistence.db` | SQLAlchemy database URL for persisted users, documents, chunks, queries, and citations. Production uses managed MySQL/Aiven. |
| `DB_CA_CERT` | Local MySQL | none | Local path to the MySQL CA certificate for TLS verification. Do not commit this file. |
| `DB_CA_CERT_B64` | HF MySQL | none | Base64-encoded CA certificate secret decoded at startup for Hugging Face deployment. |
| `PORT` | No | `7860` | Uvicorn port used by `start.py`. |
| `MAX_PDF_BYTES` | No | `15728640` | Maximum PDF size in bytes. |
| `HTTP_TIMEOUT_SECONDS` | No | `30` | Timeout for PDF URL downloads. |
| `RETRIEVAL_K_INITIAL` | No | `20` | Initial FAISS retrieval count before reranking in the app path. |
| `RETRIEVAL_K_FINAL` | No | `8` | Final chunk count after reranking in the app path. |
| `RETRIEVAL_MODE` | No | `faiss_reranker` | Retrieval path. Experimental option: `e5_bm25_reranker`. |
| `HYBRID_E5_K_INITIAL` | No | `30` | E5 candidate count for the hybrid experiment. |
| `HYBRID_BM25_K_INITIAL` | No | `20` | BM25 candidate count for the hybrid experiment. |
| `HYBRID_K_FINAL` | No | `5` | Final reranked chunk count for the hybrid experiment. |
| `MAX_CONCURRENT_QUESTIONS` | No | `4` | Concurrent question processing limit. |
| `DOCUMENT_CACHE_MAX_ITEMS` | No | `8` | Maximum number of cached document indexes. |
| `DOCUMENT_CACHE_TTL_SECONDS` | No | `3600` | Document cache TTL in seconds. |
| `EMBEDDING_MODEL_NAME` | No | `intfloat/e5-small-v2` | Hugging Face embedding model name. Set `all-MiniLM-L6-v2` to use the MiniLM fallback/baseline. |
| `RERANKER_MODEL_NAME` | No | `cross-encoder/ms-marco-TinyBERT-L-2-v2` | CrossEncoder reranker model name. |
| `LLM_MODEL_NAME` | No | `llama-3.1-8b-instant` | Groq model name. |
Frontend variable:
| Variable | Required | Default | Purpose |
| --- | --- | --- | --- |
| `VITE_API_BASE_URL` | No | same origin | Optional API base URL for local Vite development. |
## Local Development
Backend:
```powershell
py -m venv .venv
.venv\Scripts\Activate.ps1
pip install --upgrade pip
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements.txt
$env:GROQ_API_KEY="your_groq_api_key"
$env:API_TOKEN="your_local_api_token"
$env:DATABASE_URL="mysql+pymysql://..."
$env:DB_CA_CERT="certs/ca.pem"
$env:PORT="7860"
py start.py
```
Frontend:
```powershell
cd frontend
copy .env.example .env
npm install
npm run dev
```
For local Vite development, set `VITE_API_BASE_URL` in `frontend/.env` to the backend URL.
## Evaluation Commands
MiniLM baseline:
```powershell
$env:EMBEDDING_MODEL_NAME='all-MiniLM-L6-v2'
.venv\Scripts\python.exe eval\runner.py --no-llm --out eval\results\stage2d_minilm
```
E5-small-v2:
```powershell
$env:EMBEDDING_MODEL_NAME='intfloat/e5-small-v2'
.venv\Scripts\python.exe eval\runner.py --no-llm --out eval\results\stage2d_e5_small_v2
```
E5+BM25 hybrid ablation:
```powershell
$env:EMBEDDING_MODEL_NAME='intfloat/e5-small-v2'
$env:RETRIEVAL_MODE='e5_bm25_reranker'
.venv\Scripts\python.exe eval\runner.py --no-llm --out eval\results\stage2e_e5_bm25_hybrid
```
Generated files under `eval/results/` are ignored by git. Commit lightweight summaries under `docs/` instead.
## Docker / Hugging Face Spaces Deployment
The Dockerfile builds the frontend with Node 20, then creates a Python runtime image. It installs CPU-only PyTorch and Python dependencies, copies the built frontend into `frontend/dist`, exposes port `7860`, and runs `python start.py`.
Build and run locally:
```powershell
docker build -t intelligent-document-query-engine .
docker run --rm -p 7860:7860 `
--env GROQ_API_KEY=your_groq_api_key `
--env API_TOKEN=your_api_token `
--env DATABASE_URL=your_database_url `
--env DB_CA_CERT=/path/to/ca.pem `
intelligent-document-query-engine
```
For Hugging Face Spaces:
- Use the Docker SDK.
- Configure `DATABASE_URL`, `DB_CA_CERT_B64`, `GROQ_API_KEY`, and `API_TOKEN` as Space secrets.
- Store the MySQL/Aiven CA certificate as `DB_CA_CERT_B64`; do not commit `certs/*.pem`.
- Keep `app_port: 7860` in the README front matter.
- The built React frontend is served by FastAPI from the same origin.
- The live Hugging Face Space at https://shreerangss-intelligent-document-query-engine.hf.space/ is manually deployed and currently includes persistence.
- OAuth is not implemented yet. All persisted records currently use the stable `local-dev-user` placeholder until OAuth replaces `get_current_user_id()`.
## API Endpoints
### `POST /hackrx/run`
Runs the RAG pipeline against a PDF available by URL.
Headers:
```http
Authorization: Bearer <API_TOKEN>
Content-Type: application/json
```
Request body:
```json
{
"documents": "https://example.com/document.pdf",
"questions": [
"What is this document about?",
"What are the key exclusions?"
]
}
```
### `POST /hackrx/upload-run`
Runs the RAG pipeline against an uploaded PDF.
Headers:
```http
Authorization: Bearer <API_TOKEN>
```
Multipart form fields:
- `file`: PDF file upload.
- `questions_json`: JSON array of question strings.
- `request_id`: optional client-generated UUID used to recover a successfully
committed response after a lost HTTP response.
For uploads, the cache order is RAM, then MySQL chunks/embeddings, then complete
PDF extraction and embedding. A committed `request_id` retry returns the stored
answer without rerunning the RAG pipeline. Reusing an ID with different
questions or a different PDF returns HTTP 409.
### `GET /health`
Returns service status, app version, cache entry count, and whether the embedding model, reranker, and Groq client have been loaded.
### `GET /health/db`
Protected by `Authorization: Bearer <API_TOKEN>`. Runs a safe database connectivity check and verifies the seeded `local-dev-user` exists. The response does not expose database host, credentials, certificate paths, or certificate contents.
### `GET /history/documents`
Protected by `Authorization: Bearer <API_TOKEN>`. Returns persisted documents for the current placeholder user, including chunk and query counts.
### `GET /history/documents/{document_id}/queries`
Protected by `Authorization: Bearer <API_TOKEN>`. Returns persisted questions, answers, abstention status, and latency for one document owned by the current placeholder user.
### `GET /history/queries/{query_id}/citations`
Protected by `Authorization: Bearer <API_TOKEN>`. Returns persisted source citations for one stored query.
## Security Notes
- Do not commit `.env`, `.env.local`, or real API keys.
- Do not commit database credentials or CA certificates.
- Store `DATABASE_URL`, `DB_CA_CERT_B64`, `GROQ_API_KEY`, and `API_TOKEN` as Hugging Face Space secrets in production.
- Query, history, and database health endpoints require `Authorization: Bearer <API_TOKEN>`.
- Document indexes and model clients are process-local and in memory.
- URL ingestion downloads caller-provided PDFs, so deployment environments should consider network egress and SSRF risk policies.
## Existing Database Migration
Before deploying this version against an existing Aiven MySQL database, run the
idempotent migration in
`migrations/mysql/001_persistent_embeddings_and_request_recovery.sql`. Detailed
instructions and verification queries are in
[`docs/persistence_schema.md`](docs/persistence_schema.md#aiven-mysql-migration).
Running `scripts/init_db.py` alone is not sufficient because SQLAlchemy
`create_all()` does not alter existing tables.
## Limitations
- The first cache level is process-local memory and is cleared on container
restart; upload FAISS indexes are then rebuilt from persisted embeddings.
- PDF extraction depends on embedded text; scanned/image-only PDFs are not OCR-processed.
- FAISS indexes remain in memory and are reconstructed from persisted upload
embeddings after a restart. URL-only ingestion retains its existing behavior.
- OAuth is not implemented yet. The database schema has `user_id` columns and all current requests use the single `local-dev-user` placeholder.
- The frontend uses a manually entered bearer token rather than an authenticated session flow.
- Retrieval quality is improved but not perfect; remaining misses are documented in the benchmark summary.
## Lightweight Checks
Compile changed Python files:
```powershell
.venv\Scripts\python.exe -m py_compile main.py eval\pipeline_adapter.py eval\runner.py eval\smoke_test.py
```
Run the metric unit tests:
```powershell
.venv\Scripts\python.exe -m pytest eval\test_metrics.py -q
```