CATHODE COASTaccess to tools
10 min read

新しいチャットを開くと AI がまた嘘をつく——Open WebUI のナレッジベースで RAG を「常設」する

チャットに文書を添付する RAG は、その会話を閉じると消えてしまう。新しいチャットではまたハルシネーションが復活する。これを解決するため、Open WebUI のナレッジベースを作ってモデルに常時紐付けるまでの作業ログ。途中「モデル 0」で手が止まった実機の罠も含めて記録する。

shareB!

前回、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(ホスト側、日報用モデル nippouqwen2.5:7b が在籍)
  • 埋め込みモデル all-MiniLM-L6-v2 / ベクトル DB chroma

先にまとめ

急いでいる方向けに、結論を先に置きます。

  • チャットへの文書添付は「そのチャット限り」。永続化するにはナレッジベースを作り、モデルに紐付ける。
  • 紐付けの手順に罠がある。Open WebUI の「モデル」タブは、Ollama で作ったモデル(nippou 等)を自動では表示しない。初見だと「モデル 0」で手が止まる。
  • 正しい順序は、ナレッジベースを先に作り、それを土台にしたモデルを「モデル」タブで新規作成して紐付ける。既存モデルを編集するのではなく、既存モデルをベースに新しいモデルを起こすイメージ。
  • これができると、新しいチャットで文書を添付しなくても、モデルが常にナレッジを参照するようになる。ハルシネーションの再発が止まる。

なぜ添付方式では足りないのか

前回の RAG は、チャット入力欄から文書を添付する方式でした。これは手軽ですが、添付した文書はその会話のコンテキストに紐づくだけです。新しいチャットは、そのコンテキストを引き継ぎません。まっさらな素のモデルに戻るので、社内ルールのような固有情報を聞けば、また自信たっぷりに間違えます。

つまり添付方式は「揮発的」です。実際に新しいチャットで同じ質問(提出期限は何時か)をすると、前回正解した直後でも、素のモデルは平然と嘘に戻りました。これを「常設」に変えるのが今回のゴールです。

やることは概念的にはシンプルで、次の 3 段階です。

  1. 文書を、チャットから独立した永続コレクション(ナレッジベース)として登録する
  2. そのナレッジベースをモデルに紐付ける
  3. 新しいチャットで、添付なしに参照されることを確認する

「モデル 0」で手が止まる

最初、私はモデル側から手をつけようとしました。ワークスペースの「モデル」タブを開き、そこで日報用モデル nippou を編集してナレッジを足せばいい、と考えたのです。ところが開いてみると、表示はこうでした。

モデルタブが空だった
モデル | 0
(一覧に何も表示されない)

nippouqwen2.5:7b も、チャット画面のモデル選択ドロップダウンにはちゃんと出るのに、この「モデル」タブには一つも出てきません。ここでしばらく手が止まりました。

原因は、Open WebUI の「モデル」タブの性格にありました。ここに並ぶのは、Open WebUI 上で作られた(カスタマイズされた)モデルです。一方 nippouqwen2.5:7b は Ollama 側で作ったモデルで、Open WebUI から「利用できる」状態ではあっても、このタブに自動登録されるわけではありません。だから 0 件だったのです。

ここが実機で初めて分かった差分でした。「既存の nippou を編集してナレッジを足す」という発想がそもそも間違いで、正しくは「nippou を土台にして、ナレッジを紐付けた新しいモデルをこのタブで作る」でした。手順の思い込みを、画面が静かに訂正してくれた格好です。

そして順序も逆でした。モデルにナレッジを紐付けるには、紐付ける先のナレッジベースが先に存在していなければなりません。つまり、ナレッジベースの作成が先、モデル作成が後です。

正しい順序 1:ナレッジベースを作る

