Stage 6: learn_call_flow rebuilt on the learner, docs truth sweep
The call-flow learner finally gets fed: exploration mode records its IVR discoveries on the call (ActiveCall.exploration_steps) instead of throwing them away, persistence stores them in the call record's metadata, and the rebuilt learn_call_flow MCP tool turns a completed exploration call into a stored flow via CallFlowLearner — correct constructor (llm_client from get_llm, heuristic labels when the LLM is unavailable), build for a new number, merge/refine when a flow already exists. save_learned_flow/update_flow_from_model keep the CallFlow↔row mapping in call_persistence. Test gaps closed: tests/test_learner.py (discoveries→linked steps, exploration persistence, learn-then-refine through the in-memory MCP client, no-data and unknown-call answers) and tests/test_websocket.py (4401 without token, trunk-status-then-replay on connect, per-call stream filtering). Docs aligned to code: README (15 tools incl. learn_call_flow, HTTP not SSE, Python 3.12+, PostgreSQL+Alembic — no SQLite fallback, media pipeline marked stub-mode until pjsua2 installed, Alembic and honest /health checked off); docs/mcp-server.md rewritten against the actual tool surface (hangup not end_call, real params, 3 real resources, /mcp/ streamable HTTP + bearer auth); architecture/development/ configuration drift fixed. pyproject: pruned never-imported deps (websockets, librosa, soundfile, python-multipart). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
25
README.md
25
README.md
@@ -21,7 +21,7 @@ You give it a phone number and an intent ("dispute a charge on my December state
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │
|
||||
│ │ REST API │ │WebSocket │ │MCP Server │ │ Dashboard │ │
|
||||
│ │ /api/* │ │ /ws/* │ │ (SSE) │ │ /dashboard │ │
|
||||
│ │ /api/* │ │ /ws/* │ │ (HTTP) │ │ /dashboard │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └─────┬─────┘ └──────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌────┴──────────────┴──────────────┴────┐ │
|
||||
@@ -53,7 +53,7 @@ You give it a phone number and an intent ("dispute a charge on my December state
|
||||
|
||||
### Core Engine
|
||||
- **Sippy B2BUA Engine** (`core/sippy_engine.py`) — SIP call control, DTMF, bridging, conference, trunk registration
|
||||
- **PJSUA2 Media Pipeline** (`core/media_pipeline.py`) — Audio routing, recording ports, conference bridge, WAV playback
|
||||
- **PJSUA2 Media Pipeline** (`core/media_pipeline.py`) — Audio routing, recording ports, conference bridge, WAV playback (stub mode until the `pjsua2` bindings are installed — see note below)
|
||||
- **Call Manager** (`core/call_manager.py`) — Active call state tracking, lifecycle management
|
||||
- **Event Bus** (`core/event_bus.py`) — Async pub/sub with per-subscriber queues, type filtering, history
|
||||
|
||||
@@ -78,7 +78,7 @@ You give it a phone number and an intent ("dispute a charge on my December state
|
||||
### API Surface
|
||||
- **REST API** — Call management, call history, transcripts, recordings, routing rules, device DND, call flow CRUD
|
||||
- **WebSocket** — Real-time call events, transcripts, classification updates, receptionist state transitions
|
||||
- **MCP Server** — 14 tools + 3 resources for AI assistant integration (make calls, send DTMF, get transcripts, manage flows), served over streamable HTTP at `/mcp/`
|
||||
- **MCP Server** — 15 tools + 3 resources for AI assistant integration (make calls, send DTMF, get transcripts, manage flows), served over streamable HTTP at `/mcp/`
|
||||
- **Dashboard** — SvelteKit UI served at `/dashboard` with live monitor, call history with transcript playback, and a routing-rules editor
|
||||
|
||||
### Data Models
|
||||
@@ -130,7 +130,7 @@ hold-slayer/
|
||||
│ ├── calls/[call_id]/ # Detail page + transcript playback
|
||||
│ └── routing/ # Rules editor + DND toggles
|
||||
├── mcp_server/
|
||||
│ └── server.py # MCP tools + resources (10 tools)
|
||||
│ └── server.py # MCP tools + resources (15 tools)
|
||||
├── models/
|
||||
│ ├── call.py # Call state models
|
||||
│ ├── call_flow.py # IVR tree models
|
||||
@@ -139,7 +139,7 @@ hold-slayer/
|
||||
│ ├── device.py # Device models
|
||||
│ └── contact.py # Contact models
|
||||
├── db/
|
||||
│ └── database.py # SQLAlchemy async (PostgreSQL/SQLite)
|
||||
│ └── database.py # SQLAlchemy async (PostgreSQL + Alembic)
|
||||
└── tests/
|
||||
├── test_audio_classifier.py # 18 tests — waveform analysis
|
||||
├── test_call_flows.py # 10 tests — call flow models
|
||||
@@ -283,7 +283,7 @@ claude mcp add hold-slayer --transport http http://localhost:8000/mcp/ \
|
||||
--header "Authorization: Bearer $API_TOKEN"
|
||||
```
|
||||
|
||||
It exposes 14 tools and 3 resources (`gateway://status`,
|
||||
It exposes 15 tools and 3 resources (`gateway://status`,
|
||||
`gateway://call-flows`, `gateway://active-calls`):
|
||||
|
||||
| Tool | Description |
|
||||
@@ -302,6 +302,7 @@ It exposes 14 tools and 3 resources (`gateway://status`,
|
||||
| `create_call_flow` | Store a new IVR call flow |
|
||||
| `get_call_summary` | Stored summary and action items for a call |
|
||||
| `search_call_history` | Search past calls by number or intent |
|
||||
| `learn_call_flow` | Build/refine a reusable IVR flow from an exploration call |
|
||||
|
||||
## How It Works
|
||||
|
||||
@@ -352,7 +353,7 @@ All configuration is via environment variables (see `.env.example`):
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Python 3.13** + **asyncio** — Single-process async architecture
|
||||
- **Python 3.12+** + **asyncio** — Single-process async architecture
|
||||
- **FastAPI** — REST API + WebSocket server
|
||||
- **SvelteKit** — Dashboard UI (built static, served by FastAPI at `/dashboard`)
|
||||
- **Sippy B2BUA** — SIP call control and DTMF
|
||||
@@ -360,7 +361,7 @@ All configuration is via environment variables (see `.env.example`):
|
||||
- **Speaches** (Whisper) — Speech-to-text
|
||||
- **Rhema** (Kokoro) — Text-to-speech (OpenAI-compatible `/v1/audio/speech`)
|
||||
- **Ollama / vLLM / OpenAI** — LLM for IVR menu analysis and receptionist intent capture
|
||||
- **SQLAlchemy** — Async database (PostgreSQL or SQLite)
|
||||
- **SQLAlchemy + Alembic** — Async database (PostgreSQL; schema managed by migrations)
|
||||
- **MCP (Model Context Protocol)** — AI assistant integration
|
||||
|
||||
## Documentation
|
||||
@@ -384,7 +385,7 @@ Full documentation is in [`/docs`](docs/README.md):
|
||||
|
||||
- [x] Extract EventBus to dedicated module with typed filtering
|
||||
- [x] Implement Sippy B2BUA SIP engine (signaling, DTMF, bridging)
|
||||
- [x] Implement PJSUA2 media pipeline (conference bridge, audio tapping, recording)
|
||||
- [x] PJSUA2 media pipeline contract (conference bridge, audio tapping, recording) — runs in stub mode until `pjsua2` bindings are installed
|
||||
- [x] Call manager with active call state tracking
|
||||
- [x] Gateway orchestrator wiring all components
|
||||
|
||||
@@ -400,18 +401,18 @@ Full documentation is in [`/docs`](docs/README.md):
|
||||
|
||||
- [x] REST API — calls, call flows, devices, DTMF
|
||||
- [x] WebSocket real-time event streaming
|
||||
- [x] MCP server with 14 tools + 3 resources, mounted at `/mcp/` (streamable HTTP)
|
||||
- [x] MCP server with 15 tools + 3 resources, mounted at `/mcp/` (streamable HTTP)
|
||||
- [x] Notification service (WebSocket + SMS)
|
||||
- [x] Service wiring in main.py lifespan
|
||||
|
||||
### Phase 4: Production Hardening 🚧
|
||||
|
||||
- [ ] Alembic database migrations
|
||||
- [x] Alembic database migrations (baseline + upgrade-on-boot)
|
||||
- [x] API authentication — static bearer token across REST/WS/MCP
|
||||
- [x] Emergency-number guard + concurrent-call cap on outbound calls
|
||||
- [ ] Rate limiting on API endpoints
|
||||
- [ ] Structured JSON logging
|
||||
- [ ] Health check endpoints for all dependencies
|
||||
- [x] Honest /health — engine mode, DB ping, trunk registration, STT/TTS availability
|
||||
- [ ] Graceful degradation (classifier works without STT, etc.)
|
||||
- [ ] Docker Compose (Hold Slayer + PostgreSQL)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user