Практика интеграции Image API: создайте собственную асинхронную очередь
Image API APIYI работают синхронно. В этом руководстве показано, как построить поверх них асинхронную очередь задач: управление на уровне задач, развязка повторных попыток, сохранение результатов и интеграция с несколькими провайдерами.
Эта страница — технический материал / рекомендации для команд разработки, интегрирующих генерацию изображений в свои продукты. Мы делимся только инженерными практиками — здесь не требуется никаких изменений со стороны APIYI. Вы можете реализовать все это поверх существующих синхронных API.
Синхронный или async? Сначала разберитесь с моделью API APIYI
APIYI image generation API — все синхронные: эндпоинты вроде /v1/images/generations выполняют запрос «до завершения» после отправки. Даже если клиент отключится посреди выполнения, сервер все равно завершит генерацию — то есть это не async API задач, где вы «сначала получаете task_id, а затем опрашиваете результат».
На уровне шлюза APIYI уже оборачивает upstream async polling (некоторые провайдеры нативно используют цикл polling_url) в синхронный OpenAI API для изображений. Для вас это всегда «отправить один раз, получить результат один раз» — не нужно писать цикл опроса самостоятельно.
Многие команды сразу спрашивают: «Тогда как мне управлять async-задачами?» На самом деле это две разные вещи:
Синхронный — формат API APIYI (на уровне HTTP-запроса: один запрос, один результат).
Async-очередь — инженерная практика на вашей стороне (на уровне бизнес-задачи: вернуть ответ сразу, а выполнение продолжить в фоне).
Эти два подхода не конфликтуют. Ниже показано, как самостоятельно обернуть async-очередь вокруг синхронного API.
Почему командам разработки по-прежнему нужно управление «на уровне задач»
Синхронный вызов image API внутри потока запроса пользователя подходит для демо. Но как только вы строите реальный продукт для конечных пользователей, вам почти наверняка нужно отделить «бизнес-задачу» от «одного HTTP-вызова». По четырем причинам:
Успех ≠ один вызов
«Успешная задача» часто собирается из нескольких синхронных вызовов: первоначальный тайм-аут или иногда 429/503 требуют повторной попытки. Когда задача и вызов разделены, повторы, backoff и тайм-ауты полностью прозрачны для конечного пользователя — он видит только, что «это изображение в итоге успешно сгенерировалось».
Прозрачная пересылка, без хранения
APIYI выполняет только прозрачную пересылку и не хранит входные данные или результаты пользователя (prompt, reference images и сгенерированные результаты не сохраняются). Чтобы дать пользователям историю, проверку статуса и сохранение результатов, вы должны сохранять их самостоятельно — это неизбежный шаг на стороне продукта.
Более удобный UX для конечных пользователей
Пользователь получает task_id при отправке, а frontend периодически опрашивает статус задачи вместо того, чтобы держать долгое соединение. Обновление страницы или краткий обрыв сети не приведут к потере задачи; batch generation может ставиться в очередь и заполняться по одной.
Поддержка нескольких провайдеров становится возможной
Как только у вас появляется собственная абстракция задачи, слой Worker может по требованию переключаться / выполнять failover / сравнивать цену между несколькими провайдерами — как минимум это делает возможным принцип «не класть все яйца в одну корзину».
Справочная архитектура: оберните синхронный вызов в асинхронную очередь
Основная идея в одном предложении: уровень API только «получает задачу, помещает ее в очередь и возвращает task_id»; сам синхронный вызов выполняется во фоновом Worker.
Фронтенд отправляет запрос на генерацию в ваш собственный уровень API; уровень API создает запись задачи (статус pending), помещает ее в очередь и сразу возвращает task_id фронтенду. Пользователь никогда не ждет — ответ приходит за миллисекунды.
2
Помещение в очередь
Очередь может быть легковесной: Redis List / Stream, RabbitMQ / Kafka или даже таблица базы данных со столбцом status, которую сканируют по расписанию. Выбор зависит от вашего масштаба — на старте не нужно сразу тянуть тяжелую middleware.
3
Worker: синхронный вызов + повтор
Фоновый Worker забирает задачу, переводит статус в running и синхронно вызывает APIYI image API. При ошибках, которые можно повторить, он выполняет повторы с экспоненциальной задержкой (см. «Повтор и тарификация» ниже), и все это прозрачно для пользователя.
4
Сохранение
Независимо от того, успешна ли операция или нет, запишите результат обратно в базу данных: при успехе сохраните URL выходного изображения, задержку и метаданные тарификации, установите статус succeeded; при ошибке сохраните ошибку и установите статус failed. Именно эту часть APIYI не делает за вас, и вы должны реализовать ее сами.
5
Периодический опрос фронтенда
Фронтенд периодически проверяет статус задачи с помощью task_id (или вы отправляете данные через WebSocket / SSE). Когда задача завершается, покажите результат; при ошибке — дружелюбное сообщение. Браузеру пользователя никогда не нужно держать длительное соединение.
Используйте понятную машину состояний, чтобы описать жизненный цикл каждой задачи:
State
Meaning
Typical transition
pending
Поставлено в очередь, ожидает воркера
→ running
running
Воркер синхронно вызывает APIYI
→ succeeded / retrying / failed
retrying
Возникла ошибка, допускающая повтор, ожидание backoff
→ running
succeeded
Успешно сгенерировано, результат сохранен
terminal
failed
Повторные попытки исчерпаны или ошибка не подлежит повтору
terminal
Таблица задач должна содержать как минимум следующие поля (типы зависят от вашего стека):
Field
Description
task_id
Уникальный идентификатор задачи, возвращаемый фронтенду при отправке
status
Указанный выше enum статуса
provider / model
Используемые провайдер и модель (зарезервировано для поддержки нескольких провайдеров)
input
Ввод пользователя (prompt, ссылки на reference-image, размер и другие параметры)
output_url
URL результата (желательно после переноса в ваше собственное хранилище)
retry_count
Количество повторных попыток на данный момент, для ограничения по rate limit и отладки
error
Причина сбоя (код ошибки + понятное сообщение)
created_at / updated_at
Время создания и последнего обновления (включите часовой пояс, например UTC+8)
latency / cost
Метаданные о задержке и тарификации, для учета затрат и мониторинга
Перенесите сгенерированный результат в свое объектное хранилище (OSS / S3 и т. д.) и сохраните этот URL — не полагайтесь надолго на временную ссылку стороннего сервиса. Временные ссылки могут истечь; хранить собственную копию стабильнее для конечных пользователей.
Самая большая ценность «управления на уровне задачи» — правильно настроить повторы. Тарификация и стратегия повторов зависят от типа ошибки:
Сценарий
Подлежит тарификации?
Повторять?
429 / 503 (лимит запросов / занят upstream)
Не тарифицируется
✅ Повторять, экспоненциальная задержка, примерно 2 раза
Тайм-аут клиента / проактивное отключение
Все равно тарифицируется
⚠️ Можно повторять, но сначала задайте разумный тайм-аут в зависимости от разрешения (~60–600 с)
Отказ по проверке безопасности контента (status 200, все равно тарифицируется)
Все равно тарифицируется
❌ Не повторяйте; верните пользователю дружелюбное сообщение
Учитывайте отдельно “число повторных попыток бизнес-задачи” и “была ли она тарифицирована”. 429/503 повторные попытки не тарифицируются, поэтому смело увеличивайте задержку; но отключения из-за тайм-аута и отказы проверки безопасности контента тарифицируются даже когда они «сбоят» — слепые повторы увеличивают затраты. Проверьте тип ошибки, прежде чем снова тратить средства.
Полные критерии определения ошибки и дружелюбных сообщений см.:
Обработка ошибок изображений Gemini
Сигналы обнаружения сбоев, политика модерации контента и стратегия дружелюбных сообщений.
Гарантия при сбое генерации
За сбои, вызванные не вами, кредиты возмещаются по количеству.
Продвинутый уровень: одна очередь, несколько провайдеров
С помощью абстракции задачи вызов Worker может перейти от «жесткой привязки к одному endpoint» к «маршрутизации по provider». Сведите все к одной записи submit(provider, payload) и позвольте Worker определять фактический upstream на основе поля provider у задачи:
Failover: когда провайдер A продолжает сбоить, автоматически переключайтесь на B, незаметно для пользователя.
Сравнение стоимости / маршрутизация: направляйте разные задачи к разным провайдерам или моделям в зависимости от стоимости или сценария.
Canary: отправляйте небольшую долю трафика на новую модель для проверки, а затем постепенно увеличивайте объем.
В большинстве случаев вам на самом деле не нужен собственный многопровайдерный слой: сам APIYI агрегирует gpt-image-2, Nano Banana, FLUX, Seedream и другие, так что один ключ APIYI покрывает большинство потребностей в рамках одного стиля API. Собственная абстракция провайдеров — вариант «на всякий случай»; добавляйте ее только тогда, когда вам действительно нужно кросс-провайдерное аварийное переключение или сравнение стоимости.
Почему бы просто не дать мне API асинхронных задач вместо синхронного?
Генерация изображений по своей природе — это «отправить один раз, получить одно изображение»: это сильная синхронная семантика, и обернуть ее в синхронный API проще всего для подавляющего большинства вызывающих сторон (не нужно поддерживать опрос, не нужно обрабатывать истечение срока жизни задачи). Нужны ли вам асинхронная очередь, конечный автомат и персистентность, зависит от формата вашего продукта (ориентирован ли он на конечного пользователя, нужна ли вам история), поэтому эту часть вы при необходимости реализуете сами для максимальной гибкости.
Если у клиента истекло время ожидания и он отключился, задача все еще выполняется? Она тарифицируется?
Она продолжает выполняться. Как только синхронный endpoint получает запрос, он выполняется до завершения; отключение клиента не прерывает генерацию на стороне сервера, и эта генерация тарифицируется как обычно. Поэтому задайте достаточный timeout в зависимости от разрешения (~60–600s) — не ставьте его слишком коротким и не оказывайтесь в ситуации, когда вы «платите, но не получаете изображение».
Сохраняет ли APIYI историю моей генерации изображений?
Нет. APIYI выполняет только прозрачную пересылку и не хранит входные или выходные данные пользователя. Чтобы предоставить пользователям историю, просмотр статуса и сохранение результатов, вам нужно хранить это на своей стороне — именно поэтому в этом руководстве рекомендуется «управление на уровне задач».
Я уже использую один ключ APIYI — нужен ли мне все еще слой для нескольких провайдеров?
Обычно нет. APIYI уже агрегирует несколько семейств моделей в одном стиле API, и одного ключа, как правило, достаточно. Только если у вас есть явные потребности в аварийном переключении между провайдерами, сравнении цен или маршрутизации по требованиям соответствия, стоит добавить абстракцию провайдера в слой Worker — это опционально, а не обязательно.