Skip to content

feat(ultravox): support server-side external TTS voices via externalVoice - #6850

Open
ssgutierrez42 wants to merge 5 commits into
livekit:mainfrom
11x-ai:feat/ultravox-external-voice
Open

feat(ultravox): support server-side external TTS voices via externalVoice#6850
ssgutierrez42 wants to merge 5 commits into
livekit:mainfrom
11x-ai:feat/ultravox-external-voice

Conversation

@ssgutierrez42

@ssgutierrez42 ssgutierrez42 commented Aug 13, 2026

Copy link
Copy Markdown

Summary

  • Add external_voice parameter to RealtimeModel to pass an external TTS provider configuration (e.g. ElevenLabs, Cartesia, MiniMax) through Ultravox's /calls API via the externalVoice field
  • When external_voice is provided, the externalVoice key is sent in the /calls payload instead of voice, keeping the model in voice mode so native barge-in is preserved
  • The parameter accepts dict[str, Any] to accommodate all provider variants without coupling to Ultravox's evolving schema

Why

Ultravox's output_medium="text" path (text-out half-cascade) cannot support barge-in — the plugin's interruption signal (playback_clear_buffer) only fires in voice mode, and Ultravox exposes no input-side speech-onset event. Server-side externalVoice solves this by having Ultravox call the TTS provider directly, keeping the session in voice mode with working interruption.

Changes

  • _UltravoxOptions: added external_voice: NotGivenOr[dict[str, Any]]
  • RealtimeModel.__init__: added external_voice keyword argument (defaults to NOT_GIVEN), documented as mutually exclusive with voice
  • /calls payload construction: voice is now conditional — when external_voice is given, externalVoice is sent instead

References

image

@ssgutierrez42
ssgutierrez42 requested a review from a team as a code owner August 13, 2026 21:44
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@longcw longcw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me. something nit:

voice : str | UltravoxVoice
The voice to use for TTS.
external_voice : dict[str, Any], optional
The Ultravox configuration for an external TTS provider. Mutually exclusive with ``voice``.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we log a warning when both voice and external_voice are given?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

external_voice cannot work with output_medium="text" too?

"selectedTools": parse_tools(list(self._tools.function_tools.values())),
}

if is_given(self._realtime_model._opts.external_voice):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we skip a given but empty external_voice or raise an error, or fallback to voice?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants