Midjourney 接入的轮询模式、Prompt 设计、垫图、错误重试策略、并发与排错建议
整合常见问题、性能优化、错误处理的最佳实践,接入前建议通读。
任务提交与轮询
提交接口都是异步任务:提交后返回 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–5s 一次,更高频无意义且浪费配额。
- 不要在 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 字段)比依赖默认值更可控。 - 避免歧义词:
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 后 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})
- 平台对每分钟提交数有上限,超出返回
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 少一张 |
内容审核拦了部分图,看 fail_reason |
| 计费超预期 | 看 quota 字段;video 记得 × batch_size |