Chapter 11

ComfyUI 工程化 · API · 自定义节点

UI 里拖节点是入门,真正把 ComfyUI 用成产品后端要靠HTTP/WS API + 工作流 JSON + 自定义节点。这章把 ComfyUI 从"画板"变成"可编程服务"。

一、ComfyUI 的架构:UI 是前端,Server 是核心

浏览器 UI(JavaScript,基于 LiteGraph) │ WebSocket / HTTP ▼ ComfyUI Server(Python + aiohttp) ├─ /prompt POST 提交工作流 ├─ /queue GET 队列状态 ├─ /history GET 历史结果 ├─ /view GET 下载图片 ├─ /ws WebSocket 实时进度 └─ Execution Engine ├─ 解析 prompt graph ├─ 拓扑排序 nodes └─ 按序执行并缓存

这意味着:只要你能 POST 工作流 JSON,ComfyUI 就能跑——不需要 UI。Python / Node.js / 任何语言都行。

二、导出 API 格式工作流

UI 里的 .json 有两种形式:

workflow.json(UI 格式)
带节点位置、颜色、连线坐标等 UI 元信息。用于 UI 保存 / 分享 / 从图片载入。不能直接 POST 到 /prompt
workflow_api.json(API 格式)
只保留节点类型、参数、连接关系——扁平化的 JSON。可以直接 POST。通过 UI 顶部菜单 Workflow → Export (API) 保存。

API 格式长什么样

{
  "3": {
    "inputs": {
      "seed": 42,
      "steps": 25,
      "cfg": 6.5,
      "sampler_name": "dpmpp_2m",
      "scheduler": "karras",
      "denoise": 1.0,
      "model": ["4", 0],       # 引用 node 4 的第 0 个 output
      "positive": ["6", 0],
      "negative": ["7", 0],
      "latent_image": ["5", 0]
    },
    "class_type": "KSampler"
  },
  "4": {
    "inputs": {"ckpt_name": "juggernautXL_v10.safetensors"},
    "class_type": "CheckpointLoaderSimple"
  },
  "5": {
    "inputs": {"width": 1024, "height": 1024, "batch_size": 1},
    "class_type": "EmptyLatentImage"
  },
  "6": {
    "inputs": {"text": "a cat in a spacesuit", "clip": ["4", 1]},
    "class_type": "CLIPTextEncode"
  },
  ...
}

键是节点 ID(字符串数字),class_type 对应节点类型,inputs 里既有静态参数也有对其他节点的引用(["节点ID", 输出索引])。

三、Python 客户端最小示例

import json, time, uuid, urllib.request
import websocket   # pip install websocket-client

SERVER = "127.0.0.1:8188"
CLIENT_ID = str(uuid.uuid4())

def queue_prompt(workflow):
    payload = {"prompt": workflow, "client_id": CLIENT_ID}
    req = urllib.request.Request(
        f"http://{SERVER}/prompt",
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"},
    )
    return json.loads(urllib.request.urlopen(req).read())

def get_image(filename, subfolder, typ):
    url = f"http://{SERVER}/view?filename={filename}" \
          f"&subfolder={subfolder}&type={typ}"
    return urllib.request.urlopen(url).read()

def run_workflow(workflow):
    # 1) 订阅 WebSocket 看进度
    ws = websocket.WebSocket()
    ws.connect(f"ws://{SERVER}/ws?clientId={CLIENT_ID}")

    # 2) 提交
    prompt_id = queue_prompt(workflow)["prompt_id"]

    # 3) 等结束
    while True:
        msg = json.loads(ws.recv())
        if msg["type"] == "executing":
            data = msg["data"]
            if data["node"] is None and data["prompt_id"] == prompt_id:
                break   # 完成

    # 4) 取结果
    history = json.loads(urllib.request.urlopen(
        f"http://{SERVER}/history/{prompt_id}").read())
    images = []
    for node_output in history[prompt_id]["outputs"].values():
        if "images" in node_output:
            for img in node_output["images"]:
                images.append(get_image(img["filename"],
                                         img["subfolder"],
                                         img["type"]))
    return images

# 使用
workflow = json.load(open("workflow_api.json"))
workflow["3"]["inputs"]["seed"] = int(time.time())
workflow["6"]["inputs"]["text"] = "a cyberpunk cat, neon lights"
imgs = run_workflow(workflow)
open("out.png", "wb").write(imgs[0])

