Skip to content

Repository files navigation

RAG Platform

Backend CI Frontend CI

A production-grade Retrieval-Augmented Generation (RAG) system with multi-tenancy, hybrid search, real-time verification, and zero-hallucination safeguards.

Overview

This system ingests documents, indexes them into a vector database, and answers user questions by retrieving relevant context and generating cited responses. It features NLI-based verification, abstention when confidence is low, streaming responses, and LLM fallback for resilience.

Backend: FastAPI + Python 3.12
Frontend: Next.js 16 + React 19 + Tailwind CSS 4
Vector DB: Qdrant 1.12.6
Relational DB: PostgreSQL 16 (production) / SQLite (development)
Cache/Queue: Redis 7


Quick Start

Prerequisites

  • Python 3.12+
  • Node.js 22+
  • uv (Python package manager)
  • Docker & Docker Compose (optional, for infrastructure)

1. Clone & Setup

git clone https://github.com/sarthakbiswas97/rag-system.git
cd rag-system

# Backend dependencies
uv sync --all-extras

# Frontend dependencies
cd dashboard && npm install && cd ..

# Environment
cp .env.example .env
# Edit .env and set OPENAI_API_KEY and ADMIN_API_KEY

2. Run with Docker Compose (Recommended)

# Start all services (PostgreSQL, Qdrant, Redis, backend, frontend)
docker compose up -d

# Or use Make
make docker-up

Services available:

3. Run Locally (Development)

# Start infrastructure only
make dev

# Run database migrations
make db-upgrade

# Start backend server
make run

# In another terminal, start frontend
cd dashboard && npm run dev

4. Create a Tenant

curl -X POST http://localhost:8000/v1/register \
  -H "Content-Type: application/json" \
  -d '{"name": "My Team", "email": "team@example.com"}'

Use the returned api_key in the X-API-Key header for all subsequent requests.


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Next.js   │────▢│   FastAPI   │────▢│  Query Cache    β”‚
β”‚  Dashboard  │◄────│   Backend   │◄────│     (Redis)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚                  β”‚                  β”‚
        β–Ό                  β–Ό                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Qdrant     β”‚  β”‚  PostgreSQL  β”‚  β”‚  Session Store  β”‚
β”‚ Vector Store β”‚  β”‚   (Tenants,  β”‚  β”‚     (Redis)     β”‚
β”‚(Dense+Sparse)β”‚  β”‚  Documents,  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚   Usage)     β”‚
                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Core Components

Component Technology Purpose
API Layer FastAPI + Pydantic REST API, dependency injection, middleware
Embedding sentence-transformers (BAAI/bge-small-en, 384-dim) Dense vector generation; ONNX Runtime opt-in
Sparse Retrieval Custom SparseEmbedder TF-based sparse vectors in Qdrant (replaces BM25)
Vector Store Qdrant 1.12.6 HNSW index, INT8 quantization, 6 shards, dot-product distance
LLM OpenAI API (gpt-4o-mini) RAG generation with streaming SSE; fallback model support
Verification DeBERTa-v3-large MNLI Entailment checking, citation validation, abstention decisions
Reranker cross-encoder/ms-marco-MiniLM Optional cross-encoder reranking post-retrieval
Cache Redis Query response cache (TTL 5min), session store, rate limiting
Database SQLAlchemy 2.0 async Tenants, documents, usage events; SQLite dev / PostgreSQL prod
Observability structlog + Prometheus JSON-structured logs, request metrics, ingestion counters

Multi-Tenancy

  • Authentication: X-API-Key header β†’ SHA256-hashed API key lookup
  • Data Isolation: All Qdrant chunks include tenant_id payload; every search filters by tenant
  • Document Lifecycle: Tracks ACTIVE / UPDATING / DELETED status with soft deletes
  • Rate Limiting: Per-tenant Redis counters (60 queries/min, 10 ingestion/min)
  • Concurrency: Max 3 concurrent ingestion jobs per tenant via Redis semaphore

Data Flow

Ingestion Pipeline

Upload (PDF/TXT/MD/CSV)
    β”‚
    β–Ό
Load ──▢ Chunk (512 tokens, 64 overlap)
    β”‚
    β–Ό
Deduplicate (SHA256 content hash, per-tenant)
    β”‚
    β–Ό
Embed ──▢ Dense (384-dim) + Sparse (BM25-like)
    β”‚
    β–Ό
Store ──▢ Qdrant (dense + sparse vectors)
    β”‚
    β–Ό
Metadata ──▢ PostgreSQL (document record)
    β”‚
    β–Ό
