Sanjay Azhagan Logo
Journal
← Back to Projects

VeriLaw

August 2023
Enterprise AIPII RedactionPrivacy-FirstLLMMap-ReduceLangGraphFastAPIPythonNode.jsExpressReactTypeScriptDrizzle ORMPostgreSQLNeonRedisAzureLlama-3OpenRouterPDF ParsingAsynchronous Processing

VeriLaw AI is a secure, state-of-the-art legal auditing system designed to automatically scan corporate agreements, redact Personally Identifiable Information (PII) before it leaves the secure gateway, and audit the contract for material legal risks (such as uncapped liabilities and auto-renewal clauses).

VeriLaw AI — Enterprise Legal Auditing & Privacy Platform

VeriLaw AI is a secure, state-of-the-art legal auditing system designed to automatically scan corporate agreements, redact Personally Identifiable Information (PII) before it leaves the secure gateway, and audit the contract for material legal risks (such as uncapped liabilities and auto-renewal clauses).


🏛️ System Architecture

The application is built on a split-trust, privacy-first parallel architecture:

1. Secure Privacy Gateway (Node.js Express)

  • Text Extraction: Uses pdf-parse to extract clean text from uploaded files (PDFs, TXT, OCR-ready images).
  • Privacy Scrubber: Runs a fast, local Regex engine to anonymize emails, phone numbers, tax IDs, and corporate entity/personal names. Replaces PII with secure keys (e.g. [REDACTED_NAME_1]) before sending data to any external LLMs.
  • Database: Powered by Drizzle ORM connecting to a Neon PostgreSQL database. Stores users, document metadata, PII mappings, and audit results.

2. Risk Language Model (FastAPI + LangGraph)

  • Intelligent Router: Classifies contract types (MSA, NDA, SOW) and runs triage to determine complexity (using llama-3.1-8b-instant).
  • Map-Reduce Auditing: Splices the document text into pages and fans them out to background workers in parallel.
  • Worker LLMs: Standard or low-risk documents run on llama-3.1-8b-instant, while complex or high-risk contracts utilize llama-3.3-70b-versatile via Groq.
  • Synthesis & Save: Merges worker findings, filters out hallucinations, and writes the final markdown executive summary and categorized risk list directly back to the Neon PostgreSQL database.

🛠️ Technology Stack

ComponentTechnologies
FrontendReact, TypeScript, Vite, Vanilla CSS, Lucide Icons
Gateway ServiceNode.js, Express, Multer, Drizzle ORM
MicroservicePython, FastAPI, LangGraph, Pydantic
Message QueueRedis (supports Upstash Redis / local Redis)
DatabasePostgreSQL (Neon serverless Postgres)
LLM ProviderGroq Cloud APIs (llama-3.1-8b-instant / llama-3.3-70b-versatile)

The Struggle: Initially, we deployed the Express Gateway and Python FastAPI microservice to Render.com (Oregon region). However, our primary LLM provider, Groq, strictly blocked API requests originating from Render's IP addresses due to geographic/datacenter restrictions. This caused all backend API calls to silently fail with 403 or rate limit errors.

The Decision:

  1. Switched AI Providers: We migrated the entire LLM pipeline from Groq directly to OpenRouter. OpenRouter acts as an unrestricted proxy, giving us access to the exact same models (like gemma-4-26b-a4b-it:free and llama-3) without the harsh geographic IP blocks.
  2. Moved to a Private Azure VM: We abandoned the managed Render.com deployment and moved all hosting to a private Azure Ubuntu VM. This gave us a dedicated IP address (bypassing blocks) and allowed us to run Redis locally via Docker (zero network latency for the background workers) instead of relying on Upstash.

2. Worker Error Bubbling & UI Diagnostics

The Struggle: Because the architecture is heavily asynchronous (Express -> FastAPI -> Redis -> Python Worker), when an AI request failed (e.g. rate limits, invalid API keys), the background worker would crash silently. The frontend React UI would just say "Processing" forever or show a generic "Failed" state. Users had no idea why a document failed without SSHing into the server to read terminal logs.

The Decision: Instead of running a complex database migration to add an error_message column, we decided to hijack the existing progress_step text column. We modified the LangGraph Python worker to explicitly catch RateLimitError and AuthError, format them into human-readable strings (e.g., [AI Error] OpenRouter Rate Limit Exceeded), and save them directly to progress_step. The React frontend now polls this column and immediately fires a browser alert to inform the user exactly what went wrong before auto-deleting the failed row.

3. Handling Corrupted PDFs

The Struggle: Certain scanned or digitally signed PDFs were causing the pdf-parse library to crash completely with a bad XRef entry error. Because this happened before the text ever reached the AI, the backend threw a cryptic 500 Server Error.

The Decision: We wrapped the PDF parsing logic in an explicit try/catch block. Rather than rewriting the entire parser to use a heavier library, we intercept the bad XRef entry error and return a friendly, actionable message instructing the user to "Print -> Save as PDF" to flatten the document, bypassing the corruption.