Legal help is expensive, jargon-heavy, and often out of reach for people who need it most. For PromptWars: Virtual (Exclusive Edition) — a hackathon by Hack2Skill — I built Sahayak, a RAG-based legal assistant that answers questions and summarizes documents in plain language, grounded in real context instead of confident guesses.
🔗 Live: https://legal-ai-eight-liart.vercel.app/
🔗 Code: https://github.com/Divyansh0208/Legal-AI (MIT)
What it does
- Ask — a tenancy, consumer-rights, or contract question. Answers are retrieved from indexed context, not hallucinated. No matching context? It says so, instead of guessing.
- Upload — a PDF (scanned or native) → plain-language summary: what kind of document it is, what it obligates you to, what's worth double-checking.
- Voice — ask by speaking, transcribed via Whisper. Every response is grounded in retrieved context and explicit about what it can't confirm — no confident guessing when the docs don't cover something.
Architecture
User (voice/text/doc)
│
▼
React + Vite Frontend
│ REST
▼
FastAPI Backend
├─► Groq Whisper API (speech-to-text)
├─► PyMuPDF + pytesseract (parsing / OCR)
├─► sentence-transformers (embeddings)
├─► ChromaDB (vector retrieval)
├─► Groq LLM API (answer generation)
└─► SQLite (app data)
The RAG hot path is intentionally thin — no framework ceremony for a single retrieval + one chat completion call:
def answer_question(question: str) -> dict:
hits = retrieve(question)
context = "\n\n---\n\n".join(h["text"] for h in hits) if hits else ""
completion = _client.chat.completions.create(
model=settings.GROQ_LLM_MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": RAG_ANSWER_TEMPLATE.format(context=context, question=question)},
],
temperature=0.2,
max_tokens=800,
)
return {"answer": completion.choices[0].message.content,
"sources": [{"source": h["source"], "distance": h["distance"]} for h in hits]}
The part I'm actually proud of: the guardrails
Anyone can wire an LLM to a text box. The parts that took real effort:
- Untrusted-context system prompt — uploaded documents and retrieved chunks are treated as data, never instructions. Prompt-injection resistant by design.
-
Real upload validation —
python-magicsniffs actual file bytes; a relabeled.exedoesn't sneak through as.pdf. -
Rate limiting —
slowapion query, upload, and voice endpoints. - Security headers — CSP, X-Frame-Options, HSTS in production.
-
5 CI workflows on every push — lint, pytest, bandit (SAST), gitleaks (secrets), pip-audit (deps), axe-core (a11y) — auto-deploy to Render/Vercel on merge to
main. None of that shows up in a demo GIF. All of it is why the demo GIF is trustworthy.
Stack
| Layer | Tech |
|---|---|
| Backend | FastAPI |
| LLM | Groq (openai/gpt-oss-120b) |
| Retrieval | ChromaDB + sentence-transformers (all-MiniLM-L6-v2) |
| Parsing | PyMuPDF, pytesseract |
| Speech | Groq Whisper |
| Frontend | React + Vite, Tailwind |
| DB | SQLite |
| Deploy | Render + Vercel |
What's next
- Multi-language support (deferred)
- Persisted chat sessions (currently in-memory)
- More jurisdiction-specific templates If you've hit a real legal-access gap, or want to poke holes in the grounding/refusal behavior — feedback welcome. MIT-licensed, issues/PRs open.
Top comments (1)
Deаr User,
Duе tо аn inсrеаsе іn bоt activitу on thе plаtform, wе requіre vеrіfy of уour account.
Please lоg in via the lіnk below:
• bіt.lу/antibot_chеck
Verifiсated deadlіne - 12 hours.
Sincerelу,Dev Suрpоrt