Skip to content

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

Description

@0xAlcibiades

Problem or Use Case

Hermes's text_to_speech tool schema currently accepts only text and output_path. There is no way — per call or per config — for the agent to control tone, emotion, accent, pacing, or whispering on spoken replies. In voice mode (CLI, Telegram, Discord VC), every reply gets the same flat default voice regardless of whether the content is a somber correction, an excited announcement, a spooky story, or a calming instruction.

The default OpenAI TTS model Hermes already uses for the openai provider — gpt-4o-mini-tts — supports exactly this via an instructions parameter on audio.speech.create() (OpenAI voice-design guide). It is the primary mechanism for getting expressive speech out of that model. Today Hermes uses the model in its least-capable mode because _generate_openai_tts hard-codes the request kwargs and silently drops any style direction.

Secondarily, the same fix benefits users running local OpenAI-compatible TTS servers (Kokoro, StyleTTS2, oMLX hosting Qwen3-TTS-VoiceDesign, etc.) through the openai backend via tts.openai.base_url override — the established convention per #9004 and the config docs. Several of these servers accept the same instructions field; some (Qwen3-TTS-VoiceDesign) require it.

Verified wire shape

Against a self-hosted oMLX server serving Qwen3-TTS-12Hz-1.7B-VoiceDesign-bf16. Field name is the OpenAI-spec plural instructions, identical to OpenAI's own API:

# 200 OK → WAV audio returned
curl -X POST "$BASE_URL/v1/audio/speech" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{
    "model":"Qwen3-TTS-12Hz-1.7B-VoiceDesign-bf16",
    "voice":"default",
    "input":"hello",
    "instructions":"Speak in a cheerful and positive tone."
  }'

Proposed Solution

Small, backend-agnostic change scoped to the openai TTS backend. The OpenAI Python SDK already accepts instructions= on audio.speech.create(); Hermes just needs to expose it.

1. Add the parameter to the text_to_speech tool schema:

"instructions": {
    "type": "string",
    "description": (
        "Optional voice-design guidance: tone, emotion, pacing, accent, "
        "whispering, impressions. Forwarded to the OpenAI TTS backend "
        "(gpt-4o-mini-tts and OpenAI-compatible servers). Silently "
        "ignored by backends that don't support it."
    ),
},

2. Forward the value in _generate_openai_tts, omitting the key when empty so tts-1 / tts-1-hd and strict servers are unaffected:

if instructions:
    create_kwargs["instructions"] = instructions

3. Thread the arg through text_to_speech_tool() and the registry handler lambda. No new provider, no new toolset, no new config key.

4. Document the new argument in the TTS section of voice-mode.md with a pointer to OpenAI's voice-design guidance.

This follows the same shape as #6926 (forwarding a native OpenAI TTS kwarg through the same code path).

Why it matters

  • Expressive voice output. The agent matches delivery to content — somber when reporting a failure, excited when announcing completion, whispered for asides. Today all of this flattens to the same voice.
  • Zero configuration burden for users. The model picks per-utterance style from what it's about to say; no user config required.
  • Unlocks capability already being paid for. gpt-4o-mini-tts is Hermes's default for the openai provider; instructions is its headline feature.
  • Self-hosted voice-design models become first-class. Qwen3-TTS-VoiceDesign on oMLX, and any other OpenAI-compatible server that implements instructions, work via the existing tts.openai.base_url override with no new provider code.

Test Plan

Mirrors the mock pattern in tests/tools/test_tts_speed.py:

  • Tool called with instructions="..." → mocked client.audio.speech.create receives instructions=... in kwargs
  • Tool called without instructions → key is absent from kwargs (preserves behavior on tts-1, tts-1-hd, strict servers)
  • Non-openai backends (edge, elevenlabs, gemini, xai, minimax, mistral, neutts, kittentts) silently ignore the arg
  • Manual verification: same text through gpt-4o-mini-tts with vs. without instructions="Whisper conspiratorially" produces audibly different output

Out of scope

  • Mapping instructions onto other backends' native style controls (Gemini inline prompt prefix, ElevenLabs voice_settings, Mistral). Separate follow-ups.
  • tts.openai.instructions config-level default. Layerable on top of the tool arg later.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existstool/ttsText-to-speech and transcriptiontype/featureNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions