> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apimart.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Лучшие практики

> Режим опроса, проектирование промптов, генерация по образцу, стратегия повторов при ошибках, конкурентность и устранение неполадок для интеграции Midjourney

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

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

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

```python theme={null}
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`, а фронтенд пусть опрашивает асинхронно.

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

**Хороший промпт:**

```text theme={null}
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 раз                 |

```python theme={null}
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}")
```

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

```python theme={null}
# 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, два шага):

```python theme={null}
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 раз).

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

```python theme={null}
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`                                           |
