VidhiVault
Ask questions about Indian legal documents and get answers with citations. Retrieval is hybrid (vector + keyword) with reranking, built from scratch without LangChain.
Highlights
- On my 24-query retrieval test, hybrid search raised recall@5 from 0.50 (vector only) to 0.64, and the reranker took it to 0.69.
- Graded 30 real questions by hand instead of trusting a vibe check.
- Everything runs on one Postgres: vectors (pgvector + HNSW) and full-text search in the same database.
Overview
A RAG backend for Indian legal PDFs (judgments and acts). You upload a PDF and a Celery worker parses it, splits it by legal sections, embeds the chunks and stores them. Then you can ask questions and get an answer that cites the chunks it used. I didn't use LangChain or LlamaIndex, because I wanted to see and measure every stage myself.
Problem
Legal questions come in two flavours: meaning-based ("when does taking property become theft?") and exact references ("Section 439 CrPC"). Vector search is bad at the second kind and keyword search is bad at the first.
Approach
Run both: pgvector cosine search and Postgres full-text search (ts_rank_cd), 20 candidates each, merged with Reciprocal Rank Fusion. Merging by rank means I don't have to make the two kinds of score comparable. A cross-encoder then reranks the merged list, and the top 5 chunks go to the LLM, which has to cite them as [Source N].
Architecture / how it works
- Reciprocal Rank Fusion with k = 60 over a pgvector HNSW cosine index and a
tsvectorcolumn. - Embeddings come from
all-MiniLM-L6-v2. I resized chunks to 160 words with 25 overlap after finding the old 512-token chunks were being cut off at the model's 256-wordpiece limit (68% of them were). - The chunker splits on legal headings (Section, Article, Order) first, then paragraphs, then sentences.
cross-encoder/ms-marco-MiniLM-L-6-v2reranks the fused list down to 5.- The LLM is called over an OpenAI-compatible API with async
httpx. On a 429 it readsRetry-Afterand retries. - Uploads are deduped by SHA-256 file hash, and document status moves pending → processing → completed/failed with the error saved.
Tech stack
Outcomes
Retrieval, 24 test queries, recall@5: vector only 0.50, keyword only 0.49, hybrid 0.64, hybrid + rerank 0.69. The reranker isn't free though. It's about 0.8s per query while every other stage is under 20ms, and it slightly lowered MRR. Full answers, 30 questions graded by hand: 14 good, 7 partial, 2 wrong, 6 correctly refused (the answer wasn't in the documents), and 1 wrongly refused. The backend works end to end. The frontend isn't built yet.