Repository navigation
fix(gradium): stop voice_id default from overriding an explicit voice (1/2) - #7813
Conversation
`voice_id` defaulted to a fixed id and the setup message sent both `voice` and `voice_id` whenever set. The server resolves `voice_id` to an embedding first and only falls back to the `voice` name when there is none, so `TTS(voice="...")` and `update_options(voice=...)` silently kept the default voice. Default both to None, apply the default id only when neither is given, reject passing both, and have update_options clear the other selector. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
283af3e to
b93b771
Compare
| if voice is None and voice_id is None: | ||
| voice_id = DEFAULT_VOICE_ID |
There was a problem hiding this comment.
🟡 Explicit null selects the catalog voice
With voice_id=None and no voice, TTS substitutes Harper's ID instead of omitting both selectors. The provider uses Harper rather than its model-default voice.
Learn more
A Gradium setup message includes voice_id only when its stored value is not None in ChunkedStream._run and SynthesizeStream._run_ws. Previously, passing voice_id=None with no name left both selectors absent, allowing the provider to choose its model voice. The new fallback treats an explicit None exactly like an omitted argument and inserts the catalog ID.
Example: An agent constructs TTS(api_key="...", voice_id=None) to use its deployed model voice. The setup now sends voice_id="4SZHfMpw-p46Ywgs" and selects Harper instead.
Recommended fix: Distinguish an omitted voice_id from explicit None using a sentinel default, and apply DEFAULT_VOICE_ID only for the omitted case when voice is absent. Keep explicit None as an absent selector in both setup messages.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
cc @longcw |
Summary
TTS(voice="...")was silently ignored.voice_iddefaulted to a fixed id and the setup message sent bothvoiceandvoice_idwhenever set. The Gradium API resolvesvoice_idto an embedding first and only falls back to thevoicename when there is no embedding, so an explicitvoicenever reached the model, andupdate_options(voice=...)had the same problem.voiceandvoice_idboth default toNone; the previous default id is applied only when neither is given (DEFAULT_VOICE_ID).update_options(voice=...)clearsvoice_idand vice versa.update_optionsnow raisesValueError. It used to be accepted, withvoicediscarded.What each default means
voice_idisDEFAULT_VOICE_ID, the catalog voice "Harper", unchanged from before. feat(gradium): default the TTS voice to Everly #7874 (2/2), stacked on this PR, changes that default to Everly.voice="default": the embedding packaged with the TTS model itself. This is a different speaker from Harper, and it is the onlyvoicename the public API can reach today, since other names must be loaded into the worker directly.voice_idis the right selector for catalog and custom voices.Testing
tests/test_plugin_gradium_tts_voice.py(unit category, 7 tests) covering the default, each selector alone, the exclusivity error, and bothupdate_optionsdirections.ruff,ruff format --checkandmypy --strictclean on the plugin.api.gradium.ai: onmain,TTS(voice="does-not-exist")synthesizes normally because the defaultvoice_idwins; on this branch the server rejects it. Until the error-surfacing change in fix(gradium): surface TTS server errors, close codes and transport errors (1/2) #7672 lands, that rejection reads as the generic "no audio frames were pushed" rather than the server's own message.Split out of #7672.
🤖 Generated with Claude Code