Mastra 语音集成实战:使用 @mastra/voice-azure 接入 Azure Speech Services 实现 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展开,系统讲解如何通过 Azure 语音服务 为 Mastra Agent 添加文本转语音(TTS)与语音转文本(STT)能力。读完本文,你将掌握该包的环境变量配置、speechModel / listeningModel 双模型配置方式、getSpeakers/speak/listen/getListener四个核心 API 的调用方法,并从源码层面理解其底层实现、错误处理与资源管理机制,能够直接在自己的 Mastra 项目中落地可运行的语音交互方案。
安装与快速开始
@mastra/voice-azure是 Mastra 官方维护的语音集成包,其唯一核心外部依赖是微软官方 SDKmicrosoft-cognitiveservices-speech-sdk(见 voice/azure/package.json),包内以 ESM 与 CommonJS 双格式对外分发,engines要求 Node.js >= 22.13.0。
安装命令:
npm install @mastra/voice-azure安装完成后,即可创建一个同时具备"说话"(TTS)与"听话"(STT)能力的语音实例:
import { AzureVoice } from '@mastra/voice-azure'; // 同时配置语音合成与语音识别两个模型 const voice = new AzureVoice({ speechModel: { apiKey: 'your-api-key', // 可选,可改用 AZURE_API_KEY 环境变量 region: 'your-region', // 可选,可改用 AZURE_REGION 环境变量 voiceName: 'en-US-AriaNeural', // 可选,默认音色 }, listeningModel: { apiKey: 'your-api-key', // 可选,可改用 AZURE_API_KEY 环境变量 region: 'your-region', // 可选,可改用 AZURE_REGION 环境变量 language: 'en-US', // 可选,识别语言 }, }); // 列出可用音色 const voices = await voice.getSpeakers(); // 生成语音 const audioStream = await voice.speak('Hello from Mastra!', { speaker: 'en-US-JennyNeural', // 可选:覆盖默认音色 }); // 语音转文本 const text = await voice.listen(audioStream);这段代码覆盖了该包的全部核心能力:speechModel负责 TTS 方向的配置,listeningModel负责 STT 方向的配置,二者相互独立、可以只配置其中一个。getSpeakers()返回可用音色列表,speak()将文本合成为音频流,listen()将 WAV 音频流转写为文本。
配置详解:speechModel 与 listeningModel
AzureVoice构造函数接收一个可选的配置对象,包含三个顶层属性,其完整类型定义位于 voice/azure/src/index.ts:
| 配置项 | 类型 | 用途 | 关键字段 |
|---|---|---|---|
speechModel | AzureVoiceConfig | 文本转语音(TTS)配置 | apiKey、region、voiceName |
listeningModel | AzureVoiceConfig | 语音转文本(STT)配置 | apiKey、region、language |
speaker | VoiceId | 全局默认音色 ID | 可选 |
其中AzureVoiceConfig内部类型为:
interface AzureVoiceConfig { apiKey?: string; region?: string; voiceName?: string; language?: string; }环境变量回退机制
从构造函数实现(voice/azure/src/index.ts)可以看到,apiKey与region均支持环境变量回退:
AZURE_API_KEY:同时作为 speechModel 与 listeningModel 的默认 API Key;AZURE_REGION:同时作为两个模型的默认区域。
也就是说,如果在.env中配置了这两个变量,代码里甚至可以完全不传凭证:
// 依赖 AZURE_API_KEY 与 AZURE_REGION 环境变量 const voice = new AzureVoice({ speechModel: {}, listeningModel: { language: 'zh-CN' }, });凭证校验与独立配置
构造函数对两个模型分别做凭证校验(voice/azure/src/index.ts):
- 配置了
speechModel但缺少apiKey时,抛出No Azure API key provided for speech model; - 缺少
region时,抛出No region provided for speech model; listeningModel同理,错误信息为No Azure API key provided for listening model/No region provided for listening model。
因此,只要传入某个 model 配置块,就必须同时提供 apiKey 与 region(或依赖环境变量)。TTS 与 STT 是独立初始化的:只传speechModel时不会创建识别器,调用listen()会报Listening model (Azure) not configured。
默认音色的确定顺序
语音合成的默认音色按以下优先级确定(voice/azure/src/index.ts):
this.speechConfig.speechSynthesisVoiceName = speechModel.voiceName || speaker || 'en-US-AriaNeural';即:speechModel.voiceName> 构造时的speaker参数 > 内置兜底'en-US-AriaNeural'。
识别语言
listeningModel.language会被写入 Azure SDK 的speechRecognitionLanguage(voice/azure/src/index.ts),用于设置 STT 识别的源语言,例如'en-US'、'zh-CN'。TTS 场景下language字段不参与合成逻辑。
核心 API 使用指南
getSpeakers():获取可用音色列表
getSpeakers()返回Promise<Array<{ voiceId: string; language: string; region: string }>>。其实现非常轻量:直接遍历内置的AZURE_VOICES常量数组,按音色 ID 的{语言}-{区域}-{名称}格式解析出 language 与 region 两个元数据字段(voice/azure/src/index.ts)。例如en-US-AriaNeural会被解析为{ voiceId: 'en-US-AriaNeural', language: 'en-US', region: 'US' }。
调用方式:
const voices = await voice.getSpeakers(); for (const v of voices.slice(0, 5)) { console.log(`${v.voiceId} | ${v.language} | ${v.region}`); }该列表全部来自 voice/azure/src/voices.ts 中的静态定义,涵盖 50+ 语言、200+ 音色,并且通过as const断言导出严格类型VoiceId:
export type VoiceId = (typeof AZURE_VOICES)[number];这意味着在 TypeScript 中传入不存在的音色 ID 会在编译期直接报错,获得完整的类型安全。
speak():文本转语音
speak(input, options?)是 TTS 的核心方法(voice/azure/src/index.ts):
async speak( input: string | NodeJS.ReadableStream, options?: { speaker?: string; [key: string]: any }, ): Promise<NodeJS.ReadableStream>其内部处理流程为:
- 输入归一化:如果
input是流而非字符串,会先异步读取全部 chunk 并拼接为 UTF-8 字符串; - 空文本校验:
!input?.trim()时抛出Input text is empty; - 音色切换:如果传入
options.speaker,则动态改写speechConfig.speechSynthesisVoiceName; - SDK 合成:为每次请求创建新的
SpeechSynthesizer,调用 Azure 的speakTextAsync; - 超时保护:通过
Promise.race实现 5 秒超时,超时抛出Speech synthesis timed out; - 结果校验:只有
ResultReason.SynthesizingAudioCompleted才视为成功,否则抛出含errorDetails的错误; - 流式返回:将
result.audioData包装为Readable.from([...])返回。
典型用法:
// 基础合成,使用默认音色 const audio = await voice.speak('Hello World'); // 指定音色 const audio2 = await voice.speak('Bonjour le monde', { speaker: 'fr-FR-DeniseNeural', }); // 传入文本流(Node.js ReadableStream) import { Readable } from 'node:stream'; const inputStream = Readable.from(['Hello from stream']); const audio3 = await voice.speak(inputStream);返回值是一个包含单个 Buffer 的 Node.js Readable 流,音频格式由 Azure SDK 默认决定(通常为 16kHz、16-bit、单声道 PCM WAV),可以直接写盘或进一步转码。
listen():语音转文本
listen(audioStream)接收 Node.js ReadableStream,返回识别出的文本字符串(voice/azure/src/index.ts)。注意:输入音频必须是 Azure 兼容的 WAV 格式。
其处理流程为:
- 全量缓冲:将整个音频流读取到内存中;
- 构建推送流:通过
Azure.AudioInputStream.createPushStream()创建推送流,并用AudioConfig.fromStreamInput生成音频配置; - 逐块写入:以 4096 字节为块将音频数据写入推送流(voice/azure/src/index.ts);
- 单次识别:调用
recognizeOnceAsync进行单次话语识别; - 结果校验:仅当
ResultReason.RecognizedSpeech时返回result.text,否则抛出包含 reason 码与 errorDetails 的错误; - 资源释放:
finally块中关闭 recognizer。
典型用法:
// 直接转写 speak() 的输出(round-trip 验证) const audio = await voice.speak('This is a test for transcription'); const text = await voice.listen(audio); console.log(text); // 转写本地 WAV 文件 import { createReadStream } from 'node:fs'; const text2 = await voice.listen(createReadStream('recording.wav'));getListener():监听能力探测
getListener()恒定返回{ enabled: true }(voice/azure/src/index.ts)。这是 Mastra 框架用于探测语音提供方是否具备 STT 能力的约定方法,返回false时框架会认为该提供方只支持 TTS。
源码级原理:MastraVoice 基类契约
AzureVoice继承自MastraVoice抽象基类(见 packages/_internals/voice/src/voice/voice.ts),该类实现了IMastraVoice接口,并定义了所有语音提供方必须遵守的抽象契约:
speak(input, options?):文本转语音,返回音频流;listen(audioStream, options?):语音转文本,返回文本或音频流;getSpeakers():返回{ voiceId } & TSpeakerMetadata数组;getListener():返回{ enabled: boolean };- 以及
updateConfig、connect、send、addInstructions、事件订阅on/off等实时语音相关能力。
AzureVoice在构造函数中调用super()时,会把speechModel/listeningModel的 name 与 apiKey、以及speaker传给基类(voice/azure/src/index.ts),这样 Mastra 框架内部可以追踪各提供方配置了哪些模型,并在可观测性 span 中序列化这些信息——基类的serializeForSpan()会输出组件类型'VOICE'、speaker、模型名等字段,且刻意排除 apiKey,避免敏感凭证进入追踪数据。
从类结构看,AzureVoice维护四个私有状态(voice/azure/src/index.ts):
| 私有属性 | 类型 | 职责 |
|---|---|---|
speechConfig | Azure.SpeechConfig | TTS 配置(订阅凭证、默认音色) |
listeningConfig | Azure.SpeechConfig | STT 配置(订阅凭证、识别语言) |
speechSynthesizer | Azure.SpeechSynthesizer | 构造期创建的合成器实例 |
speechRecognizer | Azure.SpeechRecognizer | 构造期创建的识别器实例 |
值得注意的细节是:构造函数中虽然创建了合成器/识别器实例,但speak()与listen()在方法内部每次请求都会重新 new 一个实例,用完即 close(voice/azure/src/index.ts、voice/azure/src/index.ts),这是一种"以资源开销换取状态隔离"的取舍,从源码结构看目前没有做实例池复用。
音色清单:200+ 声音与 VoiceId 类型
voice/azure/src/voices.ts 是整个包的静态数据核心,以const数组形式声明了 200+ 个音色 ID。这些音色覆盖阿拉伯语、德语、英语、西班牙语、中文、印地语等 50+ 种语言,并为英语、德语等提供了多区域变体(如en-US、en-GB、en-AU、de-DE)。
从命名后缀可以区分四类音色:
| 类别 | 命名特征 | 示例 |
|---|---|---|
| 标准神经音色 | {语言}-{区域}-{名称}Neural | en-US-AriaNeural、de-DE-ConradNeural |
| 多语言音色 | 后缀Multilingual | en-US-EmmaMultilingualNeural、de-DE-SeraphinaMultilingualNeural |
| HD 音色 | 后缀:DragonHDLatestNeural | en-US-Aria:DragonHDLatestNeural、en-US-Andrew2:DragonHDLatestNeural |
| AI 生成 / Turbo 音色 | 前缀AIGenerate或后缀TurboMultilingualNeural | en-US-AIGenerate1Neural、en-US-AlloyTurboMultilingualNeural |
由于该数组使用as const断言,导出的VoiceId是字面量联合类型,IDE 补全与编译期校验都能直接生效——这是本包相比直接使用 Azure SDK 的一大类型安全优势。
测试验证:集成测试覆盖的行为边界
包内自带的集成测试套件 voice/azure/src/index.test.ts 完整验证了上述 API 行为,可作为实践参考:
- 初始化:默认参数初始化、环境变量回退、缺少 API Key 时抛错(
expect(() => new AzureVoice({ speechModel: { region: 'eastus' } })).toThrow('No Azure API key provided for speech model')); - getSpeakers():返回数组非空、每个元素包含
voiceId/language/region三个属性; - speak():默认参数合成、指定音色合成、传入文本流合成,断言音频 Buffer 长度大于 0,并将结果写入
test-outputs/目录下的 WAV 文件供人工检查; - listen():默认参数识别、从 WAV 文件转写、以及speak → listen 回环验证(合成后再转写,断言文本包含原始关键词);
- 错误处理:空文本输入抛出
Input text is empty。
需要说明的是,这套测试是真实的 Azure 集成测试,运行前提是环境中配置了有效的AZURE_API_KEY与AZURE_REGION(测试代码中以'fake-key'/'eastus'兜底,但实际调用会失败)。它展示了"合成 → 转写"回环自检的完整实践模式,非常适合作为你自己验证语音链路的模板。
实战注意事项与建议
综合源码实现与架构文档,在实际项目中使用@mastra/voice-azure时有几点值得关注:
- 凭证管理:推荐通过
AZURE_API_KEY/AZURE_REGION环境变量注入凭证,避免把密钥硬编码在配置对象中;不要在日志或追踪数据中打印 apiKey。 - listen() 的内存占用:
listen()会把整个音频流缓冲进内存,超长音频可能造成内存压力,生产环境建议控制单次转写的音频时长,或关注后续版本是否引入增量流式处理。 - speak() 的超时:合成内置 5 秒超时(
Speech synthesis timed out),超长文本可能触发,需要根据业务文本长度评估是否够用。 - 音频格式约束:
listen()输入必须是 Azure 兼容的 WAV 格式;如果音频来自其他来源,需先做格式转换。 - 错误信息暴露:部分异常会把 Azure 内部
errorDetails透出,生产环境对外暴露接口时建议做一层错误包装与脱敏。 - 与框架集成:作为
MastraVoice的实现类,AzureVoice可以直接接入 Mastra 的语音 Agent 编排流程,框架通过getListener()探测 STT 能力、通过getSpeakers()枚举音色,这些约定的接口保证你后续可以无痛切换到其他语音提供方。
小结
@mastra/voice-azure以约 200 行核心实现(voice/azure/src/index.ts)为 Mastra 提供了一套简洁、TypeScript 原生、类型安全的 Azure 语音能力封装:TTS 与 STT 配置完全解耦,凭证支持环境变量回退,音色选择具备编译期类型约束,内部包含超时保护与资源清理。它完整实现了MastraVoice抽象契约(packages/_internals/voice/src/voice/voice.ts),并附带覆盖回环验证的集成测试(voice/azure/src/index.test.ts)。对需要在 Mastra 中快速接入 Azure 语音合成与识别的开发者而言,安装、配置、三个 API 即可打通完整语音链路,是最直接的落地路径。
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考