基本的にやっていること
API経由で、ComfyUIのworkflowを「外から」実行してみます。
やることはシンプルで、API形式JSON(= prompt)を http://127.0.0.1:8188/prompt にPOSTする だけです。
これでComfyUIサーバは「そのworkflowを実行して」と命令を受け取り、キューに積んで実行し、prompt_id(実行ID)を返します。
少しややこしいのですが、ここで出てくる「prompt」はテキストプロンプトではなく、ワークフロー全体(実行グラフ) のことです。
テキストはその一部で、たとえば CLIPTextEncode の inputs.text などが該当します。
今回は、Pythonから実行してみましょう。
ComfyUIを起動しておく
APIは「ComfyUIを起動しなくてよくなる仕組み」ではありません。
Pythonは、起動中のComfyUIサーバに対して命令を送ります。
- ComfyUIを起動しておきます
- ブラウザで
http://127.0.0.1:8188 を開ける状態にします
API用のworkflowを用意する
今回使うworkflow(SD1.5 text2image)
ひとまず、最もシンプルなStable Diffusion 1.5のtext2imageを使います。
SD1.5_text2image_vae-ft-mse-840000.json
{
"id": "8b9f7796-0873-4025-be3c-0f997f67f866",
"revision": 0,
"last_node_id": 10,
"last_link_id": 10,
"nodes": [
{
"id": 8,
"type": "VAEDecode",
"pos": [
1209,
188
],
"size": [
210,
46
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 10
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"slot_index": 0,
"links": [
9
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.33",
"Node name for S&R": "VAEDecode"
},
"widgets_values": []
},
{
"id": 3,
"type": "KSampler",
"pos": [
863,
186
],
"size": [
315,
262
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 1
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 4
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"slot_index": 0,
"links": [
7
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.33",
"Node name for S&R": "KSampler"
},
"widgets_values": [
1234,
"fixed",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1451,
189
],
"size": [
354.2876035004722,
433.23967321788405
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.33"
},
"widgets_values": [
"ComfyUI"
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
415,
186
],
"size": [
411.95503173828126,
151.0030493164063
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 3
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"slot_index": 0,
"links": [
4
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.33",
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
416.1970166015625,
392.37848510742185
],
"size": [
410.75801513671877,
158.82607910156253
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 5
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"slot_index": 0,
"links": [
6
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.33",
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 5,
"type": "EmptyLatentImage",
"pos": [
582.1350317382813,
606.5799999999999
],
"size": [
244.81999999999994,
106
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"slot_index": 0,
"links": [
2
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.33",
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 10,
"type": "VAELoader",
"pos": [
896.9256198347109,
69.4815990090115
],
"size": [
281.0743801652891,
58
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "VAE",
"type": "VAE",
"links": [
10
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.76",
"Node name for S&R": "VAELoader"
},
"widgets_values": [
"vae-ft-mse-840000-ema-pruned.safetensors"
],
"color": "#322",
"bgcolor": "#533"
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
38.10000000000001,
363.8900000000004
],
"size": [
315,
98
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"slot_index": 0,
"links": [
1
]
},
{
"name": "CLIP",
"type": "CLIP",
"slot_index": 1,
"links": [
3,
5
]
},
{
"name": "VAE",
"type": "VAE",
"slot_index": 2,
"links": []
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.33",
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"v1-5-pruned-emaonly-fp16.safetensors"
]
}
],
"links": [
[
1,
4,
0,
3,
0,
"MODEL"
],
[
2,
5,
0,
3,
3,
"LATENT"
],
[
3,
4,
1,
6,
0,
"CLIP"
],
[
4,
6,
0,
3,
1,
"CONDITIONING"
],
[
5,
4,
1,
7,
0,
"CLIP"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
7,
3,
0,
8,
0,
"LATENT"
],
[
9,
8,
0,
9,
0,
"IMAGE"
],
[
10,
10,
0,
8,
1,
"VAE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.9090909090909091,
"offset": [
61.89999999999999,
30.518400990988496
]
},
"frontendVersion": "1.34.6",
"VHS_latentpreview": false,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
モデルのダウンロード
📂ComfyUI/
└── 📂models/
├── 📂checkpoints/
│ └── v1-5-pruned-emaonly-fp16.safetensors
└── 📂vae/
└── vae-ft-mse-840000-ema-pruned.safetensors
API用のworkflowを取得
-
- ComfyUIのノードUIで、上のworkflowを開く
-
- メニューから
File → Export (API) を選ぶ
-
- わかりやすい名前で保存(例:SD1.5_text2image_API.json)
サンプル
SD1.5_text2image_API.json
{
"3": {
"inputs": {
"seed": 1234,
"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": "v1-5-pruned-emaonly-fp16.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": [
"10",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE Decode"
}
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "Save Image"
}
},
"10": {
"inputs": {
"vae_name": "vae-ft-mse-840000-ema-pruned.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "Load VAE"
}
}
}
通常のworkflow JSONとの違い
通常のJSONは「UIで編集・共有しやすい情報」も含んでいます。
APIのworkflow は、そのような余分な情報を削ぎ落とし、サーバに渡して実行するのに都合が良い形になっています。
まず動かす
Python環境準備
仮想環境を作り、HTTP通信用に requests を入れます。
python -m venv venv
venv\Scripts\activate
pip install requests
run_min.py
SD1.5_text2image_API.json を読み込み、/prompt に投げるだけの最小コードです。
ノードUIで ▷Run を押すのと、基本的には同じです。
import json
import requests
BASE = "http://127.0.0.1:8188"
prompt = json.load(open("SD1.5_text2image_API.json", encoding="utf-8"))
res = requests.post(f"{BASE}/prompt", json={"prompt": prompt}).json()
print(res)
ファイル配置
your_project/
├── run_min.py
└── SD1.5_text2image_API.json
実行する
ターミナルで run_min.py を実行します。
cd path\to\your_project
venv\Scripts\activate
python run_min.py
実行できたか確認してみましょう。
- Python側で
{"prompt_id": "...", ...} が返ってくる(これが実行IDです。)
- ComfyUI側のターミナルに実行ログが出る
ComfyUI/output/ に画像ファイルが増える
- ノードUIで実行するときと同じ場所に保存されているはずです
ここまでできれば、API経由で実行できています。
CLIからプロンプトを変える
API用JSONを直接編集しても、もちろんパラメータは変更できます。
ただし、元ファイルを汚さずに連続実行したいなら Pythonで差し替える ほうが扱いやすいです。
どこを書き換えるのか(今回のJSONの場合)
今回は「プロンプトだけ」を変更します。
- Positive prompt:ノード
6 の inputs.text
注:ノードIDは固定ではない
このページの例では 6 を使いますが、これは この配布JSONの中でそうなっているだけです。
自分のworkflowでやる場合は、JSONを開いて以下を探すのが確実です。
e.g. SD1.5_text2image_API.json
"6": {
"inputs": {
"text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
run.py(プロンプト入力)
import json
import requests
BASE = "http://127.0.0.1:8188"
prompt = json.load(open("SD1.5_text2image_API.json", encoding="utf-8"))
pos = input("positive prompt: ").strip()
if pos:
prompt["6"]["inputs"]["text"] = pos
res = requests.post(f"{BASE}/prompt", json={"prompt": prompt}).json()
print(res)
print("done (check ComfyUI/output)")
実行する
先ほどと同様に実行します。
cd path\to\your_project
venv\Scripts\activate
python run.py
今回は途中でパラメータの入力を求められます。
positive prompt: 好きなプロンプトを入力
そのプロンプトで画像が ComfyUI/output/ に保存されていればOKです。
雰囲気が掴めたら、あとは作るだけ
今回は「実行する」だけのAPIしか使っていませんが、APIには他にも色々あります。
- 進捗をリアルタイムに受け取る(WebSocket)
- キューの制御(停止・割り込みなど)
- 画像アップロード(i2iの入力に渡す)
- ノードの入力仕様の取得(workflow編集を自動化する足がかり)
このページでは「外からworkflowを実行できる」雰囲気さえ掴めれば十分です。
あとはvibe codingでも何でも、欲しいものを実際に作ってみてください!