HiAPI
博客

教程

如何在 HiAPI 上调用 gemini-3.1-flash-tts:curl、Python 与一个可运行的请求

2026年10月31日
深靛蓝色背景上多层嵌套的宽箭头依次向右上方推进,箭头前缘为杏橙色,构成规整的信号流示意图形,这是文章的封面图。

gemini-3.1-flash-tts 在 HiAPI 上能为你做什么

gemini-3.1-flash-tts 是通过 HiAPI 统一 API 提供的文本转语音模型。你发送文本和语音配置,平台返回生成的音频。由于它通过单一、一致的接口对外暴露,你无需为每个模型单独集成——相同的认证头、相同的基础 URL 以及相同的异步任务模式适用于平台上的所有 TTS 模型。

当你需要以下能力时,可以选用这个模型:

  • 多说话人旁白。 脚本、对话或播客风格的内容,其中不同片段应听起来像不同的声音。
  • 快速周转。 flash 系列面向低延迟生成,这对交互式应用或 SLA 紧张的批处理任务尤为重要。
  • 可预测的集成界面。 一种请求形态即可覆盖从单行文本到多说话人场景的所有情况。

如果你正在构建的应用已经调用其他 HiAPI 模型,添加语音功能只需替换模型标识符并调整请求体——无需新的 SDK,也无需新的凭据流程。

前置条件与认证设置

在首次调用之前,请确保你已具备:

  1. 一个 HiAPI 账户和 API 密钥。 从控制台生成密钥。请将其视为机密——不要提交到源代码管理中。
  2. HiAPI 基础 URL。 所有请求都发往同一主机;只有每个端点的路径不同。
  3. 一个发起 HTTP 请求的工具。 用于快速测试的 curl,以及用于脚本的 Python 3.9+ 和 requests 库。

认证方式是在 Authorization 请求头中携带 bearer 令牌:

Authorization: Bearer $HIAPI_API_KEY

将密钥存储在环境变量中,而不是硬编码:

export HIAPI_API_KEY="your_key_here"

下文描述的每个端点——任务提交、状态检查和文件获取——都使用同一个请求头。如果你收到 401,说明请求头缺失、格式错误或密钥已被撤销。请在控制台中轮换密钥,而不是在不同环境之间共用一个。

为多说话人语音构建请求体

请求体是 JSON。至少需要包含模型标识符和输入内容。对于多说话人输出,你需要添加一个说话人配置,将具名说话人映射到语音,并在输入文本中用说话人标签进行标记。

一个典型的请求体如下所示:

{
  "model": "gemini-3.1-flash-tts",
  "input": {
    "text": "[Host] Welcome back to the show. [Guest] Thanks for having me.",
    "multi_speaker": true,
    "speakers": [
      { "name": "Host", "voice": "voice_a" },
      { "name": "Guest", "voice": "voice_b" }
    ]
  }
}

容易让人踩坑的关键点:

  • 说话人名称必须完全匹配。 speakers 数组中的 name 必须与文本中使用的标签(本例中为方括号内)完全一致。大小写不匹配是多说话人渲染失败最常见的原因。
  • 每个标签都必须声明。 如果文本引用了 [Narrator],但没有定义 name: "Narrator" 的说话人,请求将被拒绝。
  • 语音标识符来自平台的语音目录。 使用 HiAPI 提供的标识符,而不是自行猜测。对于单说话人任务,省略 multi_speaker 和 speakers,改为设置单个 voice 字段。
  • 文本作为单个字符串发送。 长脚本没有问题,但要注意平台上记录的每请求限制。

请求体通过校验后,任务会被接受并返回一个任务标识符,你将用它进行轮询。

使用 curl 运行

将上面的请求体保存为 payload.json,然后提交:

curl -X POST "$HIAPI_BASE_URL/v1/audio/speech/jobs" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d @payload.json

响应包含一个任务对象,其中有 id 和 status 字段(通常为 queued 或 processing):

{ "id": "job_abc123", "status": "queued" }

要检查进度,按 ID 获取任务:

curl "$HIAPI_BASE_URL/v1/audio/speech/jobs/job_abc123" \
  -H "Authorization: Bearer $HIAPI_API_KEY"

当 status 变为 succeeded 时,响应会包含一个音频文件引用(URL 或文件 ID),你可以通过后续请求下载——参见下文的轮询部分。如果任务失败,status 将为 failed,并有一个 error 字段说明原因。

对于快速实验,你可以跳过文件,直接内联 JSON:

curl -X POST "$HIAPI_BASE_URL/v1/audio/speech/jobs" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gemini-3.1-flash-tts","input":{"text":"Hello from HiAPI.","voice":"voice_a"}}'

使用 Python 运行

Python 版本使用 requests 和一个小型辅助函数来保持轮询循环的可读性。请求体与 curl 示例完全相同。

import os
import time
import requests

API_KEY = os.environ["HIAPI_API_KEY"]
BASE_URL = os.environ["HIAPI_BASE_URL"]
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

payload = {
    "model": "gemini-3.1-flash-tts",
    "input": {
        "text": "[Host] Welcome back to the show. [Guest] Thanks for having me.",
        "multi_speaker": True,
        "speakers": [
            {"name": "Host", "voice": "voice_a"},
            {"name": "Guest", "voice": "voice_b"},
        ],
    },
}

