Text to Speech
VoiceRun supports a wide variety of voices across multiple TTS providers including OpenAI, Azure, Google (Chirp 3 HD and Gemini), Cartesia, ElevenLabs, Fish Audio, Gradium, Inworld, MiniMax, Qwen3, xAI Grok, and more.
Explore Available Voices#
Use TTS Lab to browse, preview, and test provider-native voice IDs before adding them to an agent. The Explorer tab lets you enter custom text, choose a provider, generate previews, and compare time-to-first-audio across multiple voices.
The Benchmarks tab shows production TTS latency by provider and model. It reports p50/p95 time-to-first-audio, p50/p95 total generation duration, and sample counts for selectable time ranges. Benchmark data is global across all agents because the underlying TTS histograms do not include organization or agent labels.
Voice Usage#
To use a voice in your agent, specify the voice name in a TextToSpeechEvent:
yield TextToSpeechEvent( text="Hello, this is a sample message!", voice="rachel" )
A bare voice name is resolved to a provider for you — rachel is an ElevenLabs voice,
so this routes to ElevenLabs with that provider's default model.
Resolution walks the providers in a fixed order and takes the first list the name
appears in: custom voices, Azure, OpenAI, Google Chirp, Cartesia, MiniMax, Fish Audio,
Gradium, Inworld, ElevenLabs, xAI, Qwen3. Because ElevenLabs is checked late, a
name it shares with an earlier provider resolves to that earlier one — sarah,
charlotte, lily, callum, clyde and daniel all go to Cartesia, and liam goes
to Azure, even though ElevenLabs also has voices by those names. A name that matches no
provider falls through to OpenAI's default voice.
To pin the provider regardless of name collisions, use the
TextToSpeechIdentifier form below.
Audio can be made uninterruptible by setting the interruptible flag to False:
yield TextToSpeechEvent( text="This message cannot be interrupted.", voice="rachel", interruptible=False )
Using TextToSpeechIdentifier#
If you need to use a specific voice directly from a provider, you can use TextToSpeechIdentifier instead of a voice name string. This gives you direct access to any voice from a supported provider.
yield TextToSpeechEvent( text="Hello from Azure!", voice={"provider": "azure", "identifier": "en-AU-WilliamNeural"} )
Supported Providers#
| Provider | Example Identifier | Voice Reference |
|---|---|---|
azure | en-AU-WilliamNeural | Azure Voice Gallery |
cartesia | 6f84f4b8-58a2-430c-8c79-688dad597532 | Cartesia Voices |
custom | my_custom_voice | Custom Voices |
elevenlabs | 21m00Tcm4TlvDq8ikWAM | ElevenLabs Voice Library |
fish_audio | d13f84b987ad4f22b56d2b47f4eb838e | Fish Audio Discovery |
google_chirp | laomedeia | Google Chirp HD |
gradium | YTpq7expH9539ERJ | Gradium Voice Library |
inworld | Alex | Inworld Platform |
minimax | English_Aussie_Bloke | MiniMax Voices |
openai | nova | OpenAI TTS |
qwen3 | Serena | Qwen TTS |
xai | eve | xAI Grok TTS |
fish is accepted as an alias for fish_audio in relay mode only, via
spec.tts.provider. A TextToSpeechIdentifier with "provider": "fish" raises
Invalid voice — use fish_audio from a handler.
gemini is Google's Generative Language API and is a different service from
google_chirp (Google Cloud Text-to-Speech, Chirp 3 HD). It is selectable in
relay mode only, via spec.tts.provider — it is not wired into the agent
runtime, so a TextToSpeechIdentifier with "provider": "gemini" raises
Invalid voice.
google_chirp identifiers are expanded server-side: a bare name such as
laomedeia becomes {language}-Chirp3-HD-Laomedeia (language defaults to
en-US; en maps to en-US, es to es-US, and other bare two-letter codes
to xx-XX). Any identifier that already contains a hyphen is forwarded verbatim,
so you can pass a full Cloud TTS voice name directly.
custom is a meta-provider. It resolves at runtime to the vendor recorded on
your organization's custom voice, and that vendor's default model, speed clamp,
and streaming behavior then apply. An identifier that does not match one of your
custom voices raises Invalid voice: {'provider': 'custom', 'identifier': '<name>'} at synthesis time — as does any custom identifier when your
organization has no custom voices at all.
Examples#
# Azure Neural Voice yield TextToSpeechEvent( text="G'day mate!", voice={"provider": "azure", "identifier": "en-AU-WilliamNeural"} ) # Cartesia Voice (using voice ID) yield TextToSpeechEvent( text="Hello from Cartesia!", voice={"provider": "cartesia", "identifier": "6f84f4b8-58a2-430c-8c79-688dad597532"} ) # Google Chirp Voice yield TextToSpeechEvent( text="Hello from Google!", voice={"provider": "google_chirp", "identifier": "laomedeia"} ) # OpenAI Voice yield TextToSpeechEvent( text="Hello from OpenAI!", voice={"provider": "openai", "identifier": "nova"} ) # Custom Voice yield TextToSpeechEvent( text="Hello from my custom voice!", voice={"provider": "custom", "identifier": "my_voicerun_custom_voice"} ) # Fish Audio Voice yield TextToSpeechEvent( text="Hello from Fish Audio!", voice={"provider": "fish_audio", "identifier": "d13f84b987ad4f22b56d2b47f4eb838e"} ) # Gradium Voice yield TextToSpeechEvent( text="Hello from Gradium!", voice={"provider": "gradium", "identifier": "YTpq7expH9539ERJ"} ) # Inworld Voice yield TextToSpeechEvent( text="Hello from Inworld!", voice={"provider": "inworld", "identifier": "Alex"} ) # ElevenLabs Voice yield TextToSpeechEvent( text="Hello from ElevenLabs!", voice={"provider": "elevenlabs", "identifier": "21m00Tcm4TlvDq8ikWAM"} ) # xAI Grok Voice yield TextToSpeechEvent( text="Hello from xAI Grok!", voice={"provider": "xai", "identifier": "eve"} )
Models by Provider#
Set a model with model= on TextToSpeechEvent, or with spec.tts.model in a
manifest. Model strings are pass-through: the relay forwards your value to the
vendor unchanged, and neither it nor the agent runtime checks it against an
allow-list. A model the vendor accepts therefore works whether or not it is
listed below, and a model the vendor rejects fails at synthesis time with the
vendor's own error rather than with a VoiceRun one.
Default model is the relay TTS engine's built-in default, applied when a relay-mode request omits a model. Agent (coderunner) sessions instead send the agent runtime's own default explicitly; both are listed where they differ.
Also accepted is a provenance list, not a support guarantee. It records the
model ids VoiceRun has itself exercised against the live vendor APIs in its TTS
latency benchmark, plus the few ids VoiceRun's own code picks for you (the Qwen3
voice-cloning model, OpenAI's dated tts-1 snapshots). Only the default is
pinned in VoiceRun's code — the vendor can change or retire any of the others
without notice.
| Provider | Default model | Also accepted |
|---|---|---|
elevenlabs | eleven_flash_v2_5 | eleven_turbo_v2_5, eleven_multilingual_v2, eleven_v3 |
openai | gpt-4o-mini-tts | tts-1, tts-1-hd, tts-1-1106, tts-1-hd-1106 |
cartesia | sonic-3 (relay), sonic-3.5 (agent runtime) | sonic-3.5, sonic-3, sonic-2, sonic-turbo, sonic-latest |
gemini | gemini-2.5-flash-preview-tts | gemini-2.5-pro-preview-tts, gemini-3.1-flash-tts-preview |
qwen3 | qwen3-tts-flash-realtime-2025-11-27 | qwen3-tts-vc-realtime-2026-01-15 (selected automatically for cloned voices) |
minimax | speech-02-turbo (relay), speech-2.8-turbo (agent runtime) | speech-02-hd, speech-2.6-turbo, speech-2.6-hd, speech-2.8-turbo, speech-2.8-hd |
fish_audio | s2-pro (relay), fish-audio-s2 (agent runtime) | s1 |
inworld | inworld-tts-1.5-mini | inworld-tts-1.5-max, inworld-tts-2 |
Every gemini model is a Google preview model and may change or be withdrawn
by the vendor without notice.
Pin Qwen3 to a dated snapshot. The undated qwen3-tts-flash-realtime alias can
lag on the international endpoint, and DashScope silently substitutes a different
voice for any voice missing from the resolved snapshot. Voice IDs beginning
qwen-tts-vc- are cloned voices and are routed to the voice-cloning model
automatically — do not set model for them yourself.
Language is dropped for ElevenLabs models whose id does not contain v2_5: only
the v2_5 family receives a language_code.
Providers with no model parameter#
azure, google_chirp, gradium, and xai take no model. A model you supply
for these providers is transmitted and does change the TTS cache key — so it busts
cached audio — but the synthesized audio is identical. Nothing errors; it is a
silent no-op. The strings you may see for these providers on cost rows
(azure-neural-tts, en-US-Chirp3-HD, gradium-default, grok-tts) are
billing labels, not synthesis models.
Provider Features#
"Google" below is google_chirp. gemini is relay-mode only and is listed
separately.
| Feature | OpenAI | Azure | Gemini | Cartesia | ElevenLabs | Fish Audio | Gradium | Inworld | MiniMax | Qwen3 | xAI Grok | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Streaming (default) | Yes | No | Yes | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No |
| Voice Instructions | Yes | No | No | No | No | No | No | No | No | No | Yes | Inline tags |
| Model Override | Yes | No | No | Yes | Yes | Yes | Yes | No | Yes | Yes | Yes | No |
| Language | No | Yes | Yes | No | Yes | v2_5 only | No | No | Yes | Yes | Yes | Yes |
| Speed Control | Yes (unclamped) | Yes (0.5-3x) | Yes (0.25-2x) | No | Yes (unclamped) | Yes (0.7-1.2x) | Yes (0.5-2x) | Yes (saturates at 3x) | Yes (0.5-1.5x) | Yes (unclamped) | Yes (0.5-2x) | Yes (0.7-1.5x) |
| Caching | Yes | Yes | Yes | n/a | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Interruptible Control | Yes | Yes | Yes | n/a | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
n/a marks a feature that cannot be reached for gemini: it runs in relay mode
only, and the relay has neither a TTS cache nor a TextToSpeechEvent to carry
cache or interruptible.
Streaming is a default, not a capability. Every provider streams when you set
stream=True on TextToSpeechEvent; the row records what happens when stream
is left unset. The exceptions are gemini and google_chirp, whose relay
engines call batch REST endpoints (generateContent and text:synthesize): the
whole utterance is synthesized upstream before any audio comes back, so
time-to-first-audio equals total generation time no matter what stream is set
to. The runtime still hands that audio to you as chunks — which is why Google's
Streaming (default) cell reads Yes — but streaming buys no latency for
these two.
Speed clamps are silent. An out-of-range value is clamped, not rejected, so
speed=1.5 on ElevenLabs plays at 1.2x with no error. "Unclamped" means VoiceRun
forwards your value as-is and the vendor's own limits apply — OpenAI, for example,
accepts 0.25-4x on its side.
Gradium is a special case: it is never sent a speed field. VoiceRun maps speed
to Gradium's padding_bonus as -(speed - 1) * 2, clamped to ±4, so speed=3.0
already reaches the clamp and anything above it (speed=4.0, speed=10.0) plays
at the same 3x with no error.
Caching caveat for Google Chirp: when you select a Chirp voice by bare name
(voice="laomedeia"), speed is not part of the TTS cache key, so repeating
identical text at a new speed can replay audio cached at the previous speed. Pass
cache=False when varying speed on Chirp, or use the TextToSpeechIdentifier
form ({"provider": "google_chirp", "identifier": "laomedeia"}), which always
keys on speed.
