VeritasRAG
Production-grade RAG document Q&A platform: upload PDFs, DOCX, and TXT files, then query them with grounded, cited answers powered by hybrid vector search and Gemini
Role
Full Stack Developer
Team
Solo
- Proxying SSE stream from FastAPI through Django and a Next.js Route Handler to the browser without buffering or timeout
- Hybrid ANN + BM25 reciprocal rank fusion where RRF scores (~0.016) must not be compared against cosine similarity thresholds
- Cross-encoder reranker (~80MB) loaded at FastAPI startup within Render free-tier 512MB RAM constraint
- httpOnly JWT cookies set exclusively via Next.js Route Handlers: no token ever reaches browser JavaScript
- Atomic document deletion cascading Celery ingestion status, pgvector chunks, embeddings, and Cloudinary asset in one transaction
- Intent-aware retrieval routing: combined intent classification + HyDE in a single LLM call with standalone query rewriting for follow-up messages
- pgvector HNSW cosine ANN combined with PostgreSQL BM25 full-text search via reciprocal rank fusion for higher recall on technical documents
- Cross-encoder reranker (ms-marco-MiniLM-L-6-v2) scoring merged top-20 candidates down to top-5 before generation
- FastAPI internal-only architecture: Django is the sole public-facing API; FastAPI enforces X-Internal-Key on every endpoint
- Celery async ingestion pipeline decoupling upload from extract, chunk, embed, and pgvector write
- Redis cache-aside at three layers: full RAG response (1h), retrieved chunks (30min), dashboard stats (5min)
- Grounding score threshold gating: explicit low-confidence warning when top similarity scores fall below 0.75
Overview
VeritasRAG is a full-stack, production-grade Retrieval-Augmented Generation platform. Users upload PDF, DOCX, and TXT documents, which are ingested asynchronously through a multi-stage pipeline: text extraction, sliding-window chunking, embedding via Google text-embedding-004, and storage in pgvector. Once indexed, users query documents through a persistent chat interface and receive answers grounded strictly in retrieved source content, never hallucinated, with exact citations showing chunk text, source document, page number, and similarity score.
The system is a three-service architecture: a Next.js 16 (App Router) frontend, a Django 5 backend owning auth, document metadata, chat persistence, and Redis caching, and a FastAPI AI service owning the entire RAG pipeline. All services run in Docker Compose locally and deploy independently to Render and Vercel.
3
Independent services
512 / 50
Chunk tokens / overlap
768-d
HNSW vectors, pgvector
20 to 5
Reranked candidates
Key Features
Document management
Upload PDF, DOCX, TXT (max 50MB): the browser uploads directly to
Cloudinary via a Django-signed URL, so credentials never reach the client.
Celery tracks uploaded / processing / ready / failed status in
PostgreSQL, polled by the frontend every 3 seconds. Deleting a document
cascades chunks, embeddings, the Cloudinary asset, and related chat
messages atomically.
RAG pipeline
Extraction via pdfplumber, python-docx, or plain read. Sliding-window
chunking at 512 tokens with 50-token overlap preserving page numbers.
text-embedding-004 produces 768-dimension vectors stored in pgvector
with an HNSW index. Generation on gemini-2.0-flash with a strict
grounding prompt, streamed token-by-token over SSE.
Hybrid retrieval + reranking
pgvector cosine ANN (top-20) merged with PostgreSQL BM25 full-text search
(top-20) via Reciprocal Rank Fusion, then
cross-encoder/ms-marco-MiniLM-L-6-v2 reranks the merged set down to the
top-5 passed to generation.
Intent-aware retrieval
A single LLM call combines intent classification and HyDE to avoid a
redundant round-trip on factual queries. Ten intent categories, standalone
query rewriting for follow-up messages using conversation history, and
top_k tuned per intent: 20 for comparison/factual, 15 for
boolean/definition.
Citation grounding
Every answer ships citation cards with chunk text, source document, page
number, and similarity score. A per-answer grounding_score renders a
warning banner below 0.6, and if all top-5 similarities fall under 0.75
the response explicitly states the documents lack sufficient information.
Low-confidence queries are logged for admin review.
Chat interface
Named chat sessions scoped to one or more documents, progressive streaming render as SSE tokens arrive, a collapsible citation panel per answer, full session history in the sidebar, and per-message metadata: latency, retrieval score, cache hit/miss badge.
Redis caching (cache-aside)
Full RAG response cached by hash(question + doc_ids) for 1 hour,
retrieved chunks for 30 minutes, dashboard stats for 5 minutes, JWT
session data for 24 hours. Cache hits return in under 5ms, with the hit
badge visible in the UI and hit rate on the dashboard.
Auth, security, observability
JWT access (15 min) and refresh (7 day) tokens set as
httpOnly; Secure; SameSite=Strict cookies exclusively by Next.js Route
Handlers. Every Django view enforces row-level ownership. Every query is
logged with latency, similarity, grounding score, cache hit, and model.
Health checks and a keep-alive ping warm Render cold starts.
Architecture
Three independently deployable services behind a Next.js BFF layer:
Django is the sole public-facing API. FastAPI enforces an X-Internal-Key
header on every endpoint and only Django calls it, so the entire RAG surface
is unreachable from the internet and auth lives in exactly one place.
The ~80MB cross-encoder reranker loads once at FastAPI startup and has to
fit alongside the app inside Render's free-tier 512MB limit, which shaped
model choice (ms-marco-MiniLM-L-6-v2) and ruled out larger rerankers.
Ingestion Flow
Query Flow
Reciprocal Rank Fusion produces scores around 0.016 that live on a completely different scale from cosine similarity, so grounding thresholds (0.75) are checked against the raw ANN similarities, never the fused rank scores.
Data Model
Eight PostgreSQL tables plus pgvector:
users: UUID PK, email, hashed password, full name.documents: user-owned; filename, storage_key, file_type, status, chunk_count.chunks: document-scoped; chunk_index, content, token_count, page_number.embeddings: one-to-one with chunks;vector(768), model_name. Decoupled so re-embedding a new model doesn't touch chunk data.chat_sessions: user-owned; title, document_ids (jsonb array).messages: session-scoped; role, content, retrieval_score, grounding_score, latency_ms, cache_hit.citations: message-scoped; chunk_id FK, similarity_score, citation_order.query_logs: full observability row per query: question, latency, grounding score, cache hit, model used, low_confidence flag.
Outcome
VeritasRAG demonstrates a production-grade RAG system: hybrid vector + keyword retrieval, cross-encoder reranking, intent-aware query routing, streaming SSE generation, Redis multi-layer caching, and strict citation grounding across a three-service Docker-native architecture. Every answer is traceable to an exact source chunk with page-level provenance, and every architectural boundary is enforced at both the API and database layers.
