news 2026/9/15 15:23:06

Mastra 语音集成实战:使用 @mastra/voice-azure 接入 Azure Speech Services 实现 TTS 与 STT

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Mastra 语音集成实战:使用 @mastra/voice-azure 接入 Azure Speech Services 实现 TTS 与 STT

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:

配置项类型用途关键字段
speechModelAzureVoiceConfig文本转语音(TTS)配置apiKeyregionvoiceName
listeningModelAzureVoiceConfig语音转文本(STT)配置apiKeyregionlanguage
speakerVoiceId全局默认音色 ID可选

其中AzureVoiceConfig内部类型为:

interface AzureVoiceConfig { apiKey?: string; region?: string; voiceName?: string; language?: string; }

环境变量回退机制

从构造函数实现(voice/azure/src/index.ts)可以看到,apiKeyregion均支持环境变量回退:

  • 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>

其内部处理流程为:

  1. 输入归一化:如果input是流而非字符串,会先异步读取全部 chunk 并拼接为 UTF-8 字符串;
  2. 空文本校验!input?.trim()时抛出Input text is empty
  3. 音色切换:如果传入options.speaker,则动态改写speechConfig.speechSynthesisVoiceName
  4. SDK 合成:为每次请求创建新的SpeechSynthesizer,调用 Azure 的speakTextAsync
  5. 超时保护:通过Promise.race实现 5 秒超时,超时抛出Speech synthesis timed out
  6. 结果校验:只有ResultReason.SynthesizingAudioCompleted才视为成功,否则抛出含errorDetails的错误;
  7. 流式返回:将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 格式。

其处理流程为:

  1. 全量缓冲:将整个音频流读取到内存中;
  2. 构建推送流:通过Azure.AudioInputStream.createPushStream()创建推送流,并用AudioConfig.fromStreamInput生成音频配置;
  3. 逐块写入:以 4096 字节为块将音频数据写入推送流(voice/azure/src/index.ts);
  4. 单次识别:调用recognizeOnceAsync进行单次话语识别;
  5. 结果校验:仅当ResultReason.RecognizedSpeech时返回result.text,否则抛出包含 reason 码与 errorDetails 的错误;
  6. 资源释放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 }
  • 以及updateConfigconnectsendaddInstructions、事件订阅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):

私有属性类型职责
speechConfigAzure.SpeechConfigTTS 配置(订阅凭证、默认音色)
listeningConfigAzure.SpeechConfigSTT 配置(订阅凭证、识别语言)
speechSynthesizerAzure.SpeechSynthesizer构造期创建的合成器实例
speechRecognizerAzure.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-USen-GBen-AUde-DE)。

从命名后缀可以区分四类音色:

类别命名特征示例
标准神经音色{语言}-{区域}-{名称}Neuralen-US-AriaNeuralde-DE-ConradNeural
多语言音色后缀Multilingualen-US-EmmaMultilingualNeuralde-DE-SeraphinaMultilingualNeural
HD 音色后缀:DragonHDLatestNeuralen-US-Aria:DragonHDLatestNeuralen-US-Andrew2:DragonHDLatestNeural
AI 生成 / Turbo 音色前缀AIGenerate或后缀TurboMultilingualNeuralen-US-AIGenerate1Neuralen-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_KEYAZURE_REGION(测试代码中以'fake-key'/'eastus'兜底,但实际调用会失败)。它展示了"合成 → 转写"回环自检的完整实践模式,非常适合作为你自己验证语音链路的模板。

实战注意事项与建议

综合源码实现与架构文档,在实际项目中使用@mastra/voice-azure时有几点值得关注:

  1. 凭证管理:推荐通过AZURE_API_KEY/AZURE_REGION环境变量注入凭证,避免把密钥硬编码在配置对象中;不要在日志或追踪数据中打印 apiKey。
  2. listen() 的内存占用listen()会把整个音频流缓冲进内存,超长音频可能造成内存压力,生产环境建议控制单次转写的音频时长,或关注后续版本是否引入增量流式处理。
  3. speak() 的超时:合成内置 5 秒超时(Speech synthesis timed out),超长文本可能触发,需要根据业务文本长度评估是否够用。
  4. 音频格式约束listen()输入必须是 Azure 兼容的 WAV 格式;如果音频来自其他来源,需先做格式转换。
  5. 错误信息暴露:部分异常会把 Azure 内部errorDetails透出,生产环境对外暴露接口时建议做一层错误包装与脱敏。
  6. 与框架集成:作为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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 15:23:00

CANopenSocket实战:安装配置与Python读写对象字典

简介&#xff1a;CANopenSocket是一套面向Linux下CANopen协议开发的轻量级开源工具集&#xff0c;基于socketCAN接口&#xff0c;主攻嵌入式与工业自动化通信场景&#xff0c;适用于需要实现设备控制、传感器/PLC组网或学习CANopen协议栈的开发者。socketCAN借鉴TCP/IP网络编程…

作者头像 李华
网站建设 2026/9/15 15:22:58

InsForge 共享 Schemas 开发指南:用 Zod 契约统一跨包 API 数据层

InsForge 共享 Schemas 开发指南&#xff1a;用 Zod 契约统一跨包 API 数据层 【免费下载链接】InsForge The all-in-one, open-source backend platform for agentic coding. InsForge gives your coding agent database, auth, storage, compute, hosting, and AI gateway to…

作者头像 李华
网站建设 2026/9/15 15:22:06

Zvec系列100问完结篇:回顾你的向量数据库知识体系

Zvec系列100问完结篇&#xff1a;回顾你的向量数据库知识体系 【免费下载链接】zvec A lightweight, lightning-fast, in-process vector database 项目地址: https://gitcode.com/GitHub_Trending/zve/zvec Zvec 是一款开源的进程内&#xff08;嵌入式&#xff09;向量…

作者头像 李华
网站建设 2026/9/15 15:20:22

文件夹同步备份全攻略:从手动复制到自动化方案

不知道你有没有经历过这样的场景&#xff1a;U盘里拷了一半资料&#xff0c;电脑提示“磁盘已满”&#xff0c;然后你开始删照片、清缓存&#xff0c;腾出空间后重新拖拽&#xff0c;结果搞到半夜发现漏了一个昨天刚改过的文档。或者更常见的——你在公司电脑上改完一份方案&am…

作者头像 李华
网站建设 2026/9/15 15:20:11

助听器怎么选?看场景不看参数的实战指南

1. 项目概述&#xff1a;这不是一场参数对比&#xff0c;而是一场“听觉适配”的实战检验“助听器哪个好&#xff1f;”——这句话背后藏着的不是技术参数的罗列&#xff0c;而是老人在菜市场听不清摊主报价时的尴尬&#xff0c;是年轻人在开放式办公室里漏掉关键会议指令的焦虑…

作者头像 李华
网站建设 2026/9/15 15:19:33

HTTP/HTTPS实战:请求头、状态码与数据包结构全解析

HTTP/HTTPS这套东西&#xff0c;说新不新&#xff0c;但真正能把它讲透、用到实战里的人真不多。我抓包三年多&#xff0c;前后端联调常见问题翻来覆去就那几个&#xff1a;请求头没带对、状态码看错、数据包结构理解偏差&#xff0c;尤其是热词里大家搜的“a标签下载视频请求头…

作者头像 李华