Skip to content

Commit da479d3

Browse files
hatimaneesclaude
andcommitted
Voice Copilot: voice layer over a complex web app (CRM demo)
Two-tier LLM (Gemini 2.5 Flash reflexes + 2.5 Pro vectorless recall), agent-as-retriever memory (no embeddings), LiveKit RPC capability registry (navigate/execute/set_field/get_state), human-in-the-loop confirm, and live voice form-fill. Includes architecture doc, demo script, social content, and a screen-record helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 parents  commit da479d3

26 files changed

Lines changed: 3506 additions & 0 deletions

.gitignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# secrets — NEVER commit
2+
.secrets/
3+
*.json.key
4+
**/vertex.json
5+
.env
6+
.env.local
7+
agent/.env
8+
web/.env
9+
10+
# python
11+
__pycache__/
12+
*.pyc
13+
.venv/
14+
venv/
15+
*.egg-info/
16+
17+
# node
18+
node_modules/
19+
dist/
20+
.vite/
21+
*.log
22+
23+
# demo recordings (large binaries)
24+
demo/
25+
*.mp4
26+
*.mov
27+
28+
# os / editor
29+
.DS_Store
30+
Thumbs.db
31+
.idea/
32+
.vscode/

ARCHITECTURE.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Architecture
2+
3+
Voice Copilot is a **voice layer you drop onto a complex web app**. It turns
4+
"click through 6 screens / raise a ticket / ping the ops team" into "click the mic
5+
and say what you want." The victim app here is a real-estate CRM, but the pattern
6+
is app-agnostic.
7+
8+
## The whole loop
9+
10+
```mermaid
11+
flowchart LR
12+
U([🎙️ User speaks]) -->|WebRTC audio| LK[LiveKit room]
13+
LK --> STT[Deepgram STT]
14+
STT --> FLASH["Voice brain<br/>Gemini 2.5 Flash<br/>(turn-taking + tool routing)"]
15+
FLASH --> TTS[ElevenLabs TTS]
16+
TTS -->|audio track| LK --> U
17+
18+
FLASH -.->|recall query| PRO["Vectorless memory<br/>Gemini 2.5 Pro<br/>(reasons over knowledge tree)"]
19+
PRO -.->|resolved facts| FLASH
20+
21+
FLASH ==>|"navigate / execute / set_field / get_state<br/>(LiveKit RPC)"| WEB[React CRM app]
22+
WEB ==>|state + confirm result| FLASH
23+
WEB -.->|live event feed| SIDE[Debug sidebar]
24+
25+
subgraph Knowledge["Vectorless knowledge layer (docs-ready)"]
26+
PRO
27+
TREE[("CRM data + user prefs<br/>+ session context<br/>— structured tree, no embeddings")]
28+
PRO --- TREE
29+
end
30+
```
31+
32+
## Three ideas doing the heavy lifting
33+
34+
### 1. Two-tier LLM — reflexes vs. reasoning
35+
A single model is either too slow for natural turn-taking or too dumb to resolve
36+
"the villa lead who went quiet after the price." So we split it:
37+
38+
- **Gemini 2.5 Flash** is the *voice brain*: sub-second turn-taking, decides which
39+
tool to call. It deliberately knows **nothing** about the CRM data.
40+
- **Gemini 2.5 Pro** sits **behind** the `recall` tool and does the slow reasoning.
41+
Latency there is fine because it only fires when Flash lacks context.
42+
43+
### 2. Vectorless memory (agent-as-retriever) — no embeddings, no vector DB
44+
`recall(query)` hands the whole knowledge tree — CRM records + user preferences +
45+
session context — to Pro as structured text and asks it to *reason* to the answer
46+
(PageIndex / vectorless-RAG style, same shape as how Claude Code / Cursor retrieve).
47+
48+
Why it matters for the roadmap: the tree is just **readable structured data**. Drop
49+
in product docs, portal how-tos, or an SOP file and the same `recall` path answers
50+
"how do I issue a refund on Exotel" — support work, no re-architecture.
51+
52+
### 3. The app publishes its own capabilities — no DOM scraping, no vision
53+
The agent never guesses the UI. The React app **registers RPC methods** the agent
54+
calls:
55+
56+
| Tool | What the app does | Confirm? |
57+
|---|---|---|
58+
| `navigate(target, filters)` | route to dashboard / leads / pipeline / new-lead / a specific lead | no |
59+
| `get_state()` | return current view + form values + which fields are filled | no |
60+
| `set_field(field, value)` | mirror a spoken value into the New-Lead form, live | no |
61+
| `execute(action, params)` | mutate data **behind a visual Confirm card** | **yes** |
62+
| `recall(query)` | (agent-internal) vectorless lookup via Pro | no |
63+
64+
This is why it's reliable enough to demo live: navigation/mutation are typed,
65+
app-owned actions — not screenshot guesses. Any mutating action is gated by a
66+
human-in-the-loop Confirm card + spoken confirmation.
67+
68+
## Request lifecycle — "add a new lead" (voice form-fill)
69+
1. Flash calls `navigate("new_lead")` → form opens.
70+
2. Flash calls `get_state()` → sees which fields the user already typed.
71+
3. For each *empty* field, Flash asks conversationally; after each answer it calls
72+
`set_field(...)` and the field **pulses and fills on screen**.
73+
4. Flash calls `execute("create_lead", {...})`; the app **merges** its params with
74+
the on-screen draft and shows the Confirm card. User confirms → lead created.
75+
76+
## Stack (all free-tier)
77+
LiveKit Agents (Python, VAD + turn detection) · Deepgram STT · Gemini 2.5 Flash +
78+
2.5 Pro on Vertex · ElevenLabs TTS · React + Vite + livekit-client.

