AI活用

Open WebUIからComfyUIを呼び出す方法|チャットに画像生成Toolを追加する

Open WebUIのチャットからComfyUIで画像を作るには、現行のToolsで固定workflowを呼ぶ関数を1つ作り、引数をprompt・size・seedに絞ります。Pipelinesとの違い、最小Toolのコード、引数検証、画像の返し方、第三者Toolの安全性を解説します。

この記事の目次
  1. 結論:Toolは「固定workflow+小さな引数」に限定し、LLMにはJSONを触らせない
  2. 現在の拡張方式はどう整理されているか
  3. 最小Toolをどう設計するか
  4. 引数Schemaはどこまで小さくするか
  5. チャットから実行するとどう流れるか
  6. 画像結果を会話にどう返すか
  7. Toolの安全性をどう確保するか
  8. よくある質問
  9. まとめ

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に入るだけ
  • widthheight:値域と倍数で検証できる
  • 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. 1
    Open WebUIがTool定義をモデルに渡す

    型ヒントとdocstringから作られたJSON Schemaが、会話と一緒にモデルに送られる

  2. 2
    モデルがtool callを返す

    generate_image{"prompt": "a cat", ...}で呼びたい」という構造化された応答。現行はモデル側の関数呼び出し機能を使うNativeモードが既定

  3. 3
    Open WebUIが関数を実行する

    検証→workflow差し替え→POST /prompt。ここが唯一ComfyUIに触る場所

  4. 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からだけ到達できる位置に置きます。

スポンサーリンク