产品 · 2026-05-13

异步图像生成与编辑,正式上线 TheRouter

通过一个 OpenAI 兼容的 API 调用 GPT-Image、DALL·E、Imagen、Grok-Image: 提交任务、轮询状态、从 S3 下载。没有 30 秒代理超时,不需要重写前端。

为什么要走异步

前沿图像模型 —— openai/gpt-image-2openai/gpt-image-1.5google/imagen-4xai/grok-image-1 —— 单张通常需要 30–180 秒。 这超过了几乎所有反向代理的容忍度:CloudFront 上限 60 秒, Cloudflare 免费档默认 100 秒,AWS ALB 空闲超时 60 秒。 浏览器 fetch 虽然默认不超时,但用户会先关掉标签页。

文本模型可以硬扛,但图像生成不能依赖单条长连接 HTTP, 必须切到任务系统。我们把它做了出来:OpenAI 兼容(不用自定义 SDK), 覆盖所有图像模型,「成功」的定义是「字节已经躺在 S3 里、可以拿到预签名 URL」。 本文是上线公告 + 完整教程。

你能拿到什么

快速上手

1) 提交任务

curl -X POST "https://api.therouter.ai/v1/images/generations?async=true" \
  -H "Authorization: Bearer $THEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-image-2",
    "prompt": "白色大理石桌上一颗极简风格的红苹果,柔和自然光",
    "size": "1024x1024",
    "quality": "high"
  }'

响应(HTTP 202):

{
  "id": "img_uxRZK4KwxkWaaHXRfwP0fWUDST",
  "object": "image_job",
  "status": "queued",
  "model": "openai/gpt-image-2",
  "created_at": 1778637249,
  "polling_url": "https://api.therouter.ai/v1/jobs/img_uxRZK4KwxkWaaHXRfwP0fWUDST",
  "expires_at": 1779242049
}

拿到 202 后立刻把 job.id 持久化下来。客户端进程死掉也没关系, 只要 ID 还在,从任何地方都可以恢复轮询。

2) 轮询状态

curl "https://api.therouter.ai/v1/jobs/img_uxRZK4KwxkWaaHXRfwP0fWUDST" \
  -H "Authorization: Bearer $THEROUTER_API_KEY"

worker 处理完后:

{
  "id": "img_uxRZK4KwxkWaaHXRfwP0fWUDST",
  "object": "image_job",
  "status": "succeeded",
  "model": "openai/gpt-image-2",
  "created_at": 1778637249,
  "completed_at": 1778637444,
  "polling_url": "https://api.therouter.ai/v1/jobs/...",
  "content_url": "https://api.therouter.ai/v1/jobs/.../content",
  "unsigned_urls": [
    "https://therouter-media-prod.s3.us-east-2.amazonaws.com/...&X-Amz-Signature=..."
  ],
  "image_count": 1,
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "cost_credits": 9
  },
  "error": null
}

3) 下载图像

unsigned_urls[0] 是带 AWS 签名的 S3 URL,7 天内有效, 不需要再带 Authorization。

curl -L -o apple.png "<unsigned_urls[0]>"

或者带 Bearer token 访问 GET /v1/jobs/:id/content, 网关每次返回一个新的签名 302 跳转,适合需要审计每次下载的场景。

完整客户端教程

Python

import os, time, requests

API = "https://api.therouter.ai"
KEY = os.environ["THEROUTER_API_KEY"]
H = {"Authorization": f"Bearer {KEY}"}

# 1) 提交
r = requests.post(
    f"{API}/v1/images/generations?async=true",
    headers={**H, "Content-Type": "application/json"},
    json={
        "model": "openai/gpt-image-2",
        "prompt": "白色大理石桌上一颗极简风格的红苹果",
    },
    timeout=30,
)
r.raise_for_status()
job = r.json()
print("submitted:", job["id"])

# 2) 轮询
deadline = time.time() + 300
while time.time() < deadline:
    s = requests.get(job["polling_url"], headers=H, timeout=15).json()
    print("status:", s["status"])
    if s["status"] == "succeeded":
        with open("out.png", "wb") as f:
            f.write(requests.get(s["unsigned_urls"][0], timeout=60).content)
        print("saved out.png")
        break
    if s["status"] in ("failed", "cancelled", "expired"):
        raise SystemExit(s.get("error") or s["status"])
    time.sleep(5)
else:
    raise SystemExit("polling timeout")

TypeScript(Bun / Node 20+)

const API = "https://api.therouter.ai";
const KEY = process.env.THEROUTER_API_KEY!;
const H = { Authorization: `Bearer ${KEY}` };

const submit = await fetch(`${API}/v1/images/generations?async=true`, {
  method: "POST",
  headers: { ...H, "Content-Type": "application/json" },
  body: JSON.stringify({
    model: "openai/gpt-image-2",
    prompt: "白色大理石桌上一颗极简风格的红苹果",
  }),
});
if (!submit.ok) throw new Error(`submit failed: ${submit.status}`);
const job = await submit.json();
console.log("submitted:", job.id);

const deadline = Date.now() + 300_000;
while (Date.now() < deadline) {
  const s = await (await fetch(job.polling_url, { headers: H })).json();
  console.log("status:", s.status);
  if (s.status === "succeeded") {
    const buf = await (await fetch(s.unsigned_urls[0])).arrayBuffer();
    await Bun.write("out.png", buf);
    break;
  }
  if (["failed", "cancelled", "expired"].includes(s.status)) {
    throw new Error(s.error?.message ?? s.status);
  }
  await new Promise((r) => setTimeout(r, 5_000));
}

图像编辑 — 同样的形态

编辑接口用 multipart,但其余流程完全一致:?async=true 提交、拿到 polling_url、 轮询、下载。

curl -X POST "https://api.therouter.ai/v1/images/edits?async=true" \
  -H "Authorization: Bearer $THEROUTER_API_KEY" \
  -F "model=openai/gpt-image-2" \
  -F "prompt=把这个场景改成水彩画风格" \
  -F "size=1024x1024" \
  -F "image=@input.png"

现在可用的图像模型

所有模型都用同一套「提交 → 轮询 → 下载」流程。 切换模型只需要改一个字符串。响应中的 model 字段始终回显你传入的别名, 我们不会向客户端暴露上游供应商。

轮询节奏、重试和注意事项

底层做了什么

多数调用方不用关心,但简单说:网关把图像任务写进 MySQL(queued 行), 投递到 Redis 上的 BullMQ 队列,由 gateway pod 内嵌的 worker 池消费。 worker 调用对应的 provider service(每个上游一个容器),解析响应, 把字节上传到 S3,把行翻转为 succeeded。 S3 前缀按 tenant 隔离,多租户天然安全。 credits 的预留 / 消费 / 退款是每次状态翻转的原子事务 —— 不存在「钱已扣、图没出」的窗口。

同一套形态也已经覆盖音频视频aud_ 开头的音频任务和 vid_ 开头的视频任务 共享同一个 /v1/jobs/:id 轮询接口、同一套取消语义、 同一套计费不变量。一套心智模型,三种模态。

完整指南

全量参数、状态、错误速查表、最佳实践见专门的指南文档:

→ 异步图像生成与编辑指南

开始使用

dashboard.therouter.ai 获取 API key,从 模型目录 挑一个图像模型, 粘贴上面任意一段代码即可。新账号注册赠送 credits,足够端到端跑几张高质量测试图。

反馈和 bug 报告请发邮件到 hello@therouter.ai

客服支持