Murf Falcon streaming TTS backend for Hermes, with a persistent multi-sentence live session mode
Project description
hermes-murf-plugin
Murf TTS for Hermes. Streams audio over Murf's Falcon WebSocket API with sub-100ms time-to-first-audio, plus a persistent session mode for real-time, sentence-by-sentence voice agents.
Why this exists
Hermes ships with a TTSProvider interface but no first-party Murf backend. This plugin fills that gap and adds one thing most TTS integrations skip: a persistent connection mode. The naive approach opens a new WebSocket per sentence, paying a full handshake every time. For a voice agent speaking sentence-by-sentence as an LLM generates text, that's the difference between a natural reply and a choppy one. open_live_session() keeps one connection open for the whole turn instead.
Quickstart
hermes plugins install sanchitasunil/hermes-murf-plugin
pip install 'websockets>=12'
That's it. hermes plugins install clones this repo and drops it into ~/.hermes/plugins/murf-tts for you, and prompts for MURF_API_KEY on the spot (get one at murf.ai/api) if it isn't already set.
Alternative: install via pip
pip install hermes-plugin-murf-tts
Hermes auto-discovers it on next startup through the hermes_agent.plugins entry point, no manual enable step needed. Use this if you're managing Hermes plugins as regular Python dependencies (a requirements file, a Docker image, etc.) instead of through hermes plugins install.
Then in ~/.hermes/config.yaml:
tts:
provider: murf
Pick a voice with list_voices(), or just set a default:
export MURF_DEFAULT_VOICE=en-US-terrell
Features
- Streaming synthesis
stream(), a one-shot generator matching theTTSProviderABC contract. - Persistent live sessions
open_live_session()/MurfLiveSession, one connection for an entire multi-sentence reply instead of one per sentence. - Correct voice catalog lookups
list_voices()filters by model. Falcon and Gen2 have separate catalogs on Murf's side; skip the filter and you silently lose real voices. - Batch fallback
synthesize()writes a complete file for callers that don't stream.
Usage
For normal use you don't touch these directly. Set tts: provider: murf in your config and Hermes instantiates the provider and calls it for you. The examples below show the API surface for anyone building on top of it, e.g. from hermes_murf_plugin import MurfTTSProvider. (play() here is a stand-in for however you send audio to your output.)
One-shot streaming:
from hermes_murf_plugin import MurfTTSProvider
provider = MurfTTSProvider()
for chunk in provider.stream("Hello there!", voice="en-US-terrell"):
play(chunk)
Persistent session (use this when text arrives incrementally, e.g. sentence-by-sentence from an LLM):
session = provider.open_live_session(voice="en-US-terrell")
session.send_text("Hey, thanks for waiting.")
session.send_text("Here's what I found.", end=True) # end=True closes the context, not the connection
for chunk in session.iter_audio():
play(chunk)
session.close()
One thread calls send_text(), one thread iterates iter_audio(). Same one-sender/one-receiver rule websockets.sync itself enforces; don't call either method from two threads at once.
Configuration
| Env var | Required | Purpose |
|---|---|---|
MURF_API_KEY |
Yes | Your Murf API key (get one here) |
MURF_DEFAULT_VOICE |
No | Fallback voice ID when none is passed per call |
MURF_TTS_MODEL |
No | falcon-2 (default) or gen2 |
MURF_API_BASE_URL |
No | Override the REST base URL (default https://api.murf.ai) |
Verified in production
This has been running behind a live Discord voice bot doing sentence-by-sentence streaming replies, live voice switching, and multi-speaker input. These are confirmed from real usage, not just read off the docs:
| Claim | What we saw |
|---|---|
GET /v1/speech/voices?model=FALCON (or GEN2) |
Required. Without the model param, the endpoint returns an incomplete catalog. Confirmed missing real voices (en-IN-palak, en-IN-anisha) without it. |
| Context-id multiplexing | Several {"text": ..., "context_id": same_id} messages, end: true only on the last, closes that context. The connection itself stays open. Confirmed safe to reuse across a whole multi-sentence reply. |
websockets.sync.client under a threaded caller |
Works cleanly with a dedicated sender thread and reader thread. |
stream() vs open_live_session() |
stream() reconnects every call, fine for one utterance. For a multi-sentence reply, that's a full handshake per sentence. The persistent session removes that cost entirely, and it's the difference that made real-time replies sound natural instead of choppy. |
References
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hermes_plugin_murf_tts-1.0.2.tar.gz.
File metadata
- Download URL: hermes_plugin_murf_tts-1.0.2.tar.gz
- Upload date:
- Size: 12.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ab3770a9d093d91de1ad8239b377a362e42ef943af300d71cc5e8e5679309652
|
|
| MD5 |
6260d2e26e7a8424bdde64e2bfd79b3e
|
|
| BLAKE2b-256 |
3be6c9129bf0f82f279e449d2456faf865bd9bca03a04e7d1ed0d068d6da6354
|
File details
Details for the file hermes_plugin_murf_tts-1.0.2-py3-none-any.whl.
File metadata
- Download URL: hermes_plugin_murf_tts-1.0.2-py3-none-any.whl
- Upload date:
- Size: 10.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f04dd766d837f7956fc5ca24205b90a161497686cbf7c37d992fb733b295d08a
|
|
| MD5 |
06cf8038e869d24691a5ff3c9812e970
|
|
| BLAKE2b-256 |
4e5d070ff6dfcfa10eaad0abce0956e665ce26de74283d1ebcdab67099a20dec
|