Cache Invalidation ──▢ Redis (clear tenant query cache)

Optimizations:

  • Batch embedding (max 64 chunks per batch)
  • Single DB commit for batched document ingestion
  • Content deduplication prevents re-ingesting identical documents

Query Pipeline

User Question
    β”‚
    β–Ό
[Optional] Conversational Rewrite (session history)
    β”‚
    β–Ό
Dense Search ──▢ Qdrant (filtered by tenant_id)
Sparse Search ──▢ Qdrant sparse vectors
    β”‚
    β–Ό
Reciprocal Rank Fusion (RRF, K=60)
    β”‚
    β–Ό
[Optional] Cross-encoder Reranking
    β”‚
    β–Ό
Build RAG Prompt (top chunks + citations)
    β”‚
    β–Ό
LLM Generation ──▢ OpenAI (streaming supported)
    β”‚
    β–Ό
Citation Parsing + Abstention Detection
    β”‚
    β–Ό
NLI Verification (entailment per sentence)
    β”‚
    β–Ό
Abstention Decision ──▢ Return answer or "I don't know"

Verification Signals:

  1. Retrieval score below threshold β†’ abstain
  2. Reranker score below threshold β†’ abstain
  3. Citation coverage too low β†’ abstain
  4. Faithfulness score (NLI) below threshold β†’ abstain

API Reference

All authenticated endpoints require X-API-Key header.

Authentication & Tenant

Method Endpoint Auth Description
POST /v1/register β€” Create tenant, returns API key
GET /v1/me API Key Get current tenant
PUT /v1/me API Key Update tenant name
GET /v1/me/stats API Key Chunk count
GET /v1/me/usage API Key Usage events (query, ingest)

Documents

Method Endpoint Auth Description
GET /v1/documents API Key List documents (paginated)
DELETE /v1/documents/{id} API Key Soft delete + remove vectors
PUT /v1/documents/{id} API Key Replace document (re-ingest)

Ingestion

Method Endpoint Auth Description
POST /v1/ingest API Key Upload files (multipart, max 50MB)
POST /v1/ingest/async API Key Async ingestion (returns job ID)
GET /v1/ingest/jobs/{id} API Key Check async job status

Query

Method Endpoint Auth Description
POST /v1/query API Key Standard RAG query
POST /v1/query/stream API Key SSE streaming response

Admin

Method Endpoint Auth Description
GET /admin/tenants Admin Key List all tenants
POST /admin/tenants Admin Key Create tenant manually
DELETE /admin/tenants/{id} Admin Key Delete tenant

Health & Metrics

Method Endpoint Auth Description
GET /v1/health β€” Health check (DB + Qdrant + Redis)
GET /v1/livez β€” Liveness probe
GET /metrics β€” Prometheus metrics

Configuration

All configuration is via environment variables (see src/rag/config.py):

Variable Default Description
OPENAI_API_KEY (required) OpenAI API key for LLM
EMBEDDING_MODEL BAAI/bge-small-en-v1.5 Sentence-transformers model
EMBEDDING_BACKEND pytorch pytorch or onnx
EMBEDDING_ONNX_PROVIDER CPUExecutionProvider ONNX execution provider
QDRANT_HOST localhost Qdrant server host
QDRANT_PORT 6333 Qdrant server port
QDRANT_COLLECTION documents Collection name
QDRANT_SHARD_NUMBER 6 Number of shards
QDRANT_REPLICATION_FACTOR 1 Replication factor
CHUNK_SIZE 512 Document chunk size (tokens)
CHUNK_OVERLAP 64 Chunk overlap (tokens)
LLM_MODEL gpt-4o-mini Primary LLM model
LLM_FALLBACK_MODEL (empty) Fallback model name
LLM_FALLBACK_API_KEY (empty) Fallback API key (if different)
LLM_TEMPERATURE 0.1 Generation temperature
LLM_MAX_TOKENS 1024 Max tokens per response
DATABASE_URL sqlite+aiosqlite:///./data/rag.db SQLAlchemy database URL
DB_POOL_SIZE 5 Connection pool size
REDIS_URL redis://localhost:6379 Redis connection URL
ADMIN_API_KEY (empty) Admin API key for management endpoints
ENABLE_QUERY_CACHE true Enable Redis query caching
QUERY_CACHE_TTL 300 Query cache TTL (seconds)
ENABLE_RATE_LIMITING true Enable per-tenant rate limits
RATE_LIMIT_QUERIES 60 Queries per minute per tenant
RATE_LIMIT_INGESTION 10 Ingestions per minute per tenant
CORS_ORIGINS ["http://localhost:3000"] Allowed CORS origins
LOG_LEVEL INFO Logging level

