前回、Open WebUI で RAG を試しました。社内ルールを書いた文書を読ませると、それまで「17 時」と嘘をついていたモデルが「19 時 30 分」と正しく答える。文書一枚でハルシネーションが正解に変わる様子は、なかなか感動的でした(そのモデル nippou を作った経緯はさらに前の記事にあります)。
ところが、この方式には落とし穴がありました。文書はチャットに添付する形なので、その会話を閉じて新しいチャットを開くと、モデルはまた何も知らない素の状態に戻ります。そして懲りずに「17 時」と嘘をつく。毎回すべてのチャットで文書を添付し直すのは、実運用にはほど遠い。
やりたいのは、添付し忘れても常に社内文書を参照してくれる状態です。イメージとしては、質問をそのままモデルに投げるのではなく、前段でナレッジ検索をかけ、ヒットした内容を質問にくっつけてから推論させる——マネージドサービスでいう Knowledge Bases を前段にかませる構成の、ローカル版です。Open WebUI にはこれを実現する「ナレッジベース」機能があります。この記事は、それをモデルに常設するまでの記録です。
環境は前回から継続で、執筆時点(2026 年 7 月)は以下のとおりです。
- macOS 15 系(Apple Silicon / arm64、メモリ 16GB)
- Docker version 28.4.0 / Open WebUI v0.6.22
- Ollama v0.11.4(ホスト側、日報用モデル
nippouとqwen2.5:7bが在籍) - 埋め込みモデル all-MiniLM-L6-v2 / ベクトル DB chroma
先にまとめ
急いでいる方向けに、結論を先に置きます。
- チャットへの文書添付は「そのチャット限り」。永続化するにはナレッジベースを作り、モデルに紐付ける。
- 紐付けの手順に罠がある。Open WebUI の「モデル」タブは、Ollama で作ったモデル(
nippou等)を自動では表示しない。初見だと「モデル 0」で手が止まる。 - 正しい順序は、ナレッジベースを先に作り、それを土台にしたモデルを「モデル」タブで新規作成して紐付ける。既存モデルを編集するのではなく、既存モデルをベースに新しいモデルを起こすイメージ。
- これができると、新しいチャットで文書を添付しなくても、モデルが常にナレッジを参照するようになる。ハルシネーションの再発が止まる。
なぜ添付方式では足りないのか
前回の RAG は、チャット入力欄から文書を添付する方式でした。これは手軽ですが、添付した文書はその会話のコンテキストに紐づくだけです。新しいチャットは、そのコンテキストを引き継ぎません。まっさらな素のモデルに戻るので、社内ルールのような固有情報を聞けば、また自信たっぷりに間違えます。
つまり添付方式は「揮発的」です。実際に新しいチャットで同じ質問(提出期限は何時か)をすると、前回正解した直後でも、素のモデルは平然と嘘に戻りました。これを「常設」に変えるのが今回のゴールです。
やることは概念的にはシンプルで、次の 3 段階です。
- 文書を、チャットから独立した永続コレクション(ナレッジベース)として登録する
- そのナレッジベースをモデルに紐付ける
- 新しいチャットで、添付なしに参照されることを確認する
「モデル 0」で手が止まる
最初、私はモデル側から手をつけようとしました。ワークスペースの「モデル」タブを開き、そこで日報用モデル nippou を編集してナレッジを足せばいい、と考えたのです。ところが開いてみると、表示はこうでした。
モデル | 0
(一覧に何も表示されない)nippou も qwen2.5:7b も、チャット画面のモデル選択ドロップダウンにはちゃんと出るのに、この「モデル」タブには一つも出てきません。ここでしばらく手が止まりました。
原因は、Open WebUI の「モデル」タブの性格にありました。ここに並ぶのは、Open WebUI 上で作られた(カスタマイズされた)モデルです。一方 nippou や qwen2.5:7b は Ollama 側で作ったモデルで、Open WebUI から「利用できる」状態ではあっても、このタブに自動登録されるわけではありません。だから 0 件だったのです。
ここが実機で初めて分かった差分でした。「既存の nippou を編集してナレッジを足す」という発想がそもそも間違いで、正しくは「nippou を土台にして、ナレッジを紐付けた新しいモデルをこのタブで作る」でした。手順の思い込みを、画面が静かに訂正してくれた格好です。
そして順序も逆でした。モデルにナレッジを紐付けるには、紐付ける先のナレッジベースが先に存在していなければなりません。つまり、ナレッジベースの作成が先、モデル作成が後です。
正しい順序 1:ナレッジベースを作る
まず文書を永続化します。ワークスペース上部のタブから「ナレッジベース」を開き、新規作成します。
- 「ナレッジベース」タブへ移動
- 「+」で新規作成し、名前を付ける(例:
社内日報ルール) - 作成したナレッジを開き、文書をアップロードする
- ベクトル化(埋め込み)の処理完了を待つ
アップロードしたのは、前回も使った架空の社内ルール集です。提出期限「19 時 30 分」、提出先ポータル「HibiNote」、重点施策「プロジェクト・アオゾラ」といった、モデルが絶対に知り得ない固有情報が書いてあります。
ここが添付方式との決定的な違いです。この文書はもうチャットに依存せず、ナレッジベース側(chroma のベクトル DB)に永続的に住みます。ボリュームに保存されるので、Mac を再起動しても、新しいチャットを開いても消えません。
正しい順序 2:ナレッジを紐付けたモデルを作る
ナレッジができたら「モデル」タブに戻り、今度は新規モデルを作ります。0 件だったあの画面の「+」から始めます。
- 「モデル」タブで「+」を押し、新規モデル作成へ
- ベースモデルに
nippou(またはqwen2.5:7b)を選ぶ - 区別できる名前を付ける
- 「ナレッジ」の項目で、先ほど作った
社内日報ルールを追加する - 保存
これで「日報用の指示(nippou の素性)+ 社内ナレッジを常時参照」する新しいモデルが、チャットのドロップダウンに現れます。以降はこのモデルを選ぶ限り、どのチャットからでもナレッジが効きます。
検証:新しいチャットで、添付なしに答えるか
いちばん見たかったところです。新しいチャットを開き(前の会話の文脈を引き継がない、まっさらな状態がポイント)、作ったモデルを選び、文書を一切添付せずに聞きました。
Q: 日報の提出期限は何時までですか?提出先のポータル名も教えてください。
A:
- 提出期限は当日の 19時30分 までです。
- 提出先は社内ポータル「HibiNote」の日報チャネルとなります。正解です。前回、素の状態では「17 時」「業務管理システム」と嘘をついた質問に対し、今回は新規チャット・添付なしで「19 時 30 分」「HibiNote」と正しく答えました。しかも余計な日報フォーマットに整形せず、ナレッジの該当箇所だけを簡潔に返しています。狙っていた「新しいチャットを開くとハルシネーションが復活する」問題は、これで解消です。
仕組みとしては、質問がそのままモデルに渡っているのではありません。前段でナレッジベースを検索し、ヒットした文書片を質問にくっつけて(=拡張プロンプトにして)からモデルに推論させています。マネージドの Knowledge Bases を前段にかませる構成と、やっていることは同じです。違いは、それを手元の Docker コンテナの中(Open WebUI + chroma + ローカル埋め込みモデル)で完結させ、データを一切外に出していない点だけです。
本当に「永続化」されているのか、DB で裏を取る
UI で動いたのは分かりましたが、「永続化されている」ことは画面からは見えません。ボリューム内に実データがあるのか、そしてコンテナのライフサイクルと無関係に残るのかを、コンテナの中を覗いて確認しました。
まず、ベクトル DB(chroma)の実体がボリューム内に存在するかです。
docker exec open-webui sh -c "find /app/backend/data -name '*.sqlite3' -o -name 'chroma*' | head -20"/app/backend/data/vector_db/chroma.sqlite3その中身のサイズも見ておきます。
docker exec open-webui sh -c "ls -la /app/backend/data/vector_db/ 2>/dev/null; echo '---'; du -sh /app/backend/data/vector_db 2>/dev/null"-rw-r--r-- 1 root root 229376 Jul 6 15:58 chroma.sqlite3
drwxr-xr-x 2 root root 4096 Jul 6 15:58 988714bf-21e2-4d2b-a07b-757cc7a73b61
drwxr-xr-x 2 root root 4096 Jul 6 15:58 ba7e8961-e71e-4ed0-a780-398aab4e728e
drwxr-xr-x 2 root root 4096 Jul 6 15:33 e957e5df-120c-4c27-a87f-216fbb5381bd
---
5.1M /app/backend/data/vector_dbchroma.sqlite3 が 229KB、vector_db 全体で 5.1MB。UUID 名のフォルダは、chroma がベクトルを格納するコレクション単位のディレクトリです。文書がただ置かれているのではなく、埋め込みベクトルとして格納されていることが分かります。
アップロードした元文書も残っていました。
docker exec open-webui sh -c "ls -la /app/backend/data/uploads/ 2>/dev/null | head -20"-rw-r--r-- 1 root root 1534 Jul 6 15:33 (uuid)_nippou-guideline.md
-rw-r--r-- 1 root root 1534 Jul 6 15:58 (uuid)_nippou-guideline.md余談ですが、同じファイルが 2 つあります。タイムスタンプが 15:33 と 15:58 に分かれていて、これは前回の添付検証で一度、今回のナレッジベース作成でもう一度アップロードした痕跡でした。実運用では重複に気をつけたいところですが、今回はそのまま残しておきます。
次に、メインの DB にナレッジベースのレコードが登録されているかを確認します。ここでも前回同様 sqlite3 コマンドはコンテナに無いので、Python 経由です。
docker exec open-webui python -c "
import sqlite3
con = sqlite3.connect('/app/backend/data/webui.db')
rows = con.execute('SELECT name, description FROM knowledge').fetchall()
print('ナレッジベース数:', len(rows))
for r in rows:
print('-', r[0], '/', r[1])
"ナレッジベース数: 1
- 社内日報ルール / 社内日報ルールそして、いちばん見せたかった検証です。コンテナを再起動しても消えないか。 これが「永続化」の本当の証明になります。
docker restart open-webui
sleep 10
docker exec open-webui python -c "
import sqlite3
con = sqlite3.connect('/app/backend/data/webui.db')
n = con.execute('SELECT COUNT(*) FROM knowledge').fetchone()[0]
print('再起動後のナレッジベース数:', n)
"再起動後のナレッジベース数: 1コンテナを再起動しても、ナレッジは 1 件のまま残りました。データはコンテナではなくボリュームに書かれているので、コンテナのライフサイクルとは切り離されています。前回、コンテナを消してもアカウントが残った(だからボリュームごと消す必要があった)のと、まったく同じ仕組みです。あのとき厄介だった「消しても残る」性質が、ここでは「消したくないデータが残ってくれる」利点として働いています。添付方式が会話とともに消えたのとは対照的です。
ハマりどころまとめ
| 症状 | 原因 | 対処 |
|---|---|---|
| 新しいチャットにするとまた嘘をつく | チャット添付は揮発的で、会話を閉じると消える | ナレッジベースを作りモデルに常設する |
| 「モデル」タブが「モデル 0」で空 | このタブは Ollama 製モデルを自動表示しない | ナレッジを紐付けた新モデルをここで作成する |
| 既存モデルを編集しようとして詰まる | 紐付けは「編集」ではなく「新モデル作成」 | ベースに nippou を選び、新規モデルを起こす |
| モデルを先に作ろうとして紐付け先が無い | ナレッジベースが未作成だと紐付けられない | ナレッジベース作成が先、モデル作成が後 |
| 永続化されているか画面から分からない | UI にはボリュームの中身が出ない | docker exec で chroma と DB を直接覗く。再起動して残るか確認 |
やってみての所感
今回いちばんの学びは、技術そのものより「手順の順序」でした。モデルからいじろうとして「モデル 0」で止まり、実際にはナレッジベースを先に用意して、それを土台にモデルを新規作成する、という逆順が正解だった。ドキュメントを眺めているだけでは気づきにくく、実機で画面が空だったからこそ順序を組み直せました。この手の「思い込みの手順を画面に訂正される」瞬間は、ローカルツールをいじっていると何度も出会います。
RAG の常設そのものは、一度組んでしまえば快適です。添付し忘れる心配がなく、どのチャットでも社内ルールが効く。これはまさに、当初思い描いていた「前段にナレッジを挟んで拡張プロンプトにする」構成の最小実装です。マネージドサービスを使わずとも、手元の 16GB Mac の中だけで、チャットに依存しない社内ナレッジ RAG が動きました。
一点だけ、RAG の性質として補足しておきます。今回は質問がきれいにナレッジへヒットしましたが、RAG は「関連文書を検索して渡す」仕組みである以上、質問の言い回し次第で検索が外れ、取りこぼすことがあります。特に小型モデルと短い文書の組み合わせでは、聞き方の影響を受けやすい。運用に乗せる前に、文書内の別の固有情報(たとえば見積書の社内呼称)を色々な言い回しで聞いて、参照の再現性を確かめておくと安心です。
向いているのは、社内規程・手順書・用語集のように「一般常識には無いが正確さが要る固定文書」を、全チャットで常に効かせたい人です。逆に、都度変わる情報や外部システムの生データを触りたい場合は、この静的ナレッジ方式では足りません。そこは別の仕組み(外部データ源への接続)の出番になります。
まとめ
チャットへの文書添付は手軽ですが揮発的で、新しいチャットではハルシネーションが戻ってきます。これを常設に変えるには、ナレッジベースを作り、それを紐付けたモデルを用意する。順序は「ナレッジベースが先、モデル作成が後」で、Ollama 製モデルは「モデル」タブに自動では出ないため、ベースに選んで新モデルを起こすのが正解でした。
結果として、新しいチャットで文書を添付しなくても、モデルが常に社内文書を参照するようになりました。派手な仕組みではありませんが、質問の前に根拠をそっと差し出すこの一手間が、モデルの嘘を静かに止めてくれます。次は、固定文書ではなく変化するデータ源に触れる方向——外部システムとの接続を試してみようと思います。