*the part they hope you don't read
Final Year Project · FastAPI + LangGraph + Groq (Llama 3.3 70B) + FAISS + PyMuPDF
Students and first-jobbers in India sign rent agreements, education-loan papers, job offers, and app T&Cs without reading them — the language is hostile by design. The traps are always the same: deposit forfeiture, one-sided notice periods, unlimited penalties, "as decided by the lender" charges. FinePrint AI reads the document for you, marks every trap, tells you exactly what to negotiate — in plain English, Hindi and Tamil — and then answers your follow-up questions in a conversation that remembers the document, grounded in the actual law of Tamil Nadu, Madhya Pradesh, and central India.
Pick your state, upload a PDF, TXT, or photo of a document. In under 90 seconds you get:
- A verdict stamp — LOOKS FAIR / NEGOTIATE FIRST / DO NOT SIGN YET — with a 0–100 safety score
- Clause review — every risky clause quoted, highlighted 🔴 trap / 🟡 caution / 🟢 good, explained in 2 plain sentences (EN + हिन्दी + தமிழ்), with a concrete "what to do" negotiation tip
- Missing-protection audit — the clauses a fair document of this type SHOULD contain but doesn't, checked against a per-document-type checklist
- Legal grounding — findings are grounded in a curated FAISS knowledge base of Indian law (TN Rent Act 2017, MP Accommodation Control Act 1961, Shops & Establishments Acts, RBI lending rules, Contract Act, Consumer Protection Act 2019, DPDP Act 2023…), filtered to your state
- Ask FinePrint — a function-calling agent you can interrogate afterwards ("Is clause 7 risky?", "என் டெபாசிட் பணம் பாதுகாப்பானதா?") that searches the legal KB, cites acts and sections, and remembers the conversation per document
- Printable report to take to the landlord/HR/bank
Supported document types: rent agreements, loan agreements, job offers, terms & conditions, and general contracts.
cd fineprint
pip install -r requirements.txt
cp .env.example .env # paste your Groq API key inside
python -m app.kb --rebuild # build the FAISS legal index (downloads the embedding model once)
uvicorn app.main:app --reloadOpen http://localhost:8000 and drop in sample_docs/sample_rent_agreement.txt — a realistic rental agreement seeded with 6+ classic traps, perfect for demos.
Optional (scanned PDFs / photos): install Tesseract OCR, then pip install pytesseract pillow.
Get a free Groq key: console.groq.com → API Keys. The free tier is enough; the app paces its calls to respect rate limits.
Orchestration is a LangGraph state machine (app/graph.py) with two lanes sharing one state and one SQLite-checkpointed memory:
graph TD;
__start__([START]):::first
classify(classify)
retrieve(retrieve)
reformulate_query(reformulate_query)
analyze(analyze)
audit(audit)
score(score)
agent(agent)
tools(tools)
reformulate(reformulate)
__end__([END]):::last
__start__ -. report .-> classify;
__start__ -. chat .-> agent;
classify --> retrieve;
retrieve -. "confidence ≥ 0.30" .-> analyze;
retrieve -. "low confidence" .-> reformulate_query;
reformulate_query --> retrieve;
analyze --> audit;
audit --> score;
score --> __end__;
agent -. "tool_calls" .-> tools;
agent -. "answer" .-> __end__;
tools -. "ok" .-> agent;
tools -. "weak KB hits" .-> reformulate;
reformulate --> agent;
classDef first fill-opacity:0
classDef last fill:#bfb6fc
- Report lane (
POST /api/analyze) — deterministic pipeline:classify → retrieve → analyze → audit → score. Retrieval pulls the 6 most relevant legal rules from the FAISS KB (jurisdiction-filtered to the user's state) and injects them into the clause-analysis and audit prompts, so risk calls cite actual statutes. - Chat lane (
POST /api/chat) — a function-calling agent (bind_tools) dispatching across 5 tools:search_legal_kb,analyze_clause_risk,audit_missing_protections,summarize_document,extract_document_text. A custom tool node injects graph state (the loaded document, the jurisdiction) into tool runs. - Self-correction loop — every KB search carries a confidence score (mean top-3 cosine similarity). Below 0.30, a reformulate node rewrites the query with different legal terminology and retries — bounded at 2 retries by a
retry_countin state, so it can never loop forever. - Conversation memory — a
SqliteSavercheckpointer persists the full graph state perthread_idat every super-step./api/analyzereturns thethread_id; follow-up/api/chatcalls on it remember the document, the report, and the dialogue.
app/graph.py— the state machine above:ContractState(TypedDict withadd_messages), nodes, conditional edges, checkpointer.app/kb.py+app/legal_kb/rules.json— the legal KB: 42 curated plain-language rules (each with act, section, English text + Tamil/Hindi translations, doc-type tags, jurisdiction TN/MP/IN), embedded withparaphrase-multilingual-MiniLM-L12-v2— one vector space for English, Tamil, and Hindi, so «வேலை நீக்கம் நோட்டீஸ் இல்லாமல்» retrieves the English-indexed termination rule — searched via FAISS inner product over normalized vectors (= cosine, giving the 0–1 confidence signal).app/tools.py— the agent's tools with API-doc docstrings (what the LLM reads to choose a tool).analyze_clause_riskuseswith_structured_outputon a Pydantic schema.app/schemas.py— Pydantic contracts: every report is validated throughRiskReportbefore leaving the API.app/analyzer.py— the LLM steps (classify / clause findings / checklist audit) with strict-JSON prompts and defensive parsing that can even salvage complete findings from a token-truncated reply. Scoring stays deterministic Python, not LLM: 100 − 14/red − 6/yellow − 8/missing − 4/vague, +2 per good clause, clamped 3–100.app/checklists.py— per-document-type fairness checklists, encoded outside the model.app/extractor.py— PyMuPDF text extraction with OCR fallback for scans/photos.app/static/index.html— zero-framework frontend: state selector, drag-drop upload, verdict stamp, clause cards, English/हिन्दी/தமிழ் toggle, chat panel with cited acts, print stylesheet.
- Pick Tamil Nadu, upload the sample rent agreement → DO NOT SIGN YET stamp.
- Point at the red flag: "entire security deposit shall stand forfeited" — and note the explanation now cites the TN Rent Act 2017 cap of two months' deposit.
- Toggle to தமிழ் — same analysis, mother tongue. Toggle हिन्दी — same again.
- Show the missing-protection audit: no deposit-refund timeline — a trap by omission.
- Ask FinePrint: "Can the landlord evict me without notice?" — watch it call
search_legal_kb, answer with the act and section cited under the bubble, then ask a follow-up ("and what about the deposit?") to show thread memory. - Show
python -m app.graphprinting the state machine, and this README's diagram.
- Why a graph over a chain? The pipeline has cycles (the low-confidence → reformulate → retrieve loop) and conditional routing (report vs chat lane, tools vs answer) — a DAG/chain can't express either.
- How does
bind_toolswork? Tool schemas (from the docstrings + signatures) are injected into the request; the model emits structuredtool_calls; the tool node executes them and returnsToolMessages; the loop continues until the model answers without tool calls. - How do you prevent infinite loops?
retry_countlives in graph state; the conditional edge checks it against a bound (2) before routing to reformulate. Plus a recursion limit on invoke. - What does the checkpointer persist? The full
ContractStateperthread_idat each super-step — messages, document text, doc metadata, retrieved rules — in SQLite (fineprint_memory.db). - Retrieval confidence = mean top-3 cosine similarity (threshold 0.30, calibrated against junk vs relevant queries). Self-correction measurably works: "प्रीपेमेंट पेनल्टी" scores 0.26 raw, 0.56 after terminology reformulation.
- Trust boundary: the LLM explains; the score is deterministic Python; the law comes from a reviewable JSON corpus, not model memory. Citations let the user verify.
- Honest limitations: not legal advice (disclaimer in UI); the KB is a curated student corpus, not a complete statute database; LLM Tamil/Hindi phrasing can be imperfect (KB rule translations are human-written, model explanations are generated); OCR quality bounds photo uploads; analysis caps at ~15 pages.
- More states: the KB is a JSON file — add Karnataka/Maharashtra rules and a dropdown entry each
- Compare two versions of an agreement ("what changed after negotiation?")
- WhatsApp bot front-end (photo in → report out)
- Streaming chat responses (LangGraph
astream) and a visible "agent is searching the KB…" trace in the UI
fineprint/
├── app/
│ ├── main.py # FastAPI: /api/analyze + /api/chat -> graph
│ ├── graph.py # LangGraph state machine (2 lanes, retry loops, checkpointer)
│ ├── tools.py # 5 function-calling tools + Groq LLM factory
│ ├── kb.py # FAISS legal KB: build / search / confidence
│ ├── legal_kb/rules.json# 42 curated rules: TN + MP + central law (EN/TA/HI)
│ ├── schemas.py # Pydantic RiskReport & structured-output models
│ ├── analyzer.py # LLM steps + defensive JSON parsing + deterministic score
│ ├── checklists.py # fairness checklists per document type
│ ├── extractor.py # PDF/image/txt -> text (OCR fallback)
│ └── static/index.html # frontend (no framework): report + trilingual toggle + chat
├── sample_docs/sample_rent_agreement.txt # demo doc with seeded traps
├── smoke_test.py # offline tests: both lanes, retry loop, memory (mocked LLM+KB)
├── requirements.txt
└── .env.example
Run the tests any time with python smoke_test.py — they mock the LLM and the KB, so they're free and offline.