本机上跑通一个 ComfyUI 工作流,和让另一个应用稳定地调用它,中间隔着一层不少人都踩过的坑:节点连得好好的,导出成 JSON 丢给后端,却报“找不到节点类型”;或者图能跑,但换了个输入尺寸,返回的图还是上一次的。这类问题的根源通常不在模型,而在于工作流没有按 API 的格式整理、运行环境没有连同节点和模型一起打包,以及调用方不清楚该往哪个端点提交、去哪里取结果。
这篇内容沿着“本机能跑 → 应用能调”这条线走一遍完整流程:把工作流整理成可提交的 JSON、把依赖固化进运行环境、部署后发起 HTTP 请求、验证输出,最后给出上线前必须确认的权限、数据处理和费用事项。

先确定部署路线
ComfyUI 本身就是一个 HTTP 服务,启动后会暴露一套接口。围绕它通常有三条路线,选哪条取决于你想让调用方看到多少细节。
第一种是直接把 ComfyUI 服务暴露给调用方,让应用自己往 /prompt 提交工作流图,再从 /history 或 /view 取结果。这是改动最小、兼容性最好的方式,工作流里的一切节点能力都保留着,代价是调用方需要理解工作流的节点结构。
第二种是在 ComfyUI 前面加一层自己的 REST 端点,比如暴露一个 /generate,只接收 prompt、尺寸、seed 这几个业务参数,由服务端把参数填进预先保存好的工作流模板,再转发给 ComfyUI。调用方完全不需要知道节点长什么样,接口也更稳定。代价是你多维护一层参数映射代码。
第三种是使用托管式的云端 API,把工作流交给平台托管,自己只持有密钥和端点。这条路省去了环境搭建和显卡管理,但会把模型、节点和数据的可见性交给第三方,需要先确认合规要求。
下文以第二种为主、兼顾第一种来展开,因为它最贴近“内部工具复用工作流”的实际场景。
把工作流整理成 API 格式
UI JSON 与 API JSON 的区别
在 ComfyUI 界面里点“保存”得到的 JSON,是给编辑器用的,里面包含节点坐标、分组、颜色、连线位置等界面信息,节点以数组形式排列。而提交给接口的 JSON 以节点 ID 为键,每个节点只保留 class_type 和 inputs,执行时才不会解析到多余的界面字段。
导出 API 格式的步骤是:打开设置,勾选启用开发者模式选项,菜单里就会出现“保存(API 格式)”的入口,用它另存一份 JSON。这份文件才是可以直接提交给接口的输入。
一个最简结构的片段大致是这样的:
{
"3": {
"class_type": "KSampler",
"inputs": {
"seed": 12345,
"steps": 20,
"cfg": 7.0,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1.0,
"model": ["4", 0],
"positive": ["6", 0],
"negative": ["7", 0],
"latent_image": ["5", 0]
}
},
"6": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "a quiet street at dawn",
"clip": ["4", 1]
}
}
}
关键点在于 inputs 里那些形如 ["4", 0] 的值:它不是普通数据,而是“上游节点 ID + 输出槽位”的引用。修改参数时只能改真正的字面量字段(如 text、seed、steps),不要动这些引用关系。
定位要对外开放的参数
把工作流做成模板时,先想清楚调用方需要控制哪几个值,一般在三到五个之内。常见的做法是用注释或文档记录“节点 ID + 字段名”的映射,参数注入时按路径写回。例如把正向提示词放在节点 6 的 text 字段,把尺寸放在 5 的 width/height,把随机种子放在 3 的 seed。
如果工作流里有 LoadImage 这类需要外部文件的节点,输入就不是字符串而是文件名。此时调用方需要先上传图片拿到服务端文件名,再把该文件名写进对应字段,而不是直接塞一个本地路径。这个字段名在不同版本的节点定义里可能不同,部署前务必用你实际使用的节点版本核对一次。
准备运行环境
工作流能在你机器上跑,不代表部署后能跑。差异几乎总是出在三个地方。
自定义节点。 只要工作流里出现了非内置节点(比如各类控制网、放大、面部修复类节点),目标环境就必须安装同一套节点包。建议把节点清单和版本固定下来,写进镜像构建脚本或依赖清单,而不是部署后手动装。节点版本不一致时,最典型的症状是提交时报“节点类型不存在”,或者节点存在但输入字段对不上。
模型与路径。 检查点、VAE、LoRA、控制网模型都要随环境一起提供。模型文件通常体积较大,更适合放在挂载卷或对象存储里,再用路径配置指向它们,而不是打进镜像。如果沿用 ComfyUI 的多路径配置,注意容器内路径与配置文件的对应关系,否则工作流引用的模型名会解析失败。
Python 依赖。 节点包各自带依赖,冲突经常发生在同一库的不同版本要求上。稳妥的做法是先锁定一版可用的依赖,再打镜像;升级时整批升级并重新跑一遍回归工作流,而不是在生产环境里单独 pip install 一个包。
一个用于容器启动的基础命令大致如下:
python main.py --listen 0.0.0.0 --port 8188
--listen 决定服务是否对外可达。只在容器网络内部使用时可绑定到内网地址,再由你的封装服务转发;如果直接对公网暴露,务必叠加鉴权层,因为这套接口本身不承担身份校验职责。

