コンテンツにスキップ

音声パイプライン

音声を入れて音声を出す——設定をまだ共有していない2つの経路と、このページがかつて主張していたがコードの無い4つのコンポーネント。


2026-09 まで、このページは4コンポーネントのローカルファースト・パイプラインを説明していました:SenseVoice ONNX + Deepgram の ASR、Silero VAD、symphonia による音声デコード、LiveKit のマルチエージェント音声ルーム。

リポジトリ全体を sensevoice・deepgram・silero・symphonia・livekit で grep しても、コードも依存関係も出てきません——出てくるのは .cargo/audit.toml の中で livekit-api を RUSTSEC 免除の根拠として挙げた1行のコメントだけで、その crate は Cargo.lock に存在しません。この4つは一度も出荷されていません。

以下が実際に存在するものです。


最も重要な点:HTTP の音声エンドポイントと Telegram の音声ハンドラは別々に配線されています。 v1.68.0 から音声認識(config.toml [voice] stt_*)は両方に届き、LINE の音声メッセージにも使われます。音声合成(inference.toml [voice] tts_*)は引き続き HTTP エンドポイントにだけ効きます。

経路1 — HTTP エンドポイント(ダッシュボード、WebChat)

Section titled “経路1 — HTTP エンドポイント(ダッシュボード、WebChat)”
エンドポイント 認証 挙動
POST /api/stt Bearer JWT ボディ上限 10 MiB。ボディに触れる前に config.toml [voice] からプロバイダを解決し、未設定なら 501 で fail-closed——推測も文字起こしの捏造もしません
POST /api/tts Bearer JWT リクエストごとに inference.toml [voice] tts_provider / tts_voice を読み、TtsRouter の戦略を選びます(edge-tts → edge のみ、minimax / openai-tts → クラウド優先、それ以外 → ローカル優先)。none / off / disabled のときは 501 を返します
GET/POST /api/voice/config Bearer JWT [voice] の STT 設定を読み書き

STT プロバイダ(stt.rs、実装は2つ):

  • OpenAiCompatStt — POST {base_url}/audio/transcriptions、bearer key 付きの multipart。OpenAI Whisper も Groq Whisper もこの形を話します。
  • CommandStt — ローカルのサブプロセステンプレート(例:whisper-cli)。音声を一時ファイルに書き、コマンドに渡し、stdout から文字起こしを読み戻します。
[voice]
stt_provider = "openai_compat" # または "command"。未設定 ⇒ /api/stt は 501
stt_base_url = "https://api.openai.com/v1"
stt_api_key = "sk-..." # または stt_api_key_enc(AES-256-GCM)
stt_model = "whisper-1"
stt_command = "whisper-cli -m /models/ggml-base.bin -f {audio} --output-txt --no-prints"

TTS プロバイダ(tts.rs、1つの TtsProvider トレイトの下に4実装):

プロバイダ 場所 備考
PiperTtsProvider ローカル モデルディレクトリの ONNX ボイス
EdgeTtsProvider クラウド 無料、API キー不要
MiniMaxTts クラウド T2A v2。テキストを解析して CJK かラテン系のボイスを選択
OpenAiTtsProvider クラウド 文字単位課金

TtsRouter は3つの戦略のいずれかで振り分けます:LocalFirst(ローカル → edge → MiniMax → OpenAI)、EdgeOnly、CloudBest。

経路2 — Telegram の音声メッセージ

Section titled “経路2 — Telegram の音声メッセージ”

音声または音声ファイルのメッセージは transcribe_voice を起動します:bot が getFile を呼び、返ってきた file_path をトラバーサル検証(..、絶対パス、NUL、許可外文字)し、content_length と実バイト数の両方でサイズ上限を確認してダウンロードし、文字起こしします。

v1.68.0 からは stt::transcribe_channel_audio を呼び、ダッシュボードの音声認識設定(config.toml [voice] stt_*、/api/stt と同じプロバイダ)を使います。 音声認識プロバイダが未設定の場合だけ、環境変数 OPENAI_API_KEY の OpenAI Whisper にフォールバックします。設定済みのプロバイダが失敗した場合はエラーになり、フォールバックしません。言語は引き続き zh 固定です。LINE の音声メッセージも同じ経路です。v1.68.0 より前は、どちらのチャネルも常に環境変数キーの Whisper を使っていました。 音声返信(チャットごとに /voice で切替)は EdgeTtsProvider を直接構築します。これもハードコードで、音声アップロードが失敗したらテキストにフォールバックします。

つまりダッシュボードの「語音」タブが書き込む inference.toml [voice] tts_provider / tts_voice は POST /api/tts にだけ効き、現状 Telegram 経路には影響しません。UI で変えても Telegram の音声メッセージの挙動は変わりません。既知のギャップとして、運用者が自分でぶつかる前にここに明記します。

v1.67.1 から、「語音」タブには「語音回覆模式」(音声返信モード)、「語音辨識」(音声認識)、「語言」(言語)の3項目が表示されません。これらが書き込んでいた voice_reply_enabled、asr_provider、asr_language を gateway で読むコードはありません。inference.toml に既にある値はそのまま残り、タブの保存ではこれらのキーを送りません。v1.68.0 からは設定構造からもこれらのフィールドが削除され、system.update_config は受け取っても無視します。/api/stt の音声認識は同じタブの詳細カード(config.toml [voice] stt_*)で設定します。


duduclaw-inference::whisper には2つのモードがあります:Api(OpenAI Whisper API)と Local { model_path }(whisper-rs 経由の whisper.cpp)。ローカルモードは既定ではない whisper Cargo feature の後ろにあります——この crate の default は ["cpu"] です——ので、素のリリースバイナリには API モードしかありません。


discord_voice.rs は既定ではない discord-voice Cargo feature の後ろで Songbird をラップします。gateway の default は ["dashboard", "desktop"] なので、素のビルドには含まれません。その背後に VAD も ASR の配線も今日は存在せず、このモジュールが扱うのはチャンネル参加と音声再生です。


duduclaw-inference の onnx feature は OnnxEmbeddingProvider(ONNX Runtime + WordPiece トークナイザ)を有効にし、bge-small-zh のようなモデルに使います。これは音声コンポーネントではなく、メモリシステムのセマンティック検索が使う共有インフラです(10-cognitive-memory.md 参照)。そしてこれも crate の default features には含まれません。


  • チャネル — Telegram は音声・音声ファイルのメッセージを自動で文字起こしし、/voice がチャットごとに音声返信を切り替えます。他のチャネルに音声経路はありません。
  • マルチランタイム — 文字起こしはただのテキストです。エージェントが使うランタイムがそのまま処理します。
  • メモリ — 音声ターンは通常のエピソード記憶として保存されます(文字起こし+メタデータ)。
  • 匿名化 — /api/stt から入った文字起こしは他のテキストと同じで、同じ MCP 側の匿名化ルールを通ります。Telegram 経路は文字起こしを通常の返信経路に流します。

音声が動くのは2か所で、正直版はそれをそのまま言います:ダッシュボードが設定する fail-closed な HTTP のペアと、音声返信のプロバイダがまだハードコードされた Telegram ハンドラ(文字起こしは v1.68.0 からダッシュボード設定に従います)。2つ目を名指しするほうが、一度も書かれなかった VAD の図より役に立ちます。