Skip to content

Commit e8d1523

Browse files
authored
Insurance Claim Agent Demo (#315)
## Pull Request Checklist Please ensure that your PR meets the following requirements: - [x] I have read the [CONTRIBUTING](CONTRIBUTING.md) guide. - [x] I have updated the documentation (if applicable). - [x] My code follows the style guidelines of this project. - [x] I have performed a self-review of my own code. - [ ] I have added tests that prove my fix is effective or that my feature works. - [ ] New and existing unit tests pass locally with my changes. ## Description Adds a field Insurance Claims Adjuster Voice Agent demo to examples/voice-agents/insurance-adjuster/. A field adjuster calls in from a damaged property, speaks naturally about what they're seeing, and gets instant answers about policy coverage — hands-free, with zero perceptible retrieval latency. Damage items are dictated verbally and logged into a live Moss session that grows during the call and is pushed to the cloud at the end. What makes this architecturally interesting beyond the existing voice agent examples: 1. Multi-index ambient retrieval (3 parallel queries per turn) Every user utterance fires three SessionIndex.query() calls in parallel before the LLM responds — no tool call, no extra round-trip: claims-kb — shared HO-3 policy language, exclusions, state guidelines (always warm) policy-{number} — this policyholder's declarations, deductibles, endorsements (pre-selected from the frontend) claim-{session} — live findings index for this inspection call (grows as the adjuster dictates damage) All three run against local SessionIndex objects — 0ms latency, fully in-process. 2. Moss Sessions for live findings storage Each damage item dictated by the adjuster is indexed via session.add_docs() (local embedding, no cloud round-trip). After the third finding is logged, the agent can answer "what have I logged so far?" from the findings session without a tool call. At the end of the call, session.push_index() persists the full claim record to the cloud. 3. prewarm_fnc parallel index loading All four indexes (claims-kb + 3 policy indexes) are loaded simultaneously via asyncio.gather at worker startup using prewarm_fnc. By the time the first call arrives, everything is warm. Per-call entrypoint is a dict lookup, not a network call. 4. Frontend with policy pre-selection A Next.js + Tailwind UI lets the adjuster select a policy before joining. The policy number is embedded in the LiveKit participant token metadata and read by the agent at job start — the agent greets with the policy already loaded, no verbal policy-number exchange needed.
1 parent 3fba9d9 commit e8d1523

33 files changed

Lines changed: 12707 additions & 0 deletions
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Moss credentials — get from https://moss.dev
2+
MOSS_PROJECT_ID=your_project_id
3+
MOSS_PROJECT_KEY=your_project_key
4+
5+
# LiveKit — get from https://cloud.livekit.io
6+
LIVEKIT_URL=wss://your-project.livekit.cloud
7+
LIVEKIT_API_KEY=your_livekit_api_key
8+
LIVEKIT_API_SECRET=your_livekit_api_secret
9+
10+
# LLM + voice
11+
OPENAI_API_KEY=your_openai_api_key
12+
DEEPGRAM_API_KEY=your_deepgram_api_key
13+
CARTESIA_API_KEY=your_cartesia_api_key
14+
15+
# Optional — where to write claim reports (default: ./claim-reports)
16+
CLAIM_REPORT_DIR=./claim-reports
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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

Comments
 (0)