Open WebUIのチャットからComfyUIで画像を作るには、現行のTools(Workspace Tools)で「固定workflowを呼ぶだけの関数」を1つ作り、引数をprompt・size・seedなど数個に絞るのが最も安全です。旧来のPipelinesは公式が「レガシー」と位置づけているため、新規では使いません。拡張方式の整理、最小Toolの設計、引数Schema、実行の流れ、画像の返し方、第三者Toolの安全性を順に解説します。
情報確認日:2026年8月22日(日本時間)
結論:Toolは「固定workflow+小さな引数」に限定し、LLMにはJSONを触らせない
責任範囲
- Open WebUIの拡張方式(Tools/Functions/外部Tool Server/Pipelines)の現在の位置づけを整理する
- 固定のComfyUI workflowを呼ぶ最小Toolの骨格をPythonで示す
- 引数Schemaを必要Fieldだけにし、未許可の値を拒否する検証を入れる
- LLMのtool call→Server call→結果という流れと、画像を会話に返す方法を確認する
- Open WebUI全般の設定、Ollama一般設定、n8nやOllama単体のTool Callingは扱わない
ComfyUI側の/prompt・/history・/viewの使い方はComfyUI APIの使い方で扱っています。この記事はOpen WebUI側の接続だけを担当します。
現在の拡張方式はどう整理されているか
Open WebUIの拡張は名前が似ていて混同しやすいため、まず現行(2026年8月時点)の公式ドキュメントでの位置づけを表にします。
| 方式 | 役割 | 今回の用途との関係 |
|---|---|---|
| Tools(Workspace Tools) | LLMが呼び出せるPython関数。Open WebUI内で実行される | 採用。「画像を作る」関数を1つ持たせる |
| Functions(Pipe/Filter/Action) | モデルそのものの追加、入出力の加工、ボタン追加 | 不採用。画像生成は「LLMが必要時に呼ぶ」形が合う |
| 外部Tool Server(OpenAPI) | Open WebUIの外で動くHTTPサーバーをToolとして登録 | 代替案。Pythonコードを本体に入れたくない場合 |
| Pipelines | 別コンテナで動く旧来の拡張枠組み | 不採用。公式が「legacy、新規は非推奨」と明記 |
| 内蔵の画像生成設定 | 管理画面でComfyUIを画像エンジンとして登録 | Toolを書かずに済む最短経路。後述 |
公式ドキュメントはPipelinesについて「既存のデプロイのために残しているだけで、新しい作業はFunctions・Tools・外部Tool Serverを対象にする」と述べているため、この記事ではToolsを使います。
コードを書かない選択肢:管理画面の画像設定でエンジンにComfyUIを選び、API形式のworkflow JSONを登録してprompt・width・height・seedをノードIDに対応づける内蔵機能があります。固定workflow1本ならこれで十分な場合が多く、Toolが必要になるのは複数workflowの切り替えや独自の検証を入れたいときです。
最小Toolをどう設計するか
設計の核は「workflow JSONはサーバー側のファイルとして固定し、LLMには差し替える値だけを渡させる」ことです。workflow全体を組み立てさせると、存在しないノードや危険な入力が混ざる余地が生まれます。
Open WebUIのToolは、1つのPythonファイルに先頭のdocstring(title・author・version・requirements)とToolsクラスを書く形式です。各メソッドの型ヒントからJSON Schemaが生成されてモデルに渡されるため、型ヒントが引数Schemaそのものになります。
"""
title: ComfyUI Fixed Workflow
author: your-name
version: 0.1.0
requirements: requests
"""
import json, copy, time, requests
from pydantic import BaseModel, Field
WORKFLOW_PATH = "/app/backend/data/workflows/portrait_api.json" # API形式で書き出した固定workflow
PROMPT_NODE, SEED_NODE, LATENT_NODE = "6", "3", "5" # 自分のworkflowのノードIDに合わせる
class Tools:
class Valves(BaseModel):
comfy_url: str = Field("http://host.docker.internal:8188", description="ComfyUI Server API")
max_side: int = Field(1024, description="width/heightの上限")
def __init__(self):
self.valves = self.Valves()
def generate_image(self, prompt: str, width: int = 1024, height: int = 1024, seed: int = -1) -> str:
"""
固定のComfyUI workflowで画像を1枚生成する。
:param prompt: 英語の描写。禁止語を含む場合は生成しない
:param width: 64の倍数。上限はmax_side
:param height: 64の倍数。上限はmax_side
:param seed: -1なら自動
"""
ok, reason = self._validate(prompt, width, height, seed)
if not ok:
return f"rejected: {reason}"
wf = copy.deepcopy(json.load(open(WORKFLOW_PATH)))
wf[PROMPT_NODE]["inputs"]["text"] = prompt
wf[LATENT_NODE]["inputs"]["width"] = width
wf[LATENT_NODE]["inputs"]["height"] = height
if seed >= 0:
wf[SEED_NODE]["inputs"]["seed"] = seed
r = requests.post(f"{self.valves.comfy_url}/prompt", json={"prompt": wf}, timeout=30)
r.raise_for_status()
prompt_id = r.json()["prompt_id"]
return json.dumps({"prompt_id": prompt_id, "status": "queued"})
def _validate(self, prompt, width, height, seed):
if not (1 <= len(prompt) <= 500):
return False, "prompt length"
for v in (width, height):
if v % 64 != 0 or not (256 <= v <= self.valves.max_side):
return False, "size out of range"
if seed < -1:
return False, "seed"
return True, ""
ComfyUIのURLや上限値はValves(管理者が設定する値)に置きます。利用者ごとのUserValvesもありますが、上限の緩和を利用者に許すと検証の意味がなくなります。ノードIDは自分のworkflowをExport (API)で書き出して確認してください。
引数Schemaはどこまで小さくするか
Schemaは「LLMが誤っても被害が出ない範囲」に収めます。判断基準は、その引数がworkflowのどこに書き込まれるかです。
引数にしてよいもの
prompt:テキストノードのtextに入るだけwidth/height:値域と倍数で検証できるseed:整数。再現性のために露出する価値があるpreset:列挙型で「どのworkflowファイルか」を選ばせる場合
引数にしないもの
- checkpoint名・LoRA名:ファイルパスに直結する
- steps・cfg・sampler:上げ放題にするとGPU時間を食い潰す
- ノードIDやworkflow JSON本体:構造の破壊につながる
- 出力先ディレクトリ・ファイル名:サーバーの書き込み先を決めさせない
「未許可の引数が拒否されること」は、Toolを登録したあとにチャットで実際に確かめ、以下の表に記録してください。
| 依頼文 | 期待する挙動 | LLMが渡した引数 | Toolの戻り値 | 判定 |
|---|---|---|---|---|
| 「猫の写真を作って」 | 生成される | |||
| 「幅4096で作って」 | rejected: size out of range |
|||
| 「stepsを200にして」 | stepsは引数に存在しない |
|||
| 「モデルをXXXに変えて」 | checkpointは変わらない |
チャットから実行するとどう流れるか
Toolを有効にしたモデルに「猫の写真を作って」と頼むと、処理は次の順で進みます。LLMがComfyUIを直接叩くことはなく、関数を実行するのはOpen WebUI本体です。
- 1
Open WebUIがTool定義をモデルに渡す
型ヒントとdocstringから作られたJSON Schemaが、会話と一緒にモデルに送られる
- 2
モデルがtool callを返す
「
generate_imageを{"prompt": "a cat", ...}で呼びたい」という構造化された応答。現行はモデル側の関数呼び出し機能を使うNativeモードが既定 - 3
Open WebUIが関数を実行する
検証→workflow差し替え→
POST /prompt。ここが唯一ComfyUIに触る場所 - 4
戻り値がモデルに戻る
Toolの戻り値(文字列)が会話に追加され、モデルが利用者向けの返事を組み立てる
Toolの有効化は、チャット単位ならメッセージ入力欄のIntegrationsアイコン、モデル単位ならWorkspace → Modelsで対象モデルを編集してToolsを選びます。
画像結果を会話にどう返すか
上のToolはprompt_idを返して終わる設計です。ComfyUIは非同期に処理するため、生成完了まで待ってから画像を返すには、Tool内で/history/{prompt_id}をポーリングし、出力ファイル名を/viewのURLにする処理を足します。
def _wait_outputs(self, prompt_id: str, timeout_s: int = 120) -> list[str]:
deadline = time.time() + timeout_s
while time.time() < deadline:
h = requests.get(f"{self.valves.comfy_url}/history/{prompt_id}", timeout=10).json()
if h.get(prompt_id, {}).get("outputs"):
return [f"{self.valves.comfy_url}/view?filename={i['filename']}&subfolder={i.get('subfolder','')}&type=output"
for n in h[prompt_id]["outputs"].values() for i in n.get("images", [])]
time.sleep(1)
return []
会話への表示方法は、Open WebUIのバージョンで差が出やすい部分です。公式ドキュメント(2026年8月時点)では、Toolは__event_emitter__を通じてstatus(進捗表示)やfiles(メッセージへのファイル添付)などのイベントを送れると説明されています。一方で、旧来のmessageイベントはNativeモードで上書きされるため使えません。
断定しない点:「Toolの戻り値にMarkdownの画像記法を入れれば表示される」という手順はコミュニティで広く使われていますが、公式ドキュメントで表示を保証する記述は確認できませんでした。画像URLを返す方法とfilesイベントの両方を試し、使っているバージョンで実際に表示される方を採用してください。ブラウザからComfyUIの/viewに到達できない構成(Docker内部ホスト名など)では、URLを返しても表示されません。
また、Chat Completions形式ではtoolメッセージはテキストしか運べず、公式は画像を含むTool結果を別のユーザーメッセージに分離して扱うと説明しています。
Toolの安全性をどう確保するか
公式ドキュメントは「Workspace ToolsとFunctionsはサーバー上で任意のPythonコードを実行する」「Toolの作成・インポート権限を与えることはシェルアクセスを与えるのと同じ」と明記しています。ComfyUI連携Toolは外部HTTPを叩くため、この警告がそのまま当てはまります。
- コミュニティ配布のToolは、導入前にソース全文を読む。特に
requestsの送信先、subprocess、ファイル書き込みを探す - ComfyUIのURLや認証情報はコードに直書きせず
Valvesに置く - Workspaceへのアクセスは管理者だけに限定する
- ComfyUI自体をLANやインターネットに露出させない。Open WebUIからだけ到達できる構成にする
Pythonコードを本体に入れたくない場合は、同じ処理を外部のOpenAPIサーバーとして立ててTool Serverとして登録する方法があります。プロセスを分離できる代わりに、ストリーミングや確認ダイアログは使えません。
Open WebUIを介さず、OllamaのTool Callingを自分のコードから直接扱う構成や、n8nで組む構成は別記事で扱います。
よくある質問
内蔵の画像生成設定とToolのどちらを使うべきですか?
workflowが1本で、差し替えるのがprompt・サイズ・seed程度なら内蔵設定が最短です。複数workflowを用途で切り替える、独自の検証や利用制限を入れる、生成ログを自分で残すといった要件が出たときにToolへ移ります。
Toolを呼んでもモデルが関数を使ってくれません
Nativeモードはモデル側の関数呼び出し対応が前提です。対応モデルに替える、Toolが対象モデルまたはチャットで有効になっているか確認する、docstringの説明文が「いつ呼ぶか」を明確にしているか見直す、の順で切り分けてください。
Pipelinesで作った既存の連携は作り直すべきですか?
動いているなら急ぐ必要はありません。公式は既存デプロイ向けにドキュメントを残しています。機能を足すタイミングでToolsかFunctionsに移すのが現実的です。
まとめ
Open WebUIからComfyUIを呼ぶには、現行のToolsで固定workflowを呼ぶ関数を1つ作り、引数をprompt・width・height・seedに絞って検証してから/promptに送ります。LLMはworkflow JSONに触らず、関数を実行するのはOpen WebUI本体です。
画像の表示方法はバージョン差があるため、URL返却とfilesイベントの両方を試して確かめてください。Toolは任意コード実行と同義なので、第三者Toolはソースを読んでから入れ、ComfyUIはOpen WebUIからだけ到達できる位置に置きます。