Hermes Agent TTS and STT Integration Guide
Connects Hermes Agent to ten TTS providers and multiple STT engines for voice output and transcription.

This reference page documents the text-to-speech (TTS) and speech-to-text (STT) capabilities of Hermes Agent, an AI agent runtime by Nous Research. With TTS, Hermes Agent can convert text responses into spoken audio and deliver them as voice messages or audio files across Telegram, Discord, WhatsApp, and the CLI. With STT, incoming voice messages on those platforms are automatically transcribed into text that the agent processes as normal conversation. The integration supports ten TTS providers, including local engines that require no API key, and multiple STT providers with automatic fallback behavior.
What It Does
- Text-to-Speech (TTS): Converts agent text responses into audio using one of ten supported providers. Output is delivered as a voice bubble (Telegram, Discord) or audio file attachment (WhatsApp, CLI) depending on the platform and provider.
- Voice Message Transcription (STT): Automatically transcribes incoming voice messages from Telegram, Discord, WhatsApp, Slack, or Signal into text. The agent sees the transcript as normal text input.
- Custom Command Providers: Wire in any TTS or STT engine that exposes a shell command, without writing Python code. Hermes writes input to a temp file, runs your command, and reads the output.
- Python Plugin Providers: For TTS engines that require a Python SDK, streaming, or OAuth, register a Python plugin via
ctx.register_tts_provider(). The plugin coexists with command providers. - Gemini Persona Prompts: Gemini TTS can follow natural-language performance direction from a local Markdown or text file, including sections like
AUDIO PROFILE,SCENE,DIRECTOR'S NOTES, andSAMPLE CONTEXT. - Gemini Audio Tags: Enable freeform square-bracket audio tags (e.g.,
[whispers],[excitedly]) for Gemini 3.1 Flash TTS. Hermes runs a hidden rewrite pass that inserts tags into the TTS script only; the visible chat reply stays unchanged. - xAI Custom Voice Cloning: Create a custom voice in the xAI Console and use it with TTS by setting the resulting
voice_idin config. - Piper Local TTS: Fast, local neural TTS engine running entirely on CPU, supporting 44 languages with pre-trained voices. No API key needed.
- Input Length Limits: Each provider has a documented per-request character cap. Hermes truncates text before calling the provider so requests never fail with a length error. Caps can be overridden per provider.
- Speed Control: A global speed multiplier applies to all providers by default. Each provider can override it with its own
speedsetting. Provider-specific speed takes precedence over the global value. - Automatic STT Fallback: If the configured STT provider is unavailable, Hermes automatically falls back through a chain of alternatives before giving up.
Setup
TTS Setup
Installation
Hermes Agent supports TTS out of the box. Some providers require additional dependencies:
- Edge TTS (default): No installation needed. Works immediately.
- OpenAI TTS: No installation needed. Requires an API key.
- ElevenLabs TTS: No installation needed. Requires an API key.
- MiniMax TTS: No installation needed. Requires an API key.
- Mistral TTS: No installation needed. Requires an API key.
- Google Gemini TTS: No installation needed. Requires an API key.
- xAI TTS: No installation needed. Requires an API key.
- NeuTTS: Installed via
hermes tools→ Voice & TTS → NeuTTS, or manually withpip install neutts. - KittenTTS: Installed via
hermes tools→ Voice & TTS → KittenTTS, or manually withpip install kitten-tts. - Piper: Installed via
hermes tools→ Voice & TTS → Piper (Hermes runspip install piper-ttsfor you), or manually withpip install piper-tts.
Configuration File
TTS configuration lives in ~/.hermes/config.yaml. Here is the full configuration block from the official documentation:
# In ~/.hermes/config.yaml
tts:
provider: "edge" # "edge" | "elevenlabs" | "openai" | "minimax" | "mistral" | "gemini" | "xai" | "neutts" | "kittentts" | "piper"
speed: 1.0 # Global speed multiplier (provider-specific settings override this)
edge:
voice: "en-US-AriaNeural" # 322 voices, 74 languages
speed: 1.0 # Converted to rate percentage (+/-%)
elevenlabs:
voice_id: "pNInz6obpgDQGcFmaJgB" # Adam
model_id: "eleven_multilingual_v2"
openai:
model: "gpt-4o-mini-tts"
voice: "alloy" # alloy, echo, fable, onyx, nova, shimmer
base_url: "https://api.openai.com/v1" # Override for OpenAI-compatible TTS endpoints
speed: 1.0 # 0.25 - 4.0
minimax:
model: "speech-02-hd" # speech-02-hd (default), speech-02-turbo
voice_id: "English_Graceful_Lady" # See https://platform.minimax.io/faq/system-voice-id
speed: 1 # 0.5 - 2.0
vol: 1 # 0 - 10
pitch: 0 # -12 - 12
mistral:
model: "voxtral-mini-tts-2603"
voice_id: "c69964a6-ab8b-4f8a-9465-ec0925096ec8" # Paul - Neutral (default)
gemini:
model: "gemini-2.5-flash-preview-tts" # or gemini-3.1-flash-tts-preview
voice: "Kore" # 30 prebuilt voices: Zephyr, Puck, Kore, Enceladus, Gacrux, etc.
audio_tags: false # Enable hidden Gemini 3.1 TTS audio-tag insertion
persona_prompt_file: "" # Optional Markdown/text file with Gemini voice direction
xai:
voice_id: "eve" # or a custom voice ID, see docs below
language: "en" # ISO 639-1 code
sample_rate: 24000 # 22050 / 24000 (default) / 44100 / 48000
bit_rate: 128000 # MP3 bitrate; only applies when codec=mp3
# base_url: "https://api.x.ai/v1" # Override via XAI_BASE_URL env var
neutts:
ref_audio: ''
ref_text: ''
model: neuphonic/neutts-air-q4-gguf
device: cpu
kittentts:
model: KittenML/kitten-tts-nano-0.8-int8 # 25MB int8; also: kitten-tts-micro-0.8 (41MB), kitten-tts-mini-0.8 (80MB)
voice: Jasper # Jasper, Bella, Luna, Bruno, Rosie, Hugo, Kiki, Leo
speed: 1.0 # 0.5 - 2.0
clean_text: true # Expand numbers, currencies, units
piper:
voice: en_US-lessac-medium # voice name (auto-downloaded) OR absolute path to .onnx
# voices_dir: '' # default: ~/.hermes/cache/piper-voices/
# use_cuda: false # requires onnxruntime-gpu
# length_scale: 1.0 # 2.0 = twice as slow
# noise_scale: 0.667
# noise_w_scale: 0.8
# volume: 1.0 # 0.5 = half as loud
# normalize_audio: true
Authentication
- Edge TTS: No API key required.
- ElevenLabs: Set
ELEVENLABS_API_KEYenvironment variable. - OpenAI TTS: Set
VOICE_TOOLS_OPENAI_KEYenvironment variable. - MiniMax TTS: Set
MINIMAX_API_KEYenvironment variable. - Mistral TTS: Set
MISTRAL_API_KEYenvironment variable. - Google Gemini TTS: Set
GEMINI_API_KEYenvironment variable. - xAI TTS: Set
XAI_API_KEYenvironment variable. - NeuTTS: No API key required.
- KittenTTS: No API key required.
- Piper: No API key required.
For Nous Portal subscribers, OpenAI TTS is available through the Tool Gateway without a separate OpenAI API key. New installs can run hermes setup --portal to log in and turn on every gateway tool at once. Existing installs can pick Nous Subscription for just TTS via hermes model or hermes tools.
Platform Delivery Formats
| Platform | Delivery Format | Audio Format |
|---|---|---|
| Telegram | Voice bubble (plays inline) | Opus .ogg |
| Discord | Voice bubble (Opus/OGG), falls back to file attachment | Opus/MP3 |
| Audio file attachment | MP3 | |
| CLI | Saved to ~/.hermes/audio_cache/ | MP3 |
ffmpeg Requirement for Telegram Voice Bubbles
Telegram voice bubbles require Opus/OGG audio format. Some providers output formats that need conversion:
- OpenAI, ElevenLabs, and Mistral produce Opus natively, no extra setup.
- Edge TTS (default) outputs MP3 and needs ffmpeg to convert.
- MiniMax TTS outputs MP3 and needs ffmpeg to convert.
- Google Gemini TTS outputs raw PCM and uses ffmpeg to encode Opus directly.
- xAI TTS outputs MP3 and needs ffmpeg to convert.
- NeuTTS outputs WAV and needs ffmpeg to convert.
- KittenTTS outputs WAV and needs ffmpeg to convert.
- Piper outputs WAV and needs ffmpeg to convert.
Install ffmpeg with:
# Ubuntu/Debian
sudo apt install ffmpeg
# macOS
brew install ffmpeg
# Fedora
sudo dnf install ffmpeg
Without ffmpeg, Edge TTS, MiniMax TTS, NeuTTS, KittenTTS, and Piper audio are sent as regular audio files (playable, but shown as a rectangular player instead of a voice bubble). If you want voice bubbles without installing ffmpeg, switch to the OpenAI, ElevenLabs, or Mistral provider.
STT Setup
Configuration File
STT configuration also lives in ~/.hermes/config.yaml:
# In ~/.hermes/config.yaml
stt:
provider: "local" # "local" | "groq" | "openai" | "mistral" | "xai"
local:
model: "base" # tiny, base, small, medium, large-v3
openai:
model: "whisper-1" # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe
mistral:
model: "voxtral-mini-latest" # voxtral-mini-latest, voxtral-mini-2602
xai:
model: "grok-stt" # xAI Grok STT
Authentication
- Local Whisper (default): No API key required. Works out of the box when
faster-whisperis installed. - Groq Whisper API: Set
GROQ_API_KEYenvironment variable. - OpenAI Whisper API: Set
VOICE_TOOLS_OPENAI_KEYorOPENAI_API_KEYenvironment variable. - Mistral API (Voxtral Transcribe): Set
MISTRAL_API_KEYenvironment variable. Install withcd ~/.hermes/hermes-agent && uv pip install -e ".[mistral]". - xAI Grok STT: Set
XAI_API_KEYenvironment variable.
Custom Local STT Command
Set HERMES_LOCAL_STT_COMMAND if you want Hermes to call a local transcription command directly. The command template supports {input_path}, {output_dir}, {language}, and {model} placeholders. Your command must write a .txt transcript somewhere under {output_dir}.
Configuration Reference
TTS Provider Options
| Option | Description | Default |
|---|---|---|
tts.provider | TTS provider to use | "edge" |
tts.speed | Global speed multiplier (0.25-4.0 depending on provider) | 1.0 |
tts.edge.voice | Edge TTS voice name | "en-US-AriaNeural" |
tts.edge.speed | Edge TTS speed multiplier | 1.0 |
tts.elevenlabs.voice_id | ElevenLabs voice ID | "pNInz6obpgDQGcFmaJgB" (Adam) |
tts.elevenlabs.model_id | ElevenLabs model ID | "eleven_multilingual_v2" |
tts.openai.model | OpenAI TTS model | "gpt-4o-mini-tts" |
tts.openai.voice | OpenAI voice | "alloy" |
tts.openai.base_url | Override for OpenAI-compatible endpoints | "https://api.openai.com/v1" |
tts.openai.speed | OpenAI speed multiplier (0.25-4.0) | 1.0 |
tts.minimax.model | MiniMax model | "speech-02-hd" |
tts.minimax.voice_id | MiniMax voice ID | "English_Graceful_Lady" |
tts.minimax.speed | MiniMax speed (0.5-2.0) | 1 |
tts.minimax.vol | MiniMax volume (0-10) | 1 |
tts.minimax.pitch | MiniMax pitch (-12 to 12) | 0 |
tts.mistral.model | Mistral TTS model | "voxtral-mini-tts-2603" |
tts.mistral.voice_id | Mistral voice ID | "c69964a6-ab8b-4f8a-9465-ec0925096ec8" (Paul - Neutral) |
tts.gemini.model | Gemini TTS model | "gemini-2.5-flash-preview-tts" |
tts.gemini.voice | Gemini voice | "Kore" |
tts.gemini.audio_tags | Enable hidden Gemini audio-tag insertion | false |
tts.gemini.persona_prompt_file | Path to persona prompt file | "" |
tts.xai.voice_id | xAI voice ID | "eve" |
tts.xai.language | xAI language code (ISO 639-1) | "en" |
tts.xai.sample_rate | xAI sample rate | 24000 |
tts.xai.bit_rate | xAI MP3 bitrate | 128000 |
tts.neutts.ref_audio | NeuTTS reference audio path | '' |
tts.neutts.ref_text | NeuTTS reference text | '' |
tts.neutts.model | NeuTTS model | neuphonic/neutts-air-q4-gguf |
tts.neutts.device | NeuTTS device | cpu |
tts.kittentts.model | KittenTTS model | KittenML/kitten-tts-nano-0.8-int8 |
tts.kittentts.voice | KittenTTS voice | Jasper |
tts.kittentts.speed | KittenTTS speed (0.5-2.0) | 1.0 |
tts.kittentts.clean_text | Expand numbers, currencies, units | true |
tts.piper.voice | Piper voice name or .onnx path | en_US-lessac-medium |
tts.piper.voices_dir | Piper voices directory | ~/.hermes/cache/piper-voices/ |
tts.piper.use_cuda | Use CUDA for Piper (requires onnxruntime-gpu) | false |
tts.piper.length_scale | Piper length scale | 1.0 |
tts.piper.noise_scale | Piper noise scale | 0.667 |
tts.piper.noise_w_scale | Piper noise W scale | 0.8 |
tts.piper.volume | Piper volume multiplier | 1.0 |
tts.piper.normalize_audio | Normalize Piper audio | true |
Input Length Limits
Each provider has a documented per-request input-character cap. Hermes truncates text before calling the provider so requests never fail with a length error:
| Provider | Default Cap (chars) |
|---|---|
| Edge TTS | 5000 |
| OpenAI | 4096 |
| xAI | 15000 |
| MiniMax | 10000 |
| Mistral | 4000 |
| Google Gemini | 32000 |
| ElevenLabs | Model-aware (see below) |
| NeuTTS | 2000 |
| KittenTTS | 2000 |
| Piper | 5000 |
ElevenLabs picks a cap from the configured model_id:
model_id | Cap (chars) |
|---|---|
eleven_flash_v2_5 | 40000 |
eleven_flash_v2 | 30000 |
eleven_multilingual_v2 (default), eleven_multilingual_v1, eleven_english_sts_v2, eleven_english_sts_v1 | 10000 |
eleven_v3, eleven_ttv_v3 | 5000 |
| Unknown model | Falls back to provider default (10000) |
Override per provider with max_text_length: under the provider section of your TTS config:
tts:
openai:
max_text_length: 8192 # raise or lower the provider cap
Only positive integers are honored. Zero, negative, non-numeric, or boolean values fall through to the provider default, so a broken config cannot accidentally disable truncation.
STT Provider Options
| Option | Description | Default |
|---|---|---|
stt.provider | STT provider | "local" |
stt.local.model | Local Whisper model size | "base" |
stt.openai.model | OpenAI STT model | "whisper-1" |
stt.mistral.model | Mistral STT model | "voxtral-mini-latest" |
stt.xai.model | xAI STT model | "grok-stt" |
Local Whisper Model Sizes
| Model | Size | Speed | Quality |
|---|---|---|---|
tiny | ~75 MB | Fastest | Basic |
base | ~150 MB | Fast | Good (default) |
small | ~500 MB | Medium | Better |
medium | ~1.5 GB | Slower | Great |
large-v3 | ~3 GB | Slowest | Best |
How It Works

