HiAPI
Блог

Руководство

Как вызывать gpt-image-2/image-to-image на HiAPI: curl, Python и один рабочий запрос

Практика запросов image-to-image для разработчиков: отправка референсного изображения, опрос задачи и загрузка результата

Oct 31, 2026
扁平流体形态交叠的封面图,墨黑底色上浮现青瓷绿与石板灰的克制波纹,呼应教程文章。

Чего ожидает эндпоинт image-to-image

Модель gpt-image-2/image-to-image на HiAPI — это асинхронный эндпоинт генерации изображений. В отличие от синхронного вызова, который блокируется до возврата изображения, этот эндпоинт принимает ваш запрос, ставит его в очередь и возвращает дескриптор задачи. Затем вам нужно отслеживать эту задачу, пока она не завершится.

На уровне запроса вам нужно передать три вещи:

  • id модели: gpt-image-2/image-to-image.
  • Референсное изображение: исходное изображение, на основе которого модель должна выполнить преобразование или построение. Это определяющий вход для режима image-to-image — без него вы работаете в режиме text-to-image.
  • Промпт и параметры: текстовая инструкция, описывающая желаемый результат, а также необязательные поля, перечисленные в документации модели (например, размер или формат вывода). Отправляйте только те параметры, которые перечислены в документации модели.

Ответ — это не само изображение, а объект задачи, содержащий идентификатор и статус. Считайте этот статус единственным источником истины: pending или processing означает, что нужно продолжать ждать, succeeded — что результат готов, failed — что произошла ошибка и опрос следует прекратить.

С самого начала проектируйте клиент вокруг этого жизненного цикла. Всё остальное в этом руководстве — curl, Python, опрос, загрузка — строится на том факте, что «отправка» и «завершение» — это два разных момента.

Подготовьте ключ HiAPI и референсное изображение

Прежде чем писать какой-либо код, подготовьте две вещи.

API-ключ. Создайте его в консоли HiAPI и сохраните в переменной окружения, а не вставляйте напрямую в скрипт:

export HIAPI_API_KEY="your-key-here"

Обращайтесь с ключом как с секретом. Не коммитьте его в репозиторий и не записывайте в логи запросов.

Референсное изображение. Выберите локальный файл изображения, который вы хотите преобразовать. Держите разумный размер — слишком большие изображения с высоким разрешением являются самой частой причиной медленных запросов или отказов. Для первого запуска проще всего положить PNG или JPEG в тот же каталог, где находится скрипт:

ls -lh ./reference.png

Запишите путь к файлу; вы будете ссылаться на него и в примере с curl, и в примере с Python. Если вы планируете вызывать модель с сервера, убедитесь, что процесс имеет права на чтение этого пути, а файл является корректным изображением (а не переименованным файлом с неправильным расширением).

Ключ экспортирован, изображение лежит на диске — всё, что нужно для первого запроса, готово.

Один рабочий запрос curl

Самый быстрый способ проверить учётные данные и структуру тела запроса — выполнить вызов curl. Он отправит задачу и выведет объект задачи:

curl -sS https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2/image-to-image",
    "input": {
      "image": "https://example.com/reference.png",
      "prompt": "Turn this photo into a watercolor illustration"
    }
  }'

Два замечания к этому примеру. Во-первых, референсное изображение передаётся в виде URL через поле input.image; если документация эндпоинта для вашего сценария поддерживает встроенный base64 или multipart-загрузку, используйте эту форму, но сохраняйте имена полей одинаковыми. Во-вторых, промпт — это место, где вы описываете преобразование: конкретно укажите стиль, композицию и то, что должно остаться неизменным.

Запустите его и прочитайте ответ. Успешная отправка вернёт id задачи и нетерминальный статус. Если вы получили 401, значит ваш ключ отсутствует или неверен. Если вы получили 422 или аналогичную ошибку валидации, проверьте тело запроса — обычно это неверный формат id модели или поля image.

Сохраните id задачи. Все последующие шаги зависят от него.

То же самое на Python

Версия на Python должна полностью соответствовать вызову curl, явно сохраняя аутентификацию и построение тела запроса, чтобы ничего не было скрыто слоями абстракции. Используйте requests:

import os
import requests

API_KEY = os.environ["HIAPI_API_KEY"]
BASE_URL = "https://api.hiapi.ai/v1"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

payload = {
    "model": "gpt-image-2/image-to-image",
    "input": {
        "image": "https://example.com/reference.png",
        "prompt": "Turn this photo into a watercolor illustration",
    },
}

response = requests.post(f"{BASE_URL}/tasks", json=payload, headers=headers, timeout=30)
response.raise_for_status()
task = response.json()
print(task["id"], task["status"])

Если ваша интеграция отправляет локальный файл, а не URL, кодируйте и прикрепляйте изображение согласно документации эндпоинта, а не придумывайте собственные поля. Дисциплина та же, что и в curl: постройте тело запроса как обычный словарь, отправьте его, проверьте HTTP-статус перед разбором.