GOAL.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Voice Copilot — Project Goal
2+
3+
## One-liner
4+
A voice copilot you drop into any complex web app. User clicks 🎙️, speaks in plain
5+
language, and the copilot **understands → navigates → executes** — confirming and
6+
collecting missing inputs by voice. The reusable, impressive artifact is the *voice
7+
layer*; the Mini-CRM is just the "victim app" that proves it works on something
8+
genuinely painful to click through.
9+
10+
**Purpose:** portfolio piece to showcase applied voice-AI skill (intent understanding,
11+
app-state grounding, safe agentic action).
12+
13+
## The 3 capabilities → 3 agent tools
14+
| Capability | Tool | Behavior |
15+
|---|---|---|
16+
| Understand what user *really* wants | `recall(query)` | Vectorless memory search via heavier LLM; resolves entity refs + prefs |
17+
| Navigate | `navigate(target, filters?)` | Frontend routes to page/section, highlights |
18+
| Execute | `execute(action, params)` + `collect(field)` | Runs action behind confirmation gate; voice slot-fills missing params |
19+
20+
## Architecture (agent-as-retriever + two-tier LLM)
21+
```
22+
🎙️ User ──▶ LiveKit Agent ── Gemini 2.5 Flash (reflexes: turn-taking, tool routing)
23+
├─▶ recall(query) ──▶ Gemini 2.5 Pro reasons over memory tree ──▶ answer
24+
├─▶ navigate(target) ─RPC─▶ React Mini-CRM (routes + highlights)
25+
├─▶ collect(field) ─RPC─▶ React (voice slot-fills a form field)
26+
└─▶ execute(action) ─RPC─▶ React (Confirm card + 🔊/🔇 + voice confirm)
27+
```
28+
- **Frontend publishes its own capability registry** (routes + typed actions) to the agent.
29+
Agent never scrapes DOM or guesses — it calls back via **LiveKit RPC**. Reliable + safe.
30+
- **Flash** = voice loop only. **2.5 Pro** = lives *behind* `recall`, does vectorless
31+
reasoning. Latency there is acceptable (only fires when Flash lacks context).
32+
33+
## Memory = vectorless (grounded in 2025/26 SOTA)
34+
No embeddings, no vector DB. Structured, human-readable tree (JSON/markdown):
35+
session context + CRM data + persistent user prefs. `recall` hands the relevant slice
36+
to Pro and asks it to reason. Debuggable, zero infra.
37+
- Ref: PageIndex / vectorless RAG (VectifyAI, Sep 2025); agent-as-retriever (Claude Code/Cursor).
38+
- Decision: **session context + vectorless persistent prefs** for v1.
39+
40+
## Stack (all free-tier)
41+
| Layer | Choice |
42+
|---|---|
43+
| Orchestration | LiveKit Agents (Python) — free VAD, barge-in, turn detection |
44+
| STT | Deepgram streaming |
45+
| Voice brain | Gemini 2.5 Flash (Vertex) |
46+
| Recall brain | Gemini 2.5 Pro (Vertex, behind `recall`) |
47+
| TTS | ElevenLabs (fallback: Deepgram Aura) |
48+
| Frontend | React + LiveKit client SDK (owns routing, forms, Confirm card + mute) |
49+
50+
## Confirmation UX
51+
Visual Confirm/Cancel card **+** spoken "…confirm?" **+** mute (🔇) toggle.
52+
Navigation/read = no confirm; mutating actions = confirm.
53+
54+
## v1 hero flows (the 90-sec demo)
55+
1. **Navigate** — "Show me Dubai leads that went cold." → filter + route, no confirm.
56+
2. **Recall + navigate** — "Open the one I talked to yesterday about the villa." → `recall` resolves entity → opens.
57+
3. **Execute + confirm** — "Move this to Won." → Confirm card + voice → executes.
58+
4. **Execute + slot-fill** — "Schedule a follow-up." → `collect` voice-fills date/note → confirm → done.
59+
60+
## Latency target
61+
< 1.5s voice round-trip for navigate/execute. `recall` may take longer (acceptable).
62+
63+
## Milestones
64+
- **M0** — Repo scaffold: React Mini-CRM (seeded leads) + LiveKit room + agent skeleton.
65+
- **M1** — Capability registry + `navigate` RPC working end-to-end (flow 1).
66+
- **M2**`recall` tool + vectorless memory tree + Gemini 2.5 Pro (flow 2).
67+
- **M3**`execute` + Confirm card + mute + voice confirm (flow 3).
68+
- **M4**`collect` slot-filling (flow 4). Record demo video.
69+
70+
## Open items
71+
- Gemini Vertex service-account JSON — user to provide (drop at `C:\voice-copilot\.secrets\vertex.json`, gitignored).
72+
- Deepgram + ElevenLabs API keys — free-tier signup.
73+
74+
## Status
75+
- Goal locked 2026-07-12.
76+
- **M0 scaffold DONE** (2026-07-12): repo structure, agent skeleton (main.py + 4 tools),
77+
vectorless memory.py, token_server.py, React Mini-CRM (App.tsx + Confirm/collect cards),
78+
seed data, README, .gitignore. Creds verified: Vertex Flash+Pro reachable; vectorless
79+
`recall` resolves fuzzy refs end-to-end.
80+
- **Blocking to run:** LiveKit Cloud creds (URL/key/secret) in `agent/.env`; then
81+
`pip install -r agent/requirements.txt` + `npm install` in `web/`.
82+
- **Next: M1** — wire `navigate` RPC live (flow 1) once LiveKit creds land.

