第 15 章 · 语音转写与合成
本章目标:
- 掌握
transcribe函数把音频转写为文本的用法- 了解实验性的流式转写
experimental_streamTranscribe及其适用场景- 掌握
generateSpeech从文本生成语音的用法- 学会处理下载限制、中止信号与语音相关错误类型
15.1 音频转写(Transcription)
AI SDK 提供 transcribe 函数,使用 transcription model 转写音频。
import { transcribe, createGateway } from 'ai';
import { readFile } from 'fs/promises';
import 'dotenv/config';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
const transcript = await transcribe({
model: gateway.transcription('openai/whisper-1'),
audio: await readFile('audio.mp3'),
});audio 属性可以是 Uint8Array、ArrayBuffer、Buffer、字符串(base64 编码的音频数据)或 URL。
访问生成的转写结果:
const text = transcript.text; // transcript text e.g. "Hello, world!"
const segments = transcript.segments; // array of segments with start and end times, if available
const language = transcript.language; // language of the transcript e.g. "en", if available
const durationInSeconds = transcript.durationInSeconds; // duration of the transcript in seconds, if available15.2 流式转写(实验性)
流式转写是实验特性。
当你持有实时原始音频、需要在完整音频流结束前就拿到转写更新时,使用 experimental_streamTranscribe。该函数使用支持流式的转写模型;provider 专属行为通过 providerOptions 配置,流式操作本身由函数选择:
import { createGateway } from 'ai';
import { experimental_streamTranscribe as streamTranscribe } from 'ai';
import 'dotenv/config';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
const result = streamTranscribe({
model: gateway.transcription('openai/gpt-realtime-whisper'),
audio: audioStream, // ReadableStream<Uint8Array | string>
inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
providerOptions: {
openai: {
language: 'en',
streaming: {
delay: 'low',
},
},
},
});
for await (const part of result.fullStream) {
if (part.type === 'transcript-delta') {
process.stdout.write(part.delta);
}
if (part.type === 'transcript-partial') {
console.log('partial:', part.text);
}
if (part.type === 'transcript-final') {
console.log('final:', part.text);
}
}
console.log(await result.text);fullStream 是单消费者的实时流,只能被访问一次。当你同时需要流分片和最终结果时,先访问 fullStream,在消费过程中或之后再 await 结果 promise。如果先访问了结果 promise,流会被内部消费,fullStream 就不再可用——这避免了为实时音频保留无界的回放缓冲区。
获取最终转写元数据:
const text = await result.text; // final transcript text
const segments = await result.segments; // final segments with timing, if available
const language = await result.language; // language of the transcript, if available
const durationInSeconds = await result.durationInSeconds; // duration in seconds, if availableaudio 流必须包含原始音频分片:Uint8Array 分片是原始字节,字符串分片是 base64 编码的原始字节。务必设置 inputAudioFormat 与你发送的分片匹配。
官方文档指出:字符串模型 ID 会经全局默认 provider(AI Gateway 默认)解析。AI Gateway 为支持的模型提供流式转写(如
openai/gpt-realtime-whisper、elevenlabs/eleven-scribe-2-realtime、xai/grok-stt),因此字符串 ID 可用。不同厂商的流式转写模型各不相同:OpenAI 用gpt-realtime-whisper;Cartesia 用 Ink 2(cartesia.transcription('ink-2'));ElevenLabs 用 Scribe v2 Realtime;xAI 用同一xai.transcription()模型,experimental_streamTranscribe会选择其 WebSocket STT 传输。
import { xai } from '@ai-sdk/xai';
import { experimental_streamTranscribe as streamTranscribe } from 'ai';
const result = streamTranscribe({
model: xai.transcription(),
audio: audioStream,
inputAudioFormat: { type: 'audio/pcm', rate: 16000 },
providerOptions: {
xai: {
language: 'en',
keyterm: ['AI SDK', 'Grok'],
streaming: {
interimResults: true,
endpointing: 500,
},
},
},
});一些 provider 的直连流式 STT 需要 WebSocket headers——在这类运行环境中,创建 provider 时需传入 provider 专属的 webSocket 实现。
15.3 转写设置
Provider 专属设置
转写模型通常有 provider 或模型专属设置,通过 providerOptions 参数配置:
import { transcribe, createGateway } from 'ai';
import { readFile } from 'fs/promises';
import 'dotenv/config';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
const transcript = await transcribe({
model: gateway.transcription('openai/whisper-1'),
audio: await readFile('audio.mp3'),
providerOptions: {
openai: {
timestampGranularities: ['word'],
},
},
});下载大小限制
当 audio 是 URL 时,SDK 以默认 2 GiB 上限下载文件。可通过 createDownload 自定义:
import { transcribe, createDownload, createGateway } from 'ai';
import 'dotenv/config';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
const transcript = await transcribe({
model: gateway.transcription('openai/whisper-1'),
audio: new URL('https://example.com/audio.mp3'),
download: createDownload({ maxBytes: 50 * 1024 * 1024 }), // 50 MB limit
});也可以提供完全自定义的下载函数:
const transcript = await transcribe({
model: gateway.transcription('openai/whisper-1'),
audio: new URL('https://example.com/audio.mp3'),
download: async ({ url }) => {
const res = await myAuthenticatedFetch(url);
return {
data: new Uint8Array(await res.arrayBuffer()),
mediaType: res.headers.get('content-type') ?? undefined,
};
},
});下载超过大小限制时会抛出 DownloadError:
import { transcribe, DownloadError, createGateway } from 'ai';
import 'dotenv/config';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
try {
await transcribe({
model: gateway.transcription('openai/whisper-1'),
audio: new URL('https://example.com/audio.mp3'),
});
} catch (error) {
if (DownloadError.isInstance(error)) {
console.log('Download failed:', error.message);
}
}中止信号与超时
transcribe 接受可选的 AbortSignal 类型 abortSignal 参数,可用于中止转写或设置超时——与 URL 下载组合使用时可防止长时间挂起的请求:
import { transcribe, createGateway } from 'ai';
import 'dotenv/config';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
const transcript = await transcribe({
model: gateway.transcription('openai/whisper-1'),
audio: new URL('https://example.com/audio.mp3'),
abortSignal: AbortSignal.timeout(5000), // Abort after 5 seconds
});15.4 语音合成(Speech)
AI SDK 提供 generateSpeech 函数,使用 speech model 从文本生成语音。
import { generateSpeech, createGateway } from 'ai';
import 'dotenv/config';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
const audio = await generateSpeech({
model: gateway.speech('openai/tts-1'),
text: 'Hello, world!',
voice: 'alloy',
});语言设置
可以指定语音生成的语言(取决于 provider 支持)。官方示例以 LMNT 为例展示 language 参数:
import { generateSpeech } from 'ai';
import { lmnt } from '@ai-sdk/lmnt';
const audio = await generateSpeech({
model: lmnt.speech('aurora'),
text: 'Hola, mundo!',
language: 'es', // Spanish
});访问生成的音频数据:
const audioData = result.audio.uint8Array; // audio data as Uint8Array
// or
const audioBase64 = result.audio.base64; // audio data as base64 string15.5 语音合成设置
Provider 专属设置
用 providerOptions 设置模型专属选项:
import { generateSpeech, createGateway } from 'ai';
import 'dotenv/config';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
const audio = await generateSpeech({
model: gateway.speech('openai/tts-1'),
text: 'Hello, world!',
providerOptions: {
openai: {
// ...
},
},
});中止信号、自定义 Headers 与警告
generateSpeech 同样接受可选的 abortSignal 参数来中止生成或设置超时;可选的 headers 参数可为请求添加自定义 header;不支持的参数等警告会出现在 warnings 属性中:
import { generateSpeech, createGateway } from 'ai';
import 'dotenv/config';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
const audio = await generateSpeech({
model: gateway.speech('openai/tts-1'),
text: 'Hello, world!',
abortSignal: AbortSignal.timeout(1000), // Abort after 1 second
});import { generateSpeech, createGateway } from 'ai';
import 'dotenv/config';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
const audio = await generateSpeech({
model: gateway.speech('openai/tts-1'),
text: 'Hello, world!',
});
const warnings = audio.warnings;错误处理
当 generateSpeech 无法生成有效音频时,会抛出 AI_NoSpeechGeneratedError。可能原因:模型未能生成响应,或生成了无法解析的响应。
错误对象保留以下信息便于记录问题:responses(speech 模型响应元数据,含时间戳、模型、headers)与 cause(根因):
import { generateSpeech, NoSpeechGeneratedError, createGateway } from 'ai';
import 'dotenv/config';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
try {
await generateSpeech({
model: gateway.speech('openai/tts-1'),
text: 'Hello, world!',
});
} catch (error) {
if (NoSpeechGeneratedError.isInstance(error)) {
console.log('AI_NoSpeechGeneratedError');
console.log('Cause:', error.cause);
console.log('Responses:', error.responses);
}
}本章小结
transcribe统一音频转写入口:audio支持Uint8Array/ArrayBuffer/Buffer/ base64 字符串 /URL,结果含text、segments、language、durationInSeconds;experimental_streamTranscribe面向实时原始音频,fullStream单次可消费,注意「先取 fullStream 再 await 结果」的顺序约束;- URL 音频下载默认 2 GiB 上限,可用
createDownload({ maxBytes })收紧或传入完全自定义的下载函数,超限抛DownloadError; generateSpeech用 speech model 把文本变语音,支持voice、language、providerOptions、abortSignal、headers;- 语音生成失败抛出
NoSpeechGeneratedError,用isInstance判定并读取cause/responses。
🛠️ 动手实践
- 找一段本地 mp3,用
transcribe+ AI Gateway 转写,打印完整文本、语言代码和时长,并遍历segments输出每段起止时间。 - 用
createDownload({ maxBytes })把下载上限收紧到 5 MB,然后对一个较大的远程音频发起转写,捕获DownloadError并打印错误信息。 - 用
generateSpeech把一段中文文案合成为语音(尝试不同voice取值),将uint8Array写入本地文件并用播放器试听对比效果。