Оберните логику отправки в функцию, возвращающую id задачи. Тогда код опроса из следующего раздела не будет зависеть от того, как создаётся задача.

Опрос асинхронной задачи до завершения

Вызов отправки возвращается ещё до того, как изображение сгенерировано. Чтобы получить результат, опрашивайте эндпоинт статуса задачи, пока задача не перейдёт в терминальное состояние. Надёжный цикл обладает тремя свойствами: задержкой между опросами, максимальным временем ожидания и явной обработкой состояния ошибки.

import time

def wait_for_task(task_id, headers, base_url, timeout=300, interval=3):
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(f"{base_url}/tasks/{task_id}", headers=headers, timeout=30)
        r.raise_for_status()
        task = r.json()
        status = task["status"]
        if status == "succeeded":
            return task
        if status == "failed":
            raise RuntimeError(f"Task failed: {task}")
        time.sleep(interval)
    raise TimeoutError(f"Task {task_id} did not finish within {timeout}s")

Несколько практических улучшений:

  • Экспоненциальная задержка: начинайте с короткого интервала и постепенно увеличивайте его или держите достаточно широкий фиксированный интервал, чтобы не нагружать API слишком часто. Генерация изображений не происходит мгновенно.
  • Тайм-аут: всегда ограничивайте общее время ожидания. Иначе зависшая задача будет бесконечно держать ваш worker.
  • Терминальные состояния: считайте терминальными только succeeded и failed. Любое другое состояние означает, что нужно продолжать опрос. Если API в будущем добавит промежуточные состояния, ваш цикл продолжит работать корректно.

Если вы запускаете много задач, опрашивайте их в фоновом worker или очереди, а не блокируйте веб-запрос. Жизненный цикл тот же, отличается только модель параллелизма.

Загрузка и проверка результата

Как только задача сообщает об успехе, объект задачи содержит местоположение сгенерированного изображения — обычно URL в поле вывода. Загрузите его и запишите на диск:

task = wait_for_task(task_id, headers, BASE_URL)
image_url = task["output"]["image"]

img = requests.get(image_url, timeout=60)
img.raise_for_status()

with open("result.png", "wb") as f:
    f.write(img.content)

Если подписанный URL имеет срок действия, загружайте сразу после завершения или сразу сохраняйте файл, а не сохраняйте URL на будущее. Если ваш эндпоинт возвращает base64, а не URL, сначала декодируйте, затем записывайте.

Быстрая проверка целостности поможет избежать запутанных сбоев на следующих этапах:

from PIL import Image

with Image.open("result.png") as im:
    im.verify()
print("valid image, bytes:", os.path.getsize("result.png"))

Если файл не является декодируемым изображением, verify() выбросит исключение — это позволяет поймать усечённые загрузки и ошибочные страницы, сохранённые как изображение. Проверка размера в байтах также подскажет, разумен ли ответ. На этом цикл завершён: отправка, опрос, загрузка, проверка.

Устранение типичных сбоев

Большинство первых интеграций терпят неудачу несколькими предсказуемыми способами. Диагностируйте в следующем порядке.

Ошибки аутентификации (401/403). Ключ отсутствует, имеет неверный формат или недостаточно прав. Убедитесь, что переменная окружения экспортирована в shell или процессе, выполняющем запрос, и что заголовок Authorization использует именно тот префикс, которого ожидает API.

Некорректное или слишком большое референсное изображение (400/422). URL изображения может быть недоступен со стороны API, файл может быть повреждён, либо размеры и размер файла превышают ограничения. Сначала протестируйте с небольшим, заведомо исправным PNG. Если передаёте URL, убедитесь, что он публично доступен; если передаёте встроенные данные, убедитесь в правильности кодирования.

Тайм-аут опроса. Если задача постоянно достигает вашего тайм-аута, увеличьте лимит, удлините интервал или проверьте, не завершилась ли задача с ошибкой, причину которой вы просто не отображаете. Логируйте последний объект задачи перед выбросом исключения, чтобы увидеть статус.

Ограничение скорости (429). Выполняйте повторные попытки с экспоненциальной задержкой и джиттером, а не сразу. Если вы отправляете много задач параллельно, ограничьте параллелизм, чтобы пиковый трафик не вызывал ограничений, и делайте повторные попытки идемпотентными, логируя id задач.

Для любой категории ошибок логируйте HTTP-статус, усечённое тело ответа и id задачи — этих трёх вещей обычно достаточно, чтобы понять, проблема в аутентификации, теле запроса или тайминге. Сначала изолируйте проблему в вашем коде или входных данных с помощью примера curl, затем переносите исправление обратно в Python.

Похожие статьи

Все статьи →

Начните работать с постоянными артефактами ИИ

Получите API-ключ и вызывайте модели изображений, видео и аудио через один API на основе tasks.

Читать документацию API