教程
如何在 HiAPI 上调用 gemini-3.1-flash-tts:curl、Python 与一个可运行的请求
gemini-3.1-flash-tts 在 HiAPI 上能为你做什么
gemini-3.1-flash-tts 是通过 HiAPI 统一 API 提供的文本转语音模型。你发送文本和语音配置,平台返回生成的音频。由于它通过单一、一致的接口对外暴露,你无需为每个模型单独集成——相同的认证头、相同的基础 URL 以及相同的异步任务模式适用于平台上的所有 TTS 模型。
当你需要以下能力时,可以选用这个模型:
- 多说话人旁白。 脚本、对话或播客风格的内容,其中不同片段应听起来像不同的声音。
- 快速周转。
flash系列面向低延迟生成,这对交互式应用或 SLA 紧张的批处理任务尤为重要。 - 可预测的集成界面。 一种请求形态即可覆盖从单行文本到多说话人场景的所有情况。
如果你正在构建的应用已经调用其他 HiAPI 模型,添加语音功能只需替换模型标识符并调整请求体——无需新的 SDK,也无需新的凭据流程。
前置条件与认证设置
在首次调用之前,请确保你已具备:
- 一个 HiAPI 账户和 API 密钥。 从控制台生成密钥。请将其视为机密——不要提交到源代码管理中。
- HiAPI 基础 URL。 所有请求都发往同一主机;只有每个端点的路径不同。
- 一个发起 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。其他所有状态都应视为“继续等待”。
一个健壮的轮询程序应该:
- 限制总等待时间。 即使是快速模型也可能排在其他工作之后。在合理上限后停止,并暴露任务 ID,以便调用方重试。
- 指数退避。 从一到两秒开始,逐次翻倍直到上限。这样可以避免在长任务中对 API 造成过大压力。
- 显式处理失败。 当
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 字段。它通常指向内容或配置问题,而不是瞬时故障。修复请求体,重新提交,并避免盲目的重试循环——每次尝试都可能对字符计费。
音频可以下载但无法播放
在假定文件损坏之前,先检查下载文件的内容类型。使用与返回格式对应的正确扩展名保存,并验证字节长度非零。文件被截断通常意味着下载请求被中断——重试下载,而不是重新生成。
有了这些检查,大多数首日集成问题可以在几分钟内解决,而不是几小时。