やってみた 2026年10月12日

Open WebUIをpipで入れて1通送ったら、裏でLLMへの要求が4本飛んでいた

XECIN PythonOpen WebUILLMセルフホスト

GitHubのトレンドに open-webui/open-webui が上がっていました。スターは154,419、今日増えた分は142。2026-10-10(UTC)に v0.12.0 が出たばかりです。

ここ最近、「社内向けに生成AIのチャット窓口を立てたい」という相談をよく受けます。候補に必ず名前が挙がるのがOpen WebUIなのですが、私は見積もりの前に一つ知っておきたいことがありました。利用者が1通送ったとき、裏でLLMに何本、どれだけの量の要求が飛ぶのか。従量課金のAPIにつなぐなら、ここがそのまま月々の費用になります。

そこで、本物のLLMの代わりに「届いた要求をすべて保存するだけのモック」をつないで、1通送って数えてみました。

試した環境

  • 使い捨てのDockerコンテナ(python:3.12-bookworm、Debian 12.14、Python 3.12.13)。メモリ4GB・CPU 2コアに制限し、終わったら破棄
  • Open WebUI 0.12.0(PyPIの open-webui==0.12.0、main は f8ae8a6c32)
  • 入れ方はREADMEの「Installation via Python pip」どおり、pip install open-webui → open-webui serve
  • LLMは、コンテナの中に置いた自作のOpenAI互換モック(Python標準ライブラリだけ)。受け取った要求をファイルに保存し、決まった文を返します。OPENAI_API_BASE_URL でここに向けたので、APIキーも課金もありません
  • 画面の操作と撮影は、コンテナの中のヘッドレスChromium(Playwright)で行いました

READMEは「Python 3.11を使うように」と書いていますが、PyPIのパッケージは >=3.11, <3.13 を受け付けます。今回は3.12で通しましたが、起動から会話まで特に困ることはありませんでした。

利用統計の3つの環境変数(SCARF_NO_ANALYTICS / DO_NOT_TRACK / ANONYMIZED_TELEMETRY)は、公式のDockerfileと同じ値にそろえています。Dockerfileでは最初から設定されているのに、pipで入れたときは自分で設定しない限り何も設定されていないからです。

pip一発で7.6GB。CPUしかないのにCUDAが付いてきた

最初の山場はインストールでした。コマンドは1行で、待ち時間も2分少々です。ただ、入ったものの量が想像と違いました。

$ pip install --progress-bar off open-webui==0.12.0
(rc=0、133.9秒、262パッケージ)

# site-packages の大きさ(バイト)
インストール前:      7,999,633
インストール後:  7,606,491,916

# 大きい順(du -sm、MB)
3196  nvidia
1177  torch
 897  triton
 249  open_webui
 139  pyarrow
 109  scipy

$ python -c "import torch; print(torch.__version__, torch.cuda.is_available())"
2.14.1+cu130 False

# ここは自分の使い方次第。GPUの無いマシンでも、既定ではCUDA 13向けの torch と nvidia-* が入る

7.6GBのうち、nvidia・torch・tritonの3つで5,269MB、全体の7割近くになります。Open WebUI本体は249MBです。GPUの無いコンテナなので torch.cuda.is_available() は False で、5GB余りを抱えたまま一度も使わないことになります。コンテナの受信量も、pipが終わった時点で3.77GBでした。

公式のDockerfileを読むと、GPUを使わない版では pip3 install 'torch<=2.9.1' ... --index-url https://download.pytorch.org/whl/cpu を先に実行してから、残りの依存を入れています。pipで入れる場合も同じ順番にすれば軽くなるはずですが、今回はそちらを測っていません。

正直なところ、ここは「教科書どおりにやって重すぎた」典型だと思いました。READMEの手順は間違っていません。ただ、検証用の小さなVMに入れる前提で見積もると、ディスクで足が出ます。

初回起動は33.7秒。その間にHugging Faceから889MB

open-webui serve は既定で 0.0.0.0:8080 で待ち受けます。起動を指示してから、ホストのブラウザ側でHTTP 200が返るまで33.7秒でした。ログを見ると、その時間の大半はデータベースの準備と、埋め込みモデルの取得に使われていました。

$ open-webui serve
INFO  [alembic.runtime.migration] Running upgrade ... (データベースの更新が続く)
| INFO | open_webui.models.config:seed_defaults:272 - Seeded 368 new config defaults
| INFO | httpx ... GET https://huggingface.co/api/models/sentence-transformers/all-MiniLM-L6-v2/revision/main "HTTP/1.1 200 OK"
Fetching 30 files:   0%|          | 0/30
| INFO | uvicorn ... "GET / HTTP/1.1" 200

