ComfyUI开发指南
由于ComfyUI没有官方的API文档,所以想要去利用ComfyUI开发一些web应用会比 a1111 webui这种在fastapi加持下有完整交互式API文档的要困难一些,而且不像a1111 sdwebui 对很多pipeline都有比较好的封装,基本可以直接用,comfyui里,pipeline也需要自己找workflow或者自己从头搭,虽说有非常高的灵活度,但如果想要和开发对接,又对工作流节点在实际代码执行层面的逻辑没有一定的理解,很容易卡壳。
另外,comfyUI本身的请求接口是基于websocket协议,对于不了解网络请求协议或者只知道http的同学更是一道难关。
本文将会从头开始介绍如何克服上述的种种困难,让大家都能利用ComfyUI快速搭建自己AIGC产品的MVP
首先是websocket ,不像http 在客户端向服务端发送请求(post get 。。。)后服务器再根据客户端的需求把相关的数据返回,之后这次通信便到此结束,谁也不再理谁,服务器也无从联系客户端。
websocket 会先在客户端和服务端中建立一个管道连接,之后双方都可以在这个连接里交流通信
如果想要请求ComfyUI生图,虽然可以直接http 去请求,但缺点是,我们无法拿到生成完的结果
因此我们需要websocket来在最初和comfyUI服务器建立一个双向连接,其最大的作用是在我们提交生图任务后让服务器能够联系上我们,从而将生图的进度实时汇报给我们听,便于我们在收到生图完成的消息后去取我们要的结果图。
所以在一开始我们要和服务器建立一个websocket连接 ,参数是一个client id( uuid生成本次websocket连接的client id 识别客户端的唯一标识符。通过在连接URL中传递client_id,服务器可以凭此来识别不同的客户端并根据其需要进行个性化的操作或处理,这种方法也可以用于身份验证或其他目的。)

之后我们来组装 想要让comfyUI处理的具体任务对应的json。在comfyui的ui界面中,我们都知道有workflow的概念。大家搭好工作流,可以到处json 便于分享和协作。而我们开发也会用到,只不过保存时是点击有后缀的 save (api format)的按钮,这样我们就可以拿到让comfyui去处理的任务说明

api需要开启dev 模式


这里用最基础的生图工作流给大家演示

在节点中通常包含多个参数,如果其中有些参数的值需要从其他节点获取,则这些参数的结构可以通过以下方式表达:参数的键是这个参数的名字,值是一个数组,数组的第一个值是传递这个参数值的来源节点序号 "n",第二个值是来源节点中该参数值的顺序位置,从上往下从0开始计数。
直接讲还是比较抽象,那这个默认工作流和json 中的 ksample 采样器节点举例
采样器节点 在json中排第一个 序号为 “3” ,我们看它有个model 参数 model的值是一个数组 ["4",0] 什么意思呢? 首先model这个参数的作用是指定 我们用哪个模型去生图,这里是darksushi 。 数组里的第一个元素 “4”是说我们要去 序号为 ”4“的节点里,那里可以找到我们要用哪个模型去生图。我们去到4节点看 ,发现4节点中的input 字段 确实指明了 darksushi ,但程序可不会去4节点慢慢找,所以需要我们告诉他model字段在4 节点中具体位置在哪, 所以 ["4",0] 中的 0就是告诉程序可以在 4节点最开头的字段里找到想要的模型名 。
{
"3": {
"inputs": {
"seed": 1050010303642150,
"steps": 20,
"cfg": 8,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1,
"model": [
"4",
0
],
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"latent_image": [
"5",
0
]
},
"class_type": "KSampler",
"_meta": {
"title": "KSampler"
}
},
"4": {
"inputs": {
"ckpt_name": "darkSushiMixMix_brighterPruned.safetensors"
},
"class_type": "CheckpointLoaderSimple",
"_meta": {
"title": "Load Checkpoint"
}
},
"5": {
"inputs": {
"width": 512,
"height": 512,
"batch_size": 1
},
"class_type": "EmptyLatentImage",
"_meta": {
"title": "Empty Latent Image"
}
},
"6": {
"inputs": {
"text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
"7": {
"inputs": {
"text": "text, watermark",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"4",
2
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE Decode"
}
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "Save Image"
}
}
}
就这样,我们大致了解了comfyui里workflow和调用接口时 实际给到程序的json之间的对应关系 ,这样我们就可以去根据需求在workflow的基础上去 抽象 封装 把其中某些参数暴露,其他的封装隐藏起来,到达自己想要的目的了。
在组装我们想要让comfyUI处理的具体任务对应的json表示后,我们就可以去提交我们的生图任务了。 把json读取加上 client id 去调用 prompt 接口

注意 请求并不会返回本次生成的图结果 只会返回本次任务的ID和任务序号数字 以及报错信息 ex: {'prompt_id': '4b25f6ca-3dc0-41b6-8177-de694bcf8a91', 'number': 49, 'node_errors': {}}
而返回里的 prompt_id用于后面追踪这次生成的全生命周期的唯一凭证
我们可以通过浏览器中监听comfyui网页的网络请求来进一步分析



通过观察,我们大致了解了comfyui会一致将信息实时返回 ,对应到我们自己写后端代码对接,我们就需要在发起生图请求后主动关注服务器进度,让服务器将生图的进度实时汇报给我们听,便于我们在收到生图完成的消息后去取我们要的结果图。

如果type是 executing,说明正在进行生图. 会一个节点一个节点的执行
当node执行完了 最后一次值会是none
此时代表生成完成,可以去到ComfyUI取生成好的图片了 {"type": "executing", "data": {"node": null, "prompt_id": "c4c469aa-d5ac-4088-a3f2-ad11e88630a7"}}
通过prompt_id ,我们去调用 history 接口查询关于本次生图信息,最关键的是拿到生成结果图存放的位置

知道结果图在comfyui部署的服务器的位置和文件名后,就可以通过 view 接口获取结果图了

先写到这,可能涉及到很多前置知识,不懂的朋友也不用担心,之后会补充。下一期准备讲讲如何在comfyUI中开发插件节点,让大语言模型也塞到comfyui中,做到多模态融合,敬请期待