docs: Comfy Cloud 工作流执行适配器设计 spec
在 BoosAPI 内做 ComfyUI 原生协议↔Comfy Cloud API 翻译适配器:对外暴露
/comfy-cloud/{prompt,history,view},内部翻译成 cloud.comfy.org/api/* + X-API-Key。
Comfy Cloud base_url+key 经站点管理(新增 comfy_cloud 平台类型)录入。
Pixelle/comfykit 零改动,只改 comfyui_url 指向适配端点。
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# Comfy Cloud 工作流执行适配器 — 设计
|
||||
|
||||
日期:2026-06-17
|
||||
分支:`feat/comfy-cloud-adapter`
|
||||
状态:已通过 brainstorming,待落实现计划
|
||||
|
||||
## 背景
|
||||
|
||||
数字人/图片工作流走 `selfhost` 生成视频时报 `Submit workflow failed: [401] AuthenticationRequired`。
|
||||
根因经多层验证已锁定:
|
||||
|
||||
- Pixelle 的 comfykit 0.1.12 `HttpExecutor` 说的是 **ComfyUI 原生协议**:
|
||||
`POST {base_url}/prompt`、`GET {base_url}/history/{id}`、鉴权用 `Authorization: Bearer`。
|
||||
- 配置里 `comfyui_url = https://cloud.comfy.org`,但 **Comfy Cloud 的 API 契约完全不同**
|
||||
(官方文档 https://docs.comfy.org/development/cloud/overview):
|
||||
- 鉴权:`X-API-Key` 头(非 Bearer)。
|
||||
- 提交:`POST /api/prompt`(带 `/api/` 前缀)。
|
||||
- 轮询:`GET /api/job/{prompt_id}/status` → 状态 pending/in_progress/completed/failed/cancelled。
|
||||
- 详情/产出:`GET /api/jobs/{prompt_id}`(产出按 node_id 聚合 images/video/audio)。
|
||||
- 下载:`GET /api/view?filename=&subfolder=&type=output`(302 跳带签名 URL)。
|
||||
- 接受标准 ComfyUI API-format 工作流。
|
||||
- 已用官方方式验证 Comfy Cloud key 有效:`GET /api/user` 返回 200 `{"status":"active"}`。
|
||||
|
||||
所以这不是 key 失效,而是 comfykit 与 Comfy Cloud 协议不兼容(路径前缀 + 鉴权头 + 轮询端点三处全不同)。
|
||||
|
||||
## 目标
|
||||
|
||||
在 **BoosAPI** 里实现一个 **ComfyUI 原生协议 ↔ Comfy Cloud API 翻译适配器**:对外暴露
|
||||
ComfyUI 原生端点(`/prompt`、`/history/{id}`、`/view`),内部翻译成 Comfy Cloud 的
|
||||
`/api/*` 契约 + `X-API-Key` 鉴权。Pixelle/comfykit **一行代码都不改**,只把 `comfyui_url`
|
||||
指向 BoosAPI 的适配端点即可。
|
||||
|
||||
Comfy Cloud 的 base URL + API Key 通过**站点管理**录入(新增 `comfy_cloud` 平台类型),
|
||||
适配器从站点配置读取,而非散落的环境变量/配置文件。
|
||||
|
||||
## 已确认的关键决策
|
||||
|
||||
1. 采用 **Approach A**:站点管理只作「配置载体 + 前端入口」(存 base_url + X-API-Key),
|
||||
执行逻辑由独立的 Comfy Cloud 适配器承担;**不**把 Comfy Cloud 塞进 `/v1/*` token 路由
|
||||
(它是工作流执行 API,不是聊天协议,塞进 token-router 跑不通)。
|
||||
2. 适配器对外说 ComfyUI 原生协议,Pixelle/comfykit 零改动,只改 `comfyui_url` 配置。
|
||||
3. 鉴权翻译:入口端点不强制校验(本机互信)/出口注入 `X-API-Key`。
|
||||
4. 分支 `feat/comfy-cloud-adapter`,与 NIT 工作无关,不混入。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不改 Pixelle / comfykit 源码。
|
||||
- 不做 WebSocket 实时进度(comfykit HTTP 模式只轮询 `/history/{id}`,够用)。
|
||||
- 不把 Comfy Cloud 接入 LLM token 路由/failover。
|
||||
- 不做 Comfy Cloud 的计费集成(已有 `fetch_comfy_cloud_balance` 另说)。
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
Pixelle(comfykit 0.1.12, 不改)
|
||||
comfyui_url = http://<boosapi-host>/comfy-cloud
|
||||
说 ComfyUI 原生协议: POST /prompt · GET /history/{id} · GET /view · Bearer(忽略)
|
||||
│
|
||||
▼
|
||||
BoosAPI 适配端点(新增 src/routes/comfy_cloud_adapter.rs)
|
||||
· POST /comfy-cloud/prompt → 翻译 → POST cloud.comfy.org/api/prompt
|
||||
· GET /comfy-cloud/history/{id} → 翻译 → GET /api/job/{id}/status (+ /api/jobs/{id})
|
||||
拼回 ComfyUI history 格式
|
||||
· GET /comfy-cloud/view → 翻译 → GET /api/view (跟随 302)
|
||||
鉴权: 出口注入 X-API-Key(从站点配置读)
|
||||
│
|
||||
▼
|
||||
Comfy Cloud (https://cloud.comfy.org/api/*)
|
||||
```
|
||||
|
||||
## 站点管理集成(配置载体)
|
||||
|
||||
- 前端 `SITE_PLATFORM_OPTIONS`(`src/web/pages/Sites.tsx`)新增一项
|
||||
`{ value: 'comfy_cloud', label: 'Comfy Cloud', description: '工作流执行 API' }`。
|
||||
- 站点记录复用现有字段:`url` 存 `https://cloud.comfy.org`,API Key 存入现有的
|
||||
站点凭证字段(沿用站点已有的 key/header 存储方式;实现时确认 sites 表里承载 key 的列)。
|
||||
- 适配器通过一个 helper `resolve_comfy_cloud_site(state) -> (base_url, api_key)`:
|
||||
查 `platform = 'comfy_cloud'` 且 enabled 的站点,取其 url + key。无配置 → 明确报错。
|
||||
|
||||
## 适配端点详细行为
|
||||
|
||||
### POST /comfy-cloud/prompt
|
||||
- 入参:ComfyUI 原生 body `{ "prompt": {...}, "client_id": "..." }`。
|
||||
- 出口:`POST {base_url}/api/prompt`,头 `X-API-Key: {key}` + `Content-Type: application/json`,
|
||||
body 原样转发(Comfy Cloud 接受相同的 `{prompt: ...}` 包裹)。
|
||||
- 回包:Comfy Cloud 返回 `{ "prompt_id": "..." }`,原样回给 comfykit(字段名一致,无需改)。
|
||||
|
||||
### GET /comfy-cloud/history/{prompt_id}
|
||||
comfykit 轮询此端点,期待 ComfyUI history 格式:
|
||||
`{ "{prompt_id}": { "status": {...}, "outputs": { "{node_id}": { "images": [...] } } } }`。
|
||||
适配器:
|
||||
1. `GET {base_url}/api/job/{prompt_id}/status` 拿 `{ "status": "completed|in_progress|..." }`。
|
||||
2. 若未完成(pending/in_progress)→ 回 `{}`(comfykit 据此继续轮询)。
|
||||
3. 若 completed → `GET {base_url}/api/jobs/{prompt_id}` 拿产出,翻译成 ComfyUI history 形状:
|
||||
```json
|
||||
{ "{prompt_id}": {
|
||||
"status": { "completed": true, "status_str": "success" },
|
||||
"outputs": { /* node_id → {images|video|audio: [{filename,subfolder,type}]} */ }
|
||||
} }
|
||||
```
|
||||
4. 若 failed/cancelled → 回带 `status_str: "error"` 的 history,让 comfykit 终止轮询并报错。
|
||||
|
||||
### GET /comfy-cloud/view
|
||||
- 透传 query(`filename`/`subfolder`/`type`)→ `GET {base_url}/api/view?...`,头注入 X-API-Key。
|
||||
- Comfy Cloud 回 302 签名 URL → 适配器跟随重定向取字节流回传(reqwest 默认跟随,
|
||||
或手动取 Location 再 GET),Content-Type 透传。
|
||||
|
||||
## 数据流(comfykit 视角,全程无感)
|
||||
|
||||
1. comfykit `POST {comfyui_url}/prompt` → 适配器 → Comfy Cloud → 返回 prompt_id。
|
||||
2. comfykit 轮询 `GET {comfyui_url}/history/{id}` → 适配器翻译 job 状态 → 未完成回 `{}`,
|
||||
完成回 ComfyUI history。
|
||||
3. comfykit 从 outputs 取 filename → `GET {comfyui_url}/view?filename=...` → 适配器取回字节。
|
||||
|
||||
## 错误处理
|
||||
|
||||
- 站点未配置 `comfy_cloud` → 适配端点返回 503 + 中文提示「未配置 Comfy Cloud 站点」。
|
||||
- Comfy Cloud 返回非 2xx → 适配器透传状态码 + 上游错误体(便于排查,不吞错)。
|
||||
- job failed/cancelled → 在 history 里标 error,comfykit 会抛 `Submit workflow failed` 类错误
|
||||
(但这次是真实的执行失败,不是鉴权问题)。
|
||||
|
||||
## 路由挂载
|
||||
|
||||
`src/routes/mod.rs` 的 `build_router` 加 `.merge(comfy_cloud_adapter::routes())`。
|
||||
端点前缀 `/comfy-cloud/*`,不需要 admin/user 鉴权(本机内 Pixelle → BoosAPI 互信;
|
||||
如需收紧可后续加内部 token,本期不做)。
|
||||
|
||||
## 测试策略(TDD)
|
||||
|
||||
- 单测翻译函数:Comfy Cloud `job status + jobs detail` JSON → ComfyUI history JSON 的纯函数
|
||||
(`comfy_cloud_job_to_history`),覆盖 completed / in_progress / failed 三种。
|
||||
- 单测 `resolve_comfy_cloud_site`:有配置返回 (url,key);无配置报错。
|
||||
- 用 wiremock 起一个假 Comfy Cloud,集成测 `POST /comfy-cloud/prompt` 透传 + X-API-Key 注入、
|
||||
`GET /comfy-cloud/history/{id}` 轮询翻译(沿用项目现有 wiremock 测试范式)。
|
||||
|
||||
## 验证
|
||||
|
||||
- `cargo build -j 2`、`cargo test -p boosapi --lib comfy_cloud`。
|
||||
- 真实联调:把 Pixelle `comfyui_url` 指向 `http://<boosapi>/comfy-cloud`,跑一次 selfhost 视频,
|
||||
确认不再 401 且能取回产出。
|
||||
|
||||
## 提交约定
|
||||
|
||||
分支 `feat/comfy-cloud-adapter`,提交按逻辑拆分(站点平台类型 / 适配器 service + 端点 /
|
||||
路由挂载 / 前端平台选项),沿用项目中文 commit 风格。
|
||||
Reference in New Issue
Block a user