OpenRouter 文字转语音 API 五分钟教程:一个端点调用全部 TTS 模型

邱宇AI 前沿2026-09-17180 阅读💛 104 收藏

一句话结论

OpenRouter 的文字转语音走 OpenAI 兼容端点 POST /api/v1/audio/speech:发送文本、模型名和该模型支持的语音标识,即可拿到音频字节流。一个 API key 复用多家供应商的 TTS 模型,请求结构完全一致——换模型只改 model 和 voice 两行,端点、鉴权、响应处理全部不动。

文字转语音请求流转图:文本、语音与输出格式进入统一端点,路由到 Mistral Voxtral Mini TTS 等模型,返回 MP3 音频

准备工作

在 OpenRouter 设置页创建 API key 并存入环境变量,避免写进源码。macOS 或 Linux 当前终端会话:

export OPENROUTER_API_KEY="your-api-key"

所有示例的 base URL 都是 https://openrouter.ai/api/v1,鉴权用 Authorization Bearer 头。

请求字段速览

端点接受两个必填字段加一个事实必填的语音项:

  • model:选择语音模型

  • input:要转成语音的文本

  • voice:该模型支持的语音。仅当供应商文档写明有默认语音时才可省略,实践中当作必填处理

response_format 和 speed 是可选字段。但显式设置输出格式能让响应更可预期——省略时端点默认返回 PCM,而 Mistral Voxtral Mini TTS 只接受 MP3;speed 只对支持变速的模型生效。

成功请求返回原始音频字节,失败请求返回 JSON。写文件前必须校验响应。

第一个 MP3:cURL 版

用 Mistral Voxtral Mini TTS 和它的 en_paul_neutral 语音,把返回字节直接存进 output.mp3:

curl --silent \
  --show-error \
  --fail-with-body \
  --request POST \
  --url https://openrouter.ai/api/v1/audio/speech \
  --header "Authorization: Bearer $OPENROUTER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "model": "mistralai/voxtral-mini-tts-2603",
    "input": "OpenRouter turns this text into speech through one API endpoint.",
    "voice": "en_paul_neutral",
    "response_format": "mp3"
  }' \
  --dump-header output.headers \
  --output output.mp3

几个关键旗标:—fail-with-body 让 cURL 在 4xx/5xx 时以错误退出;因为设了 —output,错误 JSON 体会写进 output.mp3 而不是终端——命令失败时用 cat output.mp3 读错误信息,重试前删掉该文件,别把 JSON 当音频播。—dump-header 存响应头,用于确认 content type 和记录生成 ID。

播放前确认文件存在且有数据:macOS 用 afplay output.mp3,Linux 用 ffplay。

Python 版:校验后再落盘

先装 requests(没有的话):python -m pip install requests。这个例子检查 HTTP 状态并确认返回的是 MP3 数据再保存:

import os
from pathlib import Path

import requests

response = requests.post(
    "https://openrouter.ai/api/v1/audio/speech",
    headers={
        "Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "model": "mistralai/voxtral-mini-tts-2603",
        "input": (
            "OpenRouter turns this text into speech through one API endpoint."
        ),
        "voice": "en_paul_neutral",
        "response_format": "mp3",
    },
    timeout=60,
)

response.raise_for_status()

content_type = response.headers.get("Content-Type", "").split(";")[0]
if content_type != "audio/mpeg":
    raise RuntimeError(f"Expected audio/mpeg, received {content_type}")

Path("output.mp3").write_bytes(response.content)

generation_id = response.headers.get("X-Generation-Id")
print(f"Saved output.mp3. Generation ID: {generation_id}")

raise_for_status 在 4xx/5xx 时抛异常,防止把错误体存成音频;content-type 检查确认响应真的是音频再写文件。记录 X-Generation-Id 响应头,追踪请求或找支持时用得上。

OpenAI SDK 流式版

