HiAPI
Блог

Руководство

Как использовать nano-banana через HiAPI API: curl, Python и рабочие запросы

Полный практический цикл от первого запроса до скачивания результата

Oct 31, 2026
博客封面图:错位叠放的骨白色纸张,炭灰边缘,夹一张锈红色纸片,扁平投影。

Как выглядит запрос к nano-banana

Прежде чем писать код, полезно понять жизненный цикл вызова nano-banana на HiAPI. Каждый запрос следует одной и той же схеме: вы отправляете JSON-тело с описанием нужного результата, HiAPI принимает задачу, и затем вы либо сразу получаете готовый результат, либо получаете дескриптор задачи, с помощью которого опрашиваете статус завершения.

Его форму определяют несколько характеристик:

  • Одна конечная точка, один API-ключ. Весь трафик nano-banana проходит через единый интерфейс HiAPI. Вы аутентифицируетесь ключом HiAPI и никогда не обращаетесь к вышестоящему сервису напрямую.
  • JSON на входе, JSON на выходе. Тело запроса — JSON. Ответ тоже JSON: либо со встроенным результатом, либо с id задачи и полем статуса.
  • Текст обязателен, изображения опциональны. Как минимум нужно отправить промпт. Можно добавить референсные изображения, чтобы задать субъект, стиль или персонажа.
  • Дружелюбно к асинхронности. Генерация изображений может длиться дольше типичного HTTP-таймаута, поэтому API поддерживает асинхронные задачи, которые можно опрашивать до завершения.

Примерная структура тела запроса:

{
  "model": "nano-banana",
  "prompt": "a description of the image you want",
  "image_input": ["https://.../reference.jpg"],
  "consistency": "character",
  "async": true
}

Ответ при асинхронном режиме:

{
  "id": "job_abc123",
  "status": "queued",
  "created_at": "..."
}

Когда задача завершается и вы её получаете, status становится succeeded (или failed), а полезная нагрузка результата содержит URL вывода. Запомните эту двухэтапную модель, поскольку всё дальнейшее в этом руководстве строится на ней.

Рабочий curl-запрос

Это минимальный запрос, который генерирует изображение. Предполагается, что HIAPI_API_KEY уже экспортирован в вашем shell:

curl -X POST https://api.hiapi.ai/v1/images/generations \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nano-banana",
    "prompt": "a cozy reading nook by a rain-streaked window, warm lamp light, watercolor style",
    "async": true
  }'

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

  • model — установите в nano-banana. HiAPI использует его для маршрутизации запроса.
  • prompt — единственное строго обязательное творческое поле. Конкретно описывайте субъект, композицию и стиль; расплывчатые промпты — самая частая причина разочаровывающего результата.
  • async — при true API сразу возвращает дескриптор задачи. Если вы хотите блокироваться до завершения, установите false, но будьте готовы к более длительному времени ответа.
  • image_input — необязательный массив URL референсных изображений. Опустите его для генерации только по тексту.
  • consistency — необязательное управление согласованностью персонажа или субъекта между изображениями; подробности ниже.

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

Выполните команду и проверьте JSON. Предполагая, что async равно true, вы получите объект задачи вместо изображения. Сохраните id; это ваш дескриптор для следующего шага.

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

Вызов curl транслируется напрямую. Цель здесь — не просто написать одноразовый скрипт, а создать переиспользуемую обёртку, которую можно встроить в конвейер.

import os
import time
import requests

BASE = "https://api.hiapi.ai"
HEADERS = {
    "Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}",
    "Content-Type": "application/json",
}

def create_image(prompt, image_input=None, consistency=None, async_job=True):
    body = {"model": "nano-banana", "prompt": prompt, "async": async_job}
    if image_input:
        body["image_input"] = image_input
    if consistency:
        body["consistency"] = consistency

    resp = requests.post(
        f"{BASE}/v1/images/generations",
        headers=HEADERS,
        json=body,
        timeout=60,
    )
    if resp.status_code >= 400:
        raise RuntimeError(f"create failed: {resp.status_code} {resp.text}")
    return resp.json()

