Commit dca9de24cc1837860ee48f86aba16bf13b3fa43d
1 parent
af31f0a8
h3-console: 加完整 API 文档
给要接入 MiniMax-H3 生成能力的其它项目用。覆盖全部接口(提交/查询/ 取消/插队/软删除/批量操作/上传/系统状态),字段表、错误码、真实抓取的 响应样例都是当前线上实测得到的,不是照代码抄的猜测。 重点标注几个容易踩坑但不改代码没法从接口本身看出来的地方: POST /v1/videos 校验失败也返回 202,只能靠 status 字段判断; turbo/cfg_scale/negative_prompt 目前是 no-op; 停止 running 任务=硬重启整个服务,代价是全局15-20秒不可用; 以及本次事故本身:不要绕过 console 直连 raylight(8100)提交任务。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GFw9ZBh3eVjssk6quYQ4Ki
Showing
1 changed file
with
441 additions
and
0 deletions
h3-console/API.md
0 → 100644
| 1 | +# H3 Console API 文档 | |
| 2 | + | |
| 3 | +给要接入 MiniMax-H3 文生视频能力的项目用。这是 **console 层**的接口(8080 局域网 / 8081 | |
| 4 | +公网 `/console` 子路径),不是底层 raylight 的接口。 | |
| 5 | + | |
| 6 | +> ⚠️ **务必通过 console 提交任务,不要绕过 console 直连 raylight(内网 8100 端口)。** | |
| 7 | +> raylight 是纯计算队列,没有鉴权、没有任务持久化(进程重启就丢)、没有软删除/回收站, | |
| 8 | +> console 才是任务的唯一权威记录(sqlite)。2026-09-05 曾发生过一次事故:外部脚本直连 | |
| 9 | +> 8100 提交任务,console 的后台发现逻辑和这些直连任务的簿记之间出现竞态,同一个真实生成 | |
| 10 | +> 任务在 console 的任务列表里出现了两条记录。所有集成都应该只打 console 的 `/v1/*`。 | |
| 11 | + | |
| 12 | +--- | |
| 13 | + | |
| 14 | +## 1. 基础信息 | |
| 15 | + | |
| 16 | +| 项目 | 值 | | |
| 17 | +|---|---| | |
| 18 | +| 局域网 Base URL | `http://192.168.110.67:8080` | | |
| 19 | +| 公网 Base URL | `http://qwen.wudaima.com/console`(frp 反代,注意所有路径都要带 `/console` 前缀) | | |
| 20 | +| 内容类型 | 请求/响应都是 `application/json`,上传接口是 `multipart/form-data` | | |
| 21 | +| 字符编码 | UTF-8,中文 prompt 直接传,不需要额外转义 | | |
| 22 | +| 鉴权 | 见下 | | |
| 23 | + | |
| 24 | +### 鉴权 | |
| 25 | + | |
| 26 | +`/v1/*` 下所有接口都可能要求 Bearer token(页面本身 `/`、`/task.html/*` 不需要)。两种传法二选一,服务端逻辑完全等价: | |
| 27 | + | |
| 28 | +``` | |
| 29 | +Authorization: Bearer <token> | |
| 30 | +``` | |
| 31 | +或者拼在查询串里(给 `<video src>`、`<a download>` 这类浏览器原生请求用,因为它们加不了自定义 header): | |
| 32 | +``` | |
| 33 | +GET /v1/videos/{id}/file?token=<token> | |
| 34 | +``` | |
| 35 | + | |
| 36 | +token 是否强制取决于部署时是否设了 `H3_CONSOLE_AUTH_TOKEN` 环境变量;公网实例目前**已启用**,请找服务器管理员要 token。带错/不带 token 会收到: | |
| 37 | + | |
| 38 | +```json | |
| 39 | +// HTTP 401 | |
| 40 | +{"detail": "缺少或错误的访问令牌(Authorization: Bearer <token> 或 ?token=)"} | |
| 41 | +``` | |
| 42 | + | |
| 43 | +--- | |
| 44 | + | |
| 45 | +## 2. 核心概念 | |
| 46 | + | |
| 47 | +**任务 id**:console 生成自己的 12 位十六进制 id(跟底层 raylight 的任务 id 是两个不同的值),所有 `/v1/videos/{id}/*` 接口都用这个 console id,不要用别处看到的 raylight id。 | |
| 48 | + | |
| 49 | +**三种生成模式**,不是显式传的参数,是 console 按你传了哪些字段自动判断的: | |
| 50 | + | |
| 51 | +| 模式 | 判断依据 | | |
| 52 | +|---|---| | |
| 53 | +| `text`(文生视频) | 没传 `first_frame_url`/`last_frame_url`,也没传任何 `reference_*` | | |
| 54 | +| `fl2va`(首尾帧) | 传了 `first_frame_url` 或 `last_frame_url` | | |
| 55 | +| `ref2va`(参考图/参考视频/参考音频) | 传了 `reference_image_urls`/`reference_video_urls`/`reference_audio_urls` 任意一个非空 | | |
| 56 | + | |
| 57 | +**任务状态机**:`queued → running → done` / `failed` / `cancelled`。另外有一个独立维度 `deleted_at`(软删除/回收站),跟 status 正交——一条 `done` 的任务一样可以被删进回收站,不影响它已经生成好的文件。 | |
| 58 | + | |
| 59 | +**并发模型(重要)**:4 张 GPU 是**一个 FSDP 集群**,不是 4 个独立 worker。任何时刻只有 **1 条**任务处于 `running`,其余全部在队列里排队等这一条跑完。这不是接口限流——可以放心一次提交很多条,它们会老老实实排队,但**不要指望"提交越多并行跑得越快"**,吞吐量就是"一条接一条"。 | |
| 60 | + | |
| 61 | +--- | |
| 62 | + | |
| 63 | +## 3. 接口一览 | |
| 64 | + | |
| 65 | +| 方法 | 路径 | 用途 | | |
| 66 | +|---|---|---| | |
| 67 | +| POST | `/v1/videos` | 提交生成任务 | | |
| 68 | +| GET | `/v1/videos` | 任务列表(分页/筛选/排序) | | |
| 69 | +| GET | `/v1/videos/{id}` | 单条任务详情(会顺带触发一次状态刷新) | | |
| 70 | +| GET | `/v1/videos/{id}/file` | 下载/播放生成的视频(支持 Range) | | |
| 71 | +| GET | `/v1/videos/{id}/comfy_history` | 排查用:原始 ComfyUI 执行记录 | | |
| 72 | +| POST | `/v1/videos/{id}/cancel` | 取消排队中的任务 / 停止运行中的任务 | | |
| 73 | +| POST | `/v1/videos/{id}/prioritize` | 插队到最前面(仅排队中的任务) | | |
| 74 | +| DELETE | `/v1/videos/{id}` | 软删除(移入回收站) | | |
| 75 | +| POST | `/v1/videos/{id}/restore` | 从回收站恢复 | | |
| 76 | +| POST | `/v1/videos/batch-cancel` | **批量**取消/停止 | | |
| 77 | +| POST | `/v1/videos/batch-delete` | **批量**软删除 | | |
| 78 | +| POST | `/v1/assets` | 上传参考图/参考视频/参考音频,换取可用的 URL | | |
| 79 | +| GET | `/v1/backends` | 健康检查 + 队列深度 + GPU 显存/利用率 | | |
| 80 | +| GET | `/v1/models` | 支持的分辨率档位(含实测帧数上限) | | |
| 81 | +| POST | `/v1/admin/reset-gpu` | 硬重启 GPU 服务(危险操作,见下) | | |
| 82 | + | |
| 83 | +--- | |
| 84 | + | |
| 85 | +## 4. 提交生成任务 | |
| 86 | + | |
| 87 | +### `POST /v1/videos` | |
| 88 | + | |
| 89 | +> ⚠️ **这个接口永远返回 HTTP 202**,哪怕参数校验直接失败也是 202——**必须检查响应体里的 | |
| 90 | +> `status` 字段**,不能只看 HTTP 状态码判断成功与否。校验失败时 `status` 会是 `"failed"`, | |
| 91 | +> `error` 字段里是失败原因(原样透传自底层校验器,格式见下面例子,不算稳定 API,只用来人读)。 | |
| 92 | + | |
| 93 | +#### 请求体字段 | |
| 94 | + | |
| 95 | +| 字段 | 类型 | 默认值 | 说明 | | |
| 96 | +|---|---|---|---| | |
| 97 | +| `prompt` | string | **必填** | 生成提示词,支持中英文/超长文本 | | |
| 98 | +| `width` | int | 832 | 64–2048。跟 `height` 的组合决定能用哪些帧数(见下面「分辨率档位」) | | |
| 99 | +| `height` | int | 480 | 同上 | | |
| 100 | +| `frames` | int | 124 | 5–3000。总帧数,按 24fps 折算成秒数 | | |
| 101 | +| `steps` | int | 20 | 1–50 | | |
| 102 | +| `turbo` | bool | false | **当前是 no-op**,Raylight 还没接 Turbo LoRA,传了不生效 | | |
| 103 | +| `seed` | int | 0 | 0–2147483647 | | |
| 104 | +| `cfg_scale` | float\|null | null | **当前是 no-op**,MiniMax H3 在 Raylight 里不支持 CFG | | |
| 105 | +| `negative_prompt` | string\|null | null | **当前是 no-op**,同上 | | |
| 106 | +| `first_frame_url` | string\|null | null | fl2va 模式:首帧图 URL | | |
| 107 | +| `last_frame_url` | string\|null | null | fl2va 模式:尾帧图 URL(可选,可以只给首帧) | | |
| 108 | +| `reference_image_urls` | string[] | `[]` | ref2va 模式:参考图,数量没有上限 | | |
| 109 | +| `reference_video_urls` | string[] | `[]` | ref2va 模式:参考视频,**最多 3 个**,2–15 秒,音轨自动带上 | | |
| 110 | +| `reference_audio_urls` | string[] | `[]` | ref2va 模式:独立音频参考,**最多 3 个**,不跟参考视频绑定 | | |
| 111 | + | |
| 112 | +图片/视频/音频字段填的都是 **URL**,不是文件内容本身——本地文件先调 `POST /v1/assets` 上传换 URL(见第 8 节),或者直接给一个公网可访问的图床/视频 URL 也行(底层是 `urllib.urlopen` 直接下载,服务器要能访问到这个地址)。 | |
| 113 | + | |
| 114 | +#### 分辨率档位与帧数上限(超出直接判失败,不会真的提交给 GPU) | |
| 115 | + | |
| 116 | +只有下表这 8 个(宽,高)组合是实测过真实显存上限的,其它组合**不拦**(能提交但没人验证过会不会 OOM): | |
| 117 | + | |
| 118 | +| 分辨率 | 帧数上限 | 约合秒数(24fps) | | |
| 119 | +|---|---|---| | |
| 120 | +| 832×480 / 480×832 | 1125 | ≈46.9s | | |
| 121 | +| 448×256 / 256×448 | 1200 | ≈50.0s(这个是画质上限,不是显存上限) | | |
| 122 | +| 1024×576 / 576×1024 | 743 | ≈31.0s | | |
| 123 | +| 1344×768 / 768×1344 | 412 | ≈17.2s | | |
| 124 | + | |
| 125 | +实时以 `GET /v1/models` 返回的 `presets` 为准,这份表可能会随实测结果调整。 | |
| 126 | + | |
| 127 | +#### 示例:文生视频(text) | |
| 128 | + | |
| 129 | +```bash | |
| 130 | +curl -X POST http://192.168.110.67:8080/v1/videos \ | |
| 131 | + -H "Authorization: Bearer $TOKEN" \ | |
| 132 | + -H "Content-Type: application/json" \ | |
| 133 | + -d '{ | |
| 134 | + "prompt": "清晨的中式庭院,一位女子坐在长廊上喝茶……", | |
| 135 | + "width": 480, "height": 832, "frames": 243, | |
| 136 | + "steps": 20, "seed": 0 | |
| 137 | + }' | |
| 138 | +``` | |
| 139 | + | |
| 140 | +#### 示例:首尾帧(fl2va) | |
| 141 | + | |
| 142 | +```bash | |
| 143 | +curl -X POST http://192.168.110.67:8080/v1/videos \ | |
| 144 | + -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ | |
| 145 | + -d '{ | |
| 146 | + "prompt": "……", | |
| 147 | + "first_frame_url": "http://192.168.110.67:8080/assets/xxx__first.png", | |
| 148 | + "last_frame_url": "http://192.168.110.67:8080/assets/xxx__last.png", | |
| 149 | + "width": 480, "height": 832, "frames": 124 | |
| 150 | + }' | |
| 151 | +``` | |
| 152 | + | |
| 153 | +#### 示例:参考图/参考视频(ref2va) | |
| 154 | + | |
| 155 | +```bash | |
| 156 | +curl -X POST http://192.168.110.67:8080/v1/videos \ | |
| 157 | + -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ | |
| 158 | + -d '{ | |
| 159 | + "prompt": "……", | |
| 160 | + "reference_image_urls": ["http://.../a.png", "http://.../b.png"], | |
| 161 | + "reference_video_urls": ["http://.../ref.mp4"], | |
| 162 | + "width": 480, "height": 832, "frames": 240 | |
| 163 | + }' | |
| 164 | +``` | |
| 165 | + | |
| 166 | +#### 成功响应(202,`status` 为 queued/running/failed 之一,见下方“Job 对象”) | |
| 167 | + | |
| 168 | +#### 校验失败也是 202,靠 `status` 字段识别——真实例子 | |
| 169 | + | |
| 170 | +字段范围校验失败(`frames` 超过硬上限 3000): | |
| 171 | +```json | |
| 172 | +{ | |
| 173 | + "id": "7df2a3c356b1", | |
| 174 | + "status": "failed", | |
| 175 | + "error": "{\"detail\":[{\"type\":\"less_than_equal\",\"loc\":[\"body\",\"frames\"],\"msg\":\"Input should be less than or equal to 3000\",\"input\":99999,\"ctx\":{\"le\":3000}}]}", | |
| 176 | + "req": { "...": "..." }, | |
| 177 | + "partition": "text", | |
| 178 | + "created_at": 1788597597.86, | |
| 179 | + "backend": null, | |
| 180 | + "deleted_at": null | |
| 181 | +} | |
| 182 | +``` | |
| 183 | + | |
| 184 | +分辨率实测上限校验失败(832×480 传了 1200 帧,超过表里的 1125): | |
| 185 | +```json | |
| 186 | +{ | |
| 187 | + "id": "e545a8e5aa24", | |
| 188 | + "status": "failed", | |
| 189 | + "error": "{\"detail\":[{\"type\":\"value_error\",\"loc\":[\"body\"],\"msg\":\"Value error, 832x480 实测上限是 1125 帧,1200 帧会真实CUDA OOM(或448x256画质会退化,见notes.md),不接受这个请求\",...}]}", | |
| 190 | + "req": { "...": "..." }, | |
| 191 | + "partition": "text" | |
| 192 | +} | |
| 193 | +``` | |
| 194 | + | |
| 195 | +`error` 字段是底层校验器原样透传的 JSON 字符串,**不是稳定的 API 契约**,只建议展示给人看/记日志,不要用程序解析它的结构去做分支逻辑;要判断"这次提交有没有被接受",只看 `status !== "failed"` 就够。 | |
| 196 | + | |
| 197 | +#### 其它错误 | |
| 198 | + | |
| 199 | +| HTTP 状态 | 场景 | | |
| 200 | +|---|---| | |
| 201 | +| 401 | token 缺失/错误 | | |
| 202 | +| 409 | GPU 正在重置/硬重启窗口期(约 25 秒),`{"detail": "GPU正在重置/服务重启中,还剩约N秒,请稍后重试"}` | | |
| 203 | + | |
| 204 | +--- | |
| 205 | + | |
| 206 | +## 5. 查询任务 | |
| 207 | + | |
| 208 | +### `GET /v1/videos` — 列表 | |
| 209 | + | |
| 210 | +query 参数(全部可选): | |
| 211 | + | |
| 212 | +| 参数 | 类型 | 默认 | 说明 | | |
| 213 | +|---|---|---|---| | |
| 214 | +| `limit` | int | 20 | 每页条数 | | |
| 215 | +| `offset` | int | 0 | 偏移量,配合 `limit` 做分页 | | |
| 216 | +| `trash` | bool | false | true 只看回收站里的任务 | | |
| 217 | +| `q` | string | "" | 按任务 id 或 prompt 子串搜索(`LIKE %q%`) | | |
| 218 | +| `status` | string | "" | `queued`/`running`/`done`/`failed`/`cancelled` 精确匹配 | | |
| 219 | +| `kind` | string | "" | `text`/`fl2va`/`ref2va` 精确匹配 | | |
| 220 | +| `sort` | string | desc | `desc`(默认,按创建时间倒序)/ `asc` | | |
| 221 | + | |
| 222 | +响应: | |
| 223 | +```json | |
| 224 | +{ "jobs": [ /* Job 对象数组,见第 6 节 */ ], "total": 850 } | |
| 225 | +``` | |
| 226 | + | |
| 227 | +`total` 是**当前筛选条件下**匹配的总数(不受 `limit`/`offset` 影响),用来算分页页数。 | |
| 228 | + | |
| 229 | +### `GET /v1/videos/{id}` — 单条详情 | |
| 230 | + | |
| 231 | +会顺带触发一次状态同步:如果任务还在本地排队没提交上去,顺手再试一次提交;如果已经提交了,去底层问一次最新状态再返回。频繁轮询(比如每 2-3 秒一次)是安全的,但没必要比这更快——状态变化本来就不会快过底层真实的执行速度。 | |
| 232 | + | |
| 233 | +### `GET /v1/videos/{id}/file` — 下载/播放视频 | |
| 234 | + | |
| 235 | +只有 `status="done"` 的任务能下载。原生支持 HTTP Range(浏览器 `<video>` 标签直接能用来拖进度条)。返回 `Content-Disposition: attachment; filename="h3-{id}.mp4"`。 | |
| 236 | + | |
| 237 | +任务还没跑完/没有 `backend` 时返回 `409 {"detail": "任务还在本地排队,尚未分配到可用后端"}`。 | |
| 238 | + | |
| 239 | +### `GET /v1/videos/{id}/comfy_history` — 排查用 | |
| 240 | + | |
| 241 | +返回 ComfyUI 侧这条任务的原始执行记录(完整 workflow graph + 节点输出),排查生成问题时用,不建议在正常业务逻辑里依赖它的结构。 | |
| 242 | + | |
| 243 | +--- | |
| 244 | + | |
| 245 | +## 6. Job 对象字段 | |
| 246 | + | |
| 247 | +`POST /v1/videos`、`GET /v1/videos/{id}`、`GET /v1/videos` 里的每个元素,返回的都是同一种结构: | |
| 248 | + | |
| 249 | +| 字段 | 类型 | 说明 | | |
| 250 | +|---|---|---| | |
| 251 | +| `id` | string | console 任务 id,所有 `/v1/videos/{id}/*` 接口都用这个 | | |
| 252 | +| `status` | string | `queued` / `running` / `done` / `failed` / `cancelled` | | |
| 253 | +| `req` | object | 你提交时传的原始请求体(回显) | | |
| 254 | +| `partition` | string | `text` / `fl2va` / `ref2va`,见第 2 节 | | |
| 255 | +| `backend` | string\|null | 分配到的底层后端地址,还没排上就是 null | | |
| 256 | +| `created_at` | float | 提交时间,unix 时间戳(秒,带小数) | | |
| 257 | +| `started_at` | float\|null | 开始执行时间 | | |
| 258 | +| `finished_at` | float\|null | 结束时间(done/failed/cancelled 才有) | | |
| 259 | +| `step` / `total_steps` | int | 当前采样进度(`running` 时有意义;`total_steps` 未必等于请求的 `steps`,初始化阶段也会打印进度) | | |
| 260 | +| `file` | string\|null | 服务器本地文件路径(内部用,不建议依赖,用 `/file` 接口下载) | | |
| 261 | +| `size_bytes` | int\|null | 生成文件大小 | | |
| 262 | +| `error` | string\|null | 失败/取消原因 | | |
| 263 | +| `comfy_prompt_id` | string\|null | ComfyUI 侧的执行 id,排查用 | | |
| 264 | +| `queue_seq` | int | 排队顺序号,越小越靠前 | | |
| 265 | +| `queue_position` | int | **仅列表接口返回**:当前排在第几位(1-based,只有 `queued` 状态才有意义) | | |
| 266 | +| `deleted_at` | float\|null | 软删除时间戳,非 null 表示在回收站里 | | |
| 267 | +| `poll` / `download` | string | 底层任务原样带回来的字段,console 已经改写成用自己的 id 拼路径,可以直接用 | | |
| 268 | + | |
| 269 | +--- | |
| 270 | + | |
| 271 | +## 7. 任务管理 | |
| 272 | + | |
| 273 | +### `POST /v1/videos/{id}/cancel` — 取消/停止 | |
| 274 | + | |
| 275 | +行为取决于当前状态: | |
| 276 | + | |
| 277 | +- **`queued`**:直接标记 `cancelled`,从底层队列里真正撤掉,不会再消耗 GPU 去跑它。 | |
| 278 | +- **`running`**:⚠️ **会硬重启整个 raylight 服务**。真实计算跑在 Raylight 的 Ray 子进程里,收不到普通的打断信号,唯一能立刻把 GPU 收回来的办法就是重启整个后端——重启期间(约 15-20 秒)**所有**任务(不只是被停的这条)都无法提交/无法查询进度,会影响其它并发调用方。不是"优雅停止",请求前想清楚代价。 | |
| 279 | +- 已经是终态(`done`/`failed`/`cancelled`):`409 {"detail": "任务当前状态是 xxx,已经是终态,取消不了"}` | |
| 280 | + | |
| 281 | +### `POST /v1/videos/{id}/prioritize` — 插队 | |
| 282 | + | |
| 283 | +只能对 `status="queued"` 的任务调用,把它挪到排队最前面。非 queued 状态返回 409。 | |
| 284 | + | |
| 285 | +响应里的 `queue_position` 是插队后的新位置。 | |
| 286 | + | |
| 287 | +### `DELETE /v1/videos/{id}` — 软删除 | |
| 288 | + | |
| 289 | +移入回收站(`deleted_at` 设成当前时间)。**不影响底层任务本身**——如果这条任务还在排队/运行中,它会照常跑完,只是从 console 的主列表里消失了。真要连底层计算一起停掉,配合 `cancel` 一起调(顺序不影响结果)。 | |
| 290 | + | |
| 291 | +```json | |
| 292 | +{"id": "xxx", "deleted": true} | |
| 293 | +``` | |
| 294 | + | |
| 295 | +对已经在回收站里的任务重复调用,或 id 不存在:`404 {"detail": "无此任务,或已经在回收站里"}`。 | |
| 296 | + | |
| 297 | +### `POST /v1/videos/{id}/restore` — 恢复 | |
| 298 | + | |
| 299 | +```json | |
| 300 | +{"id": "xxx", "deleted": false} | |
| 301 | +``` | |
| 302 | + | |
| 303 | +对不在回收站里的任务调用:`404 {"detail": "无此任务,或不在回收站里"}`。 | |
| 304 | + | |
| 305 | +### `POST /v1/videos/batch-cancel` / `POST /v1/videos/batch-delete` — 批量操作 | |
| 306 | + | |
| 307 | +**2026-09-05 新增**:给脚本化批量清理用,避免大批量提交/清理时只能一个个调用单条接口。 | |
| 308 | + | |
| 309 | +请求体二选一: | |
| 310 | + | |
| 311 | +```json | |
| 312 | +// 方式一:显式给 id 列表,没有数量上限(调用方明确知道要动哪些) | |
| 313 | +{ "ids": ["id1", "id2", "id3"] } | |
| 314 | +``` | |
| 315 | +```json | |
| 316 | +// 方式二:筛选条件,跟 GET /v1/videos 的 status/kind/q 语义一致,至少给一个 | |
| 317 | +{ "status": "queued", "kind": "ref2va", "q": "某个关键词" } | |
| 318 | +``` | |
| 319 | + | |
| 320 | +两种方式不能都不给——空 body 会被拒绝: | |
| 321 | +```json | |
| 322 | +// HTTP 400 | |
| 323 | +{"detail": "必须提供 ids 列表,或至少一个筛选条件(status/kind/q),避免误操作全部任务"} | |
| 324 | +``` | |
| 325 | + | |
| 326 | +筛选条件模式有安全上限:命中数超过 **300** 条直接拒绝,不会静默截断: | |
| 327 | +```json | |
| 328 | +// HTTP 400 | |
| 329 | +{"detail": "筛选条件命中 320 条,超过单次批量操作上限 300,请缩小筛选范围(加 status/kind/q)或改成显式 ids 列表"} | |
| 330 | +``` | |
| 331 | + | |
| 332 | +响应:逐条处理,一条失败不影响其它条,成功/失败分别列出来: | |
| 333 | +```json | |
| 334 | +{ | |
| 335 | + "requested": 3, | |
| 336 | + "succeeded": ["id1", "id3"], | |
| 337 | + "failed": [{"id": "id2", "error": "任务当前状态是 done,已经是终态,取消不了"}] | |
| 338 | +} | |
| 339 | +``` | |
| 340 | + | |
| 341 | +`batch-cancel` 对批量里混进的 `running` 任务一样会触发硬重启(见上面单条 cancel 的说明)——如果批量目标里可能包含正在运行的任务,先用筛选条件把 `status` 限定成 `queued`,避免误停正在跑的任务。 | |
| 342 | + | |
| 343 | +--- | |
| 344 | + | |
| 345 | +## 8. 上传参考素材 | |
| 346 | + | |
| 347 | +### `POST /v1/assets` | |
| 348 | + | |
| 349 | +`multipart/form-data`,字段名 `file`。按扩展名分流校验: | |
| 350 | + | |
| 351 | +| 类型 | 允许扩展名 | 大小上限 | | |
| 352 | +|---|---|---| | |
| 353 | +| 图片 | jpg/jpeg/png/webp | 20MB(且必须是能被解码的有效图片,否则 400) | | |
| 354 | +| 视频 | mp4/mov/webm/mkv | 100MB | | |
| 355 | +| 音频 | mp3/wav/m4a/ogg/flac | 30MB | | |
| 356 | + | |
| 357 | +```bash | |
| 358 | +curl -X POST http://192.168.110.67:8080/v1/assets \ | |
| 359 | + -H "Authorization: Bearer $TOKEN" \ | |
| 360 | + -F "file=@/path/to/reference.png" | |
| 361 | +``` | |
| 362 | + | |
| 363 | +响应: | |
| 364 | +```json | |
| 365 | +{"id": "31417985029a4f60", "url": "http://192.168.110.67:8080/assets/31417985029a4f60__reference.png", "kind": "image"} | |
| 366 | +``` | |
| 367 | + | |
| 368 | +拿到的 `url` 直接填进 `first_frame_url`/`reference_image_urls` 等字段即可。也可以跳过这一步,直接给一个服务器能访问到的公网 URL(图床等)——底层是 `urllib.urlopen` 直接下载,本地上传出来的 URL 和外部 URL 对它没有区别,只是不做 URL 可达性预检,下载失败会在任务执行阶段才报错。 | |
| 369 | + | |
| 370 | +--- | |
| 371 | + | |
| 372 | +## 9. 系统状态 | |
| 373 | + | |
| 374 | +### `GET /v1/backends` | |
| 375 | + | |
| 376 | +```json | |
| 377 | +{ | |
| 378 | + "status": "ok", | |
| 379 | + "ready": 1, "total": 1, | |
| 380 | + "queued": 174, "running": 1, | |
| 381 | + "backends": [ | |
| 382 | + {"status": "ok", "model": "ready", "gpu": "0,1,2,3", "partition": "raylight-fsdp", | |
| 383 | + "offload": "fsdp", "queued": 174, "running": 1, "backend": "http://127.0.0.1:8100"} | |
| 384 | + ], | |
| 385 | + "gpus": [ | |
| 386 | + {"index": 0, "memory_used_mb": 5970, "memory_total_mb": 22528, "utilization_pct": 0}, | |
| 387 | + {"index": 1, "memory_used_mb": 9068, "memory_total_mb": 22528, "utilization_pct": 99} | |
| 388 | + ] | |
| 389 | +} | |
| 390 | +``` | |
| 391 | + | |
| 392 | +`total` 恒为 1(架构上就是一个集群,不是"N 个 worker 里 ready 了几个")。`queued`/`running` 是当前排队深度和是否有任务在跑,轮询这个可以判断"提交了大概要等多久"(没有精确 ETA,只能凭经验:running=1 时前面每条排队任务预计几十秒到几分钟不等,取决于分辨率/帧数/步数)。 | |
| 393 | + | |
| 394 | +### `GET /v1/models` | |
| 395 | + | |
| 396 | +```json | |
| 397 | +{ | |
| 398 | + "partitions": { | |
| 399 | + "fl2va": {"dit": "minimax_h3_fl2va_pruned_int8_convrot.safetensors", "desc": "首末帧条件生成"}, | |
| 400 | + "ref2va": {"dit": "minimax_h3_ref2va_pruned_int8_convrot.safetensors", "desc": "文生视频 / 多参考图生成"} | |
| 401 | + }, | |
| 402 | + "presets": [ | |
| 403 | + {"label": "832×480,最多 1125 帧(≈46.9s@24fps)", "width": 832, "height": 480, "max_frames": 1125} | |
| 404 | + /* ...其余档位见第 4 节表格... */ | |
| 405 | + ] | |
| 406 | +} | |
| 407 | +``` | |
| 408 | + | |
| 409 | +`presets` 是实时查底层拿到的,跟第 4 节的表格应该始终一致——集成时建议用这个接口取分辨率档位,不要在自己代码里硬编码一份可能过期的表。 | |
| 410 | + | |
| 411 | +### `POST /v1/admin/reset-gpu` ⚠️ 危险操作 | |
| 412 | + | |
| 413 | +不挂在具体任务上的兜底重置——硬重启整个 raylight 服务,回收可能因为崩溃/OOM 卡住不释放的显存。会打断当前正在运行的任务(如果有),效果和代价跟单条 `cancel` 停止 `running` 任务完全一样(15-20 秒全局不可用)。只在确认 GPU 显存异常卡住时用,不要当成常规操作调用。 | |
| 414 | + | |
| 415 | +--- | |
| 416 | + | |
| 417 | +## 10. 错误码约定 | |
| 418 | + | |
| 419 | +| HTTP 状态 | 含义 | | |
| 420 | +|---|---| | |
| 421 | +| 200 / 202 | 成功(`POST /v1/videos` 固定 202,见第 4 节警告) | | |
| 422 | +| 400 | 请求参数问题(批量接口的筛选条件缺失/命中过多,上传文件不是有效图片等) | | |
| 423 | +| 401 | token 缺失或错误 | | |
| 424 | +| 404 | 任务 id 不存在,或者已经在回收站/不在回收站(视接口而定) | | |
| 425 | +| 409 | 状态冲突(GPU 重置中;任务已是终态无法取消;非 queued 状态无法插队等) | | |
| 426 | +| 413 | 上传文件超过大小上限 | | |
| 427 | +| 502 | console 连不上底层 raylight(网络问题,重试即可) | | |
| 428 | + | |
| 429 | +除 `POST /v1/videos` 外,其它接口的错误都会原样反映在 HTTP 状态码上,body 统一是 FastAPI 默认的 `{"detail": "..."}` 形状。 | |
| 430 | + | |
| 431 | +--- | |
| 432 | + | |
| 433 | +## 11. 集成注意事项(读完再接) | |
| 434 | + | |
| 435 | +1. **只走 console,不要直连 raylight(8100)**。8100 没有鉴权、没有持久化、没有软删除,绕过 console 提交的任务 console 事后只能靠"扫描发现"机制捞回来,捞回来的记录用的是 raylight 自己的 id(跟正常提交产生的 console id 是同一种格式,肉眼无法区分),管理起来处处受限,2026-09-05 还实测触发过一次真实的重复记录 bug。 | |
| 436 | +2. **批量提交请加节流**,别用 fire-and-forget 式的无限制并发。console 是单进程 FastAPI + sqlite,扛得住合理的并发(个位数到几十 QPS 级别的短请求),但成百上千个并发 POST 会把它的 HTTP 层和数据库拖到不可用(2026-09-05 实测:150 个并发提交导致后续 11 小时 `GET /v1/backends` 响应时间从 0.3 秒退化到 38 秒)。建议:每条提交间隔 100-200ms,或者控制并发数在个位数。 | |
| 437 | +3. **`POST /v1/videos` 永远 202,靠 `status` 字段判断成功/失败**,见第 4 节。 | |
| 438 | +4. **提交前用 `GET /v1/models` 查当前支持的分辨率档位**,不要硬编码——档位可能随实测结果调整。 | |
| 439 | +5. **`turbo`/`cfg_scale`/`negative_prompt` 目前都是 no-op**,传了不会报错但也不会生效,别把它们当成可以调节生成效果的旋钮。 | |
| 440 | +6. **停止 `running` 任务(`cancel`)= 硬重启整个服务**,会影响所有并发调用方 15-20 秒,不是低成本操作。 | |
| 441 | +7. 任何时刻只有 **1 条**任务在真正计算,其余全部排队——设计并发预期时按"单机吞吐"算,不要假设 4 张卡能并行跑 4 条不同任务。 | ... | ... |