Skip to content

feat(tools): forward OpenAI TTS instructions field through text_to_speech - #14205

Closed
0xAlcibiades wants to merge 3 commits into
NousResearch:mainfrom
0xAlcibiades:feat/tts-openai-instructions
Closed

0xAlcibiades wants to merge 3 commits into
NousResearch:mainfrom
0xAlcibiades:feat/tts-openai-instructions

Conversation

@0xAlcibiades

Copy link
Copy Markdown
Contributor

Closes #14196.

Summary

  • Adds an optional instructions parameter to the text_to_speech tool schema and threads it through text_to_speech_tool()_generate_openai_tts()client.audio.speech.create().
  • The kwarg is forwarded only when truthy, so tts-1 / tts-1-hd and strict OpenAI-compatible servers that reject unknown kwargs are unaffected.
  • Unlocks gpt-4o-mini-tts's voice-design capability (tone, emotion, pacing, accent, whispering) — already supported by the model but previously unreachable through the tool.
  • Same passthrough enables self-hosted OpenAI-compatible voice-design servers (Qwen3-TTS-VoiceDesign via oMLX, etc.) that are already wired in through tts.openai.base_url — the established convention per [Bug]: TTS truncates at first newline — Kokoro and potentially other backends #9004.
  • No new provider, no new toolset, no new config key.

Docs: voice-mode.md updated with a short note under the tts.openai block and a line in the TTS Provider Comparison.

Why

gpt-4o-mini-tts is Hermes's default model on the openai TTS provider, and its instructions field (OpenAI docs) is the headline quality lever on that model. The hard-coded kwarg list in _generate_openai_tts silently dropped any style direction, so every reply in voice mode got the same flat default regardless of what was being said. This PR is the minimum change to expose that capability.

Test plan

New file tests/tools/test_tts_instructions.py (6 tests) covers:

  • _generate_openai_tts forwards instructions to audio.speech.create when provided
  • instructions key absent from create kwargs when not provided (regression guard for tts-1 / strict servers)
  • Empty-string instructions omitted (treated as absent)
  • Tool-level threading: text_to_speech_tool(instructions=...) → backend sees it
  • Tool-level omission when arg not supplied
  • Schema declares instructions as optional string, not in required

Existing tests/tools/test_tts_max_text_length.py fake helper widened to accept the new kwarg.

Run locally:

uv run scripts/run_tests.sh tests/tools/test_tts_instructions.py tests/tools/test_tts_speed.py tests/tools/test_tts_max_text_length.py
# 50 passed

Manual verification against gpt-4o-mini-tts: same text with vs. without instructions="Whisper conspiratorially" produces audibly different output. Also verified against a self-hosted oMLX server serving Qwen3-TTS-12Hz-1.7B-VoiceDesign-bf16 via tts.openai.base_url override.

Platforms tested

  • macOS (Darwin 25.2)

No platform-specific code touched; change is pure kwarg passthrough in the existing OpenAI client call.

…eech

The `text_to_speech` tool schema accepted only `text` and `output_path`,
so style direction (tone, emotion, pacing, whispering) could never reach
the OpenAI backend — even though `gpt-4o-mini-tts` (Hermes's OpenAI
provider default) treats `instructions` as its primary voice-design
control.

This plumbs an optional `instructions` argument through the tool schema,
the handler lambda, and `text_to_speech_tool()` into
`_generate_openai_tts`, where it is forwarded to
`client.audio.speech.create()` only when truthy. Empty/None values still
omit the key entirely, preserving behavior on `tts-1`/`tts-1-hd` and
strict OpenAI-compatible servers.

The same passthrough unblocks self-hosted OpenAI-compatible voice-design
servers (Qwen3-TTS-VoiceDesign on oMLX, etc.) that are already wired in
via `tts.openai.base_url` — the established convention per NousResearch#9004 and the
TTS config docs — without inventing a new provider backend.

Tests: `tests/tools/test_tts_instructions.py` covers backend passthrough,
tool-level threading, schema declaration, and the empty-string/absent
omission cases. `tests/tools/test_tts_max_text_length.py` fake_openai
signature widened to accept the new kwarg.

Refs NousResearch#14196
@0xAlcibiades
0xAlcibiades force-pushed the feat/tts-openai-instructions branch from e080ced to f109c3f Compare April 23, 2026 00:04
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have tool/tts Text-to-speech and transcription labels Apr 23, 2026
@0xAlcibiades

Copy link
Copy Markdown
Contributor Author

Any interest in getting this merged?

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the focused TTS extension. The premise remains valid on current main: tools/tts_tool.py:1059-1068 constructs the OpenAI request without instructions, while tools/tts_tool.py:2153-2156 and :2847-2867 expose and dispatch only text and output_path.

Problems

  • The two new mocks in tests/tools/test_tts_instructions.py:33 and :86 return two values from _resolve_openai_audio_client_config(). Current main now unpacks three values at tools/tts_tool.py:1027, introduced by b53ba0e188363800447b7aca4c0a85420c39d1a8; these tests will fail before reaching their assertions.

Suggested changes

  • Return ("test-key", None, False) from both mocks. The production change otherwise follows the existing speed conditional-kwarg pattern at tools/tts_tool.py:1059-1068, and the schema tests correctly keep the new field optional.

Automated hermes-sweeper review.


with patch("tools.tts_tool._import_openai_client", return_value=mock_cls), \
patch("tools.tts_tool._resolve_openai_audio_client_config",
return_value=("test-key", None)):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current main now unpacks three values from this helper (tools/tts_tool.py:1027, commit b53ba0e188363800447b7aca4c0a85420c39d1a8). Return ("test-key", None, False) here and in the second helper so these tests do not fail during setup.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 12, 2026
@teknium1

Copy link
Copy Markdown
Collaborator

Merged into main via consolidated salvage PR #73513 (merge 4aac89b429). Your instructions param (earliest of the trio) forwarding OpenAI voice-design guidance was cherry-picked as 2fdb0ab with your authorship.

Your contribution is credited to you in git history. Thank you! Closing this PR as merged-via-salvage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/tts Text-to-speech and transcription type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Expose OpenAI TTS instructions field on the text_to_speech tool

3 participants