ComfyUIはGUIで触っているだけでもとても強力ですが、実運用で特に頼もしいのは CLI・サーバー・ヘッドレス環境 での活用です。
たとえば、こんな場面です。
- GPUサーバー上でバッチ生成したい
- WebアプリやBotの裏側からComfyUIを呼び出したい
- 同じワークフローをプロンプトだけ変えて何百回も回したい
- プロンプト / seed / steps / cfg / width / height などのパラメータを外部から差し替えたい
そこで本記事では、自作ツールを題材に、普段GUIで作った既存ワークフローをそのまま取り込み、CLIからプロンプトや各種パラメータを動的に上書きして再利用する ところまで解説します。
解説に使用するツールはGithubで公開しています。 利用、改変OKです。
ComfyUIは「ノードUI」でもあり「生成サーバー」でもある
ComfyUIはブラウザUIの印象が強いのですが、実体はPython製の生成サーバーです。
ComfyUIを起動すると、通常は 8188 番ポートでHTTP APIとWebSocketが利用できるようになります。つまり、ブラウザから操作する代わりに、外部のPythonスクリプトやCLIツールからワークフローを渡して生成を実行する ことができます。
この構成を取ると、ComfyUIで次のようなことができます。
- ローカルPCではなくGPUサーバー上で生成
- 生成指示だけを別プロセス・別マシンから送信
- 進捗をWebSocketで監視
ヘッドレスでの通信はどう書くの?
ヘッドレス運用といっても、やること自体はそこまで難しくありません。 基本は HTTPでワークフローをキューに積み、WebSocketで進捗を受け取る だけです。
たとえば、最小構成のイメージは次のようになります。
import json
import uuid
import asyncio
import requests
import websockets
COMFYUI_API_URL = "http://127.0.0.1:8188"
CLIENT_ID = str(uuid.uuid4())
workflow = {
"3": {
"class_type": "KSampler",
"inputs": {
"seed": 123456,
"steps": 20,
"cfg": 7.0,
"sampler_name": "euler",
"scheduler": "normal",
"model": ["4", 0],
"positive": ["6", 0],
"negative": ["7", 0],
"latent_image": ["5", 0],
},
}
}
async def main():
payload = {"prompt": workflow, "client_id": CLIENT_ID}
response = requests.post(f"{COMFYUI_API_URL}/prompt", json=payload, timeout=30)
response.raise_for_status()
prompt_id = response.json()["prompt_id"]
ws_url = f"ws://127.0.0.1:8188/ws?clientId={CLIENT_ID}"
async with websockets.connect(ws_url) as websocket:
async for message in websocket:
event = json.loads(message)
if event.get("type") == "progress":
data = event.get("data", {})
print(f"progress: {data.get('value')} / {data.get('max')}")
if event.get("type") == "executing":
data = event.get("data", {})
if data.get("prompt_id") == prompt_id and data.get("node") is None:
print("generation finished")
break
asyncio.run(main())
このサンプルでは、まず /prompt にワークフローをPOSTして生成キューへ積み、そのあと ws に接続して進捗イベントを受け取っています。
実運用では、ここにさらに次の処理を足していく形になります。
- UI形式ワークフローのAPI形式への変換
- プロンプトや
seed/stepsの動的な上書き - 完成ファイルのダウンロード
- エラーハンドリングやタイムアウト管理
API運用で大事なのは「既存ワークフローの再利用」
ComfyUIをAPIで使うだけなら、技術的にはそこまで難しくありません。 しかし、普段ComfyUIのGUIで作っているワークフローを、実運用に載せようとすると、次のような壁にぶつかりがちです。
seedやsteps、プロンプトを実行ごとに変えたいがワークフローを投げるだけだと固定化される- 既存ワークフローが肥大化してパラメータが増大し、量産・自動化の資産として使い回しづらい
- GUIで保存したJSONをそのままAPIに投げても動かない
実際の利用においては 既存ワークフローを一度取り込めば、その後はCLIからプロンプトや主要パラメータを差し替えながら使い回せる ことが必要です。
言い換えると、GUIでの試行錯誤を、そのままサーバー運用や自動生成の資産として活かせます。
ツールの全体像
本ツールの流れはとてもシンプルです。
Step 1. ワークフローを登録する
まず、ComfyUIで作った既存のワークフローJSONを取り込みます。
- UI形式ならAPI形式へ変換
- ワークフローごとのメタ情報を保存
- プロンプトノード候補も検出
Step 2. 生成時に値を注入する
生成時には、保存済みワークフローに対して次を上書きします。
- positive prompt
- negative prompt
seedstepscfg- 必要に応じて
node_id:field形式の直接指定
Step 3. ComfyUI APIへ投入する
準備済みのワークフローをComfyUIに送り、WebSocketで進捗を受け取りながら、完成した画像や動画を保存します。
CLI実行例
リポジトリにはZimageTurboの生成サンプルもありますので参考にしてみてください。
1. 既存のComfyUIワークフローを取り込む
python comfyui_cli.py convert \
--input "C:/path/to/workflow.json" \
--workflow-id my_workflow \
--name "My Workflow"
このコマンドで、既存のワークフローを comfyui_workflows/my_workflow/ に保存します。
ポイントは、入力がUI形式でもたいていはそのまま受け付けられる ことです。 毎回API形式で保存し直す前提にしなくてよいので、GUIで作ったワークフローをそのまま運用へ持ち込みやすくなります。
とはいえまだノードによっては動かなかったり、調整の余地があるので上手くいかない場合はAPI形式のJSONを読み込ませてください
2. プロンプトとパラメータをCLIから差し替えて生成
python comfyui_cli.py generate \
--workflow-id my_workflow \
--prompt "a futuristic city at night, neon lights, cinematic, masterpiece" \
--negative "low quality, blurry, text, watermark" \
このとき行っていることは、単なる固定JSONの送信ではありません。
- 保存済みワークフローを読み込む
- プロンプトノードを自動検出して差し替える
ということをしています。 つまり、ComfyUIのGUIで作ったワークフローを、CLIから再利用している わけです。
3. 特定ノードの値をピンポイントで上書き
python comfyui_cli.py generate \
--workflow-id my_workflow \
--prompt "product photo, white background, studio lighting" \
--param seed=123456789 \
--param steps=28 \
--param cfg=7.0
--param 5:width=1024 \
--param 5:height=1024
node_id:field=value で直接paramを指定できます。
プロンプトノードを自動で見つける
CLI化で困りやすいのは、どのノードにプロンプトを入れればよいかが毎回違うことです。
本ツールでは core/comfyui_generator.py の PromptInjector.find_prompt_nodes() で、KSamplerから逆引きする形でプロンプトノードを見つけています。
@staticmethod
def find_prompt_nodes(workflow):
result = {"positive": None, "negative": None}
for node_id, node_data in workflow.items():
if node_data.get("class_type") not in KSAMPLER_CLASS_TYPES:
continue
inputs = node_data.get("inputs", {})
pos_ref = inputs.get("positive")
if isinstance(pos_ref, list) and pos_ref:
ref_id = str(pos_ref[0])
if workflow[ref_id].get("class_type") in CLIP_TEXT_ENCODE_TYPES:
result["positive"] = ref_id
neg_ref = inputs.get("negative")
if isinstance(neg_ref, list) and neg_ref:
ref_id = str(neg_ref[0])
if workflow[ref_id].get("class_type") in CLIP_TEXT_ENCODE_TYPES:
result["negative"] = ref_id
if result["positive"] or result["negative"]:
break
return result
この方式の利点はとても分かりやすいです。
- ノードIDを人間が覚えなくてよい
- ワークフローごとに違う構造へある程度追従できる
- CLI利用者が
--promptだけ指定すれば済む
つまり、ワークフロー内部構造の知識をCLIの利用者に強く求めない 設計になっています。
UI形式ワークフローをAPI形式へ変換してみる
一般的に技術記事や画像メタデータに含まれるJSONデータはGUI形式で共有されるので、なるべきそのまま使えるようにGUI由来のJSONをAPI実行可能な形へ変換すること を試してみました。
コアは core/comfyui_generator.py の WorkflowLoader.convert_ui_to_api() です。
@staticmethod
def convert_ui_to_api(ui_data, object_info=None):
nodes = ui_data.get("nodes", [])
links_list = ui_data.get("links", [])
link_map = {}
for link in links_list:
link_map[link[0]] = [str(link[1]), link[2]]
api_workflow = {}
for node in nodes:
node_id = str(node["id"])
class_type = node.get("type", "")
if class_type in {"Note", "MarkdownNote", "Reroute", "PrimitiveNode"}:
continue
api_inputs = {}
for inp in node.get("inputs", []):
name = inp.get("name", "")
link_id = inp.get("link")
if link_id is not None and link_id in link_map:
api_inputs[name] = list(link_map[link_id])
api_workflow[node_id] = {
"class_type": class_type,
"inputs": api_inputs,
}
1. linksを辿ってノード間接続を再構築する
ComfyUIのUI形式JSONでは、どの出力がどの入力に繋がっているかが links 配列で表現されています。
API形式では、これを ノードIDベースの inputs 辞書 に組み直す必要があります。
2. 表示専用ノードを落とす
Note や Reroute のような見た目補助ノードは、API実行には不要です。
これらをそのまま持ち込むと失敗の原因になりやすいため、変換段階で除外しています。
3. widget値も入力へ写像する
実装では widgets_values と object_info を使って、リンクでは渡されない数値や文字列入力も inputs に落とし込んでいます。
これにより、GUI用の保存JSONを、CLIやサーバーから実行できるJSONへ変換 できます。
seed や steps`を動的に注入する
seed や steps を柔軟に変えたり必要に応じて固定できたりすると嬉しいですよね。
その処理は PromptInjector.inject() にまとまっています。
@staticmethod
def inject(workflow, positive_prompt=None, negative_prompt=None, meta=None, extra_params=None):
wf = copy.deepcopy(workflow)
detected = PromptInjector.find_prompt_nodes(wf)
pos_node_id = detected["positive"]
neg_node_id = detected["negative"]
if positive_prompt is not None and pos_node_id and pos_node_id in wf:
wf[pos_node_id]["inputs"]["text"] = positive_prompt
if negative_prompt is not None and neg_node_id and neg_node_id in wf:
wf[neg_node_id]["inputs"]["text"] = negative_prompt
if extra_params:
for key, value in extra_params.items():
if ":" in key:
node_id, field_name = key.split(":", 1)
if node_id in wf:
wf[node_id]["inputs"][field_name] = value
continue
for node_id, node_data in wf.items():
if node_data.get("class_type") in KSAMPLER_CLASS_TYPES:
if key in node_data.get("inputs", {}):
wf[node_id]["inputs"][key] = value
break
return wf
ここで対応している上書き方法は、主に3種類あります。
1. プロンプトの自動差し替え
- positive prompt
- negative prompt
2. 汎用パラメータ名での上書き
たとえば steps=30 や cfg=7.5 を指定すると、KSampler系ノードから該当フィールドを探して注入してくれます。
3. node_id:field形式の直接指定
ワークフローごとに個別の入力を触りたい場合は、たとえば 5:width=1024 のように直接指定できます。
この3段構えにしているおかげで、
- よく使う操作は簡単に
- 変則的なワークフローにも対応可能に
というバランスにしています。
まとめ
ComfyUIをCLI・サーバー・ヘッドレスで使うこと自体は、APIを叩けば比較的簡単に実現できます。 でも、実運用で本当に価値が出る必要なのはその先です。
既存ワークフローをそのまま取り込み、CLIからプロンプトや主要パラメータを差し替えて再利用できること。 ここが整うと、ComfyUIは単なるGUIツールではなく、運用しやすい生成基盤になってくれます。
今回のツールで押さえているポイントは次の通りです。
- ComfyUIをサーバーとして使う前提で設計している
- 既存ワークフローを取り込み、必要に応じてUI形式からAPI形式へ変換できる
- プロンプトノードを自動検出し、CLIから自然に上書きできる
seed/steps/cfgなどを動的に注入できる- 進捗監視とファイル保存まで一連で扱える
ComfyUIを資産を生かして自動化したいなら、重要なのは「APIで呼べること」そのものではなく、GUIで作ったワークフローを運用で使い回せること ですね。