Repository navigation
Let the OpenAI provider speak, so a cascade can use it as its speaking leg - #272
Merged
Merged
Conversation
…g leg OpenAIClientProvider had no text-to-speech client and listed no voices, although its docs say it supports text-to-speech. A cascaded realtime deployment whose speaking leg was an OpenAI model (gpt-4o-mini-tts) failed as the session opened, "The provider 'OpenAI' does not support text-to-speech", and the voice picker was empty for that leg (CrestApps#263). - GetTextToSpeechClientAsync returns the SDK's client (AudioClient.AsITextToSpeechClient), which streams where the model can and answers in one piece for tts-1 and tts-1-hd. Like speech-to-text, a missing deployment name falls back to the connection's TextToSpeechDeploymentName. - A cascade asks its speaking leg for Pcm{rate}, Azure Speech's naming. OpenAI names the same audio "pcm" and refuses a name it does not know, and it produces raw PCM at 24 kHz only. OpenAITextToSpeechFormatClient translates Pcm24000 to pcm and refuses raw PCM at any other rate, which would play at the wrong speed. Every other format name is passed on unchanged. - GetSpeechVoicesAsync lists the model's voices (OpenAITextToSpeechVoices): thirteen for gpt-4o-mini-tts, nine for tts-1 and tts-1-hd, per OpenAI's text-to-speech guide. Tests: 8 in OpenAITextToSpeechTests. A loopback stand-in for OpenAI checks the request a cascade's Pcm24000 produces (response_format pcm, the voice, the model) and that the streamed audio comes back as audio/l16, the raw PCM the cascade plays; three rates are refused before any request; the two voice lists; a missing deployment name. Without the translation the request test and the refused rates fail. Verified on live Telnyx calls through CrestApps.OrchardCore's AI voice agent, on a cascade of gpt-4o-transcribe, gpt-4.1-mini and gpt-4o-mini-tts: the session opened 1.6-1.7 s after the answer, the opening, the answer and the goodbye were spoken, and the profile's voice picker listed the thirteen voices. Fixes CrestApps#263
This was referenced Oct 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #263
The OpenAI provider speaks, so a cascaded realtime deployment can use an OpenAI model such as
gpt-4o-mini-ttsas its speaking leg, and the voice picker lists that leg's voices.Changes
OpenAIClientProvider.GetTextToSpeechClientAsyncreturns the SDK's client (AudioClient.AsITextToSpeechClient(), Microsoft.Extensions.AI.OpenAI):gpt-4o-mini-tts), and answers in one piece fortts-1/tts-1-hd;TextToSpeechDeploymentName.OpenAITextToSpeechFormatClient(internal) handles the one name the cascade and OpenAI disagree on:Pcm{rate}, Azure Speech's naming (CascadedRealtimeClient.FormatAudioFormat). OpenAI calls the same audiopcm, refuses a name it does not know, and produces raw PCM only at 24 kHz;Pcm24000is sent aspcm, and raw PCM at any other rate is refused with a clear message, since it would play at the wrong speed;OpenAIClientProvider.GetSpeechVoicesAsynclists the model's voices (OpenAITextToSpeechVoices), per OpenAI's text-to-speech guide:gpt-4o-mini-tts;tts-1andtts-1-hd.providers/openai.md: the voices, the raw PCM rate, andgpt-4o-mini-ttsin the models table;Tests
OpenAITextToSpeechTests, 8:Pcm24000produces:response_format: pcm, the voice and the model. The streamed audio comes back asaudio/l16, the raw PCM the cascade plays.Pcm8000,Pcm16000andpcm48000are refused before any request.Without the format translation, the request test and the three refused rates fail.
The full test project passes on our fork (4,665 passed). The only 2 failures there are the known ones under a Greek locale (
EmbeddedResourceAIProfileTemplateProviderTests,OpenXmlIngestionDocumentReaderTests), and they fail without this change too. Onmainthe files this change touches are the same as on our fork, except for an unrelated change of ours toGetRealtimeClientAsync.Verified on live calls
The calls ran through CrestApps.OrchardCore's AI voice agent on Telnyx, with a cascade of
gpt-4o-transcribe,gpt-4.1-miniandgpt-4o-mini-tts.Not in this change
gpt-4o-mini-ttsspoke at about 145–160 words a minute on our calls, against 175–245 forgpt-realtime. A trial withspeed1.2 and a short styleinstructionsbrought it to 170–220, which a caller judged a normal pace. The cascade passes neither today. That would be a separate change; I can open an issue to discuss where such a setting belongs.🤖 Generated with Claude Code