fix(tts): honor config.yaml endpoints across all providers + bounded upstream buffering - #73512
Merged
Merged
Conversation
Salvaged from PR #26233 (@LeonSGP43), rebased onto the current 3-tuple _resolve_openai_audio_client_config (is_managed flag, post-#73072 layout). Same fix independently submitted earlier in PR #26209 (@zccyman) — credit to both. Resolution order now mirrors the STT resolver: tts.openai.api_key/base_url from config.yaml -> VOICE_TOOLS_OPENAI_KEY/OPENAI_API_KEY env (still honoring config base_url) -> managed gateway. _has_openai_audio_backend also counts a config api_key as an available backend. Fixes #26175
Use the shared OpenAI audio key resolver and prefer tts.openai.base_url over the global environment fallback in the Desktop streaming path. Add focused regression coverage for credential and endpoint propagation.
Salvaged from PR #66311 (@moeadham), rebased onto the current streaming registry. tts.elevenlabs.base_url (+ optional wss_url, derived from base_url when omitted) routes both the sync ElevenLabs path and the chunked ElevenLabsStreamer through an ElevenLabsEnvironment, matching the STT side's ELEVENLABS_STT_BASE_URL/config override pattern.
…g tests Class-level sweep following the ElevenLabs salvage (#66311): every cloud TTS provider section now honors tts.<provider>.base_url. xAI, MiniMax, Gemini, OpenAI and DeepInfra already did; Mistral (SDK server_url) was the remaining gap. Adds per-provider config tests locking in the ElevenLabs environment plumbing and the Mistral server_url passthrough.
Salvaged from PR #62977 (@Vissirexa) — TTS model-cache half only (the hindsight turn-buffer half is a different subsystem and was dropped). _piper_voice_cache and _kittentts_model_cache were keyed by voice/model with no eviction, and each entry is a whole loaded model (tens of MB). A surface that sweeps voices pinned one model per voice for the process lifetime. New _tts_cache_get_or_load() get-or-loads through a small LRU (_TTS_MODEL_CACHE_MAX=3), refreshing recency on a hit and evicting the least-recently-used model on a cold miss.
Contributor
૮ >ﻌ< ა ci reviewran on bd4744c ℹ️ InfoDesktop E2E visual evidence · View test artifacts · View job1 visual diff. inline evidence upload failed. Failed to upload diff-665a0833239e-onboarding-overlay-diff.png with gh image (exit code 1): Error uploading /home/runner/work/_temp/e2e-evidence/diff-665a0833239e-onboarding-overlay-diff.png: step 0 (get upload token): uploadToken not found on repo page — do you have write access to NousResearch/hermes-agent? (or, if NousResearch enforces SAML SSO, authorize at https://github.com/orgs/NousResearch/sso) |
This was referenced Jul 28, 2026
francip
added a commit
to kortexa-ai/hermes-livekit
that referenced
this pull request
Jul 28, 2026
NousResearch/hermes-agent#73512 landed the same fix with wider scope: OpenAIStreamer now prefers tts.openai.base_url and tts.openai.api_key in both stream() and available(), via the shared audio-key resolver, and ElevenLabs got the equivalent treatment. Verified against the updated checkout: resolve_streaming_provider() returns the stock OpenAIStreamer and opens http://<host>:4003/v1 with no patch installed. Keeping ours would have shadowed the upstream implementation for no benefit, since both register under "openai" and ours installed at adapter init. Our issue #73530 and PR #73536 are closed as superseded.
4 tasks
This was referenced Jul 29, 2026
meltforce
added a commit
to meltforce/hermes-agent
that referenced
this pull request
Aug 28, 2026
Every other STT provider reads a base URL from its own config block — openai (`_resolve_openai_stt_client_config`), xai, elevenlabs and deepinfra all do. `_transcribe_mistral` was the exception: it built `Mistral(api_key=api_key)` with no `server_url`, so `stt.provider: mistral` could only ever address api.mistral.ai. The TTS half already has this. NousResearch#73512 introduced what its description calls "class-level base_url parity" for TTS providers, and `_generate_mistral_tts` has read `tts.mistral.base_url` into the SDK's `server_url` since. This is the same change for the transcription side, in the shape `_transcribe_elevenlabs` uses: config block, then env, then the module constant. Unset, no `server_url` is passed at all and the SDK keeps its own endpoint, so an install that does not configure this behaves exactly as before. Why it matters: a self-hosted gateway or authenticating reverse proxy in front of Mistral is reachable for TTS and unreachable for STT, which makes the provider pair unusable in that deployment for no reason visible from the configuration.
1 task
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.
fix(tts): honor config.yaml endpoints/keys across providers + bound upstream buffering and model caches
Consolidated salvage of five contributor PRs. One theme: TTS providers should honor
config.yamlendpoints/keys the same way everywhere (class-levelbase_urlparity), plus two bounded-memory fixes.Config endpoints / keys (fixes #26175)
tts.openai.api_key+base_urlfrom config.yaml — the resolver was env-only (VOICE_TOOLS_OPENAI_KEY/OPENAI_API_KEY) and ignored thetts.openaiblock. This was first reported and fixed by @zccyman in #26209 (earliest submitter — thank you!) and independently by @LeonSGP43 in #26233; the cherry-picked commit carries the #26233 diff (it shipped with tests) rebased onto the current 3-tuple_resolve_openai_audio_client_config(is_managedflag, post-#73072 layout). Resolution order now mirrors the STT resolver: config → env (still honoring configbase_url) → managed gateway._has_openai_audio_backendalso counts a config key as an available backend.OpenAI streaming path (salvaged from #70307, @aml1973) —
OpenAIStreamerread env only; it now uses the shared audio-key resolver and preferstts.openai.base_url. Follow-up commit completes parity: the streamer also honorstts.openai.api_keyin bothavailable()andstream().Configurable ElevenLabs URLs (salvaged from #66311, @moeadham) —
tts.elevenlabs.base_url(+ optionalwss_url, derived frombase_urlwhen omitted) routes both the sync path and the chunkedElevenLabsStreamerthrough anElevenLabsEnvironment, matching the STT side'sELEVENLABS_STT_BASE_URL/config pattern.Class-level base_url parity audit — swept every cloud provider section: xAI, MiniMax, Gemini, OpenAI and DeepInfra already honored
tts.<provider>.base_url; ElevenLabs and Mistral were the gaps. Mistral now passestts.mistral.base_urlas the SDKserver_url. Every cloud TTS provider section now supportsbase_urlconsistently.Bounded buffering (fixes #55171)
Bound upstream response bodies (salvaged from #55177, @ooiuuii) — the requests-based providers (xAI, MiniMax, Gemini) buffered the entire upstream body via
response.content/response.json()with no cap. New_read_tts_response_bytes()streams with a hard 16 MiB cap (64 KiB chunks) and closes the response; JSON/file-write helpers layer on it. A hostile/broken endpoint can no longer OOM the process.Bound the Piper/KittenTTS model caches (salvaged from #62977, @Vissirexa — TTS half only; the hindsight turn-buffer half is a different subsystem and was deliberately dropped) —
_piper_voice_cache/_kittentts_model_cachehad no eviction and each entry is a whole loaded model (tens of MB). New_tts_cache_get_or_load()runs them through a small LRU (_TTS_MODEL_CACHE_MAX=3) with recency refresh on hit.Commits (contributor authorship preserved)
cd7bde218fe6d631177d2290272b0d851f75b7e2ba055b3a80c6087a898115673dfce7bd4744cfbcTests
tests/tools/test_tts_openai_config.py(6) — config/env/gateway resolution order, error message, backend availabilitytests/tools/test_tts_streaming.py(+3) — streamer honors config base_url and api_key; availability reflects config keytests/tools/test_tts_provider_base_urls.py(5) — ElevenLabs environment plumbing (incl. derived wss), Mistralserver_urlpassthrough/omissiontests/tools/test_tts_response_body_cap.py(10) — cap enforcement, streaming reads, JSON fallbacktests/tools/test_tts_model_cache_lru.py(3) — load/hit/evict/LRU-recencyAll 65 targeted tests pass (
pytest -o addopts="" -q tests/tools/test_tts_*); sabotage-verified (disabling the config branch fails the config tests). The twotest_tts_xai_speech_tagsfailures under a combined-k ttsrun reproduce identically onorigin/main(pre-existing test-isolation flake, not introduced here).Post-merge
tts.openai.api_key/tts.openai.base_urlinconfig.yaml— config-only setups cannot reach self-hosted endpoints #26175 (fixed), Built-in TTS providers buffer unbounded upstream responses #55171 (fixed)Infographic