Voice Notes

Transcribe inbound voice messages on Telegram, Slack, WhatsApp, Discord, and Signal automatically

Voice notes sent to your bot on Telegram, Slack, WhatsApp, Discord, or Signal are transcribed and handed to the agent as plain text — on by default, with a visible placeholder when transcription is off or fails.

python
from praisonaiagents import Agent
from praisonai.bots import TelegramBot

agent = Agent(name="assistant", instructions="Be helpful.")
bot = TelegramBot(token="YOUR_TOKEN", agent=agent)

# STT is on by default — send a voice note to the bot,
# the agent replies to the transcribed text.
import asyncio
asyncio.run(bot.start())

The user taps the microphone and sends a voice note; the bot transcribes it with whisper-1 and the agent replies to the recognised text.

Diagram

Voice Notes Flow

fail / off

🎤 User voice note

📱 Telegram / Slack / WhatsApp / Discord / Signal

🎙️ STT whisper-1

🧠 Agent

💬 Reply

⚠️ Voice message received

Quick Start

  1. Level 1 — Bool shorthand

    STT is on by default. Turn it off per channel with stt: false.

    yaml
    channels:
      telegram:
        platform: telegram
        token: ${TELEGRAM_BOT_TOKEN}
        stt: true    # default; set false to opt out
  2. Level 2 — Dict

    Tune behaviour with a dict — echo the transcript, force a language, or override the model.

    yaml
    channels:
      telegram:
        platform: telegram
        token: ${TELEGRAM_BOT_TOKEN}
        stt:
          enabled: true
          echo_transcripts: true
          language: en
          model: openai/whisper-1
  3. Level 3 — SttConfigSchema

    Build the channel config in Python with the validated schema.

    python
    from praisonai_bot.bots._config_schema import (
        ChannelConfigSchema,
        SttConfigSchema,
    )
    
    channel = ChannelConfigSchema(
        platform="telegram",
        token="${TELEGRAM_BOT_TOKEN}",
        stt=SttConfigSchema(
            enabled=True,
            echo_transcripts=True,
            language="en",
            model="openai/whisper-1",
        ),
    )

How It Works

The adapter downloads the voice note, transcribes it, and forwards the transcript — never dropping the turn.

Diagram
🧠 Agent🎙️ STT📱 Adapter🎤 User🧠 Agent🎙️ STT📱 Adapter🎤 Useropt[echo_transcripts:true]alt[Transcription succeeds][STT off or fails]Voice noteCache audio (SSRF-safe, kind=”audio”)transcribe_media_path(path)Transcript text“You said: …” (ack)chat(transcript)Reply💬 ReplyNonechat(“[Voice message received]”)Reply💬 Reply
StepWhat happens
DownloadAdapter caches the audio through the SSRF-safe media cache (kind="audio")
Transcribetranscribe_media_path calls stt_tool → AudioAgent.transcribe (whisper-1 by default)
SuccessTranscript passed to agent.chat(transcript); echoed back first when echo_transcripts: true
Failure / offThe [Voice message received] placeholder reaches the agent — the turn is never silently dropped

Configuration Options

Fields from SttConfigSchema / SttConfig.

OptionTypeDefaultDescription
enabledbooltrueTranscribe inbound audio. On by default.
echo_transcriptsboolfalseEcho the recognised text back to the user.
language`str \None`nullOptional forced language code ("en", "es", …).
model`str \None`nullOptional STT model override (default: openai/whisper-1).

The bare shorthand stt: true / stt: false is accepted as {"enabled": <bool>}.

Choosing a configuration

Diagram

no

yes

yes

no

yes

no

Need STT?

stt: false

Fixed language?

stt:
enabled: true
language: en

stt: true
(defaults are fine)

Accuracy-sensitive?

set model: openai/whisper-1

leave model unset


Per-Platform YAML

Telegram
channels:
  telegram:
    platform: telegram
    token: ${TELEGRAM_BOT_TOKEN}
    stt:
      enabled: true
      echo_transcripts: true
      language: en
      model: openai/whisper-1
Slack
channels:
  slack:
    platform: slack
    token: ${SLACK_BOT_TOKEN}
    app_token: ${SLACK_APP_TOKEN}
    stt:
      enabled: true
      language: en
WhatsApp
channels:
  whatsapp:
    platform: whatsapp
    token: ${WHATSAPP_TOKEN}
    stt:
      enabled: true
      echo_transcripts: true
Discord
channels:
  discord:
    platform: discord
    token: ${DISCORD_BOT_TOKEN}
    stt:
      enabled: true
      language: en
Signal
channels:
  signal:
    platform: signal
    account: ${SIGNAL_ACCOUNT}
    bridge_url: ${SIGNAL_BRIDGE_URL}   # optional; default http://localhost:8080
    stt:
      enabled: true
      language: en

Discord and Signal specifics

Both adapters transcribe the first audio/* attachment of a message that has no text, after the access gates run.

Diagram
🧠 Agent🔌 signal-cli-rest-api📱 Discord / Signal adapter🎤 User🧠 Agent🔌 signal-cli-rest-api📱 Discord / Signal adapter🎤 Useropt[Signal]alt[Rejected sender][Admitted]Audio-only messageAllowlist / access gatesStop — no download, no STTGET /v1/attachments/{id}Audio bytesTranscribe (size cap, STT policy)Transcript or “[Voice message received]”💬 Reply
  • Discord — if a message carries both text and audio, the text wins; voice is only transcribed for empty-text messages.
  • Signal — audio is fetched from the operator-configured signal-cli-rest-api bridge. Keep the bridge on localhost or a private network, as in the Signal Bot setup.

When STT Is Off or Fails

Voice notes were previously dropped silently when transcription was unavailable. Now the adapter substitutes the [Voice message received] placeholder so the agent always gets the turn — it can ask the user to retype, fall back to another channel, or acknowledge the message.

User: [voice note]  (STT disabled)
Agent: receives "[Voice message received]" and can reply
       "I got your voice note but can't transcribe it here —
        could you type it out?"

See fixed issue #2721 for context.


Best Practices

Set language when you know it

Forcing language: en (or the relevant code) improves transcription accuracy and speed over auto-detection.

yaml
stt:
  enabled: true
  language: en
Enable echo_transcripts for accessibility

echo_transcripts: true sends the recognised text back to the user so they can confirm what the bot heard — helpful for accessibility and correcting mis-hearings.

yaml
stt:
  enabled: true
  echo_transcripts: true
Opt out on privacy-sensitive channels

Turn STT off where voice content must not leave the device or reach a third-party STT provider.

yaml
stt:
  enabled: false
Ship a fallback plan

The [Voice message received] placeholder means the agent still gets the turn when transcription is off or fails. Give the agent instructions to handle that case gracefully.

python
agent = Agent(
    name="assistant",
    instructions="If a voice note can't be transcribed, ask the user to type it.",
)

Related