Несколько моментов делают его надёжным, а не просто рабочим:

  • Явный таймаут. Всегда передавайте его; значение по умолчанию «ждать вечно» превратит временную сетевую проблему в зависший процесс.
  • Проверка статуса перед разбором. Выбрасывайте исключение на ошибочный ответ, чтобы плохой запрос не возвращал молча JSON-тело, которое вы затем будете индексировать.
  • Опускайте необязательные поля, а не обнуляйте их. Вместо отправки "image_input": null добавляйте ключ только при наличии значения. Это делает логи читаемее и избегает неоднозначности, означает ли null «нет» или «не задано».

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

Отправка текстовых и графических входных данных

Генерация по тексту — простой случай. Интересная часть nano-banana — объединение промпта с референсными изображениями в одном запросе. Вы передаёте image_input как массив URL, а промпт описывает, как с ними работать.

result = create_image(
    prompt="the person in the first image standing in a Tokyo alley at night, neon reflections, cinematic",
    image_input=["https://example.com/portrait.jpg"],
)

Практические советы для смешанных модальностей:

  • Указывайте роли явно. Если вы отправляете несколько изображений, укажите в промпте, какое даёт субъект, какое — стиль, какое — фон. «Скомбинируй эти изображения» — расплывчато; «используй лицо с изображения один и освещение с изображения два» — нет.
  • Используйте доступные и стабильные URL. API должен иметь возможность получить изображение. Подписанные URL, истекающие через несколько минут, подходят для вызова в реальном времени, но не сработают, если вы повторите поставленную в очередь задачу позже.
  • Предпочитайте чистые референсы. Чёткий, хорошо освещённый портрет даёт более точное сходство персонажа, чем загромождённое групповое фото. Мусор на входе — мусор на выходе при обусловливании изображением заметнее, чем при тексте.
  • Сначала итерируйте промпт, а не референсное изображение. Если сходство неправильное, сначала скорректируйте описание субъекта, а затем меняйте исходное изображение — обычно это менее затратное изменение.

Когда референс содержит персонажа, которого вы планируете переиспользовать в нескольких генерациях, вы входите в область согласованности персонажа — это следующая секция.

Опции согласованности персонажа

Согласованность персонажа отвечает конкретной потребности: генерировать несколько изображений, которые выглядят так, будто на них один и тот же персонаж или субъект. nano-banana предоставляет это как необязательный элемент управления, а не как отдельную модель.

На высоком уровне компромиссы примерно таковы:

  • Без управления согласованностью. Лучше всего подходит для разовых изображений. Быстро и просто, но персонаж может дрейфовать при повторной генерации. Используйте для фонов, объектов и отдельных иллюстраций.
  • Согласованность на основе референсов. Вы предоставляете одно или несколько изображений персонажа и включаете опцию согласованности, чтобы модель привязывалась к ним. Это правильный выбор для раскадровок, комиксов и продуктовых серий, где идентичность должна сохраняться между кадрами. Цена — необходимость сначала иметь хорошее референсное изображение.
  • Согласованность через текстовое описание. Вы подробно описываете персонажа в каждом промпте — цвет волос, одежду, телосложение, отличительные черты — без референсов. Полезно, когда нет исходного фото, но требует дисциплины: держите описание в переиспользуемых константах, чтобы оно не дрейфовало между вызовами.

Практический рабочий процесс для серии:

  1. Сгенерируйте первый кадр без согласованности, итерируйте промпт, пока персонаж не станет выглядеть правильно.
  2. Используйте этот результат как ваше референсное изображение.
  3. Включите управление согласованностью и генерируйте последующие кадры, сохраняя референс неизменным.
  4. Если персонаж всё ещё дрейфует, сократите промпт, чтобы референс делал больше работы — длинные промпты, противоречащие референсу, сбивают модель с толку.

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

Опрос асинхронных задач

Когда вы отправляете с async: true, вы получаете id задачи. Опрос прост, но детали важнее, чем кажется.

Минимальный и корректный опросчик:

