
Mastra voice-azure 架構解析Azure 語音 TTS/STT 雙向能力的實現原理與實戰配置【免費下載鏈接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.項目地址: https://gitcode.com/GitHub_Trending/ma/mastra本文以 Mastra 倉庫中mastra/voice-azure包的架構文檔為核心深入講解該類如何封裝 Microsoft Azure Cognitive Services Speech SDK實現文本轉語音TTS與語音轉文本STT的雙向能力。讀完后你將掌握AzureVoice的完整配置方式speechModel/listeningModel/speaker、211 個內置聲音的選擇機制、speak()/listen()的底層數據流與錯誤處理策略以及如何將其接入 Mastra 語音生態。包概覽與文件組織mastra/voice-azure是 Mastra 框架的 Azure 語音集成包基于microsoft-cognitiveservices-speech-sdk當前 package.json 中聲明為^1.48.0提供標準化的語音交互接口。它繼承自 Mastra 的基類MastraVoice定義在 voice 基類源碼使 Azure 語音能力可以無縫接入 Mastra 的 Agent、Workflow 等上層組件。頂層組件結構如下AzureVoice (主類) ├── 繼承 MastraVoice (mastra/core / internal/voice) ├── 依賴 │ ├── microsoft-cognitiveservices-speech-sdk │ └── Node.js stream API ├── 配置 │ ├── speechModel (TTS 配置) │ └── listeningModel (STT 配置) └── 靜態數據 └── AZURE_VOICES (211 個聲音定義)倉庫中的文件組織非常清晰見 voice/azure 目錄/src ├── index.ts # AzureVoice 主類實現TTS STT ├── voices.ts # 靜態聲音定義211 個聲音 ID └── index.test.ts # 集成測試套件需真實 Azure 憑據安裝與基本入口npm install mastra/voice-azureimport { AzureVoice } from mastra/voice-azure;AzureVoice 類結構與構造函數配置AzureVoice在 src/index.ts 中定義繼承自MastraVoice。類內部維護四個私有屬性分別對應 TTS 與 STT 兩側的 SDK 實例speechConfig?: Azure.SpeechConfig—— TTS 操作的配置listeningConfig?: Azure.SpeechConfig—— STT 操作的配置speechSynthesizer?: Azure.SpeechSynthesizer—— TTS 合成器實例speechRecognizer?: Azure.SpeechRecognizer—— STT 識別器實例構造參數完整說明構造函數接受一個可選配置對象src/index.ts#L28-L36{ speechModel?: { apiKey?: string // Azure Speech Services API key region?: string // Azure 區域如 eastus voiceName?: string // 默認聲音如 en-US-AriaNeural language?: string // TTS 不使用該字段 }, listeningModel?: { apiKey?: string // Azure Speech Services API key region?: string // Azure 區域 language?: string // 識別語言如 en-US voiceName?: string // STT 不使用該字段 }, speaker?: VoiceId // 默認說話聲音 ID受 VoiceId 類型約束 }參數適用側說明默認值/回退speechModel.apiKeyTTSAzure Speech 訂閱密鑰回退到環境變量AZURE_API_KEYspeechModel.regionTTSAzure 區域如eastus回退到環境變量AZURE_REGIONspeechModel.voiceNameTTS合成聲音名稱默認en-US-AriaNeurallisteningModel.apiKeySTTAzure Speech 訂閱密鑰回退到AZURE_API_KEYlisteningModel.regionSTTAzure 區域回退到AZURE_REGIONlisteningModel.languageSTT識別語言寫入speechRecognitionLanguage未設置時使用 SDK 默認speakerTTS默認聲音VoiceId類型未指定時默認en-US-AriaNeural配置初始化流程構造函數的初始化邏輯src/index.ts#L49-L78遵循五個步驟環境變量回退apiKey與region均支持回退到AZURE_API_KEY/AZURE_REGION環境變量校驗任一模型配置了但缺少憑據時構造函數立即拋出No Azure API key provided for .../No region provided for ...雙配置獨立TTS 與 STT 分別創建各自的Azure.SpeechConfig.fromSubscription(apiKey, region)互不干擾默認聲音TTS 側的聲音按speechModel.voiceName || speaker || en-US-AriaNeural的優先級解析寫入speechSynthesisVoiceNameSDK 實例化構造時即創建SpeechSynthesizer與SpeechRecognizer基礎實例但每次請求會再創建新的合成器見下文。值得注意的是構造函數首先將speechModel.name、listeningModel.name、apiKey、speaker傳給基類MastraVoice的super()。對照基類實現voice.ts#L103-L112基類會把它們存入listeningModel/speechModel/speaker屬性并在serializeForSpan()中輸出不含 apiKey的觀測數據——也就是說憑據只存在于 Azure SDK 側不會泄漏到 Mastra 的 tracing span 中。公開 API 詳解AzureVoice實現了IMastraVoice契約中的四個核心方法其余如connect()、send()、answer()等實時雙工方法保留基類的默認空實現從源碼結構看Azure 側僅做單請求式合成/識別不支持 WebSocket/RTC 實時流。getSpeakers()列出可用聲音async getSpeakers(): PromiseArray{ voiceId: string; language: string; region: string; }實現src/index.ts#L86-L92是對靜態數組AZURE_VOICES做映射從聲音 ID 中解析出語言與區域return AZURE_VOICES.map(voice ({ voiceId: voice, language: voice.split(-)[0], region: voice.split(-)[1], }));這是一個同步操作包裝成 Promise僅為了與接口簽名保持一致。測試套件中對返回結構做了斷言長度大于 0且每項包含voiceId、language、region三個字段index.test.ts#L28-L36。speak()文本轉語音TTS簽名async speak( input: string | NodeJS.ReadableStream, options?: { speaker?: string; [key: string]: any } ): PromiseNodeJS.ReadableStream // WAV 格式音頻流數據流輸入文本/流 ↓ [流轉換]若輸入是 ReadableStream累積為 UTF-8 字符串 ↓ [文本校驗]trim 后為空則拋出 Input text is empty ↓ [聲音配置]若 options.speaker 存在更新 speechSynthesisVoiceName ↓ [Azure 合成]speakTextAsync ↓ [結果校驗]檢查 ResultReason SynthesizingAudioCompleted ↓ [Buffer 包裝]Readable.from([Buffer.from(result.audioData)]) ↓ 輸出音頻流源碼級要點src/index.ts#L103-L166每次請求創建新的合成器const synthesizer new Azure.SpeechSynthesizer(this.speechConfig)不復用構造時的實例5 秒超時保護用Promise.race將合成 Promise 與一個 5000ms 的setTimeoutreject Promise 競速超時拋出Speech synthesis timed out。從源碼結構看長文本合成可能需要調大該值否則會誤超時空輸入校驗!input?.trim()時拋出Input text is empty對應測試 index.test.ts#L150-L152流輸入處理非字符串輸入通過for await逐塊讀取并Buffer.concat讀取失敗時包裝為Failed to read input stream: ...資源清理finally語義上用synthesizer.close()成功與失敗路徑都會關閉合成器音頻格式由 Azure SDK 默認決定通常為 16kHz、16-bit、單聲道 PCM WAV。listen()語音轉文本STT簽名async listen(audioStream: NodeJS.ReadableStream): Promisestring // 音頻輸入必須是 WAV 格式數據流音頻流輸入 ↓ [Buffer 累積]全部 chunk 讀入內存并拼接 ↓ [Push Stream 創建]Azure.AudioInputStream.createPushStream() ↓ [音頻配置]Azure.AudioConfig.fromStreamInput(pushStream) ↓ [識別器創建]new SpeechRecognizer(listeningConfig, audioConfig) ↓ [分塊寫入]按 4096 字節塊 write 到 push stream ↓ [識別執行]recognizeOnceAsync單次語音識別 ↓ [結果校驗]僅 ResultReason.RecognizedSpeech 才 resolve ↓ 輸出文本源碼級要點src/index.ts#L183-L231全量內存累積先把整個音頻流讀進Buffer.concat(chunks)大文件會帶來內存壓力文檔中也明確建議生產環境評估流式替代方案4096 字節分塊for (let i 0; i audioData.length; i 4096)逐塊寫入 Azure 的 push stream最后pushStream.close()表示音頻結束單次識別使用recognizeOnceAsyncutterance 級非連續識別模式結果校驗非RecognizedSpeech的結果會 reject錯誤信息中帶上Azure.ResultReason[result.reason]的可讀原因碼與errorDetails資源清理finally塊中recognizer.close()防止資源泄漏。getListener()監聽能力探測async getListener(): Promise{ enabled: boolean } // 恒返回 { enabled: true }這是 Mastra 框架用來判斷 STT 是否可用的能力查詢。注意一個從源碼可觀察到的細節該方法不檢查listeningModel是否真正配置恒返回enabled: true真正的未配置錯誤會在調用listen()時以Listening model (Azure) not configured拋出。基類的默認實現返回{ enabled: false }voice.ts#L266-L270Azure 側覆蓋該行為。聲音定義與 VoiceId 類型聲音目錄集中在 src/voices.ts共211 個聲音定義對文件中*Neural條目統計確認以as const數組導出覆蓋 50 語言阿拉伯語、英語、德語、西班牙語、中文等與多個區域變體en-US、en-GB、en-AU 等。聲音類型包括標準 Neural 聲音如en-US-AriaNeural、af-ZA-AdriNeural多語言聲音Multilingual后綴如de-DE-SeraphinaMultilingualNeuralHD 聲音:DragonHDLatestNeural后綴如en-US-Andrew:DragonHDLatestNeural見 voices.ts#L201-L210AI 生成聲音如AIGenerate1NeuralTurbo 多語言聲音如AlloyTurboMultilingualNeural聲音 ID 的標準格式為{language}-{region}-{name}NeuralgetSpeakers()正是利用這一格式解析language第 1 段與region第 2 段。類型層面VoiceId通過 const 斷言導出voices.ts#L213-L215export const AZURE_VOICES [ /* ...211 個聲音 ID... */ ] as const; export type VoiceId (typeof AZURE_VOICES)[number];這使得speaker構造參數與getSpeakers()的返回值都獲得字面量級類型安全寫錯聲音名在編譯期即可發現。與 Mastra 框架的集成契約從 packages/_internals/voice/src/voice/voice.ts 的IMastraVoice接口可以確認AzureVoice履約的完整契約speak(input, options?)—— TTS返回PromiseNodeJS.ReadableStream | voidlisten(audioStream, options?)—— STT返回Promisestring | NodeJS.ReadableStream | voidgetSpeakers()—— 返回Array{ voiceId: string } 元數據getListener()—— 返回{ enabled: boolean }另有connect()/send()/answer()/close()/on()/off()/addInstructions()/addTools()等實時與工具擴展點Azure 側均繼承基類默認空實現構造函數通過super()把speechModel.name、apiKey、listeningModel.name、apiKey與speaker傳給基類使 Mastra 框架能夠追蹤當前配置了哪些模型同時基類的serializeForSpan()會把apiKey排除在可觀測性序列化之外voice.ts#L120-L129這一點對憑據安全是重要保障。錯誤處理策略與資源管理配置期錯誤構造函數立即拋出缺少 API key →No Azure API key provided for speech model/... for listening model缺少 region →No region provided for speech model/... for listening model對應的測試用例見 index.test.ts#L154-L165先刪除AZURE_API_KEY環境變量驗證只傳region時構造函數按預期拋出運行期錯誤未配置對應模型卻調用方法 →Speech model (Azure) not configured/Listening model (Azure) not configured空輸入文本 →Input text is empty流讀取失敗 → 包裝為Failed to read input stream: ...合成/識別失敗 → 帶 Azure 原因碼與errorDetails的詳細錯誤信息合成超時 → 5 秒Promise.race超時保護資源清理全部采用 try/catch/finally 模式speak()在成功與失敗路徑都調用synthesizer.close()listen()在finally中調用recognizer.close()避免 SDK 內部資源泄漏。構建與分發構建配置見 tsdown.config.ts注意當前倉庫已從 tsup 遷移到 tsdownexport default defineConfig({ entry: [src/index.ts], format: [esm, cjs], // 雙格式輸出 nodeProtocol: strip, treeshake: true, sourcemap: true, deps: { alwaysBundle: [internal/voice] }, // 基類打入產物 onSuccess: async () { await generateTypes(process.cwd(), new Set([internal/voice])); // 生成 .d.ts }, });即ESM 與 CommonJS 雙格式輸出、生成 source map、基類internal/voice被 bundle 進產物、類型定義由internal/types-builder在構建成功后生成。package.json 的 exports 同時聲明了import./dist/index.js與require./dist/index.cjs條件兩類消費者均可使用運行環境要求node 22.13.0。測試策略集成測試套件 src/index.test.ts 使用真實 Azure API讀取AZURE_API_KEY/AZURE_REGION環境變量覆蓋五個維度初始化默認參數初始化new AzureVoice()后getSpeakers()應返回非空數組、環境變量回退getSpeakers()聲音列表結構校驗、voiceId/language/region元數據完整性speak()默認參數合成、指定speakeren-US-AriaNeural/en-US-JennyNeural、文本流輸入Readablepush 字符串斷言音頻 Buffer 非空并把產物寫入test-outputs/目錄供人工檢查listen()默認參數轉寫、文件流轉寫createReadStream讀取上一步生成的 WAV、流轉寫并做round-trip 校驗——例如合成 Listening test with defaults 后轉寫斷言文本包含listening test錯誤處理空文本拒絕、缺失 API key 時構造函數拋出。測試還會創建test-outputs目錄保存合成音頻index.test.ts#L10-L26便于本地調試時直接聽 WAV 文件驗證效果。性能與安全考量內存listen()全量累積音頻流于內存大音頻文件可能帶來內存壓力文檔建議生產環境考慮流式替代方案。超時speak()的 5 秒超時可防止 Azure API 故障導致請求掛起但對長文本合成偏緊按需調整。資源管理每個請求新建 synthesizer/recognizer 實例無實例池化與復用——簡單正確優先吞吐優化留待后續如連接池、實例復用。憑據管理優先使用環境變量AZURE_API_KEY/AZURE_REGION直連配置需自行保管密鑰憑據不會進入 tracing 序列化輸出見基類serializeForSpan。輸入校驗文本僅做非空校驗未提供 SSML 注入防護錯誤信息中可能攜帶 Azure 內部細節生產環境建議對錯誤做脫敏。實戰使用示例以下示例綜合了架構文檔與 README 的用法均可直接復制運行需有效的 Azure Speech 訂閱。基礎 TTSconst voice new AzureVoice({ speechModel: { apiKey: key, region: eastus }, }); const audioStream await voice.speak(Hello World);基礎 STTconst voice new AzureVoice({ listeningModel: { apiKey: key, region: eastus }, }); const text await voice.listen(audioStream); // audioStream 為 WAV 音頻流完整雙向合成 轉寫 round-tripconst voice new AzureVoice({ speechModel: { apiKey: key, region: eastus }, listeningModel: { apiKey: key, region: eastus }, speaker: en-US-JennyNeural, }); const audio await voice.speak(Test message); const transcription await voice.listen(audio);按次覆蓋聲音const audio await voice.speak(Bonjour, { speaker: fr-FR-DeniseNeural, });完全依賴環境變量// 僅設置 AZURE_API_KEY 與 AZURE_REGION 后 const voice new AzureVoice({ speechModel: {}, listeningModel: { language: en-US }, });列出全部聲音const voices await voice.getSpeakers(); // [{ voiceId: af-ZA-AdriNeural, language: af, region: ZA }, ...] 共 211 項小結mastra/voice-azure是對 Azure Cognitive Services Speech SDK 的 TypeScript 原生封裝通過繼承MastraVoice接入 Mastra 統一的語音提供方接口以 TTS/STT 雙配置模型獨立管理兩側憑據與區域提供 211 個類型安全的聲音選項并具備環境變量回退、超時保護與完整的資源清理。架構上它優先保證簡單與正確——單請求式合成/識別、每請求新建 SDK 實例、無池化——這使其適合 Mastra 生態內的基礎語音合成與識別場景文檔同時列出了清晰的演進方向listen()的流式增量處理、顯式 SSML 支持、實例緩存復用、音頻格式選擇與連續識別模式、以及 metrics/telemetry 可觀測性。深入閱讀可參考 主實現、聲音目錄、集成測試 與 基類契約。【免費下載鏈接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.項目地址: https://gitcode.com/GitHub_Trending/ma/mastra創作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考