---
title: Doc Ingestion RAG Demo
emoji: 📚
colorFrom: blue
colorTo: indigo
sdk: streamlit
sdk_version: "1.37.0"
app_file: spaces/app.py
pinned: false
license: mit
---
# Doc-Ingestion
Citation-aware RAG system for ingesting documents and generating grounded answers with truthfulness scores — CLI, API, and Streamlit UI.
> **[Try the live demo on Hugging Face Spaces](https://huggingface.co/spaces/vampokala/doc-ingestion)** — no install required.
## What it does
- Ingests `.pdf`, `.docx`, `.txt`, `.md`, `.html` files into a hybrid BM25 + vector index
- Retrieves using Reciprocal Rank Fusion across sparse and dense search, with optional cross-encoder reranking
- Generates answers via any LLM provider (Ollama, OpenAI, Anthropic, Gemini) with citation tracking
- Scores every response for **truthfulness** (NLI faithfulness + citation groundedness)
- Exposes a FastAPI backend and a Streamlit UI
---
## Quickstart
### Option 1 — Try online (no install)
Open the [Hugging Face Spaces demo](https://huggingface.co/spaces/vampokala/doc-ingestion). Sample documents about RAG, vector databases, and BM25 are pre-loaded. Paste your OpenAI, Anthropic, or Gemini key in the sidebar.
In hosted demo mode (`DOC_PROFILE=demo`), Streamlit executes queries in-process through the shared orchestrator so demo usage is not blocked by localhost API startup races.
---
### Option 2 — Run locally with Docker (one command)
```bash
git clone https://github.com/vampokala/Doc-Ingestion
cd Doc-Ingestion
cp docker/.env.example docker/.env
# Edit docker/.env to add your API keys (OPENAI_API_KEY etc.)
docker compose -f docker/docker-compose.yml up
```
Open http://localhost:8501 (Streamlit UI) or http://localhost:8000 (API).
---
### Option 3 — Run from source (Python venv)
```bash
git clone https://github.com/vampokala/Doc-Ingestion
cd Doc-Ingestion
bash scripts/bootstrap_demo.sh # creates venv, installs deps, ingests sample docs
```
The script pulls Ollama models automatically if Ollama is installed.
To use a cloud provider instead, skip Ollama and set:
```bash
export OPENAI_API_KEY=...
# or ANTHROPIC_API_KEY / GEMINI_API_KEY
```
Then start:
```bash
source .venv/bin/activate
# API server
PYTHONPATH=. uvicorn src.api.main:app --reload --port 8000
# Streamlit UI (second terminal)
PYTHONPATH=. streamlit run src/web/streamlit_app.py
# Or query from CLI
PYTHONPATH=. python -m src.query "What is RAG?"
```
Note: non-demo local mode keeps the standard split architecture (Streamlit calls FastAPI over HTTP), so running both API and UI processes is still required.
---
## Features
- Multi-format ingestion (PDF, DOCX, TXT, MD, HTML)
- Hybrid retrieval — BM25 + vector with weighted RRF fusion
- Optional cross-encoder reranking (`cross-encoder/ms-marco-MiniLM-L-6-v2`)
- Multi-provider LLM routing: Ollama (local), OpenAI, Anthropic, Gemini — switchable per request
- Citation tracking and per-citation verification
- **Inline truthfulness scoring** on every response (NLI faithfulness + citation groundedness)
- **Offline eval harness** — RAGAS-style metrics over a golden dataset
- FastAPI with auth, rate limiting (Redis or in-memory), streaming SSE
- Streamlit UI with per-request provider/model switching and truthfulness badge
---
## Architecture
System diagram
```mermaid
flowchart LR
userClient[UserClient] --> cliLayer[CLI]
userClient --> apiLayer[FastAPI]
userClient --> streamlitUi[StreamlitUI]
cliLayer --> orchestrator[RAGOrchestrator]
apiLayer --> orchestrator
streamlitUi --> orchestrator
orchestrator --> hybridRetriever[HybridRetriever]
hybridRetriever --> reranker[CrossEncoderReranker]
reranker --> contextOptimizer[ContextOptimizer]
contextOptimizer --> generator[RAGGenerator]
generator --> citationTracker[CitationTracker]
citationTracker --> citationVerifier[CitationVerifier]
citationVerifier --> truthfulness[TruthfulnessScorer]
generator --> llmRouter[LLMProviderRouter]
llmRouter --> ollamaProvider[Ollama]
llmRouter --> openaiProvider[OpenAI]
llmRouter --> anthropicProvider[Claude]
llmRouter --> geminiProvider[Gemini]
orchestrator --> bm25Store[BM25Index]
orchestrator --> vectorStore[ChromaOrQdrant]
```
Query flow
```mermaid
flowchart TD
q[UserQuery] --> retrieve[HybridRetrieve]
retrieve --> fuse[RRFWeightedFusion]
fuse --> rerank[RerankOptional]
rerank --> prompt[BuildPrompt]
prompt --> llm[ProviderModelSelectedPerRequest]
llm --> cite[ExtractAndVerifyCitations]
cite --> truth[TruthfulnessScore]
truth --> response[APIorCLIorUIResponse]
```
---
## API usage
```bash
uvicorn src.api.main:app --reload --port 8000
```
```bash
curl -X POST http://127.0.0.1:8000/query \
-H "X-API-Key: dev-key-1" \
-H "Content-Type: application/json" \
-d '{"query": "What is hybrid retrieval?", "provider": "ollama", "model": "qwen2.5:7b"}'
```
Response includes a `truthfulness` block:
```json
{
"answer": "Hybrid retrieval combines BM25 sparse search with dense vector search...",
"truthfulness": {
"nli_faithfulness": 0.87,
"citation_groundedness": 0.91,
"uncited_claims": 1,
"score": 0.89
},
"citations": [...]
}
```
Endpoints: `GET /health`, `GET /metrics`, `POST /query`, `POST /query/stream` (SSE).
---
## Evaluation
### Inline (every response)
Every `/query` response includes a `truthfulness` object with:
| Field | What it measures |
|-------|-----------------|
| `nli_faithfulness` | Fraction of response sentences entailed by the retrieved chunks (NLI model) |
| `citation_groundedness` | Mean citation verification score |
| `uncited_claims` | Count of sentences without a citation marker |
| `score` | Weighted aggregate (60% NLI + 40% groundedness) |
The Streamlit UI renders a coloured badge: 🟢 ≥ 0.8 / 🟡 0.5–0.8 / 🔴 < 0.5.
### Offline batch evaluation
Run the RAGAS-style harness against the included golden dataset:
```bash
# Install eval extras
pip install -r requirements/eval.txt
# Run against full dataset (needs a running LLM)
PYTHONPATH=. python -m evals.run_evals \
--dataset evals/datasets/sample.jsonl \
--judge-provider ollama \
--judge-model qwen2.5:7b \
--output evals/reports/
# Smoke test (no LLM required — for CI / quick check)
PYTHONPATH=. python -m evals.run_evals \
--dataset evals/datasets/smoke.jsonl \
--mock \
--no-nli \
--output evals/reports/
```
Reports are written to `evals/reports/` as JSON + Markdown.
---
## Project map
| Path | Purpose |
|------|---------|
| `src/core/` | Retrieval, reranking, generation, citations, orchestration |
| `src/api/` | FastAPI models and routes |
| `src/web/` | Streamlit UI and ingestion service |
| `src/evaluation/` | Truthfulness scorer, generation and retrieval metrics |
| `src/utils/` | Config and vector database integrations |
| `evals/` | Offline eval harness, golden datasets, RAGAS adapter |
| `data/sample/` | Pre-ingested sample documents for demos |
| `spaces/` | Hugging Face Spaces deployment files |
| `docker/` | Docker Compose stack (API + Streamlit + Redis + Qdrant) |
| `Docs/` | Roadmap, runbook, and phase documentation |
---
## Development
```bash
.venv/bin/python -m pytest tests/unit -q
.venv/bin/python -m pytest tests/integration -q
```
Multi-provider API key environment variables:
```bash
export OPENAI_API_KEY=...
export ANTHROPIC_API_KEY=...
export GEMINI_API_KEY=...
```
---
## Troubleshooting
- **Empty results after ingest:** Run `python -m src.ingest --docs data/documents` and verify `data/embeddings/` exists.
- **Embedding model error:** Ensure Ollama is running and `nomic-embed-text` is pulled, or switch to a different embedding provider in `config.yaml`.
- **Dimension mismatch after model change:** Re-ingest all documents to rebuild the vector index.
- **Cloud provider fails:** Check the relevant `*_API_KEY` env var is set.
- **Truthfulness score always 0:** The NLI model (`cross-encoder/nli-deberta-v3-small`) downloads on first use (~140 MB). Check internet access or set `evaluation.inline_enabled: false` in `config.yaml` to disable.
---
## Documentation
- [Production Runbook](Docs/RUNBOOK.md)
- [Roadmap](Docs/ROADMAP.md)
- [Project overview](Docs/PROJECT_OVERVIEW.md)