TTS Architecture
Hermes Agent's TTS system is provider-agnostic. When the agent generates a text response, the text_to_speech tool routes the text to the configured provider. The provider returns audio bytes, which Hermes formats according to the target platform's requirements (Opus/OGG for Telegram voice bubbles, MP3 for WhatsApp attachments, etc.).
Speed control works hierarchically: the global tts.speed value applies to all providers by default. Each provider can override it with its own speed setting (e.g., tts.openai.speed: 1.5). Provider-specific speed takes precedence over the global value. The default is 1.0 (normal speed).
Custom Command Providers
If a TTS engine is not natively supported, you can wire it in as a command-type provider without writing any Python. Hermes writes the input text to a temp UTF-8 file, runs your shell command, and reads the audio file the command produced. Declare one or more providers under tts.providers.<name> and switch between them with tts.provider: <name>.
Placeholders
Your command template can reference these placeholders. Hermes substitutes them at render time and shell-quotes each value for the surrounding context (bare / single-quoted / double-quoted), so paths with spaces and other shell-sensitive characters are safe.
| Placeholder | Meaning |
|---|---|
{input_path} | Path to the temp UTF-8 text file Hermes wrote |
{text_path} | Alias for {input_path} |
{output_path} | Path the command must write audio to |
{format} | mp3 / wav / ogg / flac |
{voice} | tts.providers.<name>.voice, empty when unset |
{model} | tts.providers.<name>.model |
{speed} | Resolved speed multiplier (provider or global) |
Use {{ and }} for literal braces.
Optional Keys
| Key | Default | Meaning |
|---|---|---|
timeout | 120 | Seconds; the process tree is killed on expiry (Unix killpg, Windows taskkill /T) |
output_format | mp3 | One of mp3 / wav / ogg / flac. Auto-inferred from the output extension if Hermes picks a path |
voice_compatible | false | When true, Hermes converts MP3/WAV output to Opus/OGG via ffmpeg so Telegram renders a voice bubble |
max_text_length | 5000 | Input is truncated to this length before rendering the command |
voice / model | empty | Passed to the command as placeholder values only |
Behavior Notes
- Built-in names always win. A
tts.providers.openaientry never shadows the native OpenAI provider, so no user config can silently replace a built-in. - Default delivery is a document. Command providers deliver as regular audio attachments on every platform. Opt in to voice-bubble delivery per-provider with
voice_compatible: true. - Command failures surface to the agent. Non-zero exit, empty output, or timeout all return an error with the command's stderr/stdout included so you can debug the provider from the conversation.
type: commandis the default whencommand:is set. Writingtype: commandexplicitly is good practice but not required; an entry with a non-emptycommandstring is treated as a command provider.{input_path}/{text_path}are interchangeable. Use whichever reads better in your command.
Security
Command-type providers run whatever shell command you configure, with your user's permissions. Hermes quotes placeholder values and enforces the configured timeout, but the command template itself is trusted local input. Treat it the same way you would a shell script on your PATH.
Python Plugin Providers
For TTS engines that cannot be expressed as a single shell command (Python SDKs without a CLI, streaming engines, voice-listing APIs, OAuth-refreshing auth), register a Python plugin via ctx.register_tts_provider(). The plugin coexists with the Custom command providers registry; pick the surface that fits your engine.
When to Pick Which
| Your backend has… | Use |
|---|---|
| A single CLI reading text from a file/stdin and writing audio to a file/stdout | Command provider (no Python needed) |
| Two or three CLIs chained with shell pipes | Command provider |
| A Python SDK only, no CLI | Plugin |
| Streaming bytes you want to deliver chunked (mid-generation voice bubbles) | Plugin (override stream()) |
A voice-listing API used by hermes setup | Plugin (override list_voices()) |
| OAuth refresh flow (not a static bearer token) | Plugin |
Built-ins always win, and command providers win over a same-name plugin. So plugins are safe to register against any non-built-in name without worrying about shadowing your existing config.
Minimal Plugin Example
Drop this in ~/.hermes/plugins/my-tts/:
plugin.yaml:
name: my-tts
version: 0.1.0
description: "My custom Python TTS backend"
__init__.py:
from agent.tts_provider import TTSProvider
class MyTTSProvider(TTSProvider):
@property
def name(self) -> str:
return "my-tts" # what tts.provider matches against
@property
def display_name(self) -> str:
return "My Custom TTS"
def is_available(self) -> bool:
# Return False when credentials/deps are missing, picker skips
# this row but the dispatcher still routes here on explicit config.
import os
return bool(os.environ.get("MY_TTS_API_KEY"))
def synthesize(self, text, output_path, *, voice=None, model=None, speed=None, format="mp3", **extra) -> str:
# Write audio bytes to output_path, return the path.
# Raise on failure, the dispatcher converts exceptions to a
# standard error envelope.
import my_tts_sdk
client = my_tts_sdk.Client()
audio_bytes = client.synthesize(text=text, voice=voice or "default")
with open(output_path, "wb") as f:
f.write(audio_bytes)
return output_path
def register(ctx):
ctx.register_tts_provider(MyTTSProvider())
Enable it (hermes plugins enable my-tts), point tts.provider at it (tts.provider: my-tts in config.yaml), and the text_to_speech tool will route through your plugin.
Optional Hooks
Override these on your provider class for richer integration:
list_voices()→ list of{id, display, language, gender, preview_url}dicts shown inhermes tools.list_models()→ list of{id, display, languages, max_text_length}dicts.get_setup_schema()→ return{name, badge, tag, env_vars: [{key, prompt, url}]}to power the picker row inhermes tools/hermes setup. Without this, the plugin still works but its row in the picker is minimal.stream(text, *, voice, model, format, **extra)→ iterator yielding audio bytes for streaming delivery (default raisesNotImplementedError).voice_compatibleproperty → setTrueif your output is Opus-compatible and the gateway should deliver it as a voice bubble (defaultFalse= regular audio attachment).
See agent/tts_provider.py for the full ABC including docstrings.
STT Architecture
Voice messages sent on Telegram, Discord, WhatsApp, Slack, or Signal are automatically transcribed and injected as text into the conversation. The agent sees the transcript as normal text.
Fallback Behavior
If your configured provider is not available, Hermes automatically falls back:
- Local faster-whisper unavailable → Tries a local
whisperCLI orHERMES_LOCAL_STT_COMMANDbefore cloud providers. - Groq key not set → Falls back to local transcription, then OpenAI.
- OpenAI key not set → Falls back to local transcription, then Groq.
- Mistral key/SDK not set → Skipped in auto-detect; falls through to next available provider.
- Nothing available → Voice messages pass through with an accurate note to the user.
Auto-detection order puts xAI after Groq. Explicitly set stt.provider: xai to force it.
STT Custom Command Providers
If the STT engine you want is not natively supported (Doubao ASR, NVIDIA Parakeet, a whisper.cpp build, an open-source SenseVoice CLI, anything else that exposes a shell command), wire it in as a command-type provider without writing any Python. Hermes runs your shell command against the audio file and reads back the transcript.
Declare one or more providers under stt.providers.<name> and switch between them with stt.provider: <name>, same shape as the TTS command-provider registry, adapted for the input=audio → output=transcript direction.
stt:
provider: parakeet # pick any name under stt.providers
providers:
parakeet:
type: command
command: "parakeet-asr --model nvidia/parakeet-tdt-0.6b-v2 --in {input_path} --out {output_path}"
format: txt
language: en
timeout: 300
whispercpp:
type: command
command: "whisper-cli -m ~/models/ggml-large-v3.bin -f {input_path} -otxt -of {output_dir}/transcript"
format: txt
sensevoice:
type: command
command: "sensevoice-cli {input_path} --json | tee {output_path}"
format: json
This complements the legacy HERMES_LOCAL_STT_COMMAND escape hatch. That env var still works untouched via the built-in local_command path. Use stt.providers.<name> when you want multiple shell-driven STT engines, a name you can pick via stt.provider, or anything that needs per-provider language / model / timeout.
STT Placeholders
| Placeholder | Meaning |
|---|---|
{input_path} | Absolute path to the input audio file (original location, read-only) |
{output_path} | Absolute path the command should write the transcript to |
{output_dir} | Parent directory of {output_path} (handy for whisper-style tools) |
{format} | Configured output format: txt / json / srt / vtt |
{language} | Configured language code (defaults to en) |
{model} | stt.providers.<name>.model, empty when unset |
Use {{ and }} for literal braces (handy when embedding JSON snippets in the command).
How the Transcript Is Read Back
After your command exits successfully:
- If
{output_path}exists and is non-empty → Hermes reads it as UTF-8 text. - Otherwise, if the command wrote to stdout → Hermes uses that.
- Otherwise → error: "Command STT provider wrote no output file and produced no stdout".
This lets you use the registry for tools that write to a file, tools that write to stdout, or tools that do both.
Requirements and Limitations
TTS Requirements
- Edge TTS: No API key. Works on any platform with internet access.
- ElevenLabs: Requires
ELEVENLABS_API_KEY. Paid service. - OpenAI TTS: Requires
VOICE_TOOLS_OPENAI_KEY. Paid service. Also available through Nous Portal subscription without a separate key. - MiniMax TTS: Requires
MINIMAX_API_KEY. Paid service. - Mistral TTS: Requires
MISTRAL_API_KEY. Paid service. - Google Gemini TTS: Requires
GEMINI_API_KEY. Free tier available. - xAI TTS: Requires
XAI_API_KEY. Paid service. - NeuTTS: No API key. Local. Requires
pip install neutts. - KittenTTS: No API key. Local. Requires
pip install kitten-tts. - Piper: No API key. Local. Requires
pip install piper-tts. Runs entirely on CPU. Supports 44 languages with pre-trained voices.
STT Requirements
- Local Whisper: No API key. Requires
faster-whisperinstalled. Runs on CPU by default, GPU if available. - Groq Whisper API: Requires
GROQ_API_KEY. Free tier available. - OpenAI Whisper API: Requires
VOICE_TOOLS_OPENAI_KEYorOPENAI_API_KEY. Paid service. - Mistral Voxtral Transcribe: Requires
MISTRAL_API_KEY. Supports 13 languages, speaker diarization, and word-level timestamps. Requirescd ~/.hermes/hermes-agent && uv pip install -e ".[mistral]". - xAI Grok STT: Requires
XAI_API_KEY. Posts tohttps://api.x.ai/v1/sttas multipart/form-data.
Known Limitations
- Telegram Voice Bubbles: Require Opus/OGG format. Providers that output MP3, WAV, or PCM need ffmpeg installed for conversion. Without ffmpeg, audio is sent as a regular file attachment.
- Piper Advanced Knobs:
length_scale,noise_scale,noise_w_scale,volume,normalize_audio, anduse_cudacorrespond 1:1 to Piper'sSynthesisConfig. They are ignored on olderpiper-ttsversions. - Custom Command Providers: Built-in names always win. A
tts.providers.openaientry never shadows the native OpenAI provider. Default delivery is a document; opt in to voice-bubble delivery withvoice_compatible: true. - Input Length Override: Only positive integers are honored. Zero, negative, non-numeric, or boolean values fall through to the provider default.
- STT Auto-Detection Order: xAI is placed after Groq. Explicitly set
stt.provider: xaito force it. - Mistral STT: If the key or SDK is not set, Mistral is skipped in auto-detect and falls through to the next available provider.
Troubleshooting
TTS Issues
Problem: No audio output or incorrect provider used.
Fix: Verify tts.provider is set correctly in ~/.hermes/config.yaml. Check that the required API key environment variable is set. For local providers, confirm the package is installed (e.g., pip list | grep piper-tts).
Problem: Telegram voice bubble not appearing; audio sent as file attachment instead.
Fix: Install ffmpeg (see installation commands above). If you do not want to install ffmpeg, switch to OpenAI, ElevenLabs, or Mistral provider, which output Opus natively.
Problem: TTS request fails with a length error.
Fix: The provider has a character cap. Hermes truncates text automatically, but if you need longer input, override the cap with max_text_length: under the provider section. Only positive integers are honored.
Problem: Custom command provider returns an error.
Fix: Check the command's stderr/stdout, which is included in the error message. Verify the command template uses correct placeholders. Ensure the command writes audio to {output_path}. Increase timeout if the command takes longer than 120 seconds.
Problem: Piper voice not found.
Fix: On the first TTS call for a voice that is not cached locally, Hermes runs python -m piper.download_voices and downloads the model (~20-90MB depending on quality tier) into ~/.hermes/cache/piper-voices/. Ensure internet access for the download. Subsequent calls reuse the cached model.
Problem: Gemini audio tags not working.
Fix: Ensure tts.gemini.audio_tags is set to true. The rewrite uses auxiliary.tts_audio_tags and defaults to your main chat model. Override that auxiliary task if you want tag insertion handled by a cheaper or faster model.
STT Issues
Problem: Voice messages not being transcribed.
Fix: Check that stt.provider is set correctly. Verify the required API key is set for cloud providers. For local Whisper, confirm faster-whisper is installed. If nothing is available, voice messages pass through with an accurate note to the user.
Problem: Custom STT command provider produces no output.
Fix: Ensure the command writes a .txt file to {output_path} or produces stdout. If the command writes to a different location, adjust the command template. Check the timeout; increase if the command takes longer than the default.
Problem: Mistral STT not working.
Fix: Confirm MISTRAL_API_KEY is set. Install the Mistral extras with cd ~/.hermes/hermes-agent && uv pip install -e ".[mistral]". If the key or SDK is not set, Mistral is skipped in auto-detect.
Problem: xAI Grok STT not being used despite having the key.
Fix: Auto-detection order puts xAI after Groq. Explicitly set stt.provider: xai to force it.
Sources & References
This page was researched from 1 independent source, combined and verified for completeness.
- 1.Voice & TTSOfficial documentation · primary source
The #1 AI Newsletter
The most important ai updates, guides, and fixes — one weekly email.
No spam, unsubscribe anytime. Privacy policy