You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Nyaya (also spelled "Nyaay") is a full-stack, production-ready AI-powered legal technology platform built to democratize access to Indian law. It serves as an intelligent legal advisor that combines a multi-role user system, a sophisticated RAG-based AI pipeline, and a verified professional marketplace.
Problem Statement
Indian legal information is scattered, complex, and inaccessible to the average citizen. People either don't know their rights or cannot afford a lawyer consultation. Nyaya bridges this gap with AI that understands Indian law deeply.
Target Users
Role
Description
Citizen
Asks legal questions, uploads documents for analysis, browses lawyers
Lawyer
Gets Bar Council-verified, lists services on the marketplace
Judge
Gets government-ID verified by admins, accesses judicial tools
AuthContext useEffect on mount:
→ POST /api/auth/refresh (cookie auto-sent)
→ rotateRefreshToken():
1. jwt.verify(oldToken, REFRESH_SECRET)
2. DB lookup: if revokedAt set → SECURITY: revoke ALL user tokens
3. Revoke old token, issue new pair
→ Frontend: setAccessToken(newToken) in Axios instance
Google OAuth Flow
1. User clicks "Sign in with Google"
2. Browser redirects to Google OAuth URL (implicit grant)
3. Google redirects back to /login#access_token=...
4. useEffect reads hash params, calls Google userinfo API
5. POST /api/auth/google/token { access_token, email, name, googleId }
6. Backend upserts user, issues JWT pair
7. Frontend login() + redirect
5. RAG Pipeline (Deep Dive)
Ingestion (Offline)
ingest_legal_pdfs.py:
Parses 17 raw Indian Legal PDF Acts (BNS, BNSS, BSA, Constitution, CPC, etc.)
→ Sections → Inserts/Updates Act & Section DB tables
generate_embeddings.py:
Splits Sections into 8,166 LegalChunks (chunkSize 600, overlap 100)
Adds metadata headers: [Act: ...] [Year: ...] [Section/Article: ...] [Title: ...]
SentenceTransformers / FastEmbed ONNX (all-MiniLM-L6-v2) → 384-dim normalized vector
Bulk inserts into LegalChunk.embedding (pgvector type) with DB reconnect retries
PostgreSQL GIN index on fts (tsvector)
HNSW index on embedding for O(log N) ANN search
Query Time (chat.ts: POST /conversations/:id/messages)
Step A: Context-Aware Query Expansion
priorUserMessages.slice(-2) + currentQuery joined with " | "
Prevents "What does that mean?" from retrieving garbage chunks
Step B: Optional embed expanded query
→ FastEmbed ONNX all-MiniLM-L6-v2 in full vector mode
→ 384-dim vector
→ skipped on 512MB Render with RAG_VECTOR_SEARCH=false
Step C: Retrieval (rag/retrieval.py: hybrid_search())
Full mode Postgres raw query (pgvector + FTS via RRF over 8,166 chunks):
WITH vector_search AS (
SELECT id, content, ROW_NUMBER() OVER(ORDER BY embedding <=> queryVec) as rnk
FROM "LegalChunk" LIMIT 30
),
keyword_search AS (
SELECT id, content, ROW_NUMBER() OVER(ORDER BY ts_rank_cd(fts, query)) as rnk
FROM "LegalChunk" WHERE fts @@ websearch_to_tsquery(...) LIMIT 30
)
SELECT COALESCE(v.id, k.id),
(1.0/(60+v.rnk) + 1.0/(60+k.rnk)) as rrf_score
FROM vector_search v FULL OUTER JOIN keyword_search k ON v.id = k.id
ORDER BY rrf_score DESC LIMIT 20
Then: Hydrate chunks with Act + Section relations
Render 512MB mode:
skip local embeddings
use Postgres FTS (`websearch_to_tsquery`) + ILIKE fallback
keep Cohere reranking and Groq generation unchanged
Step D: Reranking (retrieval.ts: rerankCandidates())
Top 20 → Cohere rerank-english-v3.0 → Top 8 "golden" chunks
Fallback: slice(0, limit) if Cohere unavailable
Step E: Prompt Construction
SystemMessage with retrieved context interpolated directly (NOT LangChain template)
Reason: legal text has {braces} that break LangChain template parser
Structured output format enforced: Confidence, Act, Section, Explanation, Punishment, Source
Step F: LLM Call
ChatGroq(groq/compound, temperature=0.1)
.pipe(StringOutputParser()).invoke(messages)
Confidence score extracted via regex: /🔹\s*Confidence:\s*(\d+)/
Prepended as sentinel: [[NYAYA_CONFIDENCE:85]] (stripped before display)
Step G: Persist
Save user + assistant messages to DB
Increment user.queriesCount
Update conversation.updatedAt
RRF Formula
score = 1/(60 + rank_vector) + 1/(60 + rank_keyword)
k=60 is traditional constant preventing high ranks from dominating
Time complexity: O(log N) for HNSW vector search vs O(N) for naive cosine in JS
Space complexity: O(N × d) for the index where d=384
Generated via crypto.randomBytes(4) % 1000000 — cryptographically secure
10-minute expiry
Max 5 attempts → auto-invalidated
Existing OTPs invalidated before new one is created (prevents overlap attacks)
Rate Limiting (rateLimiter.ts)
Endpoint
Window
Max
Login
15 min
10
OTP Send
10 min
5
OTP Verify
5 min
10
Register
1 hour
5
Password Reset
30 min
3
Token Refresh
5 min
30
Plan Limiter (planLimiter.ts)
FREE: 100 API calls/30 days
BASIC: 1,000/30 days
PRO: 10,000/30 days
ENTERPRISE: 100,000/30 days
Auto-provisions FREE subscription on first touch
Resets counter on billing period rollover
Fixed backend startup; auth guards became router.replace('/') on public route
2
Prisma types crash after schema change
UserRole enum not exported
npx prisma generate after every schema change
3
BullMQ crash on start
ioredis emitting error event before connection
Moved connection.on('error', ()=>{}) before connection.ping()
4
Chat frozen, messages not sending
useChat hook from @ai-sdk/react state mismatch
Removed useChat, implemented custom fetch-based streaming with ReadableStream
5
OTP invalid despite correct code
Multiple registrations → older OTP not invalidated → user submitting wrong one
Added .updateMany({ used: true }) before creating new OTP
6
Login "Invalid credentials"
Email casing mismatch (Email@ vs email@)
.toLowerCase().trim() on all auth email inputs
7
Refresh token 401 cross-origin
SameSite=Strict blocked port-crossing cookies
Changed to SameSite=None; Secure=true
8
NEXT_PUBLIC_API_URL 404 HTML
Missing /api suffix → receiving HTML 404 as JSON
Added /api suffix to env variable
9
Render RAG out of memory
Local embedding stack/model exceeded 512MB at runtime
Switched Render to text-search mode, lazy-loaded FastEmbed, pinned Python 3.11.11
13. Scalability Considerations
Current Bottlenecks
Local embedding model: first vector-mode query can cold-load FastEmbed/ONNX
Synchronous LLM call: No streaming to browser (response waits for full LLM output)
N+1 on hydration: hybridSearch runs raw SQL then second Prisma query to hydrate chunks
Production Upgrades (from rag_architecture.md)
pgvector HNSW index → O(log N) ANN search instead of O(N) cosine in JS
BM25 via tsvector GIN index → native Postgres full-text search
Hierarchical chunking with RecursiveCharacterTextSplitter (1200 char, 250 overlap)
Streaming responses via TextDecoderStream / SSE
Redis caching of embeddings and frequent query results
Hosted embeddings or larger RAG instance to keep semantic search enabled in production
Horizontal Scaling
Express is stateless → multiple instances behind load balancer
Refresh tokens in DB → any instance can validate
Neon Postgres auto-scales compute
14. Resume Bullet Points
• Built Nyaya, a full-stack AI legal platform (Next.js 14 + Node.js/Express + PostgreSQL/pgvector)
implementing a 4-role auth system with JWT rotation, OTP verification, and Google OAuth
• Engineered a production RAG pipeline: Hybrid BM25 + vector search (Reciprocal Rank Fusion)
+ Cohere cross-encoder reranking achieving O(log N) retrieval via HNSW index
• Designed a multi-model AI system using Groq LLaMA 3.3 70B, FastEmbed semantic retrieval,
Cohere reranking, and confidence scoring/hallucination guards
• Implemented enterprise security: bcrypt adaptive hashing, refresh token rotation with reuse
detection, Helmet.js headers, per-endpoint rate limiting (express-rate-limit)
• Built freemium SaaS monetization: Razorpay payment verification (HMAC-SHA256 signature
validation), plan-tier API quota middleware, and subscription management
• Integrated BullMQ + Redis background job queue for async email/WhatsApp notifications
with graceful Redis fallback preventing server crashes in dev environments
LinkedIn Description
Nyaya | AI Legal Platform for Indian Law
Tech: Next.js • Node.js • PostgreSQL/pgvector • Groq LLaMA 3.3 70B • LangChain • Cohere
Built a production-grade LegalTech platform democratizing access to Indian law through:
- RAG-powered AI chat grounded in real Indian Acts (BNS, CrPC, Constitution)
- PostgreSQL FTS + optional pgvector semantic search with cross-encoder reranking
- Multi-role system: Citizens, Verified Lawyers, Judges, Admins
- Secure multi-modal auth: JWT rotation, OTP (email+SMS), Google OAuth
- Lawyer marketplace with Bar Council document verification
- Razorpay freemium model with plan-tier API quotas