← All projects
GenAI· Production & Systems· In progress· Apr 2026

VidhiVault

Ask questions about Indian legal documents and get answers with citations. Retrieval is hybrid (vector + keyword) with reranking, built from scratch without LangChain.

one Postgres · no LangChain · every stage measurableQUERYs. 439 CrPC?pgvector · cosineHNSW · MiniLM-L6tsvector · ts_rank_cdexact section numbersRRFk = 60cross-encoderrerank → top 5LLM → answercites [Source N]ingest: PDF → section-aware chunks (160 words) → embed → pgvector

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 tsvector column.
  • 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-v2 reranks the fused list down to 5.
  • The LLM is called over an OpenAI-compatible API with async httpx. On a 429 it reads Retry-After and retries.
  • Uploads are deduped by SHA-256 file hash, and document status moves pending → processing → completed/failed with the error saved.

Tech stack

FastAPIPostgreSQL + pgvectorsentence-transformersCross-encoder rerankerCelery + RedisPyMuPDFpdfplumberAlembic

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.

Resume

Want my resume?

I share it on request, so you always get the latest version. Drop me an email with a line about the role or why you're asking, and I'll send it over.

devanshpandeyji4321@gmail.com
Write the email →