异步图像与视频:respond-async、轮询与 Webhook
视频需要一段时间才能生成,图像有时也是如此。Hyperouter 提供三种方式,让你无需长时间占用连接就能等到结果:为图像发送 Prefer: respond-async、轮询记录,或者在任务结束时由 Webhook 通知你。本指南逐一介绍。
各模型的应答方式
GET /v1/models 中的每个模型都会列出 response_modes:
sync:保持连接,返回一次完整结果。async:发送Prefer: respond-async,得到202,然后轮询。stream:"stream": true。
仅列出 async 的模型必须带上 Prefer 请求头;缺少时,响应为 400,code 为 async_required。视频通过 Videos API 始终是异步的,无论是否带该请求头。
图像:Prefer: respond-async
对列出 async 的模型,加上该请求头即可。下面的模型 id 只是示例:请查看你所用模型的 response_modes。这是 RFC 7240,因此可以重复出现,也可以携带其他偏好,例如 Prefer: wait=10, respond-async。
curl -i https://api.hyperouter.app/v1/images/generations \
-H "Authorization: Bearer $HYPEROUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "Prefer: respond-async" \
-d '{"model": "x-ai/grok-imagine-image", "prompt": "A lighthouse at dusk", "n": 1}'
你会立即收到 202,响应头为 Preference-Applied: respond-async:
{ "id": "0194f2c4-7a1e-7b3c-9d02-5f6a1c0e8b21", "status": "queued" }
对于应答方式为 sync 的模型,如果发送时没有带该请求头,请求最多会等待图像 90 秒。到时图像仍未就绪,就会返回 503,code 为 timeout,带有响应头 x-should-retry: false,并以 error.generation_id 给出任务的 id。任务会继续运行,只有成功时才会计费。请按下文所述轮询该 id。
轮询图像
GET /v1/generations/{id} 返回记录:status 为 queued、submitting、running、done 或 failed。对于图像,outputs 为 [{"url", "expires_at"}],即有效期 24 小时的签名链接。结果可获取 30 天。
import os
import time
import requests
headers = {"Authorization": f"Bearer {os.environ['HYPEROUTER_API_KEY']}"}
def wait_for_image(generation_id: str, every: float = 3.0, limit: float = 600.0) -> dict:
deadline = time.monotonic() + limit
while time.monotonic() < deadline:
r = requests.get(
f"https://api.hyperouter.app/v1/generations/{generation_id}",
headers=headers,
timeout=30,
)
r.raise_for_status()
rec = r.json()["data"]
if rec["status"] in ("done", "failed"):
return rec
time.sleep(every)
raise TimeoutError(generation_id)
状态为 failed 时,记录中的 error 包含 code 和 message。失败的任务不收费。读取结果不计入你的速率限制。
视频:始终是任务
调用 POST /v1/videos 会立即返回 200,响应是状态为 queued 的 Video 对象。Prefer 在这里不起任何作用。
curl https://api.hyperouter.app/v1/videos \
-H "Authorization: Bearer $HYPEROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "x-ai/grok-imagine-video", "prompt": "A lighthouse at dusk", "seconds": 8}'
轮询 GET /v1/videos/{id},直到 status 为 completed(也可能是 queued、in_progress 或 failed)。progress 从 0 到 100。然后下载文件:
async function waitForVideo(id: string): Promise<void> {
const headers = { Authorization: `Bearer ${process.env.HYPEROUTER_API_KEY}` };
for (;;) {
const res = await fetch(`https://api.hyperouter.app/v1/videos/${id}`, { headers });
const video = await res.json();
if (video.status === "completed") break;
if (video.status === "failed") throw new Error(video.error?.code ?? "failed");
await new Promise((r) => setTimeout(r, 5000));
}
const file = await fetch(`https://api.hyperouter.app/v1/videos/${id}/content`, { headers });
// file is video/mp4; ?variant=thumbnail returns the thumbnail
}
视频会保留到其 expires_at,即创建后 30 天。之后请求会返回 404 not_found,因此想保留的内容请及时下载。费用在任务开始前预扣,任务失败时会全额释放。
用 Webhook 代替轮询
在控制台中添加一个端点,任务结束时就会有请求发送到该端点:
| 事件 | 发送时机 |
|---|---|
video.completed, video.failed | 视频任务结束。 |
image.completed, image.failed | 你以 Prefer: respond-async 发送的图像任务结束。 |
载荷只携带 id,不含结果的其他内容,因此请使用你的 API 密钥获取对象:data.id 就是 GET /v1/videos/{id} 和 GET /v1/generations/{id} 所接受的 id。每次投递都采用 Standard Webhooks 方案签名,因此 OpenAI SDK 可以直接校验。请在解析之前,对原始请求体进行校验:
const event = client.webhooks.unwrap(rawBody, req.headers, process.env.HYPEROUTER_WEBHOOK_SECRET);
if (event.type === "video.completed") {
const video = await client.videos.retrieve(event.data.id);
}
请在 10 秒内返回 2xx。其他情况会重试,最多再重试八次,每次重试的 webhook-id 都相同,可用它去重。Webhook 参考给出了端点规则、请求头和完整的重试时间表。
如果某个端点的所有投递连续 3 天失败,该端点会被停用,控制台会说明原因。在你重新启用之前,请改用轮询。
下一步
- 用 Idempotency-Key 安全重试让创建调用本身也可以放心重复。
- 图像和视频是完整的参考。