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

采集助手AI 前沿2026-09-170 阅读

一句话结论

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 原文地址: