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

Отправка задачи и опрос

Все эндпоинты отправки — асинхронные задачи: после отправки возвращается task_id, затем периодически запрашивайте статус через GET /v1/midjourney/{task_id}, пока он не станет SUCCESS / FAILURE.
import time, httpx

def wait_task(task_id, timeout=300):
    deadline = time.time() + timeout
    while time.time() < deadline:
        resp = httpx.get(f"{HOST}/v1/midjourney/{task_id}",
                         headers={"Authorization": f"Bearer {API_KEY}"}).json()
        if resp["status"] in ("SUCCESS", "FAILURE"):
            return resp
        if resp["status"] == "MODAL":
            raise RuntimeError(f"task {task_id} требует вызова /modal для дозаполнения параметров")
        time.sleep(3)
    raise TimeoutError(task_id)
  • Ритм опроса: рекомендуется раз в 3–5 с; более высокая частота бессмысленна и тратит квоту.
  • Не блокируйте синхронно ожидание завершения задачи внутри web-запроса — после отправки сразу возвращайте task_id, а фронтенд пусть опрашивает асинхронно.

Проектирование промптов

Хороший промпт:
a serene mountain lake at sunrise, photorealistic, soft golden light,
mist rising from water, snow-capped peaks in distance --ar 16:9 --v 8.1 --s 100
  • Субъект в начале: сначала субъект, затем описание сцены, в конце модификаторы.
  • Явные структурированные параметры: использовать --ar / --v / --s (или соответствующие поля body) контролируемее, чем полагаться на значения по умолчанию.
  • Избегайте двусмысленных слов: photorealistic точнее, чем realistic.
Избегайте: излишней абстрактности («make it good»), размытого субъекта (несколько равноправных объектов без выделения главного), кавычек вокруг слов (будут восприняты как буквальное значение). Аниме Niji: передайте niji: true + version: "7", платформа нормализует это в --niji 7, тарификация идёт по midjourney@imagine-niji7.

Лучшие практики генерации по образцу

ИсточникРекомендуемый подходПримечание
Загрузка пользователемСначала сохраните в свой OSS / CDN, при отправке передавайте этот URLНе передавайте base64 напрямую (трата трафика)
Публичный URLПередавайте напрямуюУчитывайте SSRF (должен быть доступен из интернета) и лимит 12 MiB
Сторонние / иные артефактыСначала перенесите в свой OSSСторонние URL могут истечь
  • Сожмите до < 5 MiB: лимит платформы — 12 MiB, но небольшие изображения передаются / обрабатываются быстрее.
  • Подойдут форматы PNG / JPG / WebP, рекомендуется качественный JPG.
  • Разрешения 1024–2048 px достаточно, выше — расточительно.
  • Вес образца iw (0–3, по умолчанию 1): >1 — ближе к оригиналу, <1 — свободнее.

Обработка ошибок и стратегия повторов

codeЗначениеСтратегия повтора
1 / 200Успех
4 VALIDATION_ERRORОшибка параметров❌ Не повторять, исправьте параметры
3 NOT_FOUNDНет доступного инстанса / task_id не существуетЕсли инстанс недоступен — можно повторить позже; если task_id не существует — не повторять
9 FAILUREОтказ сервиса / внутренняя ошибка⏳ Можно повторить, экспоненциальная задержка (1s, 4s, 16s)
21 MODALНе финальное состояние✅ Продолжайте вызовом /modal
24 BANNED_PROMPTЗапрещённое слово❌ Не повторять, измените prompt; возврат средств уже выполнен автоматически
429Ограничение частоты⏳ Экспоненциальная задержка + jitter
5xx / сетевая ошибкаСервер / сеть⏳ Экспоненциальная задержка, при сетевой ошибке можно сразу повторить 1 раз
import time, random, httpx

