Building an Automated Voice AI Testing Framework for Welcomr.ai

How we built a scenario-driven test runner in Go that replays pre-recorded human speech into live agent endpoints and validates entire conversation flows, turn by turn


The Problem

Welcomr.ai is a multi-tenant Agentic Voice AI platform for hospitality — guests call or connect via web widget and speak to an AI concierge that handles bookings, room service, and inquiries in real-time. The system supports two voice channels: WebRTC (browser-based, Opus codec at 48kHz) and Twilio (phone calls, mulaw codec at 8kHz).

Testing this manually meant someone had to actually call the agent, speak scripted lines, and verify responses — every time we pushed a change. That doesn't scale. We needed automated end-to-end conversation testing that exercises the full pipeline: audio encoding → network transport → speech-to-text → LLM reasoning → tool execution → text-to-speech → audio playback.

The Approach

We built a scenario-driven test runner in Go that replays pre-recorded human speech into live agent endpoints and validates the entire conversation flow, turn by turn.

1. Scenario Design

Each test scenario is a folder containing:

Our first scenario: a 6-turn hotel room booking in English — check availability, ask for the most expensive room (with barge-in interruption), confirm, provide personal details across two turns, and say goodbye.

2. The Test Runner Architecture

Orchestrator
  ├── Loads scenario + turn metadata
  ├── Creates channel-specific client (WebRTC or Twilio)
  ├── For each turn:
  │     ├── Waits for configured delay
  │     ├── Streams pre-recorded audio (Opus RTP or base64 mulaw)
  │     ├── Waits for agent response completion
  │     └── Captures STT transcript, agent response, events
  └── Runs assertions against captured data

The key insight: both clients implement the same ConversationClient interface, so the orchestrator is channel-agnostic. The protocol differences are encapsulated:

3. Hard Problems We Solved

VAD end-of-speech detection

The agent's Voice Activity Detector needs silence after speech to know the user stopped talking. A recording file just... ends. No silence follows. The VAD never fires speech_ended. Fix: append 1.5–2 seconds of encoded silence frames after each recording.

Barge-in (interruption) testing

Turn 2 interrupts the agent mid-sentence. The orchestrator uses WaitForAgentStart to detect the agent has begun speaking (via tts_playback started on WebRTC, first outbound media event on Twilio), then immediately streams the next turn's audio. The client handles the cascade of interrupted/completed events without leaking stale results into subsequent turns.

Multi-sentence response detection

The agent's response to "what rooms are available?" can be 5 sentences long. On WebRTC, we track tts_started.total_sentences and count tts_playback completed events. On Twilio, we use a settling-timeout approach — wait for mark events, and when none arrive for 10 seconds, consider the response complete.

Audio loudness

Raw recordings from a phone were too quiet (−34 dB) for the STT energy threshold (400k). We added EBU R128 loudness normalization via ffmpeg's loudnorm filter, boosting to −16 dB mean without clipping.

Recording at the right fidelity

The agent's STT model (gpt-4o-transcribe) works with resampled audio internally, but the transport must match the channel exactly — Opus at 48kHz for WebRTC (the browser codec), mulaw at 8kHz for Twilio (telco standard). Same human speech, two encodings, validated against the same assertions.

4. What We Test

Per turn:

Across the scenario:

5. Results

The framework runs as a standard go test with flags:

go test ./tests/conversations/... -args -channel=both -target=local

Our hotel booking scenario: 6/6 turns passing on both WebRTC and Twilio. The full run takes ~2.5 minutes per channel (real-time audio streaming + agent processing). It exercises: audio codec encoding, WebRTC/Twilio transport, speech-to-text, LLM reasoning with tool calling (MCP protocol to a hotel PMS), text-to-speech, and audio playback — all automated, no human in the loop.

The Bigger Picture

This isn't unit testing. It's not mocking. It's streaming real audio over real protocols to a real agent, and validating that a human-like conversation flows correctly end to end. It catches:

The scenarios are extensible — adding a new test is recording yourself speaking, writing a YAML file, and running encode.sh.