まず文書を永続化します。ワークスペース上部のタブから「ナレッジベース」を開き、新規作成します。

  1. 「ナレッジベース」タブへ移動
  2. 「+」で新規作成し、名前を付ける(例:社内日報ルール
  3. 作成したナレッジを開き、文書をアップロードする
  4. ベクトル化(埋め込み)の処理完了を待つ

アップロードしたのは、前回も使った架空の社内ルール集です。提出期限「19 時 30 分」、提出先ポータル「HibiNote」、重点施策「プロジェクト・アオゾラ」といった、モデルが絶対に知り得ない固有情報が書いてあります。

ここが添付方式との決定的な違いです。この文書はもうチャットに依存せず、ナレッジベース側(chroma のベクトル DB)に永続的に住みます。ボリュームに保存されるので、Mac を再起動しても、新しいチャットを開いても消えません。

正しい順序 2:ナレッジを紐付けたモデルを作る

ナレッジができたら「モデル」タブに戻り、今度は新規モデルを作ります。0 件だったあの画面の「+」から始めます。

  1. 「モデル」タブで「+」を押し、新規モデル作成へ
  2. ベースモデルに nippou(または qwen2.5:7b)を選ぶ
  3. 区別できる名前を付ける
  4. 「ナレッジ」の項目で、先ほど作った 社内日報ルール を追加する
  5. 保存

これで「日報用の指示(nippou の素性)+ 社内ナレッジを常時参照」する新しいモデルが、チャットのドロップダウンに現れます。以降はこのモデルを選ぶ限り、どのチャットからでもナレッジが効きます。

検証:新しいチャットで、添付なしに答えるか

いちばん見たかったところです。新しいチャットを開き(前の会話の文脈を引き継がない、まっさらな状態がポイント)、作ったモデルを選び、文書を一切添付せずに聞きました。

新規チャット・添付なしでの回答
Q: 日報の提出期限は何時までですか?提出先のポータル名も教えてください。
 
A:
- 提出期限は当日の 19時30分 までです。
- 提出先は社内ポータル「HibiNote」の日報チャネルとなります。

正解です。前回、素の状態では「17 時」「業務管理システム」と嘘をついた質問に対し、今回は新規チャット・添付なしで「19 時 30 分」「HibiNote」と正しく答えました。しかも余計な日報フォーマットに整形せず、ナレッジの該当箇所だけを簡潔に返しています。狙っていた「新しいチャットを開くとハルシネーションが復活する」問題は、これで解消です。

仕組みとしては、質問がそのままモデルに渡っているのではありません。前段でナレッジベースを検索し、ヒットした文書片を質問にくっつけて(=拡張プロンプトにして)からモデルに推論させています。マネージドの Knowledge Bases を前段にかませる構成と、やっていることは同じです。違いは、それを手元の Docker コンテナの中(Open WebUI + chroma + ローカル埋め込みモデル)で完結させ、データを一切外に出していない点だけです。

本当に「永続化」されているのか、DB で裏を取る

UI で動いたのは分かりましたが、「永続化されている」ことは画面からは見えません。ボリューム内に実データがあるのか、そしてコンテナのライフサイクルと無関係に残るのかを、コンテナの中を覗いて確認しました。

まず、ベクトル DB(chroma)の実体がボリューム内に存在するかです。

chroma のDB本体を探す
docker exec open-webui sh -c "find /app/backend/data -name '*.sqlite3' -o -name 'chroma*' | head -20"
出力:chroma のDBが実在する
/app/backend/data/vector_db/chroma.sqlite3

その中身のサイズも見ておきます。

ベクトルDBのサイズと構成
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_db

chroma.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:3315: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 製モデルは「モデル」タブに自動では出ないため、ベースに選んで新モデルを起こすのが正解でした。

結果として、新しいチャットで文書を添付しなくても、モデルが常に社内文書を参照するようになりました。派手な仕組みではありませんが、質問の前に根拠をそっと差し出すこの一手間が、モデルの嘘を静かに止めてくれます。次は、固定文書ではなく変化するデータ源に触れる方向——外部システムとの接続を試してみようと思います。