def submit_with_retry(payload, max_attempts=5):
    for attempt in range(max_attempts):
        try:
            r = httpx.post(f"{HOST}/v1/midjourney/generations/imagine",
                           json=payload,
                           headers={"Authorization": f"Bearer {API_KEY}"},
                           timeout=30)
            data = r.json()
            if r.status_code == 200 and data["code"] in (1, 200):
                return data
            if data["code"] in (4, 24):
                raise ValueError(data["description"])      # не повторять
            if data["code"] == 3 and "task" in data["description"]:
                raise ValueError(data["description"])      # task_id не существует
            # остальные (9 / 429 / 5xx) можно повторить
        except httpx.RequestError:
            pass
        time.sleep((4 ** attempt) + random.uniform(0, 1))  # 1s / 4s / 16s ...
    raise RuntimeError(f"достигнуто максимальное число повторов {max_attempts}")

Процесс вторичных операций

# imagine → опрос → upscale
imagine_id = submit({"prompt": "a cat"})["data"][0]["task_id"]
result = wait_task(imagine_id)           # grid_image_url + 4 шт. image_urls + buttons
upscale_id = submit_to("/upscale", {"task_id": imagine_id, "index": 2})["data"][0]["task_id"]
final = wait_task(upscale_id)            # upscale собирается локально, 1–2s
single_image = final["image_urls"][0]
Локальная перерисовка (inpaint → modal, два шага):
imagine_id = submit({"prompt": "a portrait"})["data"][0]["task_id"]; wait_task(imagine_id)
upscale_id = submit_to("/upscale", {"task_id": imagine_id, "index": 1})["data"][0]["task_id"]; wait_task(upscale_id)

inpaint_id = submit_to("/inpaint", {"task_id": upscale_id})["data"][0]["task_id"]  # status=modal
# фронтенд рисует mask (белое = область перерисовки), загружает в свой OSS и получает mask_url
final = submit_to("/modal", {
    "task_id": inpaint_id,
    "prompt": "replace the eyes with cybernetic blue eyes",
    "mask_url": "https://your-oss.com/mask.png"
})
wait_task(final["data"][0]["task_id"])
⚠️ После перехода inpaint в MODAL необходимо вызвать /modal в течение 30 минут, иначе задача в фоне будет автоматически отменена (CANCEL) с возвратом средств.

Контроль тарификации video

  • Один сегмент: batch_size: 1 → списывается 1 × midjourney@video
  • Пакет из 4 сегментов: batch_size: 4 → списывается 4 × midjourney@video
  • Один HD-сегмент: video_type: "vid_1.1_i2v_720" + batch_size: 1 → списывается 1 × midjourney@video-720p
Рекомендация: если для итогового ролика нужен лишь 1 сегмент — используйте batch_size=1; 4 — только для пакетного сравнения вариантов, не включайте 4 по умолчанию (стоимость вырастет в N раз).

Конкурентность и пропускная способность

import asyncio
sem = asyncio.Semaphore(10)  # не более 10 одновременных отправок на клиенте

async def submit_one(prompt):
    async with sem:
        return await submit({"prompt": prompt})
  • У платформы есть лимит на число отправок в минуту; при превышении возвращается 429, нужен повтор с задержкой.
  • Реальная конкурентность генерации определяется ёмкостью системы; при превышении задачи становятся в очередь; долгое нахождение задачи в SUBMITTED обычно означает очередь.
  • При опросе обязательно используйте sleep, не зацикливайтесь без sleep.

Рекомендации по мониторингу

МетрикаОриентировочный порогЗначение
Доля SUCCESS (за последний 1 ч)> 95%Низкое значение указывает на проблемы сервиса / сети
Среднее время завершения< 90sВысокое значение указывает на очередь
Число задач, застрявших в MODALблизко к 0Большое значение означает, что клиент не вызывает /modal
Доля code=24< 5%Высокое значение означает, что prompt часто срабатывает на запрещённые слова

Чек-лист устранения неполадок

СимптомНаправление диагностики
Задача долго в SUBMITTEDОчередь в системе, проверьте позже
Задача долго в NOT_STARTПлатформа позже автоматически выполнит таймаут с возвратом средств, ручного вмешательства не требуется
Задача MODAL более 30 минутКлиент не вызвал /modal, задача автоматически отменена (CANCEL) с возвратом средств
Поле prompt пустоеТекстовый результат задачи describe находится в поле description
В image_urls не хватает изображенияЧасть изображений отклонена модерацией контента, смотрите fail_reason
Тарификация выше ожидаемойСмотрите поле quota; для video не забывайте × batch_size