API 參考
非同步任務
圖片、影片和音樂任務共用一套持久化生命週期:只提交一次、儲存 task_id、等待終態,然後取得結果。只需實作一個任務執行器,再依模型選擇端點分類。
#所有非同步模型共用一套生命週期
- 1
只提交一次
使用唯一的 Idempotency-Key,並立即儲存回應。
POST /v1/{capability} - 2
持久儲存 task_id
在 worker 確認任務之前先完成儲存。
task_id - 3
輪詢或接收 Webhook
狀態比較忽略大小寫,並在進入終態時停止。
GET /{task_id} - 4
取得並儲存結果
成功後呼叫 /content,並儲存平台回傳的網址。
GET /{task_id}/content
#端點分類
| 能力 | 提交 | 輪詢 | 結果內容 |
|---|---|---|---|
| 圖片 | /v1/images/tasks | /v1/images/tasks/{task_id} | /v1/images/tasks/{task_id}/content |
| 影片 | /v1/videos | /v1/videos/{task_id} | /v1/videos/{task_id}/content |
| 音樂 | /v1/music/generations | /v1/music/tasks/{task_id} | /v1/music/tasks/{task_id}/content |
#狀態取值
同一套生命週期,對外有兩套狀態詞彙:圖片、音樂、3D 任務用 OpenAI 任務那一套;影片以及所有 webhook 負載用 OpenAI 影片那一套。為其中一類寫的狀態判斷,在另一類裡永遠不會命中、而且不會報錯——本表就是兩者的對照。
| 內部狀態 | 圖片 · 音樂 · 3D | 影片 · webhook | 終態 | 含義 |
|---|---|---|---|---|
| NOT_START | processing | queued | 否 | 閘道已受理,尚未分發到通道。 |
| SUBMITTED | queued | queued | 否 | 已提交上游並被廠商確認。 |
| QUEUED | queued | queued | 否 | 在廠商佇列中排隊。 |
| IN_PROGRESS | processing | in_progress | 否 | 生成中。progress 是形如 "60%" 的百分比字串。 |
| SUCCESS | succeeded | completed | 是 | 已完成。透過 /content 取得結果。 |
| FAILURE | failed | failed | 是 | 已確定失敗。fail_reason 說明原因,預扣額度會退回。 |
| UNKNOWN | unknown | unknown | 否 | 廠商不再回傳閘道能識別的狀態。請設定截止時間繼續輪詢,逾時後轉為對帳——不要當成成功或失敗處理。 |
判斷終態集合,不要去判斷「還沒跑完」
只把 succeeded / completed 和 failed 當作停止條件,其餘一切——包括本表沒列出的取值——都按「仍在進行」處理。中間狀態未來可能新增;終態不會,新增終態屬於破壞性變更。
UNKNOWN 不等於失敗
當廠商不再回傳閘道能識別的狀態時,任務會停留在 UNKNOWN。請設定截止時間繼續輪詢,逾時後交給人工或對帳任務——當成失敗會因重試而重複扣費,當成成功則會把空結果交給使用者。
#安全輪詢迴圈
依你所輪詢的那一類的終態詞停止;其餘狀態都要有一個實際時間上的截止時間,並做退避,避免卡住的任務變成無窮迴圈。
poll.py
import time, requests
# The two vocabularies. Pick the one that matches the endpoint you poll.
TASK_DONE, TASK_FAILED = {"succeeded"}, {"failed"} # images / music / 3D
VIDEO_DONE, VIDEO_FAILED = {"completed"}, {"failed"} # videos + webhooks
DEADLINE_SECONDS = 15 * 60
def wait_for_image_task(task_id, api_key):
started = time.monotonic()
delay = 2
while True:
response = requests.get(
f"https://www.aisplice.store/v1/images/tasks/{task_id}",
headers={"Authorization": f"Bearer {api_key}"},
timeout=30,
)
response.raise_for_status()
task = response.json()
status = task.get("status", "")
if status in TASK_DONE:
return task
if status in TASK_FAILED:
raise RuntimeError((task.get("error") or {}).get("message", "task failed"))
# queued / processing / unknown all mean "not finished". Wait — but
# never forever: hand an overdue task to reconciliation.
if time.monotonic() - started > DEADLINE_SECONDS:
raise TimeoutError(f"task {task_id} still {status} after the deadline")
time.sleep(delay)
delay = min(delay * 1.5, 15)重啟後繼續執行
儲存 task_id、端點分類、模型、建立時間和你自己的業務識別碼。worker 重啟後可以繼續輪詢,無需再次提交並產生重複計費。
#結果留存
僅在成功後取得 /content。把平台回傳的網址儲存到業務記錄中;如留存要求需要獨立儲存,再下載一份私有副本。
還是沒解決?
把請求 ID 或錯誤訊息傳給我們,我們來查。