def wait_for_job(job_id, timeout=300, initial_delay=2, max_delay=15):
    deadline = time.time() + timeout
    delay = initial_delay
    while time.time() < deadline:
        resp = requests.get(
            f"{BASE}/v1/images/generations/{job_id}",
            headers=HEADERS,
            timeout=30,
        )
        resp.raise_for_status()
        data = resp.json()

        status = data.get("status")
        if status == "succeeded":
            return data
        if status in ("failed", "cancelled"):
            raise RuntimeError(f"job {job_id} ended as {status}: {data}")

        time.sleep(delay)
        delay = min(delay * 1.5, max_delay)

    raise TimeoutError(f"job {job_id} did not finish within {timeout}s")

Обоснование каждого выбора:

  • Приблизительно экспоненциальная задержка с верхним пределом. Начинайте с нескольких секунд, умножайте на 1,5 до предела. Фиксированный опрос раз в секунду для тридцатисекундной задачи тратит десятки запросов; фиксированный опрос раз в тридцать секунд добавляет ненужную задержку для быстрых задач.
  • Жёсткий дедлайн. Без него зависшая задача будет бесконечно блокировать ваш рабочий процесс. Лучше громко упасть.
  • Обработка конечных состояний. Считайте failed и cancelled исключениями, а не состояниями для продолжения опроса. Задача в состоянии failed никогда не завершится успешно при следующем опросе.
  • Новый GET на каждой итерации. Не кэшируйте первый ответ и не предполагайте, что он обновится.

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

Скачивание и проверка результатов

После успешного завершения задачи полезная нагрузка результата содержит URL вывода. Скачивание — обычный GET, но до проверки не следует предполагать, что эти байты являются изображением.

def download_image(url, path):
    with requests.get(url, stream=True, timeout=60) as r:
        r.raise_for_status()
        content_type = r.headers.get("Content-Type", "")
        if not content_type.startswith("image/"):
            raise RuntimeError(f"unexpected content type: {content_type}")
        with open(path, "wb") as f:
            for chunk in r.iter_content(chunk_size=8192):
                f.write(chunk)
    return path

Проверки разумности, которые стоит делать при каждом скачивании:

  • Content-Type. Если вы запрашиваете изображение, а получаете application/json или text/html, вы на самом деле получили страницу ошибки. Проверка заголовков позволяет поймать это до записи повреждённого файла.
  • Потоковая передача. Пишите на диск потоком, а не загружайте всё тело ответа в память; это поддерживает стабильное потребление памяти при параллельных скачиваниях.
  • Ненулевой размер. Файл нулевого размера означает, что что-то пошло не так, даже если HTTP-статус в норме.
  • Логируйте контрольную сумму или число байт. Записывайте их вместе с id задачи, чтобы можно было проследить плохой файл до конкретной генерации.

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

Частые точки отказа

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

Аутентификация и маршрутизация

  • Отсутствующий или неверно сформированный заголовок Authorization возвращает ошибку аутентификации, а не изображение. Убедитесь, что заголовок читается как Bearer <key> с одним пробелом посередине.
  • Отправка неверного значения model маршрутизирует не туда или сразу терпит неудачу. Оно должно быть ровно nano-banana.

Проблемы с телом запроса

  • Расплывчатые промпты. Самая частая единственная причина «плохого вывода». Прежде чем менять что-либо ещё, добавьте детали композиции, освещения и стиля.
  • Недоступные URL изображений. Референсные URL, возвращающие 404, требующие аутентификации или указывающие на локальный путь, потерпят неудачу или будут молча проигнорированы. Сначала сами получите URL один раз, чтобы убедиться, что он работает.
  • Отправка async: false с длинным промптом и попадание в клиентский таймаут. Переключитесь на асинхронный режим и опрос вместо увеличения таймаута.

Обработка задач

  • Опрос задачи, которой никогда не существовало. Опечатка в id задачи даёт ошибку «не найдено», которая выглядит как сбой; логируйте id, которые вы отправляли.
  • Считать failed всё ещё работающим. Всегда выходите из цикла опроса в конечном состоянии.
  • Отсутствие дедлайна. Самый дорогостоящий баг в пакетных задачах: одно зависшее изображение задерживает весь запуск.

Обработка вывода

  • Не проверять Content-Type. Как указано выше, страница ошибки, сохранённая с расширением .png, позже выглядит как повреждённое изображение.
  • Полагаться на просроченный URL результата спустя часы после генерации. Скачивайте вовремя.

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

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

Все статьи →

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

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

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