Skip to main content

Voice Runtime

Configure TTS (text-to-speech), STT (speech-to-text), and VAD for voice-enabled agents.

Wunderland uses the shared AgentOS speech runtime — a provider-agnostic abstraction layer for all voice capabilities. Configure once, swap providers freely.


Quick Setup

The fastest path to voice is through the setup wizard:

wunderland setup
# Voice is included in both QuickStart and Advanced modes

If you chose OpenAI as your LLM provider, voice is zero extra config — your existing OPENAI_API_KEY covers both TTS and Whisper STT.

Manual Configuration

# Set provider via config
wunderland config set voiceProvider openai
wunderland config set voiceModel tts-1
wunderland config set voiceVoice nova
wunderland config set sttProvider openai-whisper
wunderland config set sttModel whisper-1

# Or set environment variables
export OPENAI_API_KEY=sk-... # OpenAI TTS + Whisper STT
export ELEVENLABS_API_KEY=... # ElevenLabs TTS
export DEEPGRAM_API_KEY=... # Deepgram STT
export WHISPER_LOCAL_BASE_URL=http://127.0.0.1:8080/v1 # Local OpenAI-compatible STT

Supported Providers

TTS (Text-to-Speech)

ProviderModelsVoicesKey RequiredNotes
OpenAI TTStts-1, tts-1-hd, gpt-4o-mini-ttsnova, alloy, echo, onyx, fable, shimmerOPENAI_API_KEYStreaming, fast, good quality
ElevenLabseleven_turbo_v2_5, eleven_multilingual_v2, eleven_monolingual_v1Custom + clonedELEVENLABS_API_KEYVoice cloning, 29 languages
PiperONNX models (lessac, amy, alan, etc.)Model-dependentNoneFree, offline, local
macOS SaySystem voicesSystem-dependentNoneBuilt-in, no install needed
Coqui TTSVariousModel-dependentNoneOpen-source, local
Azure TTSNeural voices400+ voicesAZURE_SPEECH_KEYEnterprise-grade

STT (Speech-to-Text)

ProviderModelKey RequiredNotes
OpenAI Whisperwhisper-1OPENAI_API_KEYBatch transcription, word timestamps
Deepgramnova-2DEEPGRAM_API_KEYReal-time streaming, punctuation, diarization
Whisper.cppbase, small, medium, large-v3NoneFree, offline, local
Azure STTVariousAZURE_SPEECH_KEYEnterprise-grade, real-time
VoskVariousNoneOffline, lightweight

VAD (Voice Activity Detection)

ProviderNotes
Silero VADNeural network-based, highly accurate
WebRTC VADBuilt-in, low latency
Energy-basedSimple threshold detection

Callable Voice Tools

Beyond the library API and CLI, Wunderland agents can call TTS and STT as tools during conversations. The voice-synthesis extension pack exposes two tools that any agent can invoke mid-turn.

text_to_speech Tool

Converts text to audio. Auto-detects the best available provider from API keys.

Provider resolution order: OPENAI_API_KEY > ELEVENLABS_API_KEY > Ollama (local fallback)

{
"text": "Hello from your Wunderbot",
"voice": "nova",
"model": "tts-1-hd",
"provider": "auto",
"format": "mp3"
}

Returns audioBase64 (base64-encoded audio), contentType, provider, voice, and durationEstimateMs.

Voices by provider:

ProviderAvailable Voices
OpenAIalloy, echo, fable, onyx, nova (default), shimmer
ElevenLabsrachel (default), domi, bella, antoni, josh, arnold, adam, sam, or any custom voice ID
OllamaDepends on loaded model

speech_to_text Tool

Transcribes audio using OpenAI Whisper, Deepgram, or a local OpenAI-compatible Whisper runtime. Accepts base64 audio or a fetchable URL.

{
"audioBase64": "UklGRi...",
"provider": "auto",
"language": "en",
"model": "whisper-1",
"responseFormat": "verbose_json"
}

Returns text, provider, model, language, durationSeconds, and optional segments with word-level timestamps or utterance groupings.

Provider resolution order: OPENAI_API_KEY > DEEPGRAM_API_KEY > explicitly configured WHISPER_LOCAL_BASE_URL

Example: Agent Uses TTS in Conversation

User: Can you read this summary aloud?
Agent: Sure — let me synthesize the audio.
[calls text_to_speech { text: "Q3 revenue grew 12%...", voice: "nova" }]
Agent: Here's the audio version of the summary.
[returns audio/mpeg base64 payload to client]

The client receives the base64 audio in the tool result and can play it directly or save it.

Example: HTTP API with Audio Response

# Start your agent server
wunderland start

