# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Workflow gate (READ FIRST) Before changing anything, read **`SPEC.md`** — it defines the mandatory process: sanity tests (`tests/test_sanity.py`) must pass before any commit (enforced by the git pre-commit hook installed via `bash scripts/install-hooks.sh`), and the **How To Guide** tab in `ui/app.py` must be updated on any user-facing UI change. ## Project **G.U.I.D.E.** (Grievance Utility for Information Extraction, Drafting and Enrichment) — a privacy-first consumer complaint assistant for Indian consumers. Users describe their complaint in plain language; the system redacts PII locally, drafts a formal letter, and routes to the correct Indian regulatory authority. ## Startup ### 1. Configure environment Create a `.env` file in the project root with the following keys (all three are required): ```bash cp .env.example .env ``` Then edit `.env` and fill in: ``` ANTHROPIC_API_KEY=sk-ant-... # Claude API — powers the managed agent HF_TOKEN=hf_... # HuggingFace token — needed to download model checkpoints from sarav95/guide-models LANGCHAIN_API_KEY=ls__... # LangSmith API key — used for tracing / observability ``` > The `.env` file is git-ignored and must never be committed. ### 2. Install dependencies ```bash pip install -r requirements.txt python -m spacy download en_core_web_lg ``` ### 3. Run ```bash # Run everything (auto-downloads models from HF if missing, then starts both servers) python start.py # Access # API docs → http://localhost:8000/docs # UI → http://localhost:7860 ``` ### `start.py` flags | Flag | Effect | |------|--------| | _(none)_ | Auto-download missing checkpoints from HF, auto-train any still missing, then serve | | `--download-models` | Force download all checkpoints from `sarav95/guide-models` on HuggingFace | | `--no-train` | Skip training; start servers immediately (models must exist) | | `--train` | Force re-train all three models even if checkpoints exist | | `--train-only` | Train then exit — no servers started | | `--cfpb_csv PATH` | Override default CFPB CSV path (`data/raw/complaints.csv`) | `--no-train` and `--train` are mutually exclusive. `--train-only` can be combined with `--train`. Model checkpoints are stored in `models/` (git-ignored). They are hosted on HuggingFace at `sarav95/guide-models` and auto-downloaded on first run when the directory is missing. ### Train individual models ```bash # EvidenceNER — synthetic data generated in-memory, no download required python -m src.ner.train --output_dir models/evidence_ner # NextActionPredictor — synthetic data, <30 s on CPU python -m src.next_action.train --output_path models/next_action/model.pt # DomainClassifier — needs CFPB CSV first (one-time Kaggle download) python -m src.classifier.train \ --cfpb_csv data/raw/complaints.csv \ --output_dir models/domain_classifier ``` Without a DomainClassifier checkpoint the system falls back to keyword heuristics (confidence=0.0 sentinel). Without a NextActionPredictor checkpoint it falls back to `DOMAIN_ACTION_PRIORS` rule mapping. Both fallbacks keep the pipeline fully functional. ## Architecture ### Layer 1 — Privacy (runs first, locally, on every message) `src/privacy/redactor.py` — Microsoft Presidio (`presidio-analyzer` + `presidio-anonymizer`) with spaCy `en_core_web_lg`. Detects and replaces PII with `` placeholders **before** any text leaves the process. Fails open: on any error the original text is returned unchanged so the pipeline is never blocked. Entity types redacted: `PERSON`, `PHONE_NUMBER`, `EMAIL_ADDRESS`, `CREDIT_CARD`, `IBAN_CODE`, `US_BANK_NUMBER`, `IN_AADHAAR`, `IN_PAN`, `IN_VEHICLE_REGISTRATION`. ### Layer 2 — Deep Learning Models | Module | Architecture | Checkpoint | Fallback | |--------|-------------|-----------|---------| | `src/classifier/` | DistilBERT + linear head, 6 classes | `models/domain_classifier/` | keyword heuristics | | `src/ner/` | DistilBERT token classifier, BIO tags, 6 entity types | `models/evidence_ner/` | _(none — required)_ | | `src/next_action/` | MLP 12→64→64→6 | `models/next_action/model.pt` | `DOMAIN_ACTION_PRIORS` | | `src/document_processor/` | Tesseract OCR + ViT patch-variance scoring | HuggingFace `google/vit-base-patch16-224` | OCR-only | **6 domain classes:** `ecommerce` · `telecom` · `banking` · `cibil` · `insurance` · `general` **6 NER entity types:** `ORG` · `AMOUNT` · `DATE` · `REF_ID` · `ACCOUNT` · `PERSON` **6 escalation actions:** `company_support` · `nch` · `trai` · `rbi_ombudsman` · `irdai` · `legal` Document processing path: PDF (pdfplumber) → EvidenceNER; image (pytesseract + Pillow greyscale/threshold/deskew) → EvidenceNER + DocumentViT. Both paths merge entities by `(text.lower(), label)` key, keeping the higher-confidence span. ### Layer 3 — Claude Managed Agent `src/agent/` — one `GUIDEAgent` instance per session; model is env-driven via `GUIDE_MODEL` (default `claude-sonnet-4-6`). Agent loop: stream response → on `stop_reason == "tool_use"` dispatch all tool_use blocks via `execute_tool()` → append `tool_result` messages → repeat until `end_turn`. Loop capped at 12 rounds. **7 tools:** `classify_domain` · `extract_entities` · `process_document` · `draft_complaint` · `recommend_action` · `store_memory` · `get_memory` `draft_complaint` is handled internally by Claude (returns `{"status": "proceed"}` from Python); the actual letter is generated as text in the next assistant turn. **HITL gate (mandatory):** Claude must present a numbered entity summary and receive `[USER CONFIRMED]: {...}` (injected by `POST /api/session/{id}/validate-entities`) before calling `draft_complaint()`. This is enforced in the system prompt (Rule 4 + Rule 5) and in `GUIDEAgent.confirm_entities()`. ### Layer 4 — FastAPI + Gradio **FastAPI** (`src/api/`) — singleton startup order in `lifespan()`: Presidio → DomainClassifier → EvidenceNER → NextActionPredictor → (DocumentProcessor lazy). All blocking model calls and CMA calls use `run_in_threadpool`. **Gradio** (`ui/app.py`) — tabs: Chat (🔒 privacy badge + side-by-side redaction reveal showing original-vs-sent text) · Verify Entities (HITL editable fields + Confirm button) · Complaint Draft (copy + .txt/PDF download via `reportlab`) · Escalation Guide · **Privacy Audit** (timestamped outbound/local event log with verified "0 raw identifiers transmitted" guarantee) · About. **Privacy reveal & audit:** `POST /message` returns `original_text`, `redacted_text`, and `redactions` (per-span `RedactionSpan`s) so the UI renders a side-by-side reveal — these are returned ONLY to the same user's browser, never forwarded externally. Each `/message` and `/upload` appends an entry to `Session.audit`; `GET /session/{id}/audit` serves the trail. The audit's `leak_check` re-verifies that no original PII value survives into the transmitted text. ## Critical Files | File | Role | |------|------| | `start.py` | Single entry point — training + server launch + `[API]`/`[UI]` log prefixing | | `src/privacy/redactor.py` | `PIIRedactor` singleton; `init_redactor()` / `get_redactor()` | | `src/agent/prompts.py` | `SYSTEM_PROMPT` — all 7 CMA rules including HITL gate | | `src/agent/tools.py` | `TOOL_DEFINITIONS` (JSON Schema) + `execute_tool()` dispatcher | | `src/agent/memory.py` | `SessionMemory` — per-session key-value store | | `src/agent/agent.py` | `GUIDEAgent` — streaming agent loop, `send_message()`, `confirm_entities()`, `add_document()` | | `src/api/main.py` | FastAPI app, `lifespan()` startup order, `_component_status` dict | | `src/api/routes.py` | All HTTP endpoints; Presidio redaction wired into `POST /message` | | `src/api/sessions.py` | Session registry (`session_id → Session(agent, history)`) | | `src/next_action/priors.py` | `DOMAIN_ACTION_PRIORS` + `ACTION_METADATA` (URLs) — training source of truth | | `ui/app.py` | Gradio Blocks app (`build_app()`), all event handlers, API client helpers | | `.env` | `ANTHROPIC_API_KEY`, `HF_TOKEN`, `LANGCHAIN_API_KEY` — never committed (in `.gitignore`) | ## Key Invariants - **PII never reaches Anthropic.** Presidio runs in-process before every `POST /message`. The Gradio UI sends raw text to FastAPI; FastAPI redacts before calling `GUIDEAgent.send_message()`. The agent only ever sees `` placeholders. - **HITL gate is mandatory before drafting.** `draft_complaint()` must not be called until `[USER CONFIRMED]` arrives. This is encoded in both the system prompt (Rules 4-5) and the route handler separation (`/message` vs `/validate-entities`). - **All model inference is blocking I/O.** Every call to a DL model or the Anthropic API uses `run_in_threadpool` to avoid blocking the FastAPI async event loop. - **Fallbacks keep the pipeline alive.** DomainClassifier → keyword fallback. NextActionPredictor → `DOMAIN_ACTION_PRIORS`. Both degrade gracefully with no code changes required. - **`add_document()` queues, not processes.** The document path is held in `_pending_documents` and prepended as `[Document uploaded: ]` on the next `send_message()` call, triggering Rule 3 of the system prompt. ## Model Checkpoint Paths ``` models/ evidence_ner/ ← HuggingFace format (config.json + pytorch_model.bin) domain_classifier/ ← HuggingFace format next_action/ model.pt ← PyTorch state_dict + metadata dict ``` `start.py` detects checkpoints by checking for `config.json` (HuggingFace) or file existence (`.pt`). The `--train` flag bypasses these checks and retrains unconditionally. ## Environment Variables | Variable | Default | Purpose | |----------|---------|---------| | `ANTHROPIC_API_KEY` | _(required)_ | Anthropic API key — powers the Claude managed agent | | `HF_TOKEN` | _(required)_ | HuggingFace token — authenticates `snapshot_download` from `sarav95/guide-models` | | `LANGCHAIN_API_KEY` | _(required)_ | LangSmith API key — tracing and observability | | `GUIDE_MODEL` | `claude-sonnet-4-6` | Claude model for the CMA. Use an Anthropic ID (e.g. `claude-opus-4-8`, `claude-haiku-4-5-20251001`) or a gateway/Bedrock ID when `LITELLM_PROXY_URL` is set | | `LITELLM_PROXY_URL` | _(unset)_ | If set, route Anthropic calls through a LiteLLM gateway instead of the direct API | | `API_PORT` | `8000` | FastAPI port | | `GRADIO_PORT` | `7860` | Gradio port | | `LOG_LEVEL` | `info` | Uvicorn log level |