resp = requests.post(
    f"{BASE_URL}/v1/audio/speech/jobs",
    headers={**HEADERS, "Content-Type": "application/json"},
    json=payload,
    timeout=30,
)
resp.raise_for_status()
job = resp.json()
job_id = job["id"]
print("submitted", job_id)

while True:
    r = requests.get(f"{BASE_URL}/v1/audio/speech/jobs/{job_id}", headers=HEADERS, timeout=30)
    r.raise_for_status()
    status = r.json()
    if status["status"] in ("succeeded", "failed"):
        break
    time.sleep(2)

if status["status"] == "failed":
    raise RuntimeError(status.get("error"))

print("audio ready:", status["output"])

注意事项:

  • 超时很重要。 对每个请求显式设置超时;挂起的轮询循环比失败的循环更糟。
  • 不要过于频繁地轮询。 对于短脚本,两秒的间隔是合理的;对于长脚本则应退避。
  • 将请求体放在函数中。 在实际代码中,将文本、说话人和语音参数化,这样你可以对单说话人任务复用相同的提交逻辑。

轮询异步任务并下载音频

语音生成是异步的:你提交任务,然后检查状态,直到任务达到终态。存在两种终态——succeeded 和 failed。其他所有状态都应视为“继续等待”。

一个健壮的轮询程序应该:

  1. 限制总等待时间。 即使是快速模型也可能排在其他工作之后。在合理上限后停止,并暴露任务 ID,以便调用方重试。
  2. 指数退避。 从一到两秒开始,逐次翻倍直到上限。这样可以避免在长任务中对 API 造成过大压力。
  3. 显式处理失败。 当 status == "failed" 时,读取 error 字段并记录——它通常指向请求体问题(未声明的说话人、空文本、不支持的语音)。

当任务成功时,状态响应会包含一个输出引用。使用相同的认证头下载它:

out = status["output"]
audio = requests.get(out["url"], headers=HEADERS, timeout=60)
audio.raise_for_status()
with open("narration.wav", "wb") as f:
    f.write(audio.content)

如果响应返回的是文件 ID 而不是 URL,请通过 HiAPI 上对应的文件端点获取它。将原始任务 ID 与保存的文件一起存储——这是以后将不良渲染追溯到其请求的唯一可靠方式。

理解按字符计费

HiAPI 上的 gemini-3.1-flash-tts 按输入文本的字符数计费。没有按请求或按分钟计费的组成部分需要建模——任务成本随你发送的文本量而扩展,这使得估算变得简单:所有脚本的总字符数乘以当前的每字符费率。

实际影响:

  • 上线前先估算。 汇总你预期的每月文本量,并与定价面板上显示的当前费率进行核对。不要将费率硬编码到代码中;在构建时从平台读取,或将其保存在配置中。
  • 长内容主导成本。 十分钟的旁白主要是文本成本。如果你要生成有声书或课程材料,请先对体量进行建模。
  • 重试会产生费用。 在生成后失败的任务,根据失败模式的不同,仍可能被计费。在提交前进行客户端验证——检查每个说话人标签都已声明且文本非空。
  • 积极缓存。 如果同一脚本被重复渲染(例如演示或测试夹具),请保存生成的音频并复用,而不是对相同字符重复计费。

有关当前费率和任何量级分层,请查阅网站上的定价面板——那是唯一的事实来源。

常见故障点及其修复方法

大多数集成问题都归入少数几类。请按顺序逐一排查。

401 未授权

你的 Authorization 请求头缺失或格式错误。确认密钥已在环境中设置,且请求头内容恰好为 Bearer <key>。如果密钥已轮换,请更新你的环境。

400 错误请求——未知说话人

文本中包含一个未在 speakers 中声明的 [Name] 标签,或大小写不一致。使标签和 name 字段逐字节完全相同。

400 错误请求——输入为空或过大

空文本会立即无法通过验证。非常长的脚本可能超出每请求限制;将其拆分为多个任务,而不是截断。

400 错误请求——无效的语音标识符

语音名称必须来自平台的目录。请精确复制标识符;不要自行编造友好名称。

任务卡在 processing 状态

这通常是排队,而不是 bug。应用退避和总超时。如果它在远超正常时间后仍然持续,记录任务 ID,并用较小的请求体重试一次,以判断大小是否是问题所在。

任务 failed 并带有生成错误

读取 error 字段。它通常指向内容或配置问题,而不是瞬时故障。修复请求体,重新提交,并避免盲目的重试循环——每次尝试都可能对字符计费。

音频可以下载但无法播放

在假定文件损坏之前,先检查下载文件的内容类型。使用与返回格式对应的正确扩展名保存,并验证字节长度非零。文件被截断通常意味着下载请求被中断——重试下载,而不是重新生成。

有了这些检查,大多数首日集成问题可以在几分钟内解决,而不是几小时。

立即开始构建持久 AI 产物

获取 API 密钥,通过一个基于任务(task)的 API 调用图像、视频和音频模型。

阅读 API 文档