# Send a message that triggers TTS
curl -X POST http://localhost:3777/chat \
-H "Content-Type: application/json" \
-d '{"message": "Read the last paragraph aloud using the shimmer voice"}'

The agent calls text_to_speech internally. The response reply field contains the agent's text, and the tool result (with audioBase64) is available in the conversation context.

Configuring Provider Defaults

Set provider defaults via the CLI:

# Configure the voice-synthesis extension
wunderland extensions configure voice-synthesis

# Or set defaults directly
wunderland extensions set-default tts openai
wunderland extensions set-default stt openai

In agent.config.json, use providerDefaults to pin provider preferences:

{
"providerDefaults": {
"tts": "elevenlabs",
"stt": "openai"
},
"extensions": ["voice-synthesis"]
}

Or set the TTS_PROVIDER environment variable globally:

export TTS_PROVIDER=elevenlabs
export STT_PROVIDER=deepgram

Library API

Enable Voice in Your App

import { createWunderland } from 'wunderland';

const app = await createWunderland({
llm: { providerId: 'openai' },
extensions: {
voice: ['speech-runtime'],
},
});

Text-to-Speech

const session = app.session();

// Simple synthesis
const audio = await session.speech.synthesize('Hello from Wunderland');

// With options
const audio = await session.speech.synthesize('Hello!', {
provider: 'openai',
model: 'tts-1-hd',
voice: 'nova',
speed: 1.0,
format: 'mp3',
});

// Streaming synthesis
for await (const chunk of session.speech.synthesizeStream('A longer response...')) {
process.stdout.write(chunk);
}

Speech-to-Text

// Transcribe a file
const transcript = await session.speech.transcribe('./audio.wav');
console.log(transcript.text);

// With word timestamps
const result = await session.speech.transcribe('./audio.wav', {
provider: 'openai-whisper',
model: 'whisper-1',
timestamps: true,
language: 'en',
});

for (const word of result.words) {
console.log(`${word.start}s - ${word.end}s: ${word.text}`);
}

Full Voice Loop

// Listen → Transcribe → Process → Speak
const session = app.session();

// Start listening (VAD-aware)
session.speech.startListening({
vad: 'silero',
onSpeechStart: () => console.log('User speaking...'),
onSpeechEnd: async (audio) => {
const transcript = await session.speech.transcribe(audio);
const response = await session.sendText(transcript.text);
await session.speech.synthesize(response.text);
},
});

CLI Commands

# Check provider readiness
wunderland voice status

# List TTS providers and their configuration state
wunderland voice tts

# List STT providers and their configuration state
wunderland voice stt

# Synthesize a test phrase through your configured TTS
wunderland voice test "Hello from Wunderland"

# Voice cloning guidance (ElevenLabs)
wunderland voice clone

Example Output

$ wunderland voice status

Voice Runtime Status
────────────────────
TTS Provider: openai (tts-1, voice: nova) ✓ ready
STT Provider: openai-whisper (whisper-1) ✓ ready
VAD: silero ✓ loaded
Telephony: not configured

Configuration Reference

Config File (~/.wunderland/config.json)

{
"voiceProvider": "openai",
"voiceModel": "tts-1",
"voiceVoice": "nova",
"sttProvider": "openai-whisper",
"sttModel": "whisper-1"
}

Per-Agent Config (agent.config.json)

{
"voice": {
"tts": {
"provider": "elevenlabs",
"model": "eleven_turbo_v2_5",
"voiceId": "custom-clone-id"
},
"stt": {
"provider": "deepgram",
"model": "nova-2"
}
}
}

Environment Variables

# TTS
OPENAI_API_KEY=sk-... # OpenAI TTS + Whisper
ELEVENLABS_API_KEY=... # ElevenLabs TTS
OPENAI_TTS_DEFAULT_MODEL=tts-1 # Override TTS model
OPENAI_TTS_DEFAULT_VOICE=nova # Override default voice

# STT
DEEPGRAM_API_KEY=... # Deepgram STT
WHISPER_MODEL_DEFAULT=base # Whisper.cpp model size

# Azure (if used)
AZURE_SPEECH_KEY=... # Azure Speech Services
AZURE_SPEECH_REGION=eastus # Azure region

Provider Selection Guide

Cloud vs Local

CriterionCloud (OpenAI/ElevenLabs)Local (Piper/Whisper.cpp)
Latency~200-500ms network RTT~50-200ms (hardware-dependent)
QualityHighestGood (model-dependent)
PrivacyData sent to providerFully private
CostPer-token/characterFree
OfflineNoYes
SetupAPI key onlyBinary install + model download

Best quality (cloud):

  • TTS: ElevenLabs eleven_turbo_v2_5 — natural, expressive, cloning
  • STT: OpenAI Whisper whisper-1 — accurate, handles accents well