部署后如何调用
ComfyUI 的服务接口里,最常用的几个端点是:提交任务的 /prompt、查询历史的 /history、下载图像的 /view、上传素材的 /upload/image,以及用于实时事件的 /ws。
提交流程通常是这样的:生成一个客户端标识(可以用 UUID),把工作流 JSON 放进请求体的 prompt 字段提交,拿到一个任务 ID;随后用这个任务 ID 去查历史记录,或者通过 WebSocket 接收进度和完成事件。
import json, uuid, urllib.request
BASE = "http://127.0.0.1:8188"
client_id = str(uuid.uuid4())
workflow = json.load(open("workflow_api.json", encoding="utf-8"))
workflow["6"]["inputs"]["text"] = "a quiet street at dawn"
workflow["3"]["inputs"]["seed"] = 12345
payload = json.dumps({"prompt": workflow, "client_id": client_id}).encode()
req = urllib.request.Request(
f"{BASE}/prompt",
data=payload,
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as resp:
result = json.load(resp)
prompt_id = result["prompt_id"]
print("submitted:", prompt_id)
拿到 prompt_id 之后有两种等待方式。轮询 /history/{prompt_id} 实现简单,适合低频调用;WebSocket 能拿到逐节点的执行进度,适合需要展示进度的场景。轮询要设超时上限,否则工作流卡住时请求会一直挂着。
import time, urllib.request
for _ in range(120):
with urllib.request.urlopen(f"{BASE}/history/{prompt_id}") as resp:
history = json.load(resp)
if prompt_id in history:
outputs = history[prompt_id]["outputs"]
print("done:", list(outputs.keys()))
break
time.sleep(1)
else:
raise TimeoutError("workflow did not finish in time")
输出节点会给出图像的文件名、子目录和类型,用这些信息拼出 /view 的查询参数即可取回图片。注意返回的图可能有多张,业务侧要明确取哪一张,不要默认只有一个结果。
验证结果是否真的对
提交成功不等于结果正确。建议每次部署后跑一组固定的回归用例:同样的工作流、同样的 seed、同样的输入图片,比对输出是否稳定。判断维度至少包括三项——图能正常下载且格式可解析、尺寸与工作流设定一致、内容与预期输入存在可见关联。
如果怀疑参数没有真正生效,最直接的验证方式是只改一个明显影响输出的字段(比如把正向提示词换成一个差异极大的描述,或把尺寸改成明显不同的值),观察输出是否随之改变。若输出毫无变化,多半是参数注入的路径写错了,改到了界面 JSON 而不是 API JSON,或者改到了不被执行的分支节点上。
常见的失败与排查方向
| 现象 | 常见原因 | 排查方向 |
|---|---|---|
| 提交时报节点类型不存在 | 目标环境缺少对应自定义节点或版本不一致 | 对比节点清单与版本,补齐后重新构建镜像 |
| 模型加载失败 | 模型文件缺失或路径映射不对 | 确认工作流引用的模型名与实际文件一致 |
| 请求返回成功但输出为空 | 输出节点未被执行到,或工作流被裁剪 | 核对节点连线,确认输出节点在依赖链上 |
| 一直查不到完成结果 | 工作流报错但未被轮询逻辑捕获 | 读取历史记录中的状态字段,查看具体报错 |
| 换了输入但结果不变 | 参数写在界面 JSON 或错误的节点路径 | 改用 API 格式 JSON,逐条核对映射表 |
| 并发上来后大量超时 | 显存或计算资源不足,任务排队 | 限制并发数、加队列,而不是无限放大超时时间 |
上线前的三项核验
权限。 这套接口默认不做身份校验。放在内网也要确认谁能访问,跨网络暴露时必须加网关或反向代理做鉴权,并限制上传接口的文件类型和体积,避免被当作任意文件写入通道。
数据处理。 工作流里如果有 LoadImage 之类的输入节点,调用方上传的素材会落在服务端。需要明确这些文件的保存位置、保留时长和清理策略,涉及用户素材时还要确认是否符合所在组织的数据处理要求。
费用与资源。 自建部署的成本主要来自显卡资源和运行时长,托管方式则按调用量或算力计费。具体价格、额度和限流规则会随时间调整,务必以发布时官方文档为准;本文不给出具体数字。另外要留意模型许可证是否允许你的使用场景,尤其是商用和二次分发。
接下来可以做的调整
跑通之后,比较自然的下一步是把参数映射表抽象成配置文件,让新增工作流不必改代码;再往后是给提交端点加队列和限流,把长任务与短任务分开处理。如果后续要接入多个工作流,建议统一约定输出节点的命名和返回结构,否则调用方的解析逻辑会随工作流数量线性膨胀。

最后提醒一点:接口字段、节点输入名称和鉴权方式都属于会变化的时效性信息,不同 ComfyUI 版本之间可能存在差异。正式上线前,请以你实际部署版本对应的官方文档为准,把版本号记录在部署说明里,后续升级时才有对照依据。

评论列表 (1条):
加载更多评论 Loading...