# 取得した埋め込みモデルのキャッシュ: 889MB(同じモデルが形式違いで並ぶ)
91,005,696  tf_model.h5
90,888,945  pytorch_model.bin
90,887,379  rust_model.ot
90,868,376  model.safetensors
90,405,214  model.onnx (ほか onnx の最適化版・量子化版、openvino 版)

# データベースの置き場所
$ find / -name webui.db
/usr/local/lib/python3.12/site-packages/open_webui/data/webui.db

埋め込みモデルの all-MiniLM-L6-v2 は、30ファイルをまとめて取りに行っていました。TensorFlow用・PyTorch用・Rust用・ONNXの各種と、同じモデルが10種類近くの形式で並んでいます。使うのはこのうち1つのはずですが、どれを読んでいるかまでは確かめていません。

もう一つ気になったのが webui.db の場所です。pipで入れると、会話やユーザーを入れたデータベースが site-packages の中にできます。同じ場所の readme.txt には docker dir for backend files (db, documents, etc.) とだけありました。Dockerならボリュームに逃がす前提の場所なので、pipで本番運用するなら、DATA_DIR を自分で外に向けておかないと、入れ直しのときに危ないなと感じています。

初めて開くと、名前・メールアドレス・パスワードの3つだけの画面が出ます。最初に作ったアカウントがそのまま管理者になります。

Open WebUIの初回画面。「Open WebUIをはじめる」の見出しの下に「Open WebUI は外部接続を行わず、データはローカルでホストされているサーバー上に安全に保持されます。」と注記があり、名前・メールアドレス・パスワードの入力欄と「管理者アカウントを作成」ボタンが並んでいる

画面には「外部接続を行わず」と書かれています。会話のデータを外に送るわけではない、という意味だと読みましたが、ひとつ前のログのとおり、起動の時点でモデルの取得にはしっかり外へ出ています。閉じたネットワークで使う予定なら、モデルを先に置いておく段取りが要りそうです。

1通送ると、モックには4本届いた

ログインすると、モックの mock-model が最初から選ばれていました。ここで「コンテナの中からこんにちは。Open WebUI を初めて触っています。」と1通送ります。

Open WebUIの会話画面。左上の題名が「モックが付けた題名」になり、送った文の下にmock-modelの「こんにちは。これはコンテナ内のモックが返した固定の応答です。」が表示され、その下に関連質問として「モックの続きの質問1」「モックの続きの質問2」が並んでいる

左上の題名と、下の「関連質問」も、モックが返した文がそのまま入っています。つまり、画面に見えている部品の分だけ、LLMが呼ばれていたわけです。モック側の記録はこうでした(POSTだけ抜き出し、時刻は秒の小数部のみ)。

.796 POST /v1/chat/completions n=1 task=title      stream=False bytes=1255  last='### Task:\nGenerate a concise title summarizing the chat hist'
.859 POST /v1/chat/completions n=2 task=chat       stream=True  bytes=26174 last='コンテナの中からこんにちは。Open WebUI を初めて触っています。'
.100 POST /v1/chat/completions n=3 task=follow_ups stream=False bytes=1258  last='### Task:\nSuggest 3-5 relevant follow-up questions or prompt'
.112 POST /v1/chat/completions n=4 task=tags       stream=False bytes=1004  last='### Task:\nGenerate 1-3 broad tags categorizing the main them'
順用途要求の大きさいつ送られたか
1題名を付ける1,255バイト(max_tokens 1000)本体より63ミリ秒先
2本体(応答を流す)26,174バイト送信直後
3続きの質問を作る1,258バイト応答が流れ終わった直後
4タグを付ける1,004バイト3の12ミリ秒後

本数より驚いたのは、本体の大きさです。送った文は86バイトなのに、要求は26,174バイトありました。中身を分けてみると、tools に組み込みツールが39個入っていて、その定義だけで26,001バイト。要求のほぼ全部がツールの説明書だったりします。

並んでいたのは、ナレッジベースの検索、メモリの読み書き、ノート、スキル、タスク、自動実行の登録、カレンダーの予定、時刻の計算といった、Open WebUI自身の機能を呼ぶためのツールです。ナレッジベースもカレンダーも何も登録していない、まっさらな状態でも39個すべてが毎回付いてきます。