Best value (cloud):

  • TTS: OpenAI tts-1 — fast, 6 voices, reuses your LLM key
  • STT: OpenAI Whisper whisper-1 — same key

Fully offline:

  • TTS: Piper en_US-lessac-medium — free, fast, decent quality
  • STT: Whisper.cpp small — free, good accuracy/speed balance

Hybrid:

  • TTS: ElevenLabs (production) + Piper (dev/testing fallback)
  • STT: Deepgram nova-2 (real-time) + Whisper local (batch)

Voice Cloning (ElevenLabs)

ElevenLabs supports voice cloning from audio samples:

# Get guidance on cloning setup
wunderland voice clone

Requirements:

  • ElevenLabs Professional plan or higher
  • Clean audio samples (30 seconds minimum, 3+ minutes recommended)
  • No background noise or music

Once cloned, use the voice ID in your config:

{
"voiceProvider": "elevenlabs",
"voiceModel": "eleven_turbo_v2_5",
"voiceVoice": "your-cloned-voice-id"
}

Troubleshooting

"No voice provider configured"

Run wunderland setup or set config manually:

wunderland config set voiceProvider openai

"TTS synthesis failed"

  1. Check API key: echo $OPENAI_API_KEY
  2. Test connectivity: wunderland doctor
  3. Try a different model: wunderland config set voiceModel tts-1

"STT transcription returned empty"

  1. Verify audio format (WAV, MP3, M4A supported)
  2. Check audio isn't silent: play it back locally
  3. Try a larger Whisper model: wunderland config set sttModel small

High Latency

  1. Use streaming endpoints where available (tts-1 supports streaming)
  2. For local: use smaller models (base instead of large-v3)
  3. Check network: wunderland doctor tests provider connectivity

Streaming Voice Pipeline — Provider Options

The streaming voice pipeline (VoicePipelineOrchestrator) forwards sttOptions and ttsOptions to providers as providerOptions. This enables provider-specific features without changing core interfaces.

Deepgram STT Options

const orchestrator = new VoicePipelineOrchestrator({
stt: 'deepgram-streaming',
tts: 'elevenlabs-streaming',
sttOptions: {
sentiment: true, // Per-utterance sentiment (positive/negative/neutral)
smart_format: true, // Auto-punctuation, capitalization, number formatting
diarize: true, // Speaker identification labels
utterance_end_ms: 1000, // Server-side silence endpoint (ms)
keywords: [ // Keyword boosting (name:weight)
'Gideon:2',
'fireball:1.5',
],
},
});
OptionTypeEffect
sentimentbooleanReturns TranscriptEvent.sentiment with label + confidence
smart_formatbooleanAuto-punctuates, capitalizes, formats numbers
diarizebooleanLabels speaker: 0, speaker: 1 per word
utterance_end_msnumberServer-side silence detection (supplements client heuristic)
keywordsstring[]Boosts recognition of specific terms (name:weight format)

Sentiment in transcripts: When sentiment: true is enabled, each TranscriptEvent includes:

event.sentiment = {
label: 'positive' | 'negative' | 'neutral',
confidence: 0.95,
};

ElevenLabs TTS Expressiveness

const orchestrator = new VoicePipelineOrchestrator({
stt: 'deepgram-streaming',
tts: 'elevenlabs-streaming',
ttsOptions: {
stability: 0.3, // 0.0-1.0: lower = more expressive intonation
similarityBoost: 0.75, // 0.0-1.0: voice clone fidelity
style: 0.6, // 0.0-1.0: style exaggeration
useSpeakerBoost: true, // Clarity enhancement
speed: 0.85, // 0.1-5.0: speaking rate
},
});
OptionRangeDefaultEffect
stability0.0-1.00.5Intonation variability
similarityBoost0.0-1.00.75Voice clone fidelity
style0.0-1.00.0Exaggeration of the voice's natural style
useSpeakerBoostbooleantrueClarity filter
speed0.1-5.01.0Speaking rate multiplier

These are sent in the ElevenLabs WebSocket BOS message as voice_settings and generation_config.speed. Change ttsOptions between turns for per-utterance expressiveness modulation (mood-reactive voices, character-specific delivery).

ElevenLabs Style Directives

ElevenLabs v2 models interpret parenthetical directives in the text:

// These affect synthesis delivery
"(whispering) The door creaks open..." // Quiet, breathy
"(shouting) RUN!" // Loud, intense
"(excitedly) I found it!" // Upbeat delivery
"(sadly) She's gone." // Somber tone
"(angrily) How dare you!" // Aggressive delivery
"(in a hushed, tense voice) Something moved in the dark..."

Prepend these to the TTS input text based on scene context, character emotion, or game state.


Next Steps