README.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<h1 align="center">🎙️ Voice Copilot</h1>
2+
3+
<p align="center">
4+
<b>A voice layer you drop onto any complex web app.</b><br/>
5+
Click the mic, say what you want — it <b>navigates</b>, <b>fills forms</b>, and
6+
<b>acts</b> (with confirmation), instead of you clicking through 6 screens or
7+
raising a ticket.
8+
</p>
9+
10+
<p align="center">
11+
<i>Demo app: a real-estate CRM. The reusable part is the voice layer.</i>
12+
</p>
13+
14+
---
15+
16+
## Why
17+
18+
So much software makes you **connect with a team to do a basic thing**. Government
19+
portals. Telephony consoles like **Exotel / Acefone / Twilio**. Internal admin
20+
tools. You know *what* you want — "move this lead to Won", "add a new lead", "show
21+
me the cold Dubai deals" — but the UI makes you hunt, and the deeper actions mean a
22+
ticket and a wait.
23+
24+
**Voice Copilot collapses that.** You talk; it does the clicking. And because the
25+
knowledge layer is [vectorless and docs-ready](./ARCHITECTURE.md), the same thing
26+
that resolves *"the villa lead who went quiet after the price"* can later answer
27+
*"how do I issue a refund on this portal"* — turning it into a support copilot too.
28+
29+
> ⚠️ This is a **rough prototype to showcase the pattern**, not a polished product.
30+
31+
## What it does (demo flows)
32+
33+
| You say | It does |
34+
|---|---|
35+
| *"Show me Dubai leads that went cold"* | filters the table live |
36+
| *"Open the villa lead who went quiet after the price"* | **vectorless recall** resolves the fuzzy reference → opens the lead |
37+
| *"Move Meera to Won"* | shows a **Confirm card** (+ spoken confirm) → updates the pipeline |
38+
| *"Add a new lead"* | walks you through it by voice; **each field fills in live**; confirm → created |
39+
40+
A live **debug sidebar** shows the whole pipeline as it runs: 🎧 STT → 🧠 LLM → 🔧 tool calls.
41+
42+
## How it works
43+
44+
Two-tier LLM (fast **Gemini 2.5 Flash** for the voice loop + **2.5 Pro** behind a
45+
`recall` tool for reasoning), a **vectorless** knowledge layer (no embeddings, no
46+
vector DB), and an app that **publishes its own capabilities over LiveKit RPC** so
47+
the agent never scrapes the DOM. Every mutating action is gated by a human confirm.
48+
49+
👉 **[Full architecture + diagram →](./ARCHITECTURE.md)**
50+
51+
```
52+
🎙️ User ─▶ LiveKit ─▶ Gemini 2.5 Flash (voice reflexes, tool routing)
53+
├─ recall(query) ─▶ Gemini 2.5 Pro over vectorless tree
54+
├─ navigate / get_state / set_field ─RPC▶ React CRM
55+
└─ execute(...) ─RPC▶ Confirm card + voice confirm
56+
```
57+
58+
**Stack (all free-tier):** LiveKit Agents · Deepgram STT · Gemini 2.5 Flash + Pro (Vertex) · ElevenLabs TTS · React + Vite.
59+
60+
## Run it locally
61+
62+
**Prereqs:** Python 3.11+, Node 18+, and accounts for LiveKit Cloud, Deepgram,
63+
ElevenLabs, and Google Vertex (all have free tiers).
64+
65+
1. **Secrets** — create `.secrets/` with:
66+
- `vertex.json` — a Vertex service-account key
67+
- `.env``DEEPGRAM_API_KEY`, `ELEVENLABS_API_KEY`, and your LiveKit
68+
`LIVEKIT_URL` / `LIVEKIT_API_KEY` / `LIVEKIT_API_SECRET`
69+
70+
(`.secrets/` is gitignored — nothing sensitive is committed.)
71+
72+
2. **Agent** (two terminals):
73+
```bash
74+
cd agent && python -m venv .venv && . .venv/Scripts/activate
75+
pip install -r requirements.txt
76+
python token_server.py # terminal 1 — LiveKit tokens on :8787
77+
python main.py dev # terminal 2 — voice worker
78+
```
79+
80+
3. **Web**:
81+
```bash
82+
cd web && npm install && npm run dev # http://localhost:5173
83+
```
84+
85+
Open the app → **Talk to Copilot** → try the flows above.
86+
87+
## Repo layout
88+
```
89+
agent/ Python LiveKit agent — voice loop + tools (recall/navigate/execute/set_field/get_state)
90+
agent/memory.py vectorless recall via Gemini 2.5 Pro (no embeddings)
91+
web/ React + Vite CRM — RPC handlers, 4 views, Confirm card, debug sidebar
92+
data/ seed CRM data (also the memory source of truth)
93+
content/ demo script + social posts
94+
scripts/ screen-recording helper + smoke tests
95+
```
96+
97+
## Roadmap
98+
- **Docs-aware support:** ingest portal how-tos/SOPs into the vectorless tree → answer "how do I do X" and do it.
99+
- Risk-tiered confirmation (auto-run reads, confirm mutations).
100+
- Persistent, learned user preferences.
101+
- Drop-in SDK so any React app can register its capability registry.
102+
103+
## License
104+
MIT. Built as a portfolio showcase — feedback and ideas welcome.

