Skip to content

fix(tts): keep configured tts.provider authoritative; drop model schema override - #91292

Closed
mooserini wants to merge 1 commit into
NousResearch:mainfrom
mooserini:fix/tts-provider-authority
Closed

mooserini wants to merge 1 commit into
NousResearch:mainfrom
mooserini:fix/tts-provider-authority

Conversation

@mooserini

Copy link
Copy Markdown

Summary

Closes #90109. tts.provider in config.yaml is the operator's only backend selector, but the text_to_speech tool schema advertised a per-call provider override and the registered handler forwarded it. A model (or a leaked platform hint) could route speech through a different backend than the one the operator set — the inverse of what operators expect from config.yaml.

This PR enforces config-as-authority:

  • provider is removed from the model-facing TTS_SCHEMA parameters.
  • The registered handler always passes provider=None; the configured tts.provider is the only selector.
  • The Python function argument is retained for trusted programmatic callers (CLI, internal code) — the restriction is at the model-facing boundary, not the function.

This matches how STT, model, and most other Hermes knobs already work: config is authoritative, the schema does not invite override.

Scope clarification

This is the standalone fix for the tts.provider authority gap. It does not touch voice.auto_tts (that is #90103 / PR #90121) — different leak, different surface.

Verification

New regression suite tests/tools/test_tts_provider_authority.py (74 lines, 4 cases):

  • programmatic callers retain explicit provider selection;
  • inner dispatch reaches synthesis with the programmatic provider (not reverted to config);
  • the registered handler discards any provider arg from model payloads;
  • the model-facing schema no longer exposes a provider property.

Contract-style assertions (invariants on what crosses the boundary), not change-detector snapshots.

Test plan

  • pytest tests/tools/test_tts_provider_authority.py -q (4 passed locally)
  • python3 -m py_compile tools/tts_tool.py tests/tools/test_tts_provider_authority.py
  • git diff origin/main..HEAD --stat confirms only the schema/handler change + test

This is a fork PR, so workflows don't start until a maintainer clicks "Approve and run workflows" on the first run. @teknium1 @shannonsands — this closes the second voice/authority issue (#90109) independently of the A2A auto-TTS and Photon Spectrum 12 campaigns; happy to fold it into a combined voice PR if you prefer fewer drops.

@alt-glitch alt-glitch added type/bug Something isn't working tool/tts Text-to-speech and transcription area/config Config system, migrations, profiles P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades duplicate This issue or pull request already exists labels Aug 21, 2026
@alt-glitch

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Duplicate of #90120. Both PRs remove the model-facing provider field from the TTS schema/registered handler so configured tts.provider remains authoritative, while retaining the trusted programmatic override. #90120 was opened first.

@mooserini

Copy link
Copy Markdown
Author

Campaign context

This is one of four PRs in a single voice + Photon improvement campaign:

PR What it fixes Size
#90121 runner no longer auto-TTSs A2A text replies small
#91129 inbound iMessage CAF voice notes recognized; ffmpeg-static declared small
#91215 Photon sidecar migrated Spectrum 8 → 12.8.0 large
#91292 text_to_speech schema no longer exposes per-call provider override; config is authoritative small

Why this one matters for everyone

A model (or a leaked platform hint) could previously route speech through a different backend than the operator configured in config.yaml. This PR removes the provider argument from the model-facing text_to_speech schema and forces the registered handler to honor tts.provider. Config-as-authority, same as STT/model. The Python function still accepts an explicit provider for trusted programmatic callers — the restriction is at the model boundary.

See the campaign framing comment on #91215 for the full picture and the low-friction review path.

@mooserini

Copy link
Copy Markdown
Author

Closing as duplicate of #90120.

Same fix, narrower coverage. #90120 does the same schema/handler removal and
goes further: it also enforces config authority at the function level
(text_to_speech_tool + _text_to_speech_single warn and force the config
default when a per-call provider disagrees), and ships a broader test
surface (6 cases, real synthesis path, env teardown).

Mine was correct but partial: it guarded the model-facing boundary cleanly
(provider dropped from schema, handler never forwards it) without catching
direct programmatic calls that bypass the registry. Thank you to liuhao1024
for picking this up and for the broader implementation.

Reopening note for anyone browsing: the model-facing invariant still held
here — assert 'provider' not in entry.schema['parameters']['properties'] and
assert seen['provider'] is None on the registered handler path — and that
reads well as a design check even though the full fix is in #90120.

@mooserini mooserini closed this Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/tts Text-to-speech and transcription type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tts.provider is not authoritative; tool schema lets the model pick another backend

2 participants