メインコンテンツへスキップ
よくある問題、パフォーマンス最適化、エラー処理のベストプラクティスをまとめています。接続前に一読することを推奨します。

タスク送信とポーリング

送信系のエンドポイントはすべて非同期タスクです。送信後に 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 秒に 1 回を推奨。これより高頻度にしても意味がなく、クォータの無駄になります。
  • web リクエスト内で同期的にブロックしてタスク完了を待たないでください。送信後はすぐに task_id を返し、フロントエンドで非同期にポーリングさせます。

Prompt 設計

良い prompt:
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 フィールド)を使う方が、デフォルト値に依存するよりも制御しやすくなります。
  • 曖昧な語を避けるphotorealisticrealistic よりも明確です。
避けるべきこと: 抽象的すぎる指定(“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 の 2 ステップ):
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 に入った後、30 分以内/modal を呼ぶ必要があります。そうしないとバックエンドが自動的に CANCEL + 返金します。

video の課金コントロール

  • 単一クリップ:batch_size: 1 → 1 × midjourney@video を消費
  • バッチ 4 クリップ:batch_size: 4 → 4 × midjourney@video を消費
  • 高解像度の単一クリップ: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})
  • プラットフォームには 1 分あたりの送信数に上限があり、超過すると 429 が返るためバックオフしてリトライしてください。
  • 実際の生成の並行数はシステム容量によって決まり、超過分はキューに入ります。タスクが長時間 SUBMITTED のままの場合、通常はキュー待ちです。
  • ポーリングには必ず sleep を入れ、sleep なしの無限ループにしないでください。

モニタリングの推奨

指標参考しきい値意味
タスク SUCCESS 率(直近 1h)> 95%低い場合はサービス / ネットワークの異常を示す
平均完了時間< 90s高い場合はキュー待ちを示す
MODAL 滞留タスク数0 に近い多い場合はクライアントが /modal を呼んでいないことを示す
code=24 の比率< 5%高い場合は prompt が頻繁に禁止ワードに触れていることを示す

トラブルシューティングチェックリスト

現象確認すべき方向
タスクが長時間 SUBMITTEDシステムでキュー待ち中。後ほど再照会
タスクが長時間 NOT_STARTプラットフォームが後ほど自動でタイムアウト返金する。手動対応は不要
タスクが MODAL で 30 分超過クライアントが /modal を呼んでいない。すでに自動 CANCEL + 返金済み
prompt フィールドが空describe タスクのテキスト結果は description フィールドにある
image_urls が 1 枚足りない内容審査で一部の画像がブロックされた。fail_reason を確認
課金が想定を超えるquota フィールドを確認。video は batch_size を掛けることを忘れずに