Как устроена задача
Изображение, видео и речь делаются от десятков секунд до нескольких минут — держать всё это время
открытым соединение нечем. Поэтому генерация оформляется задачей, и путь всегда один и тот же из
трёх шагов: поставить задачу, дождаться её, забрать файл. Так же устроен вызов у поставщика, так
что код, написанный под его createTask, работает у нас сменой адреса и
ключа.
Генерация занимает десятки секунд, поэтому изображение не приходит в ответ на запрос. Первый вызов
ставит задачу и сразу отвечает её номером, второй по этому номеру отдаёт состояние задачи, а когда
она готова — ссылку на результат. Ключ передаётся в обоих запросах заголовком
Authorization: Bearer sk-… и выпускается
в кабинете. На один ключ шлюз пропускает
30 постановок задачи и 300
запросов состояния в минуту.
Незавершённых задач на аккаунте может быть не больше 50 — свыше этого
числа постановка отвечает 429.
Пример вызова
# 1. Поставить задачу — в ответе придёт её номер
TASK=$(curl -s https://api.gen202.com/api/v1/jobs/createTask \
-H "Authorization: Bearer sk-ваш-ключ" \
-H "Content-Type: application/json" \
-d '{
"model": "nano-banana/2",
"input": {
"prompt": "Рыжий кот в скафандре на фоне Земли, мягкий свет",
"aspect_ratio": "1:1",
"resolution": "1K"
}
}' | jq -r '.data.taskId')
# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
# generating означают «работа идёт», success и fail — окончательные
while true; do
RECORD=$(curl -s "https://api.gen202.com/api/v1/jobs/recordInfo?taskId=$TASK" \
-H "Authorization: Bearer sk-ваш-ключ")
STATE=$(echo "$RECORD" | jq -r '.data.state')
case "$STATE" in success|fail) break;; esac
sleep 3
done
# 3. Забрать результат. Ссылка на файл лежит в resultJson — это строка JSON
# внутри JSON, поэтому её разбирают вторым разбором (fromjson)
echo "$RECORD" | jq -r '.data |
if .state == "fail"
then "отказ \(.failCode): \(.failMsg)"
else .resultJson | fromjson | .resultUrls[0]
end'
import json
import time
import requests
headers = {"Authorization": "Bearer sk-ваш-ключ"}
# 1. Поставить задачу — в ответе придёт её номер
created = requests.post(
"https://api.gen202.com/api/v1/jobs/createTask",
headers=headers,
json={
"model": "nano-banana/2",
"input": {
"prompt": "Рыжий кот в скафандре на фоне Земли, мягкий свет",
"aspect_ratio": "1:1",
"resolution": "1K"
}
},
).json()
task_id = created["data"]["taskId"]
# 2. Спрашивать состояние, пока задача не закончится: waiting, queuing и
# generating означают «работа идёт», success и fail — окончательные
while True:
task = requests.get(
"https://api.gen202.com/api/v1/jobs/recordInfo",
headers=headers,
params={"taskId": task_id},
).json()["data"]
if task["state"] in ("success", "fail"):
break
time.sleep(3)
# 3. Разобрать итог. У неудачи причина в failCode и failMsg
if task["state"] == "fail":
raise SystemExit("отказ " + task["failCode"] + ": " + task["failMsg"])
# resultJson — строка JSON внутри JSON, поэтому разбор второй
result = json.loads(task["resultJson"])
print(result["resultUrls"][0])
print("списано кредитов:", task["creditsConsumed"])
Пример проходит весь путь: ставит задачу, повторяет запрос состояния раз в
3 секунды, пока задача не закончится, и разбирает ответ до ссылки
на файл. Разборов два, потому что поле resultJson — строка JSON
внутри JSON. В теле первого запроса стоят обязательные поля модели и значения из её
перечислений, поэтому он проходит проверку шлюза как есть.
Подставить остаётся только свой ключ.
Пример на оболочке разбирает ответ через jq; на Python своего
ничего не нужно, кроме requests.
Состояния задачи
Состояние лежит в поле state ответа
/api/v1/jobs/recordInfo. Первые три означают, что работа идёт: запрос надо
повторить примерно через 3 секунды — чаще спрашивать нечего, столько же
ждёт между опросами сам шлюз. Два последних состояния окончательные: после них задача не меняется.
| state | Что происходит |
|---|---|
| waiting | Задача принята и стоит в очереди шлюза; поставщику она ещё не отправлена. |
| queuing | Поставщик задачу принял и поставил в свою очередь. |
| generating | Генерация идёт. |
| success | Готово: ссылки на результат лежат в resultJson, в creditsConsumed — сколько списано. |
| fail | Задача не удалась: причина в failCode и failMsg, удержанные кредиты возвращены целиком. |
Опрос не бывает бесконечным: задача на изображение живёт не дольше 15
минут, после чего шлюз закрывает её сам состоянием fail и возвращает
удержанные кредиты целиком. Причина неудачи приходит двумя полями:
failCode из таблицы ниже и failMsg словами.
| failCode | Что произошло |
|---|---|
| 501 | Поставщик вернул отказ: генерация не удалась. |
| 408 | Результата нет дольше крайнего срока задачи (15 минут для картинки). |
| 404 | Поставщик не знает такой задачи. |
| 429 | Поставщик отбил создание задачи по своему лимиту. |
| 500 | Поломка на нашей стороне; подробности остаются в журнале шлюза. |
Ссылка на готовый файл живёт 14 дней — столько его хранит поставщик. Файл,
который нужен дольше, скачивайте к себе сразу после того, как задача пришла в состояние
success.
Уведомление о готовности
Ждать результат можно не только опросом. Если при постановке задачи передать поле
callBackUrl, шлюз сам постучится на этот адрес, когда задача
закроется — успехом или отказом. Тело уведомления — то же самое, что отдаёт запрос состояния:
конверт { code, msg, data }, а внутри
data — та же запись задачи с
state, resultJson и
creditsConsumed. Разбирать уведомление тем же кодом, что и ответ
опроса, — это и есть замысел: у поставщика устроено так же.
Запрос приходит методом POST с типом
application/json; номер задачи продублирован заголовком
X-Gen202-Task-Id. Принимающая сторона должна ответить кодом из
двухсотых в течение 10 секунд. Любой другой ответ, обрыв
и молчание считаются недоставкой: шлюз повторит попытку — всего их
4, с паузами 30 с, 2 мин, 8 мин. После последней попытки
уведомление больше не шлётся, но состояние задачи всё это время доступно опросом, и он остаётся
надёжным способом узнать исход.
Требования к адресу: http или https и имя,
которое разрешается в адрес открытой сети. На внутренние адреса шлюз не ходит — ни на
127.0.0.1, ни на адреса домашних и служебных сетей, — и
перенаправлениям не следует: адрес назначения должен принимать запрос сам.
Что лежит в ответе каждого из двух запросов и как достать из него ссылку на файл — в разделе Формат ответа. Чем отвечает шлюз, когда запрос не принят, — в разделе Отказы.