語音管線
語音進、語音出——兩條目前不共用設定的路徑,以及本頁原本宣稱、但程式碼裡不存在的四個元件。
2026-09 之前,本頁描述的是一套四元件的本地優先管線:SenseVoice ONNX + Deepgram ASR、Silero VAD、symphonia 音訊解碼、LiveKit 多 agent 語音房。
對整個 repo grep sensevoice、deepgram、silero、symphonia、livekit,沒有任何程式碼、也沒有任何依賴——只有 .cargo/audit.toml 裡一行註解把 livekit-api 寫成某條 RUSTSEC 豁免的理由,而 Cargo.lock 裡根本沒有那個 crate。這四項從未出貨過。
以下是真正存在的東西。
兩條路徑、兩套設定
Section titled “兩條路徑、兩套設定”最重要的一點: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 | body 上限 10 MiB。在碰 body 之前先從 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,兩個實作):
OpenAiCompatStt—POST {base_url}/audio/transcriptions,multipart 帶 bearer key。OpenAI Whisper 與 Groq Whisper 都講這個形狀。CommandStt— 本機子行程樣板(例如whisper-cli);音訊寫到暫存檔、餵給指令、從 stdout 讀回逐字稿。
[voice]stt_provider = "openai_compat" # 或 "command";未設定 ⇒ /api/stt 回 501stt_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,同一個 TtsProvider trait 下四個實作):
| 供應商 | 位置 | 說明 |
|---|---|---|
PiperTtsProvider |
本機 | 從模型目錄讀 ONNX 語音 |
EdgeTtsProvider |
雲端 | 免費、不用 API key |
MiniMaxTts |
雲端 | T2A v2;分析文字內容自動挑 CJK 或拉丁語音 |
OpenAiTtsProvider |
雲端 | 依字元計費 |
TtsRouter 以三種策略之一在它們之間分派: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 切換)直接 new 一個 EdgeTtsProvider,同樣寫死;音訊上傳失敗時會退回純文字。
所以儀表板「語音」分頁寫入的 inference.toml [voice] tts_provider/tts_voice 只影響 POST /api/tts,目前對 Telegram 路徑沒有任何影響。在 UI 改了它們,Telegram 的語音訊息行為不會變。這是已知缺口,寫在這裡,免得操作者自己撞上。
v1.67.1 起,「語音」分頁拿掉了「語音回覆模式」「語音辨識」「語言」三個欄位。它們寫入的 voice_reply_enabled、asr_provider、asr_language 在 gateway 裡沒有任何程式讀取。已經存在 inference.toml 的值保持原樣,分頁儲存時不再送這三個鍵;v1.68.0 起設定結構也拿掉了這三個欄位,system.update_config 收到時忽略。/api/stt 的語音轉文字設定在同一分頁的進階卡片(config.toml [voice] stt_*)。
本機 Whisper
Section titled “本機 Whisper”duduclaw-inference::whisper 有兩種模式:Api(OpenAI Whisper API)與 Local { model_path }(透過 whisper-rs 的 whisper.cpp)。本機模式在非預設的 whisper Cargo feature 之後——該 crate 的 default 是 ["cpu"]——所以出廠 release binary 只有 API 模式。
Discord 語音
Section titled “Discord 語音”discord_voice.rs 在非預設的 discord-voice Cargo feature 之後包裝 Songbird;gateway 的 default 是 ["dashboard", "desktop"],所以出廠 build 不含它。它背後今天也沒有 VAD、沒有接 ASR——這個模組涵蓋的是加入頻道與播放音訊。
ONNX embedding
Section titled “ONNX embedding”duduclaw-inference 的 onnx feature 啟用 OnnxEmbeddingProvider(ONNX Runtime + WordPiece tokenizer),用於 bge-small-zh 之類的模型。它不是語音元件,而是記憶系統語意搜尋共用的基礎設施(見 10-cognitive-memory.md),而且同樣不在該 crate 的 default features 裡。
與其他系統的互動
Section titled “與其他系統的互動”- 通道 — Telegram 會自動轉錄語音與音訊訊息,
/voice逐聊天室切換語音回覆。其他通道沒有語音路徑。 - Multi-runtime — 逐字稿就是普通文字:agent 用哪個 runtime 就由誰處理。
- 記憶 — 語音輪次以普通情節記憶儲存(逐字稿+metadata)。
- 去識別化 — 從
/api/stt進來的逐字稿與其他文字一樣,套用同一套 MCP 側去識別化規則;Telegram 路徑把逐字稿餵進一般回覆路徑。
語音在兩個地方會動,而誠實版本就直說:一組由儀表板設定的 fail-closed HTTP 端點,加上一個語音回覆供應商仍寫死的 Telegram handler(轉錄自 v1.68.0 起跟著儀表板設定)。點名第二個,比畫一張從來沒寫過的 VAD 流程圖有用得多。