端点遵循 OpenAI Audio Speech API 形状,把 OpenAI 客户端指到 OpenRouter base URL 即可流式落盘:

import os
from pathlib import Path

from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OPENROUTER_API_KEY"],
    base_url="https://openrouter.ai/api/v1",
)

with client.audio.speech.with_streaming_response.create(
    model="mistralai/voxtral-mini-tts-2603",
    voice="en_paul_neutral",
    input="OpenRouter can stream this response into an audio file.",
    response_format="mp3",
) as response:
    response.stream_to_file(Path("output.mp3"))

这种写法增量读取响应边存文件;渐进播放需要支持缓冲分块的播放器。

JavaScript 版

JS 用 arrayBuffer 读同一响应,同样先查状态和 content type 再建文件:

import { writeFile } from "node:fs/promises";

const response = await fetch(
  "https://openrouter.ai/api/v1/audio/speech",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "mistralai/voxtral-mini-tts-2603",
      input: "OpenRouter returns audio bytes to JavaScript.",
      voice: "en_paul_neutral",
      response_format: "mp3",
    }),
  },
);

if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}

const contentType = response.headers.get("content-type")?.split(";")[0];
if (contentType !== "audio/mpeg") {
  await response.body?.cancel();
  throw new Error(`Expected audio/mpeg, received ${contentType}`);
}

await writeFile("output.mp3", Buffer.from(await response.arrayBuffer()));
console.log(response.headers.get("x-generation-id"));

格式选择:MP3 文件小、标准播放器通吃;PCM 免压缩、兼容的实时流水线里延迟更低。MP3 返回 audio/mpeg,PCM 返回 audio/pcm(可带 rate 和 channels 参数),可用格式取决于所选模型——Mistral Voxtral Mini TTS 只收 MP3,要 PCM 会返回 400。播原始 PCM 需要正确的音频参数,改扩展名为 .mp3 不会转换格式。

换模型换语音:必须成对改

语音标识属于特定模型。同模型内换语音只改一行,例如 Grok Voice TTS 1.0 内置五个语音 eve/ara/rex/sal/leo,从 eve 换 ara 只动 voice 那行。

跨供应商换模型时,model 和 voice 必须一起改——每家供应商的模型 ID 和语音 ID 各自独立,而端点、鉴权、输入和响应检查保持不变:

- "model": "mistralai/voxtral-mini-tts-2603",
- "voice": "en_paul_neutral"
+ "model": "x-ai/grok-voice-tts-1.0",
+ "voice": "eve"

发请求前到所选模型页确认两个值——模型可用性和语音目录会变。查当前 TTS 模型列表:

curl "https://openrouter.ai/api/v1/models?output_modalities=speech"

当前代表模型:

  • mistralai/voxtral-mini-tts-2603:en_paul_neutral,经 OpenRouter 语音端点输出 MP3

  • x-ai/grok-voice-tts-1.0:eve/ara/rex/sal/leo,五语音覆盖 20+ 语言

  • microsoft/mai-voice-2:en-US-Harper:MAI-Voice-2,支持 speed、Azure 风格与风格强度

MAI-Voice-2 接受 Azure 语音名,speed 文档范围 0.5-2.0,还支持 Azure 表达选项,通过 provider.options.azure 传 style 和 styledegree。这些控制是供应商专属的:不支持的供应商会忽略 speed,风格依赖所选语音。截至 2026 年 9 月,目录里没有 OpenAI 语音模型,依赖供应商专属字段前先查当前模型列表。

第一次跑通后先做三件小事:播放生成的 MP3 确认语音自然度、查看响应头里的 X-Generation-Id 并记下来、用 ls -lh 看文件大小是否合理(几秒语音通常几十 KB)。这三步能在接入早期就发现”存了 JSON 当音频”或”空文件”两类最常见事故。