这 50 行代码就是"ComfyUI 作为后端"的完整骨架——所有 SaaS AI 绘画产品底层都长这样。

四、WebSocket 进度事件

/ws 会推送多种事件:

typedata 字段含义
statusqueue_remaining队列还有几个任务
execution_startprompt_id开始执行某任务
executingnode, prompt_id正在执行某节点(node=None 代表全部完成)
progressvalue, max, nodeKSampler 等的步数进度
executednode, output某节点完成,带输出(图片文件名等)
execution_errorerror traceback执行失败

产品 UI 里"正在生成... 42%"就是订阅 progress 事件实时显示。

五、并发与队列

ComfyUI 自带队列——多个请求排队顺序执行。进阶部署常见模式:

单实例 + 队列
适合小团队 / demo。ComfyUI 本身 POST /prompt 即入队,客户端用 prompt_id 追进度。
多 GPU 多实例 + 负载均衡
每张 GPU 起一个 ComfyUI(端口不同),前端用 Nginx / HAProxy / 自写 LB 分发。最简单的扩展。
Serverless(RunPod/Replicate/Modal)
按请求启动容器,冷启动 20-60s。按用量计费,低频场景成本低。下一章细讲。

取消任务

六、参数化工作流:模板 + 变量

产品场景:一个工作流跑多种变体,不可能每次改 JSON。常见做法:

def build_workflow(template, *, prompt, seed, ckpt, steps=25, cfg=6):
    wf = copy.deepcopy(template)
    wf["3"]["inputs"]["seed"] = seed
    wf["3"]["inputs"]["steps"] = steps
    wf["3"]["inputs"]["cfg"] = cfg
    wf["4"]["inputs"]["ckpt_name"] = ckpt
    wf["6"]["inputs"]["text"] = prompt
    return wf

# 用节点名而非节点 ID 更稳
def find_node(wf, class_type, title=None):
    for nid, node in wf.items():
        if node["class_type"] == class_type:
            if title is None or node.get("_meta", {}).get("title") == title:
                return nid, node
    raise KeyError(f"no {class_type} node")
节点 ID 是随机的,别写死
——UI 里保存的 workflow_api.json,节点 ID 可能是 "3","4"... 也可能是 "10","15"——取决于你建节点的顺序。生产代码里用 class_type + 节点 title 查找,不要写 wf["3"] 这种硬编码。

七、子工作流:Group Node & Subgraph

工作流超过 30 节点就开始眼花。ComfyUI 提供两种打包方式:

Group Node(旧)
选中一批节点 → 右键 Convert to Group Node → 折叠成一个节点,双击展开。只改显示不改执行。
Subgraph(2025 新)
真正的子工作流——可复用、可传参,类似"函数"。新版 ComfyUI 原生支持,适合团队协作把"标准 Hi-Res Fix""FaceDetailer 管道"封成子图。

八、自定义节点开发

要集成业务逻辑(比如调公司内部 API、做特殊图像处理),就得写自定义节点。

最小节点骨架

# custom_nodes/my_pack/__init__.py
class MyPromptEnhancer:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "prompt": ("STRING", {"multiline": True}),
                "style": (["cinematic", "anime", "photorealistic"],),
                "strength": ("FLOAT", {"default": 1.0, "min": 0, "max": 2}),
            },
        }

    RETURN_TYPES = ("STRING",)
    RETURN_NAMES = ("enhanced_prompt",)
    FUNCTION = "enhance"
    CATEGORY = "my_pack/prompt"

    def enhance(self, prompt, style, strength):
        suffix = {
            "cinematic": ", cinematic lighting, 35mm film, dramatic composition",
            "anime": ", anime style, vibrant colors, detailed line art",
            "photorealistic": ", photorealistic, 8k, sharp focus",
        }[style]
        result = f"{prompt}{suffix} (weight:{strength})"
        return (result,)

NODE_CLASS_MAPPINGS = {"MyPromptEnhancer": MyPromptEnhancer}
NODE_DISPLAY_NAME_MAPPINGS = {"MyPromptEnhancer": "🎨 Prompt Enhancer"}

