HiAPI
Блог

Руководство

Как вызвать seedream-4.5/image-to-image через HiAPI API: curl и Python

Полная демонстрация рабочего запроса, включая ввод нескольких референсных изображений, асинхронный опрос и обработку результатов

Oct 31, 2026
深墨绿底上,两组宽扁丝带以平纹编织方式交错成开放式网格,穿插克制的黄铜色与深松绿色条带,为教程文章封面图。

Что делает этот эндпоинт и когда его выбирать

seedream-4.5/image-to-image преобразует одно или несколько референсных изображений вместе с текстовым промптом в новое изображение. Это часть серии image-to-image на HiAPI: вы предоставляете визуальный контекст, а модель переосмысляет его согласно вашим инструкциям. Именно в этом ключевое отличие от text-to-image — в text-to-image промпт является единственным входом, и модель создаёт всё с нуля.

Выбирайте image-to-image, когда хотите, чтобы визуальный результат оставался привязанным к существующим материалам: рестайлинг существующих ассетов, изменение освещения или фона с сохранением формы и компоновки продукта, итерация композиции без потери субъекта или обеспечение единообразного вида партии результатов за счёт повторного использования одних и тех же референсов.

Выбирайте text-to-image, когда у вас нет референсных материалов и вы просто хотите описать сцену словами.

Этот эндпоинт асинхронный. Вы отправляете задачу, получаете идентификатор, затем опрашиваете, пока задача не перейдёт в терминальное состояние, после чего получаете результат. Планируйте этот жизненный цикл с самого начала; остальная часть руководства строит рабочий запрос, а затем делает его надёжным.

Создание рабочего curl-запроса

Каждый запрос к этому эндпоинту имеет одинаковую базовую структуру. Разберём её поле за полем.

Эндпоинт и метод

Запрос отправляется методом POST на маршрут image-to-image под базовым URL HiAPI с идентификатором модели seedream-4.5/image-to-image. Имя модели — это значение в теле запроса, а не часть пути.

Аутентификация

Передайте ваш API-ключ HiAPI как bearer token в заголовке Authorization. Храните его в переменной окружения, чтобы он не попал в историю shell или систему контроля версий:

export HIAPI_API_KEY="your-key-here"

Минимальное тело запроса

curl -sS -X POST "$HIAPI_BASE_URL/v1/images/generations" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedream-4.5/image-to-image",
    "prompt": "restyle this photo as a warm studio portrait",
    "image": "https://example.com/reference.jpg"
  }'

Разбор по полям:

  • model — буквальный идентификатор seedream-4.5/image-to-image.
  • prompt — что нужно сделать с референсным изображением. Описывайте процесс преобразования, а не только субъект.
  • image — референсный вход. Для одного изображения это одно значение; несколько референсов рассматриваются в следующем разделе.

Чтение первого ответа

Этот вызов возвращает принятую задачу, а не готовое изображение. Захватите идентификатор задачи из тела ответа и сохраните его; он понадобится при опросе. Не считайте статус класса 200 означающим «изображение готово».

Прежде чем писать клиентский код, запустите этот запрос один раз как есть. Тело запроса, которое падает в curl, упадёт и в Python по той же причине, а отлаживать в shell быстрее.

Перенос запроса в Python

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

import os
import requests

BASE_URL = os.environ["HIAPI_BASE_URL"]
API_KEY = os.environ["HIAPI_API_KEY"]

payload = {
    "model": "seedream-4.5/image-to-image",
    "prompt": "restyle this photo as a warm studio portrait",
    "image": "https://example.com/reference.jpg",
}

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

resp = requests.post(
    f"{BASE_URL}/v1/images/generations",
    json=payload,
    headers=headers,
    timeout=30,
)
resp.raise_for_status()
job = resp.json()
print(job["id"])

Следующие замечания сэкономят время в дальнейшем:

  • Стройте тело запроса как Python dict, позволяя json= выполнить сериализацию. Написанные вручную JSON-строки — источник ошибок с кавычками.
  • Держите построение аутентификации в одной вспомогательной функции. Если формат заголовка изменится, править нужно в одном месте.
  • Всегда передавайте явный timeout и в вызове отправки. Зависание сокета на этапе отправки неотличимо от медленной модели.
  • Сразу сохраняйте возвращённый идентификатор задачи, а не выводите его заново из логов.

Код опроса в последующих разделах использует те же BASE_URL, API_KEY и headers, поэтому разместите их в области модуля или в небольшом клиентском классе.

Передача нескольких референсных изображений

Ввод нескольких референсов — самое частое место ошибок в запросах image-to-image, обычно потому, что модель получает три изображения, а промпт описывает только одно из них.

Явная организация входов

Поместите референсные изображения списком в то же поле image, в нужном вам порядке, и пусть промпт указывает роль каждой позиции:

payload = {
    "model": "seedream-4.5/image-to-image",
    "prompt": (
        "Use the first reference for the subject's pose, "
        "the second for the background setting, "
        "and the third for color grading. Keep the subject sharp."
    ),
    "image": [
        "https://example.com/pose.jpg",
        "https://example.com/background.jpg",
        "https://example.com/grading.jpg",
    ],
}

Почему аннотация лучше позиции