agent/.env.example

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copy to ../.secrets/.env is NOT used by the agent; the agent reads THIS file's
2+
# real counterpart at agent/.env OR the values below via the shell.
3+
# Fill LiveKit values from LiveKit Cloud (livekit.io -> project -> Settings -> Keys).
4+
5+
# --- LiveKit (REQUIRED — the one remaining gap) ---
6+
LIVEKIT_URL=wss://YOUR-PROJECT.livekit.cloud
7+
LIVEKIT_API_KEY=
8+
LIVEKIT_API_SECRET=
9+
10+
# --- Vertex / Gemini (already provisioned) ---
11+
GOOGLE_APPLICATION_CREDENTIALS=C:\voice-copilot\.secrets\vertex.json
12+
VERTEX_PROJECT_ID=your-gcp-project-id
13+
VERTEX_LOCATION=us-central1
14+
VOICE_LLM_MODEL=gemini-2.5-flash
15+
RECALL_LLM_MODEL=gemini-2.5-pro
16+
17+
# --- STT / TTS (loaded from ../.secrets/.env by config.py) ---
18+
# DEEPGRAM_API_KEY and ELEVENLABS_API_KEY live in C:\voice-copilot\.secrets\.env
19+
ELEVENLABS_VOICE_ID=

agent/config.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Central config. Loads secrets from C:\\voice-copilot\\.secrets\\.env and the
2+
agent's own .env, and exposes typed settings for the voice pipeline + Vertex."""
3+
from __future__ import annotations
4+
import os
5+
from pathlib import Path
6+
from dotenv import load_dotenv
7+
8+
ROOT = Path(__file__).resolve().parent.parent # C:\voice-copilot
9+
SECRETS = ROOT / ".secrets"
10+
11+
# Load .secrets/.env first (Deepgram/ElevenLabs keys), then agent/.env (overrides).
12+
load_dotenv(SECRETS / ".env")
13+
load_dotenv(Path(__file__).resolve().parent / ".env")
14+
15+
16+
def _req(name: str) -> str:
17+
v = os.getenv(name)
18+
if not v:
19+
raise RuntimeError(f"Missing required env var: {name}")
20+
return v
21+
22+
23+
# LiveKit — the transport/room layer
24+
LIVEKIT_URL = os.getenv("LIVEKIT_URL", "")
25+
LIVEKIT_API_KEY = os.getenv("LIVEKIT_API_KEY", "")
26+
LIVEKIT_API_SECRET = os.getenv("LIVEKIT_API_SECRET", "")
27+
28+
# Vertex / Gemini
29+
GOOGLE_APPLICATION_CREDENTIALS = os.getenv(
30+
"GOOGLE_APPLICATION_CREDENTIALS", str(SECRETS / "vertex.json"))
31+
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = GOOGLE_APPLICATION_CREDENTIALS
32+
VERTEX_PROJECT_ID = os.getenv("VERTEX_PROJECT_ID", "your-gcp-project-id")
33+
VERTEX_LOCATION = os.getenv("VERTEX_LOCATION", "us-central1")
34+
VOICE_LLM_MODEL = os.getenv("VOICE_LLM_MODEL", "gemini-2.5-flash")
35+
RECALL_LLM_MODEL = os.getenv("RECALL_LLM_MODEL", "gemini-2.5-pro")
36+
37+
# STT / TTS
38+
DEEPGRAM_API_KEY = os.getenv("DEEPGRAM_API_KEY", "")
39+
ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY", "")
40+
ELEVENLABS_VOICE_ID = os.getenv("ELEVENLABS_VOICE_ID", "iWNf11sz1GrUE4ppxTOL")
41+
42+
DATA_DIR = ROOT / "data"
43+
LEADS_FILE = DATA_DIR / "leads.json"

0 commit comments

Comments
 (0)