task_id 时,还要保存模型、业务任务 ID、提交时间和截止时间。应用没有记录的
业务上下文,无法从任务查询接口恢复。
可直接运行的 Python 轮询器
import base64
import os
import random
import time
from pathlib import Path
import requests
task_id = os.environ["YINGTU_TASK_ID"]
deadline = time.monotonic() + 180
delay = 2.0
while True:
if time.monotonic() >= deadline:
raise TimeoutError(f"任务轮询超过截止时间:{task_id}")
response = requests.get(
f"https://api.yingtu.ai/v1/tasks/{task_id}",
headers={"Authorization": f"Bearer {os.environ['YINGTU_API_KEY']}"},
timeout=30,
)
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
wait = float(retry_after) if retry_after else delay
time.sleep(min(wait, max(0, deadline - time.monotonic())))
delay = min(delay * 1.5, 10)
continue
response.raise_for_status()
task = response.json()
status = task["status"]
if status == "completed":
if task["model"].startswith("gemini-"):
image = next(
part["inlineData"]
for candidate in task["result"].get("candidates", [])
for part in candidate.get("content", {}).get("parts", [])
if part.get("inlineData", {}).get("data")
)
suffix = {
"image/png": ".png",
"image/jpeg": ".jpg",
"image/webp": ".webp",
}[image["mimeType"]]
encoded = image["data"]
else:
image = task["result"]["data"][0]
encoded = image["b64_json"]
suffix = ".png" # 当前两个逆向图片接口返回 PNG
Path(f"{task_id}{suffix}").write_bytes(base64.b64decode(encoded))
break
if status == "failed":
error = task.get("error", {})
raise RuntimeError(
f"{error.get('code', 'task_failed')}: "
f"{error.get('message', '未知任务错误')}"
)
if status == "expired":
raise RuntimeError("result_expired:仍需要图片时重新创建任务")
if status not in {"queued", "in_progress"}:
raise RuntimeError(f"未知任务状态:{status}")
time.sleep(delay + random.uniform(0, min(0.5, delay * 0.1)))
delay = min(delay * 1.5, 10)
result 刻意保持不同,不能用同一套
result.data[].mime_type 逻辑解析。progress 只是状态提示,不能换算预计完成时间;
客户端也不需要看到每一个中间状态。
核对任务结构
查看原生结果、结构化错误和过期状态。
回到任务提交
设置幂等键并保存返回的任务 ID。
判断能否重试
只在安全且未超时的情况下重新创建任务。