放到 ComfyUI/custom_nodes/my_pack/,重启 ComfyUI 就能在节点菜单 my_pack → prompt 下找到。

输入类型

UI 增强(JS 端)

如果节点需要自定义 UI(比如动态刷新下拉选项),在目录下建 web/ 放 JS 文件:

// custom_nodes/my_pack/web/my_widget.js
import { app } from "../../scripts/app.js";

app.registerExtension({
  name: "MyPack.PromptEnhancer",
  async nodeCreated(node) {
    if (node.comfyClass === "MyPromptEnhancer") {
      // 加自定义按钮 / 颜色 / 回调
    }
  },
});

九、常用工程化 Custom Node 包

用途
ComfyUI-Manager模型/节点管理的 UI,必装
ComfyUI-Custom-Scripts(pythongosssss)Wildcards, 显示图片 metadata, 文本实时预览
rgthree-comfy节点合并/快速切换/muting 切 LoRA 组
was-node-suite-comfyui200+ 实用节点(字符串处理/图片操作/数学)
ComfyUI-Impact-PackFaceDetailer/SAM/Detector 集合
ComfyUI_essentials补齐原生缺的基础节点
ComfyUI-Crystools系统监控(GPU/VRAM 实时显示)
ComfyUI-VideoHelperSuite视频输入/输出/组合

十、从 ComfyUI 到产品后端:架构模板

用户(Web/Mobile) │ HTTPS ▼ 业务后端(FastAPI/Node.js) ├─ 鉴权、限流、计费 ├─ 任务持久化(Postgres) ├─ 图片 CDN 上传(S3/OSS) └─ 队列(Redis/Celery/BullMQ) │ ▼ Worker(Python) ├─ 取任务 ├─ 构造 workflow_api.json(参数替换) ├─ 调 ComfyUI /prompt ├─ WS 订阅进度 → 推送给用户 └─ 拿图 → 上传 CDN → 写结果 │ ▼ ComfyUI 集群(多 GPU 实例) ├─ 实例 1(GPU 0) ├─ 实例 2(GPU 1) └─ ...

关键设计点:

十一、调试技巧

  1. ComfyUI 启动加 --verbose:看详细日志
  2. 节点不熟时截图发 UI:右键 Copy (Clipspace)Save (API Format) 比手写 JSON 稳
  3. 画图输出 debug 文本:用 Show Text(rgthree)节点把中间 prompt / 条件 / 变量直接显示在画布上
  4. Preview Image 节点监控中间 latent:Hi-Res Fix 两阶段之间插一个 Preview 查第一阶段出图
  5. /object_info:GET /object_info 列出所有可用节点 + 输入类型,写自动化脚本时查手册

十二、反模式

  1. 用 workflow.json(UI 版)POST 到 /prompt:必然报错,要用 workflow_api.json。
  2. 节点 ID 硬编码:换一台机器或加新节点就全崩,应按 class_type 查找。
  3. ComfyUI 直接暴露到公网:UI 能上传任意文件 + 执行任意工作流,被入侵就是 RCE。
  4. 没做队列超时:大图 OOM 导致任务挂死,后续全堵。业务层设超时 + /interrupt
  5. 自定义节点不写 requirements.txt:依赖装不全,别人部署直接报错。
  6. 多 GPU 实例共享 models 目录:读 OK,但写(下载模型)会冲突。只读挂载或做锁。
  7. 不用 Crystools 看 VRAM:OOM 经常突然发生,有监控才知道临界点在哪。
  8. 自定义节点副作用:节点里开网络请求 / 写文件不加超时,整个图阻塞或卡死。

十三、本章小结

记住:
① ComfyUI 是可编程服务:POST workflow_api.json 到 /prompt,WebSocket 看进度,/history 取结果。
② 产品后端套路:业务层 + 队列 + Worker + ComfyUI 集群,ComfyUI 永不对公网。
③ 参数化用 class_type 查节点,不要硬编码 ID。
④ 自定义节点三件套:INPUT_TYPES / RETURN_TYPES / FUNCTION,加 NODE_CLASS_MAPPINGS 注册即生效。
⑤ 常装:Manager + Custom Scripts + rgthree + Impact-Pack + VideoHelperSuite + Crystools——这套是工程化生态的核心。