Voice reply mirrors inbound Voice Notes (Speech-to-Text), but it is off by default. Set
voice.mode: match_inbound for the intuitive “speak in / speak out” symmetry.Quick Start
1
Level 1 — Bool shorthand
voice: true turns replies into voice notes for every message (mode: always).2
Level 2 — Mode shorthand
A bare
mode implies enabled: true. Use match_inbound to speak back only when the user sent a voice memo.3
Level 3 — Dict
Tune the voice, model, speed, format, and length cap.
4
Level 4 — TtsConfigSchema
Build the channel config in Python with the validated schema.
How It Works
The gateway synthesises the reply after the text is already sent, so voice is always a bonus — never a blocker.Graceful degradation
Voice reply is best-effort — the text reply is always delivered first.- Empty / whitespace text → skipped silently.
- Reply longer than
max_chars→ skipped,INFOlog entry. - TTS tool import fails (missing
openai/litellmdeps) → skipped,WARNINGlog entry. - Synthesis error → skipped,
ERRORlog entry. - Adapter
send_voicefails → logged; the text reply already went out.
Configuration Options
Fields fromTtsConfig / TtsConfigSchema.
Bool shorthand
Resolution order
The effective policy is resolved from, in order:config.metadata["voice"](operator override),config.metadata["tts"](alias),- a direct
config.voiceattribute (schema-backed configs), - a direct
config.ttsattribute, then - the off-by-default
TtsConfig().
Choosing a Mode
Pick the mode that matches how chatty your users want the bot to be.Sentence-Level Streaming
Streaming TTS synthesises and delivers your reply sentence-by-sentence, so the user hears the first clause while the rest is still being spoken. Withstream: false (default) the whole reply is synthesised into one clip before anything plays — the user waits for full-response latency. With stream: true time-to-first-audio drops to first-sentence latency.
When streaming kicks in
Streaming is used only when all three preconditions hold — otherwise the whole-file path runs.stream: trueis set.- The full reply is under
max_chars(the cap is checked against the whole reply before the per-sentence loop, so long replies can’t sneak through as many under-cap clauses). - The reply splits into more than one sentence. A single-sentence reply falls through to the whole-file path — streaming buys nothing there.
Failure semantics
Failures are audible-output-aware, so the user never hears a reply restart.- Nothing played yet + the first clip fails → falls back to whole-file synth on the full text.
- Some audio already played + a clause fails → skip that clause and continue; never replays from the beginning.
When to enable
Flipstream: true when replies are usually multi-sentence and users are waiting for audio.
Per-Platform YAML
Telegram is wired today; other adapters share the same_tts helper and can adopt it without further core changes.
Platform Support
The
[[audio_as_voice]] manual escape hatch continues to work for pre-rendered audio on Telegram.
User Interaction Flow
A voice-in / voice-out conversation on Telegram withmode: match_inbound.
- The user taps 🎙️ and records a voice note on Telegram.
- Inbound STT transcribes it → the agent processes the text → produces a plain-text reply.
- The gateway sees
voice.mode: match_inboundand that the inbound was a voice memo → synthesises the reply withopenai/tts-1. - The user receives both: the text reply and a native voice note they can play right in the chat.
- If the user next sends a text message, they get only a text reply — no unnecessary audio.
- When
stream: trueis set, the user hears the first sentence as soon as it is synthesised while the rest of the reply is still being spoken.
Best Practices
Prefer match_inbound over always
Prefer match_inbound over always
match_inbound speaks back only when the user spoke first — the symmetry users expect. Reserve always for voice-first or accessibility bots.Cap length for chatty agents
Cap length for chatty agents
Set
max_chars low so long analytical replies don’t become multi-minute audio clips. The text reply still carries the full answer.Ship your TTS provider's API key
Ship your TTS provider's API key
openai/tts-1 is the default, so set OPENAI_API_KEY. Any LiteLLM-supported TTS model works via the model: field.Trust the graceful-degradation contract
Trust the graceful-degradation contract
Voice reply is best-effort — the plain-text reply is always delivered first, so users never lose a message even when synthesis fails.
Stream long replies for lower time-to-first-audio
Stream long replies for lower time-to-first-audio
Set
stream: true when replies are typically more than one sentence and users are waiting for audio. Time-to-first-audio drops from full-response latency to first-sentence latency, and the whole-file path is still used automatically for single-sentence replies or replies over max_chars.Keep the manual escape hatch for pre-rendered audio
Keep the manual escape hatch for pre-rendered audio
The
[[audio_as_voice]] marker still works on Telegram for audio you’ve already produced — e.g. a recording pulled from a knowledge base.Related
Voice Notes (Speech-to-Text)
The inbound counterpart — transcribe voice notes into text for the agent.
Audio Tools
tts_tool, stt_tool, and AudioAgent.speech.Voice Notes
The inbound voice-notes doc for gateway bots.
Streaming Replies
The text-streaming counterpart — stream the reply text into chat as it is generated.
Gateway
The overall gateway pattern — voice reply is one of several channel features.

