M4: voice pipeline (software-only telephony)
- AudioSocket server (asyncio TCP, Asterisk wire protocol) bridging calls into the same M2 agent used by chat - Call session engine: greeting → energy-VAD utterance collection → STT → agent turn → TTS playback with barge-in (120ms of caller speech stops playback); inactivity + max-duration guards; unintelligible audio reaches the agent as '[nerazumljivo]' so the two-attempt rule stays in the prompt - STTProvider (faster-whisper, lang hint 'sr', GPU/CPU via env) and TTSProvider (Azure Neural raw-8k PCM / ElevenLabs Flash) + test fakes - Recording (mixed caller+agent WAV), transcripts, per-turn latency trace, metering via record_agent_call (§12) - Internal dialplan API: /internal/calls/register (ring targets computed from working hours + ring settings §5.2), /answered (human outcome, no minutes), /hangup (abandoned → missed-call SMS §5.5); shared-token auth - Asterisk config generator (pjsip.conf + extensions.conf from DB): worker endpoints, per-tenant test-caller endpoint, register→Dial→AudioSocket dialplan with h-extension reporting — validated against a real Asterisk 20 container (modules, dialplan, endpoints all load) - docker-compose 'voice' profile (voice server + Asterisk), Dockerfile.voice, docs/VOICE_TESTING.md softphone runbook - 16 new tests incl. full fake-call e2e and a real-TCP AudioSocket wire test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
69
docs/VOICE_TESTING.md
Normal file
69
docs/VOICE_TESTING.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# M4 — Software-only voice testing (no GSM hardware)
|
||||
|
||||
Everything here runs on a laptop/DC per §16: desktop/mobile softphones register
|
||||
directly to Asterisk over IP; the GSM leg is the only thing not exercised
|
||||
(that's M5 + the joint hardware session).
|
||||
|
||||
## 1. Prereqs
|
||||
|
||||
- Postgres running, schema migrated, demo tenant seeded:
|
||||
```bash
|
||||
.venv/bin/alembic upgrade head
|
||||
.venv/bin/python -m gogo.seed
|
||||
```
|
||||
- `.env` with at least `GOGO_ANTHROPIC_API_KEY` (agent) and one TTS key
|
||||
(`GOGO_AZURE_SPEECH_KEY` + region, or `GOGO_ELEVENLABS_API_KEY`).
|
||||
- Whisper model downloads on first run (`GOGO_WHISPER_MODEL=small` is fine on CPU).
|
||||
|
||||
## 2. Start the stack
|
||||
|
||||
```bash
|
||||
# backend API (internal dialplan endpoints live here)
|
||||
.venv/bin/uvicorn gogo.main:app --host 0.0.0.0 --port 8000
|
||||
|
||||
# voice pipeline (AudioSocket server on :9092)
|
||||
.venv/bin/python -m gogo.voice.server
|
||||
|
||||
# generate Asterisk config from the DB, then start Asterisk
|
||||
.venv/bin/python -m gogo.telephony.asterisk --out ./asterisk/conf \
|
||||
--backend http://127.0.0.1:8000 --voice 127.0.0.1:9092
|
||||
docker compose --profile voice up asterisk
|
||||
```
|
||||
|
||||
## 3. Softphones
|
||||
|
||||
Two SIP accounts exist per tenant after config generation:
|
||||
|
||||
| Role | Username | Password | Purpose |
|
||||
|---|---|---|---|
|
||||
| Worker | from dashboard → Telefonija → QR | generated | rings before the agent |
|
||||
| Test caller | `test-caller-salon-merima` | `test-<first 10 chars of widget key>` (in pjsip.conf) | simulates the customer |
|
||||
|
||||
Register the worker softphone (e.g. Linphone on a phone, scan the QR) and the
|
||||
test-caller softphone (e.g. Zoiper on the laptop) against the machine's IP.
|
||||
|
||||
## 4. Test scenarios
|
||||
|
||||
1. **Ring-group first (§5.2):** from the test-caller dial any number (e.g. `100`).
|
||||
The worker softphone rings for `ring_timeout_s`; answer → normal human call,
|
||||
dashboard logs outcome `javilo se osoblje`, no agent minutes metered.
|
||||
2. **Agent fallback:** dial again, don't answer → after the timeout the agent
|
||||
picks up, greets with the recording disclosure, books a request. Check:
|
||||
dashboard → Zahtjevi (new request), Pozivi (transcript + audio player),
|
||||
Potrošnja (minutes metered), owner email (proposal + .ics).
|
||||
3. **Out of hours (A.5):** set working hours to closed (dashboard → Salon),
|
||||
dial → agent answers immediately with the out-of-hours greeting.
|
||||
4. **Barge-in:** interrupt the agent mid-sentence — playback must stop and the
|
||||
agent must respond to what you said.
|
||||
5. **Abandoned call (§5.5):** dial, hang up while the worker phone still rings →
|
||||
`Pozivi` shows `prekinut` and the missed-call SMS appears in the backend log
|
||||
(console SMS provider).
|
||||
6. **Latency (§15):** `calls.trace` (call detail JSON) stores per-turn
|
||||
`stt_ms`/`llm_ms` — target < 1500 ms combined before TTS starts.
|
||||
|
||||
## 5. Automated coverage
|
||||
|
||||
`tests/test_voice.py` covers the same flows without Asterisk: VAD, the full
|
||||
call loop against fakes (booking, unclear speech, hangup, inactivity, barge-in),
|
||||
the AudioSocket wire protocol over real TCP, the dialplan internal API
|
||||
(ring targets, out-of-hours, abandoned + SMS) and config generation.
|
||||
Reference in New Issue
Block a user