|
| 1 | +# Insurance Claims Adjuster Voice Agent |
| 2 | + |
| 3 | +A field voice agent for property insurance claims adjusters. Adjusters call in from the inspection site, describe damage, and get instant answers about coverage — hands-free, sub-10ms retrieval. |
| 4 | + |
| 5 | +## What this demonstrates |
| 6 | + |
| 7 | +| Pattern | Description | |
| 8 | +|---------|-------------| |
| 9 | +| **Three-index ambient retrieval** | Every adjuster utterance fires up to three `SessionIndex.query()` calls in parallel before the LLM responds — no tool call, no extra round-trip. | |
| 10 | +| **Moss sessions for live findings** | Each logged damage item is indexed via `session.add_docs()` (local embedding, no cloud round-trip). The agent can answer "what have I logged so far?" from the findings session without a tool call. `submit_report` calls `session.push_index()` to persist the claim record to the cloud. | |
| 11 | +| **`prewarm_fnc` parallel loading** | All four indexes load simultaneously via `asyncio.gather` at worker startup. By the time the first call arrives, every `SessionIndex` is warm — per-call entrypoint is a dict lookup, not a network call. | |
| 12 | +| **Policy pre-selection from the frontend** | The adjuster selects a policy on the welcome screen. The policy number is embedded in the LiveKit participant token metadata and read by the agent at job start — no verbal policy exchange needed. | |
| 13 | +| **On-device embedding** | Moss embeds locally using the bundled MiniLM model. PII never reaches an external embedding API. | |
| 14 | +| **Web ingestion pipeline** | `ingest/crawl.py` fetches public insurance documentation (iii.org, FEMA, state DOI sites) and chunks it into Moss documents for the shared claims-kb. | |
| 15 | + |
| 16 | +## Architecture |
| 17 | + |
| 18 | +``` |
| 19 | +Worker starts (prewarm_fnc) |
| 20 | + │ |
| 21 | + └── asyncio.gather ──────────────────────────────────────────────────┐ |
| 22 | + moss.session("claims-kb") moss.session("policy-fl-ho3-001") ... (all 4 in parallel) |
| 23 | + All SessionIndex objects warm before the first call arrives. |
| 24 | +
|
| 25 | +
|
| 26 | +Adjuster utterance (on_user_turn_completed, fires BEFORE LLM) |
| 27 | + │ |
| 28 | + └── asyncio.gather ──────────────────────────────────────────────────┐ |
| 29 | + │ │ │ |
| 30 | + ▼ ▼ ▼ |
| 31 | + kb_session.query() policy_session.query() claim_session.query() |
| 32 | + HO-3 language, This policy's declarations, Damage items logged |
| 33 | + exclusions, state rules deductibles, endorsements so far this call |
| 34 | + (always runs) (runs once policy loaded) (runs once first finding logged) |
| 35 | + │ │ │ |
| 36 | + └──────────────── all results merged ──────────────────────────┘ |
| 37 | + │ |
| 38 | + System messages injected into chat context |
| 39 | + │ |
| 40 | + ▼ |
| 41 | + LLM responds (1 round-trip) |
| 42 | +``` |
| 43 | + |
| 44 | +## Tools |
| 45 | + |
| 46 | +The agent exposes three write tools. All reads are ambient. |
| 47 | + |
| 48 | +| Tool | When the LLM calls it | What it does | |
| 49 | +| ---- | --------------------- | ------------ | |
| 50 | +| `load_policy(policy_number)` | Adjuster provides a policy number verbally | Looks up the pre-warmed `SessionIndex` for that policy; activates it for ambient retrieval; creates the per-call findings session | |
| 51 | +| `log_finding(description, estimated_value, covered, note)` | Adjuster dictates a damage item | Appends to `SessionData.findings`; indexes the item into the live `claim_session` via `add_docs()`; publishes a `claim_update` data message to the frontend | |
| 52 | +| `submit_report()` | Adjuster says to wrap up | Calls `claim_session.push_index()` to persist findings to the cloud; writes a local JSON report | |
| 53 | + |
| 54 | +## Indexes |
| 55 | + |
| 56 | +| Index | Contents | Scope | |
| 57 | +| ----- | -------- | ----- | |
| 58 | +| `policy-fl-ho3-001` | FL Cape Coral HO-3: $485K Coverage A, 2% hurricane deductible, water backup + ordinance endorsements | Per-policy | |
| 59 | +| `policy-ca-ho3-002` | CA Pasadena HO-3: $620K Coverage A, 50% ordinance endorsement, scheduled jewelry/watches | Per-policy | |
| 60 | +| `policy-tx-ho3-003` | TX Katy HO-B: $540K Coverage A, 1% wind/hail deductible, cosmetic damage exclusion | Per-policy | |
| 61 | +| `claims-kb` | HO-3 standard policy language, coverage sections, exclusions, state guidelines (FL/CA/TX), NFIP overview, adjuster workflow | Shared — always warm | |
| 62 | + |
| 63 | +The live `claim-{policy}-{timestamp}` session is created per call and is not pre-warmed — it starts empty and grows as the adjuster logs findings. |
| 64 | + |
| 65 | +## Demo scenarios |
| 66 | + |
| 67 | +### FL-HO3-001 (Florida, post-hurricane) |
| 68 | + |
| 69 | +- "Is the pool cage covered?" → Coverage B, up to $48,500 |
| 70 | +- "What deductible applies to this wind damage?" → 2% hurricane deductible = $9,700 |
| 71 | +- "The slab cracked — is that covered?" → Not flood; check if pipe-related |
| 72 | +- "Is mold from the water intrusion covered?" → Up to $10,000 sub-limit if from a covered peril |
| 73 | +- "What have I logged so far?" → Agent queries the live findings session, answers without a tool call |
| 74 | + |
| 75 | +### CA-HO3-002 (California, water damage) |
| 76 | + |
| 77 | +- "Is the earthquake damage covered?" → No, separate CEA policy |
| 78 | +- "What does the ordinance endorsement cover?" → 50% of Coverage A = $310,000 |
| 79 | +- "Her engagement ring was stolen — what's the limit?" → Scheduled at $18,500, no deductible |
| 80 | +- "The flat roof leaked — covered?" → Storm damage yes; ponding/maintenance no |
| 81 | + |
| 82 | +### TX-HO3-003 (Texas, hail) |
| 83 | + |
| 84 | +- "What's the wind/hail deductible?" → 1% of Coverage A = $5,400 |
| 85 | +- "Hail dented the roof but didn't breach it — covered?" → Cosmetic exclusion applies |
| 86 | +- "The insured wants to invoke appraisal" → Confirm the mandatory Texas appraisal clause process |
| 87 | +- "Pipes burst in the winter storm — covered?" → Yes (sudden/accidental), $5,000 deductible |
| 88 | + |
| 89 | +## Setup |
| 90 | + |
| 91 | +### 1. Install dependencies |
| 92 | + |
| 93 | +```bash |
| 94 | +cd examples/voice-agents/insurance-adjuster |
| 95 | +uv sync |
| 96 | +``` |
| 97 | + |
| 98 | +### 2. Configure credentials |
| 99 | + |
| 100 | +```bash |
| 101 | +cp .env.example .env |
| 102 | +# Fill in MOSS_PROJECT_ID, MOSS_PROJECT_KEY, LIVEKIT_*, OPENAI_API_KEY, |
| 103 | +# DEEPGRAM_API_KEY, CARTESIA_API_KEY |
| 104 | +``` |
| 105 | + |
| 106 | +### 3. Build indexes (one-time) |
| 107 | + |
| 108 | +```bash |
| 109 | +uv run python create_indexes.py |
| 110 | +``` |
| 111 | + |
| 112 | +This creates `claims-kb`, `policy-fl-ho3-001`, `policy-ca-ho3-002`, and `policy-tx-ho3-003` in your Moss project. |
| 113 | + |
| 114 | +**Optional — enrich the claims-kb with crawled public insurance docs:** |
| 115 | + |
| 116 | +```bash |
| 117 | +uv run python -m ingest.crawl --out data/crawled_kb.json |
| 118 | +uv run python create_indexes.py --include-crawled data/crawled_kb.json |
| 119 | +``` |
| 120 | + |
| 121 | +### 4. Run the agent |
| 122 | + |
| 123 | +```bash |
| 124 | +uv run python agent.py dev |
| 125 | +``` |
| 126 | + |
| 127 | +At startup you will see all four indexes load in parallel: |
| 128 | + |
| 129 | +``` |
| 130 | +INFO insurance-adjuster - pre-warming 4 indexes in parallel: ['claims-kb', 'policy-fl-ho3-001', ...] |
| 131 | +INFO insurance-adjuster - claims-kb ready |
| 132 | +INFO insurance-adjuster - policy-fl-ho3-001 ready |
| 133 | +... |
| 134 | +INFO insurance-adjuster - all indexes warm, worker ready |
| 135 | +``` |
| 136 | + |
| 137 | +When a call comes in, entrypoint activates the pre-selected policy instantly: |
| 138 | + |
| 139 | +``` |
| 140 | +INFO insurance-adjuster - policy FL-HO3-001 activated from pre-warmed sessions |
| 141 | +``` |
| 142 | + |
| 143 | +**Optional:** Override the policy via env var (useful for testing without the frontend): |
| 144 | + |
| 145 | +```bash |
| 146 | +POLICY_NUMBER=TX-HO3-003 uv run python agent.py dev |
| 147 | +``` |
| 148 | + |
| 149 | +### 5. Run the frontend (optional) |
| 150 | + |
| 151 | +```bash |
| 152 | +cd ui |
| 153 | +cp .env.example .env.local # fill in LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET |
| 154 | +npm install |
| 155 | +npm run dev |
| 156 | +``` |
| 157 | + |
| 158 | +## Project layout |
| 159 | + |
| 160 | +```text |
| 161 | +insurance-adjuster/ |
| 162 | +├── agent.py # LiveKit voice agent — prewarm_fnc, ambient retrieval, Moss sessions |
| 163 | +├── create_indexes.py # Build per-policy + shared claims-kb indexes |
| 164 | +├── pyproject.toml |
| 165 | +├── .env.example |
| 166 | +├── data/ |
| 167 | +│ ├── claims_kb.json # Hand-authored HO-3 policy language (35 documents) |
| 168 | +│ └── policies/ |
| 169 | +│ ├── policy_HO3_FL001.json # Florida HO-3 (Cape Coral) |
| 170 | +│ ├── policy_HO3_CA002.json # California HO-3 (Pasadena) |
| 171 | +│ └── policy_HO3_TX003.json # Texas HO-B (Katy) |
| 172 | +├── ingest/ |
| 173 | +│ ├── crawl.py # Web crawler for public insurance docs |
| 174 | +│ └── chunk.py # PDF and long-text chunking utilities |
| 175 | +└── ui/ # Next.js 15 + Tailwind v4 frontend |
| 176 | + ├── components/app/ # Welcome screen, voice center, damage worksheet |
| 177 | + ├── hooks/ # useClaimState (data channel), useMossInsuranceEvents |
| 178 | + └── lib/policies.ts # Policy fixture data for the frontend |
| 179 | +``` |
| 180 | + |
| 181 | +## Key difference from airline-pnr example |
| 182 | + |
| 183 | +The airline PNR agent queries **one index** per turn (the active booking) and uses `load_index()` per call. This agent: |
| 184 | + |
| 185 | +- Queries **up to three** `SessionIndex` objects in parallel per turn |
| 186 | +- Pre-warms **all indexes** at worker start via `prewarm_fnc` — no per-call `load_index()` call |
| 187 | +- Adds a **third live index** (the findings session) that grows during the call and is queryable in real time |
| 188 | +- Uses **`session.push_index()`** at the end of the call to persist the claim record |
0 commit comments