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#

ProviderExample IdentifierVoice Reference
azureen-AU-WilliamNeuralAzure Voice Gallery
cartesia6f84f4b8-58a2-430c-8c79-688dad597532Cartesia Voices
custommy_custom_voiceCustom Voices
elevenlabs21m00Tcm4TlvDq8ikWAMElevenLabs Voice Library
fish_audiod13f84b987ad4f22b56d2b47f4eb838eFish Audio Discovery
google_chirplaomedeiaGoogle Chirp HD
gradiumYTpq7expH9539ERJGradium Voice Library
inworldAlexInworld Platform
minimaxEnglish_Aussie_BlokeMiniMax Voices
openainovaOpenAI TTS
qwen3SerenaQwen TTS
xaievexAI 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.

ProviderDefault modelAlso accepted
elevenlabseleven_flash_v2_5eleven_turbo_v2_5, eleven_multilingual_v2, eleven_v3
openaigpt-4o-mini-ttstts-1, tts-1-hd, tts-1-1106, tts-1-hd-1106
cartesiasonic-3 (relay), sonic-3.5 (agent runtime)sonic-3.5, sonic-3, sonic-2, sonic-turbo, sonic-latest
geminigemini-2.5-flash-preview-ttsgemini-2.5-pro-preview-tts, gemini-3.1-flash-tts-preview
qwen3qwen3-tts-flash-realtime-2025-11-27qwen3-tts-vc-realtime-2026-01-15 (selected automatically for cloned voices)
minimaxspeech-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_audios2-pro (relay), fish-audio-s2 (agent runtime)s1
inworldinworld-tts-1.5-miniinworld-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.

FeatureOpenAIAzureGoogleGeminiCartesiaElevenLabsFish AudioGradiumInworldMiniMaxQwen3xAI Grok
Streaming (default)YesNoYesNoYesYesYesYesYesYesYesNo
Voice InstructionsYesNoNoNoNoNoNoNoNoNoYesInline tags
Model OverrideYesNoNoYesYesYesYesNoYesYesYesNo
LanguageNoYesYesNoYesv2_5 onlyNoNoYesYesYesYes
Speed ControlYes (unclamped)Yes (0.5-3x)Yes (0.25-2x)NoYes (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)
CachingYesYesYesn/aYesYesYesYesYesYesYesYes
Interruptible ControlYesYesYesn/aYesYesYesYesYesYesYesYes

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.

voicesttsaudio