環境変数で止めたつもりが、止まらなかった

題名・タグ・続きの質問の3本は、環境変数 ENABLE_TITLE_GENERATION / ENABLE_TAGS_GENERATION / ENABLE_FOLLOW_UP_GENERATION で止められます。そこで3つとも false にして起動し直し、別の会話で1通送りました。結果は、4本のまま。題名もタグも、何事もなかったように付きました。

理由は、初回起動のログにあった Seeded 368 new config defaults です。Open WebUIは最初の起動で設定の既定値を368件データベースに書き込み、2回目以降はそちらを優先します。open_webui/models/config.py のコメントにも Existing DB values take precedence over defaults. とありました。理屈はそうなんですが、Dockerの -e で設定を足していく運用に慣れていると、ここはまず引っかかると思います。

# B: 3つの生成を false にして起動し直す → 4本のまま
$ open-webui serve   (ENABLE_TITLE_GENERATION=false ENABLE_TAGS_GENERATION=false ENABLE_FOLLOW_UP_GENERATION=false)
n=5 task=title bytes=1253 / n=6 task=chat bytes=26172 / n=7 task=follow_ups bytes=1256 / n=8 task=tags bytes=1002

# C: さらに ENABLE_PERSISTENT_CONFIG=false → 本体だけになる
n=9 task=chat bytes=26168

# D: さらに ENABLE_TOOL_SEARCH=true → 本体が3分の1近くに
n=10 task=chat bytes=9212 (system 4,822バイト + tools 13個 4,091バイト)

ENABLE_PERSISTENT_CONFIG=false を足すと、ようやく環境変数が効き、要求は本体の1本だけになりました。題名は送った文がそのまま入り、関連質問も出ません。

ENABLE_PERSISTENT_CONFIG=falseで起動し直した後の会話画面。左上の題名が送った文「今度は ENABLE_PERSISTENT_CONFIG=false も付けて起動し直しました。」そのものになり、mock-modelの応答の下に関連質問が表示されていない

本体に付いてくる39個のツールは、ENABLE_TOOL_SEARCH=true にすると減りました。定義が短い12個と、残りを探すための search_tools の計13個だけを送り、それ以外はsystemメッセージに名前の一覧として載せる方式です。本体は26,168バイトから9,212バイトになりました。

起動のしかた1通あたりの要求本体の大きさ
A 既定のまま4本(合計29,691バイト)26,174バイト(ツール39個)
B 起動後に3つの生成を false4本のまま26,172バイト
C Bに ENABLE_PERSISTENT_CONFIG=false1本26,168バイト(ツール39個)
D Cに ENABLE_TOOL_SEARCH=true1本9,212バイト(ツール13個)

なお、2回目以降の起動は初回のようなダウンロードが無く、再起動を指示してから11〜14秒ほどで応答が返りました。起動していたプロセスのメモリは、1通目を送った後で約1.3GB(RSS)です。

試してみての所感

フレームワークは目的ではなく道具、とよく言っています。Open WebUIもそれと同じで、既定値は「全部入り」に倒してあり、どれを削ってどれを残すかを決めるのは入れる側の仕事なんですよね。今回の数字で言えば、題名・タグ・続きの質問の3本は会話の見通しを良くする分の追加費用で、39個のツールは「Open WebUIの機能をLLMから呼べる」分の固定費です。

自分が社内に入れるなら、次の順で決めると思います。

  • 従量課金のAPIにつなぐなら、まずツール検索を有効にするかを決める。1通あたり17KB近く送る量が減るのは、利用者が増えるほど効いてきます
  • 題名とタグは、どちらか1つ残せば十分な気がしています。続きの質問は、使われ方を見てからでも遅くありません
  • 設定は環境変数より管理画面で変える前提にする。環境変数で管理したいなら ENABLE_PERSISTENT_CONFIG=false ですが、そのとき管理画面で変えた値がどう扱われるかは、今回確かめていません

逆に、ローカルのモデルにつなぐなら、ツールの分はお金ではなく文脈の長さと速さの問題になります。小さなモデルで39個の定義を毎回読ませるのが割に合うかは、ここは好みが分かれるところですが、私は様子見です。

インストールの7.6GBと、初回起動の889MBのダウンロードは、検証環境のディスクを見積もるときにそのまま使える数字でした。次はCPU版のtorchを先に入れる手順で、どこまで縮むかを測ってみるつもりです。