Порядок списка — это соглашение, но именно промпт сообщает модели, как интерпретировать этот порядок. Пустой массив с расплывчатым промптом вынуждает модель угадывать, какой референс имеет приоритет при конфликте. Указание роли каждого изображения в промпте превращает неявные предположения о позиции в явные инструкции.

Практические правила:

  • Держите число референсов небольшим и каждый — с чёткой целью. Лишние референсы размывают, а не обогащают результат.
  • Указывайте разрешение конфликтов: если референс позы и референс фона расходятся в кадрировании, укажите, какой из них главный.
  • Используйте стабильные публично доступные URL или эквивалентный механизм загрузки. Референс, возвращающий 404 посреди задачи, приведёт к провалу всего запроса.

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

Обработка жизненного цикла асинхронной задачи

Генерация изображений требует времени, поэтому эндпоинт построен на задачах. Рассматривайте жизненный цикл как четыре отдельных шага и пишите код для каждого.

1. Отправка

Отправьте показанный выше POST. Прежде чем делать что-либо ещё, сохраните идентификатор задачи из ответа.

2. Опрос

С теми же заголовками аутентификации выполняйте GET-запросы к маршруту задачи, пока статус не станет терминальным:

import time

def poll_job(job_id, interval=2.0, max_interval=30.0, deadline=300.0):
    start = time.monotonic()
    while True:
        r = requests.get(
            f"{BASE_URL}/v1/images/generations/{job_id}",
            headers=headers,
            timeout=30,
        )
        r.raise_for_status()
        data = r.json()
        status = data["status"]
        if status in ("succeeded", "failed"):
            return data
        if time.monotonic() - start > deadline:
            raise TimeoutError(f"job {job_id} not finished within {deadline}s")
        time.sleep(interval)
        interval = min(interval * 2, max_interval)

3. Определение терминального состояния

Цикл завершают только два исхода: успех или неудача. Всё остальное означает продолжать ожидание. Никогда не предполагайте, что результат появится мгновенно, и никогда не задавайте фиксированное число опросов без реального предельного срока.

4. Получение результата

Рассматривается в следующем разделе.

Backoff важен. Плотный цикл sleep(0.1) тратит ваш лимит запросов на проверки статуса. Начав примерно с двух секунд и удваивая до上限, можно сохранить низкую задержку, не перегружая API.

Получение и хранение результата

Когда опрос возвращает успешный статус, ответ содержит ссылку на результат. Возвращается указатель на сгенерированное изображение, не обязательно сами байты.

Осознанная загрузка

data = poll_job(job_id)
url = data["output"][0]["url"] if isinstance(data["output"], list) else data["output"]

with requests.get(url, stream=True, timeout=60) as r:
    r.raise_for_status()
    with open(f"{job_id}.png", "wb") as f:
        for chunk in r.iter_content(chunk_size=8192):
            f.write(chunk)

Настраивайте путь по фактической структуре ответа, наблюдаемой у первой реальной задачи, а не предполагайте фиксированную схему.

Правила хранения, предотвращающие переделки

  • Называйте файлы по идентификатору задачи. Он уникален, отслеживаем и связывает артефакт с породившим его запросом.
  • Загружайте promptly. Ссылки на результаты могут иметь срок действия, поэтому сохраняйте сразу после успешного опроса, а не кэшируйте URL на потом.
  • Потоково обрабатывайте большие файлы, а не читайте их целиком в память.
  • Записывайте промпт и список референсов вместе с файлом. Когда результат неожиданный, входные данные — это журнал отладки.

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

Ошибки, повторы и контроль затрат

Большинство сбоев относятся к одной из трёх категорий, и каждая требует своего подхода.

Клиентские ошибки

Некорректное тело запроса, отсутствие model, недоступный URL референса или неверный ключ. Эти ошибки возвращаются сразу и будут возвращаться при повторе. Исправьте запрос; не повторяйте в цикле. Проверяйте, что URL референсов разрешаются, перед отправкой.

Временные серверные и сетевые ошибки

Таймауты, сбросы соединения, ответы об ограничении частоты. Их стоит повторять с экспоненциальным backoff и jitter, ограничивая небольшим максимальным числом попыток. Повторяйте конкретный шаг — отправку, опрос или загрузку — а не весь конвейер. Повторная отправка после успешной отправки — именно так возникают неожиданные двойные списания.

Ошибки уровня задачи

Задача переходит в терминальное состояние неудачи. Повтор с тем же идентификатором задачи не поможет. Явно решите, повторно ли отправлять с изменённым промптом или набором референсов, и фиксируйте причину сбоя, чтобы отличать ошибки входных данных от временных сбоев.

def with_retries(fn, attempts=4, base=1.0):
    for i in range(attempts):
        try:
            return fn()
        except requests.RequestException:
            if i == attempts - 1:
                raise
            time.sleep(base * (2 ** i))

Контроль затрат

Оплата привязана к генерации, поэтому дорогостоящая ошибка — это повторная отправка. Устанавливайте реальный предельный срок для опроса, делайте отправку идемпотентной за счёт сохранения идентификатора задачи перед опросом и ограничивайте параллелизм, чтобы шторм повторов не превратился в десятки оплачиваемых задач. Записывайте каждую отправку и её идентификатор задачи; этот журнал — ваш аудиторский след расходов.

Подключите эти три аспекта к клиенту один раз, и один и тот же код будет обрабатывать запросы с одним и несколькими референсами без изменений.

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

Все статьи →

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

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

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