长文本直接整段发给 TTS 模型容易超时或截断,正确做法是按句子或段落边界切分后逐段请求,再按顺序拼接音频。切分点选句号、问号或换行处,避免把一个词从中间切开;拼接工具要与输出格式匹配,MP3 直接二进制追加即可,PCM 则要按采样率处理。

按字符计费意味着成本与文本长度线性相关:同一段文案反复生成会线性烧钱,生成记录里保留模型、语音、格式和字符数,账单对得上。预算敏感的场景先用最短可接受文本测价,再放量。

如果团队已有 OpenAI SDK 代码,迁移成本几乎为零:只改 base_url 和 api_key 两个值,其余请求结构、流式接口、错误处理全部沿用。这也是统一端点设计的直接好处——供应商锁定被降到最低,换语音模型不再重写传输层。

生产化清单

长文本按句子或段落边界切分、逐段请求、用格式感知的工具拼接——首段返回更快、整体更可靠。每一段都执行同样的响应检查:

  • 非 2xx 状态码立即停止

  • 确认 Content-Type 与请求格式一致

  • 拒绝空响应

  • 把 X-Generation-Id 与模型、语音、格式、应用请求 ID 一起记录

  • 重试前先分类响应:永久性请求失败不进退避循环

重试策略:429/502/503/524/529 可以重试(限流、供应商错误、临时不可用、超时、过载都可能自愈),带 Retry-After 头就按它来,否则用封顶指数退避、少量次数封顶。400/401/402 不要重试——先改请求、凭据或充值。

计费按输入文本字符数计,各模型各供应商价格不同,估算生产成本前查当前模型页或 Models API。

教程覆盖的完整链路按官方结构走:认证、合成、响应校验、流式、换模型换语音五步。每一步的代码都可以直接复制运行,唯一的前置条件是一个有效的 OpenRouter API key。

供应商专属选项的传递位置在 provider.options 下,按供应商名分组。Azure 风格与风格强度只对 MAI-Voice-2 生效,speed 也仅部分模型支持——把供应商选项写在匹配的模型配置旁边,避免”传了没效果”的排查时间。

对语音质量有要求时,先用同一段文本在两三个模型间做对比试听,再固定生产配置。语音自然度是主观指标,官方示例语音(如 en_paul_neutral、eve)只是起点,不是终点。

配合语音转文字接口,这套端点还能组成完整的音频工作流:先用 TTS 生成内容音频,再按需转写归档。两条链路共用同一个 key 和同一套鉴权,运维上只维护一套凭据。

常见故障排查

  • MP3 里有 JSON:API 返回了错误但程序没查状态就存了 body。写文件前调 raise_for_status 或检查状态码。

  • 音频文件为空或损坏:请求没返回音频数据,或存错了格式。查响应大小和 Content-Type;audio/mpeg 存 MP3,audio/pcm 按原始 PCM 用正确播放参数处理。

  • 语音被拒:语音标识因模型而异。查所选模型页、发送它支持的语音;每次换模型都重查。

  • 供应商选项没效果:供应商控制只到达对应供应商。OpenAI 指令放 provider.options.openai,Azure 风格放 provider.options.azure;部分供应商静默忽略不支持的 speed 值。

原文信息

  • 作者:OpenRouter 官方博客
  • 发布时间:2026-09-11 原文地址:

文章评论(9

龙文博28 分钟前

楼主辛苦了,内容很有参考价值。

回复
晨沐雨38 分钟前

收藏了,以后慢慢研究。

回复
一叶知秋18 分钟前

这篇文章分析得很透彻,收藏了!

回复
林观澜57 分钟前

整理得太全面了,省了我不少时间。

回复
山问津21 分钟前

写得挺用心的,支持一下。

回复
林观澜3 小时前

讲解得很细致,新手也能看懂。

回复
漾漾其华33 分钟前

整理得太全面了,省了我不少时间。

回复
龙文博12 分钟前

内容翔实,正好需要,先收藏再看。

回复
一叶知秋58 分钟前

很有价值的分享,感谢整理。

回复