fix: update inworld tts to current api contract - #2277
Conversation
the service now requires voiceId/modelId (voice/model return 400 "voice_id can not be empty"), nests streamed audio under result.audioContent, returns each streamed chunk as a self-contained wav, and reports bad credentials as 403. send audio_config, strip the riff header per chunk, and treat 401/403 as fatal invalid-key errors.
standalone suite covering dump, flush, params passthrough, error contracts, ttfb metrics, robustness, and state transitions, plus tests/configs for the tts guarder. guarder run: 15 passed, 2 skipped (subtitle alignment, disabled by default).
ReviewGood, well-scoped fix. The four API changes are clearly explained in the description and each one maps to a specific diff hunk, and keeping the external param names ( Two things I'd want addressed before merge, then some smaller notes. 1. Wrong env var in the new test configs (blocking)All three guarder configs use "api_key": "${env:INWORLD_TTS_API_KEY}",But everywhere else in the repo the key is 2. The fix itself has no test coverage (blocking-ish)Every one of the ~1500 new test lines patches @patch("inworld_tts_python.extension.InworldTTSClient")
def test_params_passthrough(MockInworldTTSClient):So the suite exercises the base-class plumbing (dump, flush, TTFB, state machine, error codes) but never Worth adding one focused test against
That's the part that would have caught this regression, and it needs no live credentials. 3. RIFF strip silently forwards the header on a missif chunk[:4] == b"RIFF":
data_pos = chunk.find(b"data", 12)
if data_pos != -1:
chunk = chunk[data_pos + 8 :]If Also, 4. Mixed camelCase / snake_case in the payload"voiceId": ...,
"modelId": ...,
"audio_config": {"audio_encoding": ..., "sample_rate_hertz": ...},You've verified this works, and proto-JSON gateways do accept both spellings — but relying on that leaves the request half in each style. If the endpoint accepts Smaller notes
Confirmed good
I have not run the suite or hit the live API — the notes above are from reading the diff against |
|
Guarder test result (run against the live Inworld API): The 2 skips are the subtitle-alignment tests, disabled by default for all TTS extensions. Standalone extension suite: Version bumped to 0.1.2 in both |

What
The
inworld_tts_pythonextension is currently silent for everyone: the Inworld TTS API has drifted since the extension landed (#2010) and every synthesis request now fails.Changes to
inworld_tts.py:voiceId/modelId(the previousvoice/modelare rejected with400: voice_id can not be empty), and output format moved toaudio_config(audio_encoding,sample_rate_hertz, verified honored at 16k/24k).result.audioContentin each NDJSON line; the old top-level paths are kept as fallbacks.-1000contract holds.Tests
tests/(dump, flush, params passthrough, error contracts incl. invalid key, TTFB metrics, robustness, state machine): 9 passed.tests/configsfor the TTS guarder; full guarder run against the live API: 15 passed, 2 skipped (subtitle alignment, disabled by default).All verified end-to-end in a voice-assistant graph (deepgram asr → openai llm → inworld tts) with audible output.