Development

Backend

# Run all tests
make test

# Run with coverage
uv run pytest --cov=src/rag --cov-report=term-missing

# Lint and format
make lint
make format

# Run evaluation
make eval DATASET=data/eval/my-dataset.json

# Run locally
make run

Frontend

cd dashboard

# Dev server
npm run dev

# Lint
npm run lint

# Production build
npm run build

Database Migrations

# Create migration
make db-migrate MSG="add users table"

# Apply migrations
make db-upgrade

# Rollback one
make db-downgrade

Load Testing

# Seed benchmark data
make seed-bench DOCS=100

# Run load test
make load-test USERS=50 RATE=10 TIME=60s

Deployment

Docker

# Build all images
docker compose build

# Start production stack
docker compose up -d

The backend Dockerfile uses multi-stage builds with gunicorn + uvicorn.workers.UvicornWorker. Worker count auto-scales from 2 to 8 based on CPU cores (override with WEB_CONCURRENCY).

CI/CD

GitHub Actions workflows:

  • Backend CI: lint β†’ test β†’ docker build/push to GHCR
  • Frontend CI: lint β†’ build β†’ docker build/push to GHCR

Images pushed to ghcr.io/sarthakbiswas97/rag-system-backend and ghcr.io/sarthakbiswas97/rag-system-dashboard.

Railway

The project includes railway.toml for Railway deployment. Set the required environment variables in the Railway dashboard and link the repository.


Project Structure

rag-system/
β”œβ”€β”€ src/rag/                  # Backend source
β”‚   β”œβ”€β”€ api/                  # FastAPI routers, schemas, middleware
β”‚   β”œβ”€β”€ ingestion/            # Document loading, chunking, embedding
β”‚   β”œβ”€β”€ retrieval/            # Vector search, hybrid fusion, reranking
β”‚   β”œβ”€β”€ generation/           # LLM client, prompt builder, streaming
β”‚   β”œβ”€β”€ verification/         # NLI entailment, citation validation
β”‚   β”œβ”€β”€ tenancy/              # Auth, repositories, models
β”‚   β”œβ”€β”€ session/              # Redis-backed conversation sessions
β”‚   β”œβ”€β”€ models/               # Core dataclasses
β”‚   β”œβ”€β”€ observability/        # Logging, metrics
β”‚   β”œβ”€β”€ config.py             # Pydantic settings
β”‚   └── main.py               # App factory
β”œβ”€β”€ dashboard/                # Next.js frontend
β”‚   β”œβ”€β”€ src/app/              # App router pages
β”‚   β”œβ”€β”€ src/lib/api.ts        # API client
β”‚   └── src/components/       # React components
β”œβ”€β”€ tests/                    # Test suite
β”‚   β”œβ”€β”€ unit/                 # 439 unit tests
β”‚   β”œβ”€β”€ integration/          # 19 integration tests
β”‚   └── load/                 # Locust load tests
β”œβ”€β”€ scripts/                  # Evaluation, seeding, ingestion
β”œβ”€β”€ migrations/               # Alembic database migrations
β”œβ”€β”€ docker-compose.yml        # Local infrastructure stack
β”œβ”€β”€ Dockerfile                # Backend container
β”œβ”€β”€ pyproject.toml            # Python dependencies
└── Makefile                  # Common commands

Key Design Decisions

  1. Hybrid Search: Dense vectors for semantic similarity + sparse vectors for keyword matching, fused via Reciprocal Rank Fusion (RRF).

  2. Sparse Vectors in Qdrant: Instead of maintaining an in-memory BM25 index, sparse embeddings are stored natively in Qdrant alongside dense vectors. This eliminates memory pressure and simplifies scaling.

  3. Verification Pipeline: Every answer is checked with an NLI model. If the answer isn't sufficiently supported by retrieved context, the system abstains rather than hallucinating.

  4. Streaming with Verification: The /v1/query/stream endpoint streams tokens to the client, then runs verification after generation completes. If verification fails, an abstention event is sent.

  5. ONNX Opt-in: Embedding backend defaults to PyTorch (best on GPU/Linux) but can switch to ONNX Runtime via EMBEDDING_BACKEND=onnx for CPU-optimized inference.

  6. Sharding Strategy: Qdrant collections use 6 shards with hash-based routing. No online resharding is supported; migration requires collection recreation (see MIGRATIONS.md).


License

MIT

About

A rag system which can handle around 10 million docs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages