Documentação oficial desta página · v0.20.6
Voz e síntese de fala
O Hermes Agent faz tanto a leitura em voz alta quanto a transcrição de mensagens de áudio, em todas as plataformas de mensagem.
Com uma assinatura paga do Nous Portal, a síntese da OpenAI vem
pelo Tool Gateway, sem chave separada. Instalações novas podem
rodar hermes setup --portal para autenticar e ligar todas as ferramentas do gateway de uma vez;
instalações existentes escolhem Nous Subscription só para a síntese, em hermes model ou
hermes tools.
Síntese de fala
Converta texto em voz com onze provedores:
| Provedor | Qualidade | Custo | Chave |
|---|---|---|---|
| Edge TTS (padrão) | Boa | Grátis | Nenhuma |
| ElevenLabs | Excelente | Pago | ELEVENLABS_API_KEY |
| OpenAI TTS | Boa | Pago | VOICE_TOOLS_OPENAI_KEY |
| MiniMax TTS | Excelente | Pago | MINIMAX_API_KEY ou MINIMAX_CN_API_KEY |
| Mistral (Voxtral TTS) | Excelente | Pago | MISTRAL_API_KEY |
| Google Gemini TTS | Excelente | Camada gratuita | GEMINI_API_KEY |
| xAI TTS | Excelente | Pago | XAI_API_KEY |
| DeepInfra TTS | Boa | Pago | DEEPINFRA_API_KEY |
| NeuTTS | Boa | Grátis (local) | Nenhuma |
| KittenTTS | Boa | Grátis (local) | Nenhuma |
| Piper | Boa | Grátis (local) | Nenhuma |
O Edge TTS, que é o padrão e não pede chave, traz vozes brasileiras nativas —
pt-BR-FranciscaNeural, pt-BR-AntonioNeural e pt-BR-ThalitaNeural, entre outras. O Piper também
cobre português nas suas 44 línguas. Para começar em português sem gastar nada, é o caminho.
Entrega por plataforma
| Plataforma | Entrega | Formato |
|---|---|---|
| Telegram | Bolha de voz (toca na conversa) | Opus .ogg |
| Discord | Bolha de voz (Opus/OGG), com recuo para anexo | Opus/MP3 |
| Arquivo de áudio anexado | MP3 | |
| CLI | Salvo em ~/.hermes/audio_cache/ | MP3 |
Configuração
# em ~/.hermes/config.yaml
tts:
provider: "edge" # "edge" | "elevenlabs" | "openai" | "minimax" | "mistral" | "gemini" | "xai" | "deepinfra" | "neutts" | "kittentts" | "piper" — ou "nous", para o Tool Gateway gerenciado
speed: 1.0 # multiplicador global (as opções por provedor sobrescrevem)
edge:
voice: "pt-BR-FranciscaNeural" # 322 vozes, 74 idiomas
speed: 1.0 # convertido em porcentagem de ritmo (+/-%)
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" # para endpoints compatíveis
speed: 1.0 # 0,25 a 4,0
# language: "pt" # enviado como lang_code — só em endpoints compatíveis que aceitam (Kokoro, por exemplo)
minimax:
region: "global" # "global" ou "cn"; veja as regras abaixo
model: "speech-02-hd" # speech-02-hd (padrão), speech-02-turbo
voice_id: "English_expressive_narrator"
speed: 1 # 0,5 a 2,0
vol: 1 # 0 a 10
pitch: 0 # -12 a 12
# base_url: "https://tts.exemplo/v1/t2a_v2" # troca o endpoint da região escolhida
mistral:
model: "voxtral-mini-tts-2603"
voice_id: "c69964a6-ab8b-4f8a-9465-ec0925096ec8" # Paul - Neutral (padrão)
gemini:
model: "gemini-2.5-flash-preview-tts" # ou gemini-3.1-flash-tts-preview
voice: "Kore" # 30 vozes prontas: Zephyr, Puck, Kore, Enceladus, Gacrux etc.
audio_tags: false # insere marcações de áudio ocultas do Gemini 3.1
persona_prompt_file: "" # arquivo opcional com direção de voz
xai:
voice_id: "eve" # ou um identificador de voz próprio
language: "pt-BR" # código BCP-47, ou "auto" para detectar
speed: 1.0 # 0,7 a 1,5 (padrão: 1,0)
auto_speech_tags: false # insere marcações expressivas por reescrita do modelo
text_normalization: false # normaliza números, abreviações e símbolos para a fala
optimize_streaming_latency: 0 # 0 a 2, troca qualidade por menos espera
sample_rate: 24000 # 22050 / 24000 (padrão) / 44100 / 48000
bit_rate: 128000 # taxa do MP3; só vale com codec mp3
# base_url: "https://api.x.ai/v1" # ou pela variável XAI_BASE_URL
neutts:
ref_audio: ''
ref_text: ''
model: neuphonic/neutts-air-q4-gguf
device: cpu
kittentts:
model: KittenML/kitten-tts-nano-0.8-int8 # 25 MB int8; também kitten-tts-micro-0.8 (41 MB) e kitten-tts-mini-0.8 (80 MB)
voice: Jasper # Jasper, Bella, Luna, Bruno, Rosie, Hugo, Kiki, Leo
speed: 1.0 # 0,5 a 2,0
clean_text: true # expande números, moedas e unidades
piper:
voice: pt_BR-faber-medium # nome da voz (baixada sozinha) OU caminho absoluto de um .onnx
# voices_dir: '' # padrão: ~/.hermes/cache/piper-voices/
# use_cuda: false # exige onnxruntime-gpu
# length_scale: 1.0 # 2.0 = duas vezes mais lento
# noise_scale: 0.667
# noise_w_scale: 0.8
# volume: 1.0 # 0.5 = metade do volume
# normalize_audio: true
O MiniMax escolhe região, endpoint e credencial juntos:
region: "global"usahttps://api.minimax.io/v1/t2a_v2, com aMINIMAX_API_KEY.region: "cn"usahttps://api.minimaxi.com/v1/t2a_v2, com aMINIMAX_CN_API_KEY.- Sem
region, aMINIMAX_API_KEYtem precedência, por compatibilidade. Se só aMINIMAX_CN_API_KEYestiver configurada, o Hermes escolhecn. - Uma região escolhida explicitamente precisa ter a credencial correspondente. O Hermes nunca pega
emprestada a chave da outra região. Uma sobrescrita de
base_urlnão muda a credencial, e uma sobrescrita apontando para o endpoint oficial da outra região é recusada.
Controle de velocidade: o tts.speed global vale para todos os provedores por padrão. Cada
provedor pode sobrescrever com o próprio speed (por exemplo, tts.openai.speed: 1.5). O valor do
provedor vence o global. O padrão é 1.0.
Prompts de persona no Gemini
O Gemini TTS segue direção de atuação em linguagem natural. Aponte
tts.gemini.persona_prompt_file para um arquivo local em Markdown ou texto, descrevendo a persona.
O arquivo pode ter seções no estilo do Gemini, como AUDIO PROFILE, SCENE, DIRECTOR'S NOTES,
SAMPLE CONTEXT e TRANSCRIPT.
Se o arquivo tiver {transcript} ou {{ transcript }}, o Hermes substitui pelo texto que vai ser
falado. Senão, ele acrescenta uma seção TRANSCRIPT sozinho. O prompt de persona fica local e não
aparece na resposta da conversa.
tts:
provider: gemini
gemini:
voice: Algieba
persona_prompt_file: ~/.hermes/tts/voz-mordomo.md
Marcações de áudio (Gemini e xAI)
O Gemini 3.1 Flash TTS e o Grok TTS da xAI aceitam marcações livres entre colchetes, como
[whispers], [excitedly], [very slow] e [laughs], entre outras notas de interpretação. Ligue
tts.gemini.audio_tags ou tts.xai.auto_speech_tags para o Hermes fazer uma reescrita oculta antes
da síntese. A reescrita insere as marcações só no roteiro de voz; a resposta visível na conversa não
muda.
tts:
provider: gemini
gemini:
model: gemini-3.1-flash-tts-preview
audio_tags: true
xai:
auto_speech_tags: true
A reescrita usa o auxiliary.tts_audio_tags, que por padrão é o seu modelo principal. Troque essa
tarefa auxiliar se quiser um modelo mais barato ou mais rápido cuidando das marcações.
Idioma em endpoints compatíveis com a OpenAI: o tts.openai.language é repassado como parâmetro
lang_code. Serve para servidores compatíveis que aceitam esse campo — por exemplo, o
Kokoro-FastAPI, em que language: "pt" escolhe o
fonemizador de português em vez do inglês. Deixe sem valor na API oficial da OpenAI, que não aceita
esse parâmetro. Sem valor, nada extra é enviado.
Limites de tamanho de entrada
Cada provedor tem um teto documentado de caracteres por requisição. O Hermes divide as respostas mais longas em pedaços ordenados, respeitando as frases, antes de chamar o provedor — então o texto inteiro é preservado, em vez de cortado em silêncio:
| Provedor | Teto padrão (caracteres) |
|---|---|
| Edge TTS | 5000 |
| OpenAI | 4096 |
| xAI | 15000 |
| MiniMax | 10000 |
| Mistral | 4000 |
| Google Gemini | 32000 |
| ElevenLabs | Depende do modelo (abaixo) |
| NeuTTS | 2000 |
| KittenTTS | 2000 |
| Piper | 5000 |
A ElevenLabs escolhe o teto pelo model_id:
model_id | Teto |
|---|---|
eleven_flash_v2_5 | 40000 |
eleven_flash_v2 | 30000 |
eleven_multilingual_v2 (padrão), eleven_multilingual_v1, eleven_english_sts_v2, eleven_english_sts_v1 | 10000 |
eleven_v3, eleven_ttv_v3 | 5000 |
| Modelo desconhecido | Cai no padrão do provedor (10000) |
Para sobrescrever por provedor, use max_text_length: na seção dele:
tts:
openai:
max_text_length: 8192 # aumenta ou diminui o teto
Só inteiros positivos são aceitos. Zero, negativo, texto ou booleano caem no padrão do provedor, então uma configuração quebrada não fura o limite da API por acidente.
Bolhas de voz no Telegram, e o ffmpeg
Bolhas de voz no Telegram exigem áudio em Opus/OGG:
- OpenAI, ElevenLabs e Mistral produzem Opus nativamente — nada a fazer
- Edge TTS (padrão) produz MP3 e precisa do ffmpeg para converter
- MiniMax TTS produz MP3 e precisa do ffmpeg
- Google Gemini TTS produz PCM cru e usa o ffmpeg para codificar Opus direto
- xAI TTS produz MP3 e precisa do ffmpeg
- NeuTTS, KittenTTS e Piper produzem WAV e também precisam do ffmpeg
# Ubuntu e Debian
sudo apt install ffmpeg
# macOS
brew install ffmpeg
# Fedora
sudo dnf install ffmpeg
Sem o ffmpeg, o áudio do Edge, do MiniMax, do NeuTTS, do KittenTTS e do Piper é enviado como arquivo comum (toca, mas aparece como um tocador retangular, e não como bolha de voz).
Se você quer bolha de voz sem instalar o ffmpeg, use a OpenAI, a ElevenLabs ou a Mistral.
Vozes próprias na xAI (clonagem)
A xAI permite clonar a sua voz e usá-la na síntese. Crie uma voz no
console da xAI e coloque o voice_id na
configuração:
tts:
provider: xai
xai:
voice_id: "nlbqfwie" # o identificador da sua voz
Veja a documentação de vozes da xAI para gravação, formatos suportados e limites.
Piper (local, 44 idiomas)
O Piper é um motor de voz neural local e rápido, da Open Home Foundation (quem mantém o Home Assistant). Roda inteiramente no processador, suporta 44 idiomas com vozes pré-treinadas (português incluído) e não pede chave de API.
Instale por hermes tools → Voice & TTS → Piper — o Hermes roda o pip install piper-tts para
você. Ou na mão: pip install piper-tts.
Mudar para o Piper:
tts:
provider: piper
piper:
voice: pt_BR-faber-medium
Na primeira chamada com uma voz que ainda não está em cache, o Hermes roda
python -m piper.download_voices <nome> e baixa o modelo (de 20 a 90 MB, conforme a qualidade) em
~/.hermes/cache/piper-voices/. As chamadas seguintes reaproveitam.
Escolher uma voz. O
catálogo completo cobre
português, inglês, espanhol, francês, alemão, italiano, holandês, russo, polonês, turco, chinês,
árabe, hindi e mais — cada um com as qualidades x_low, low, medium e high. Ouça amostras em
rhasspy.github.io/piper-samples.
Usar uma voz já baixada. Coloque em tts.piper.voice um caminho absoluto terminando em .onnx:
tts:
piper:
voice: /caminho/para/minha-voz.onnx
Os ajustes avançados (tts.piper.length_scale, noise_scale, noise_w_scale, volume,
normalize_audio, use_cuda) correspondem um a um ao SynthesisConfig do Piper. Versões antigas do
piper-tts os ignoram.
Provedores por comando
Se o motor de voz que você quer não é suportado nativamente (VoxCPM, MLX-Kokoro, XTTS por linha de comando, um script de clonagem, qualquer coisa com CLI), dá para ligá-lo como provedor do tipo comando, sem escrever Python. O Hermes grava o texto num arquivo temporário em UTF-8, roda o seu comando e lê o arquivo de áudio que ele produziu.
Declare um ou mais provedores em tts.providers.<nome> e troque entre eles com
tts.provider: <nome> — do mesmo jeito que se troca entre os embutidos.
tts:
provider: voxcpm # qualquer nome sob tts.providers
providers:
voxcpm:
type: command
command: "voxcpm --ref ~/voz.wav --text-file {input_path} --out {output_path}"
output_format: mp3
timeout: 180
voice_compatible: true # tenta entregar como bolha de voz no Telegram
mlx-kokoro:
type: command
command: "python -m mlx_kokoro --in {input_path} --out {output_path} --voice {voice}"
voice: af_sky
output_format: wav
piper-custom: # o Piper nativo também aceita .onnx próprio em tts.piper.voice
type: command
command: "piper -m /caminho/custom.onnx -f {output_path} < {input_path}"
output_format: wav
Valores aceitos em output_format: mp3 (padrão), wav, ogg, flac, m4a, aac, amr,
opus. O seu comando precisa produzir mesmo aquele formato (por exemplo, via ffmpeg); o Hermes só
valida o valor declarado e nomeia o arquivo de acordo. Um valor desconhecido cai em mp3. O formato
escolhido também é exposto ao comando pelo espaço reservado {format}.
Ambiente do subprocesso: provedores por comando (de síntese e de transcrição) rodam com os
segredos do Hermes removidos do ambiente filho — tokens de bot, chaves de provedor de modelo e
credenciais internas saem; PATH, HOME, localidade e outras variáveis normais ficam. Se o seu
comando precisa de uma chave própria do ambiente (numa chamada de curl, por exemplo), liste os
nomes em env_passthrough:
tts:
providers:
mycloud:
type: command
command: 'curl -s -H "Authorization: Bearer $MYCLOUD_API_KEY" ... -o {output_path}'
env_passthrough: [MYCLOUD_API_KEY]
Exemplo: Doubao (seed-tts-2.0, chinês)
Para síntese de alta qualidade em chinês, pela API de transmissão bidirecional
seed-tts-2.0 da ByteDance, instale o pacote
doubao-speech e ligue como provedor por comando:
pip install doubao-speech
export VOLCENGINE_APP_ID="seu-app-id"
export VOLCENGINE_ACCESS_TOKEN="seu-token"
tts:
provider: doubao
providers:
doubao:
type: command
command: "doubao-speech say --text-file {input_path} --out {output_path}"
output_format: mp3
max_text_length: 1024
timeout: 30
As credenciais vêm do ambiente do seu shell (VOLCENGINE_APP_ID e VOLCENGINE_ACCESS_TOKEN) ou do
~/.doubao-speech/config.yaml. Escolha uma voz acrescentando --voice zh-female-warm (ou outro
apelido de doubao-speech list-voices) ao comando. O doubao-speech também traz transcrição em
transmissão contínua — veja a seção de transcrição. Código e
documentação completa: github.com/Hypnus-Yuan/doubao-speech.
Espaços reservados
O seu modelo de comando pode usar estes espaços. O Hermes substitui na hora e coloca aspas conforme o contexto (sem aspas, com aspas simples, com aspas duplas), então caminhos com espaço e outros caracteres sensíveis ao shell ficam seguros.
| Espaço reservado | Significado |
|---|---|
{input_path} | Caminho do arquivo temporário de texto em UTF-8 |
{text_path} | Apelido de {input_path} |
{output_path} | Caminho em que o comando precisa gravar o áudio |
{format} | mp3 / wav / ogg / flac |
{voice} | tts.providers.<nome>.voice, vazio quando não definido |
{model} | tts.providers.<nome>.model |
{speed} | Multiplicador de velocidade resolvido (do provedor ou global) |
Use {{ e }} para chaves literais.
Chaves opcionais
| Chave | Padrão | Significado |
|---|---|---|
timeout | 120 | Segundos de inatividade; saída padrão ou de erro reinicia a contagem. A árvore de processos é encerrada depois disso (killpg no Unix, taskkill /T no Windows). |
output_format | mp3 | Um de mp3, wav, ogg ou flac. Deduzido da extensão quando o Hermes escolhe o caminho. |
voice_compatible | false | Com true, o Hermes converte a saída MP3 ou WAV para Opus/OGG com o ffmpeg, para o Telegram mostrar bolha de voz. |
max_text_length | 5000 | Máximo de caracteres por invocação; texto maior é dividido em pedaços ordenados. |
voice / model | vazio | Passados ao comando só como valores dos espaços reservados. |
Detalhes de comportamento
- Nomes embutidos sempre vencem. Uma entrada
tts.providers.openainunca cobre o provedor nativo da OpenAI, então nenhuma configuração de usuário substitui um embutido em silêncio. - A entrega padrão é como documento. Provedores por comando entregam como anexo de áudio comum
em todas as plataformas. Para bolha de voz, use
voice_compatible: truenaquele provedor. - Falhas do comando chegam ao agente. Saída diferente de zero, saída vazia ou tempo esgotado devolvem um erro com a saída de erro e a saída padrão do comando, para você depurar pela conversa.
type: commandé o padrão quando hácommand:. Escrevertype: commandexplicitamente é boa prática, mas não é obrigatório.{input_path}e{text_path}são intercambiáveis. Use o que ler melhor.
Segurança
Provedores por comando rodam qualquer coisa que você configurar, com as permissões do seu usuário. O Hermes coloca aspas nos valores e respeita o tempo limite, mas o modelo de comando em si é entrada local confiável — trate do mesmo jeito que trataria um script de shell no seu PATH.
Provedores por plugin em Python
Para motores que não cabem num comando só — SDKs de Python sem CLI, motores de transmissão contínua,
APIs de listagem de voz, autenticação com renovação de OAuth — registre um plugin com
ctx.register_tts_provider(). O plugin convive com o registro de provedores por comando (não o
substitui); escolha a superfície que combina com o seu motor.
Qual escolher
| Seu backend tem… | Use |
|---|---|
| Uma CLI que lê texto de arquivo ou entrada padrão e grava áudio num arquivo | Provedor por comando (sem Python) |
| Duas ou três CLIs encadeadas com pipes | Provedor por comando |
| Só um SDK de Python, sem CLI | Plugin |
| Bytes em transmissão contínua, para entrega em pedaços | Plugin (sobrescrevendo stream()) |
Uma API de listagem de vozes, usada pelo hermes setup | Plugin (sobrescrevendo list_voices()) |
| Fluxo de renovação OAuth (não um token estático) | Plugin |
Embutidos sempre vencem, e provedores por comando vencem um plugin de mesmo nome — então plugins são seguros de registrar com qualquer nome que não seja embutido.
Plugin mínimo
Coloque isto em ~/.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" # com o que o tts.provider casa
@property
def display_name(self) -> str:
return "My Custom TTS"
def is_available(self) -> bool:
# Devolva False quando faltar credencial ou dependência — o seletor
# pula essa linha, mas o despachante ainda roteia aqui se configurado.
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:
# Grave os bytes de áudio em output_path e devolva o caminho.
# Levante exceção em caso de falha — o despachante converte em erro padrão.
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())
Habilite (hermes plugins enable my-tts), aponte o tts.provider para ele
(tts.provider: my-tts, no config.yaml) e a ferramenta text_to_speech passa pelo seu plugin.
Ganchos opcionais
Sobrescreva na sua classe, para uma integração mais rica:
list_voices()→ lista de dicionários{id, display, language, gender, preview_url}, mostrados emhermes tools.list_models()→ lista de{id, display, languages, max_text_length}.get_setup_schema()→ devolve{name, badge, tag, env_vars: [{key, prompt, url}]}, para montar a linha do seletor emhermes toolsehermes setup. Sem isso, o plugin funciona, mas a linha fica mínima.stream(text, *, voice, model, format, **extra)→ iterador de bytes de áudio, para entrega em transmissão contínua (por padrão, levantaNotImplementedError).- Propriedade
voice_compatible→Truese a sua saída é compatível com Opus e o gateway deve entregar como bolha de voz (padrãoFalse, que entrega como anexo comum).
Veja o agent/tts_provider.py para a classe base completa.
Transcrição de mensagens de áudio
Mensagens de voz enviadas no Telegram, Discord, WhatsApp, Slack ou Signal são transcritas automaticamente e injetadas como texto na conversa. O agente vê a transcrição como texto normal.
| Provedor | Qualidade | Custo | Chave |
|---|---|---|---|
| Whisper local (padrão) | Boa | Grátis | Nenhuma |
| Whisper da Groq | Boa a ótima | Camada gratuita | GROQ_API_KEY |
| Whisper da OpenAI | Boa a ótima | Pago | VOICE_TOOLS_OPENAI_KEY ou OPENAI_API_KEY |
A transcrição local funciona de imediato com o faster-whisper instalado. Se ele não estiver
disponível, o Hermes também usa uma CLI whisper local, de locais comuns de instalação (como
/opt/homebrew/bin), ou um comando próprio, definido em HERMES_LOCAL_STT_COMMAND.
Configuração
# em ~/.hermes/config.yaml
stt:
provider: "local" # "local" | "groq" | "openai" | "mistral" | "xai" | "elevenlabs" | "deepinfra"
language: "pt" # dica global de idioma, aplicada a todo provedor sem language próprio; "" volta à detecção automática
local:
model: "base" # tiny, base, small, medium, large-v3
language: "" # dica ISO-639-1 opcional; vazio usa HERMES_LOCAL_STT_LANGUAGE, senão detecta
groq:
language: ""
openai:
model: "whisper-1" # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe, gpt-transcribe
mistral:
model: "voxtral-mini-latest" # voxtral-mini-latest, voxtral-mini-2602
xai:
model: "grok-stt"
language: ""
Detalhes dos provedores
Local (faster-whisper) — roda o Whisper localmente, pelo faster-whisper. Usa o processador por padrão, e a placa de vídeo quando há. Tamanhos:
| Modelo | Tamanho | Velocidade | Qualidade |
|---|---|---|---|
tiny | ~75 MB | O mais rápido | Básica |
base | ~150 MB | Rápido | Boa (padrão) |
small | ~500 MB | Médio | Melhor |
medium | ~1,5 GB | Mais lento | Ótima |
large-v3 | ~3 GB | O mais lento | A melhor |
Groq — exige GROQ_API_KEY. Boa reserva na nuvem, quando você quer uma opção hospedada
gratuita. Defina stt.groq.language (ou a variável global HERMES_LOCAL_STT_LANGUAGE) para pular a
detecção automática e reduzir a espera em áudio de idioma conhecido.
OpenAI — aceita primeiro a VOICE_TOOLS_OPENAI_KEY e cai na OPENAI_API_KEY. Suporta
whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe e gpt-transcribe.
Mistral (Voxtral Transcribe) — exige MISTRAL_API_KEY. Usa os modelos
Voxtral Transcribe. Suporta 13
idiomas, separação de falantes e marcação de tempo por palavra. Instale com
cd ~/.hermes/hermes-agent && uv pip install -e ".[mistral]".
xAI Grok STT — exige XAI_API_KEY. Envia para https://api.x.ai/v1/stt, como
multipart/form-data. Boa escolha se você já usa a xAI para conversa ou voz e quer uma chave só. Na
detecção automática, ele vem depois da Groq — defina stt.provider: xai para forçar.
Comando local próprio — defina HERMES_LOCAL_STT_COMMAND se quiser que o Hermes chame um
comando de transcrição direto. O modelo aceita {input_path}, {output_dir}, {language} e
{model}. O Hermes divide o comando renderizado numa lista de argumentos e executa sem shell, então
operadores como |, >, && e ; chegam como argumentos literais. O seu comando precisa gravar um
.txt com a transcrição em algum lugar dentro de {output_dir}.
Exemplo: Doubao e Volcengine ASR
Se você usa o doubao-speech para a síntese Doubao, o
mesmo pacote faz a transcrição, pelo caminho de comando local:
pip install doubao-speech
export VOLCENGINE_APP_ID="seu-app-id"
export VOLCENGINE_ACCESS_TOKEN="seu-token"
export HERMES_LOCAL_STT_COMMAND='doubao-speech transcribe {input_path} --out {output_dir}/transcript.txt'
Se um modelo local confiável precisa mesmo de pipes ou redirecionamentos, invoque o shell explicitamente. Mantenha os caminhos dinâmicos fora do programa do shell, passando como argumentos posicionais:
export HERMES_LOCAL_STT_COMMAND='sh -c '\''whisper "$1" --output_format txt --output_dir "$2" | tee "$2/whisper.log"'\'' _ {input_path} {output_dir}'
No Windows, use um invólucro explícito de cmd /c ou PowerShell. Um invólucro explícito torna a
interpretação pelo shell uma escolha consciente, e não uma propriedade implícita de todo comando
local de transcrição.
stt:
provider: local_command
O Hermes grava a mensagem de voz recebida em {input_path}, roda o comando e lê o .txt produzido
em {output_dir}. O idioma é detectado sozinho pelo endpoint da Volcengine.
Comportamento de reserva
Uma escolha explícita em stt.provider (gravada no config.yaml, por hermes tools, por
exemplo) é respeitada estritamente — se aquele provedor não roda, a transcrição falha com um erro
claro (stt is configured to use <provedor> (set via hermes tools), but <falha>. Run 'hermes tools' to change it.)
em vez de trocar de motor em silêncio. Vale notar que stt.provider: local gravado na sua
configuração conta como escolha explícita.
Quando nenhum provedor foi escolhido, o Hermes detecta pelo que houver:
- faster-whisper local indisponível → tenta uma CLI
whisperlocal ou oHERMES_LOCAL_STT_COMMAND, antes dos provedores de nuvem - chave da Groq não definida → pulado; vai para o próximo
- chave da OpenAI não definida → pulado; vai para o próximo
- chave ou SDK da Mistral não definidos → pulado na detecção; vai para o próximo
- nada disponível → as mensagens de voz passam adiante, com uma nota precisa para a pessoa
Provedores de transcrição por comando
Se o motor que você quer não é suportado nativamente (Doubao ASR, NVIDIA Parakeet, uma compilação do whisper.cpp, uma CLI do SenseVoice, qualquer coisa com comando de shell), ligue como provedor do tipo comando, sem escrever Python. O Hermes roda o seu comando contra o arquivo de áudio e lê a transcrição de volta.
Declare um ou mais em stt.providers.<nome> e troque com stt.provider: <nome> — mesmo formato do
registro de comandos da síntese, adaptado para o sentido de áudio na
entrada e transcrição na saída.
stt:
provider: parakeet # qualquer nome sob 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: pt
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
Isso complementa o escape antigo do HERMES_LOCAL_STT_COMMAND, pelo caminho embutido
local_command. Diferente do registro por comando, o modelo antigo é dividido em argumentos e roda
sem interpretação implícita de shell. Use o stt.providers.<nome> quando quiser vários motores
de transcrição por shell, um nome escolhível em stt.provider, ou qualquer coisa que precise de
language, model ou timeout por provedor.
Espaços reservados da transcrição
O seu modelo de comando pode usar estes. O Hermes substitui na hora e coloca aspas conforme o contexto, então caminhos com espaço ficam seguros.
| Espaço reservado | Significado |
|---|---|
{input_path} | Caminho absoluto do áudio de entrada (local original, só leitura) |
{output_path} | Caminho absoluto em que o comando deve gravar a transcrição |
{output_dir} | Diretório pai do {output_path} (útil para ferramentas estilo whisper) |
{format} | Formato de saída configurado: txt, json, srt ou vtt |
{language} | Código de idioma configurado (padrão: en) |
{model} | stt.providers.<nome>.model, vazio quando não definido |
Use {{ e }} para chaves literais (útil ao embutir JSON no comando).
Como a transcrição é lida de volta
Depois que o seu comando termina com sucesso:
- Se o
{output_path}existe e não está vazio → o Hermes o lê como texto UTF-8. - Senão, se o comando escreveu na saída padrão → o Hermes usa isso.
- Senão → erro: "Command STT provider wrote no output file and produced no stdout".
Assim o registro serve tanto para CLIs que gravam arquivo (whisper-cli, parakeet-asr) quanto para
comandos de uma linha, estilo curl, que despejam a transcrição na saída padrão
(curl … | jq -r .text).
Para format: json, srt ou vtt, o Hermes devolve o conteúdo cru do arquivo no campo
transcript. Extrair o .text de um JSON está fora do escopo do executor — configure
format: txt, ou processe o JSON depois.
Chaves opcionais da transcrição por comando
| Chave | Padrão | Significado |
|---|---|---|
timeout | 300 | Segundos; a árvore de processos é encerrada no vencimento (start_new_session no Unix, taskkill /T no Windows). |
format | txt | Um de txt, json, srt ou vtt. Define a extensão do {output_path}. |
language | en | Repassado ao {language}. Cai no stt.language e então em en. |
model | vazio | Repassado ao {model}. O argumento model= do transcribe_audio() sobrescreve. |
Detalhes de comportamento
- Embutidos sempre vencem. Declarar
stt.providers.openai: type: commandNÃO substitui o manipulador real do Whisper da OpenAI. O nome embutido é resolvido antes de o resolvedor de comandos rodar. - Limpeza da árvore de processos. Um comando que passa do
timeouttem a árvore inteira encerrada, não só o invólucro de shell. Pipelines de transcrição que criam subprocessos de carregamento de modelo são recolhidos de forma confiável. - As aspas são automáticas. Espaços reservados dentro de
'…'recebem escape seguro para aspas simples; dentro de"…", escape de$, crase e"; fora de aspas,shlex.quote. Não coloque aspas você mesmo nos valores.
Segurança
O comando roda com o mesmo usuário do Hermes, com acesso total ao sistema de arquivos — mesmo modelo
de confiança do tts.providers.<nome>: type: command e do HERMES_LOCAL_STT_COMMAND. Só declare
provedores por comando de fontes em que você confia.
Provedores de transcrição por plugin em Python
Para motores que não são embutidos E não cabem num comando de shell (precisam de SDK Python,
autenticação com renovação de OAuth, pedaços em transmissão contínua), registre um plugin com
ctx.register_transcription_provider(). O plugin convive com os 8 provedores embutidos (local,
local_command, groq, openai, mistral, xai, elevenlabs, deepinfra) e com o registro
stt.providers.<nome>: type: command — os embutidos mantêm a implementação nativa e sempre vencem em
caso de nome igual; provedores por comando vencem plugins de mesmo nome (configuração é mais local
que instalação de plugin).
Qual escolher
| O seu backend tem… | Use |
|---|---|
| Um comando de shell que recebe um áudio e emite texto | stt.providers.<nome>: type: command (sem Python) |
| Só o escape antigo, de um comando único | A variável HERMES_LOCAL_STT_COMMAND (dividida em argumentos, sem shell implícito) |
| Um SDK Python sem CLI | Plugin com register_transcription_provider() |
| Autenticação com renovação de OAuth, transmissão em pedaços, metadados de vozes | Plugin com register_transcription_provider() |
Um embutido já cobre (local, groq, openai…) | Defina stt.provider: <nome> — os embutidos são internos |
Ordem de resolução
stt.provideré um nome embutido → despacho embutido. Sempre vence.stt.providercasa com umstt.providers.<nome>que temcommand:→ executor de comando. Vence um plugin de mesmo nome.stt.providercasa com umTranscriptionProviderregistrado por plugin → despacho pelo plugin:- se o
is_available()do plugin devolveFalse(falta credencial ou SDK), a chamada devolve um erro de indisponibilidade identificando o plugin — não a mensagem genérica de "No STT provider available". - senão, o
transcribe()do plugin é chamado commodel(do argumento públicomodel=, caindo emstt.<provedor>.model) elanguage(destt.<provedor>.language).
- se o
- Nenhuma correspondência → erro "No STT provider available".
Espaço de configuração por provedor
Os plugins leem a configuração deles em stt.<provedor>, no config.yaml, espelhando como os
embutidos leem stt.openai.model e stt.mistral.model:
stt:
provider: my-stt
my-stt:
model: whisper-large-v3
language: pt # repassado como language= ao transcribe()
# outras chaves específicas do plugin vão aqui; leia pelo seu próprio
# acesso ao config.yaml, no __init__, no is_available ou no transcribe
O despachante repassa model e language dessa seção; o resto, o plugin lê sozinho.
Plugin mínimo
Coloque em ~/.hermes/plugins/my-stt/.
plugin.yaml:
name: my-stt
version: 0.1.0
description: "My custom Python STT backend"
__init__.py:
from agent.transcription_provider import TranscriptionProvider
class MySTTProvider(TranscriptionProvider):
@property
def name(self) -> str:
return "my-stt" # com o que o stt.provider casa
@property
def display_name(self) -> str:
return "My Custom STT"
def is_available(self) -> bool:
# Devolva False quando faltar credencial ou dependência — o seletor
# pula essa linha, mas o despachante ainda roteia aqui se configurado.
import os
return bool(os.environ.get("MY_STT_API_KEY"))
def transcribe(self, file_path, *, model=None, language=None, **extra):
# Devolva o envelope padrão:
# {"success": bool, "transcript": str, "provider": str, "error": str}
# NÃO levante exceção — converta em envelope de erro, para quem chama
# (gateway ou CLI) ver um formato consistente na falha.
try:
import my_stt_sdk
client = my_stt_sdk.Client()
text = client.transcribe(open(file_path, "rb"))
return {
"success": True,
"transcript": text,
"provider": "my-stt",
}
except Exception as exc:
return {
"success": False,
"transcript": "",
"error": f"my-stt failed: {exc}",
"provider": "my-stt",
}
def register(ctx):
ctx.register_transcription_provider(MySTTProvider())
Habilite (hermes plugins enable my-stt), defina stt.provider: my-stt no config.yaml, e a
transcrição de mensagens de voz passa pelo seu plugin.
Ganchos opcionais
Sobrescreva na sua classe, para uma integração mais rica:
list_models()→ lista de{id, display, languages, max_audio_seconds}.default_model()→ o texto devolvido quando a pessoa não escolhe modelo.get_setup_schema()→ devolve{name, badge, tag, env_vars: [{key, prompt, url}]}, para montar as linhas do seletor emhermes toolsehermes setup(a categoria de transcrição ainda não foi publicada no seletor — esses metadados existem para compatibilidade futura).
Veja o agent/transcription_provider.py para a classe base completa.