はじめに
前編では環境構築から始め、ファイルを添付してRAGが問題なくできるところまでを確認しました。
今回はOpen WebUIでのRAGがどのように実行されているのか、コードを実際に見ていきます。
確認したバージョン
・Open WebUI Ver. 0.6.2
・Ollama Ver. 0.6.1
ファイルアップロードのコード実装
Open WebUIには事前に貯めておきたい知識を置いておくナレッジベースという機能があります。
ファイルを添付したときにどのように処理されているのかを確認していきます。
なお、公式のドキュメントにはドキュメントパース部分はget_loader関数を見ろ、書いてあります。
ただしそのリンクは古くなっており、現在(v0.6.2)のコードではありません。
この部分を追うために、テストファイルを添付した際のログから確認していきます。
テスト用のナレッジベースを作り、テストファイルとしてはRFC3501のPDF版であるrfc3501.txt.pdfを添付します。
アップロードの際にどのような処理が行われているのか、ログを確認します。
docker container logs open-webui
画像の1行目にopen_webui.routers.files:upload_fileという記載があります。
これはopen_webui/routers/files.pyのupload_file関数が呼ばれていることを意味します。
GitHubのコードで言うとこの81行目です。
このupload_file関数の一番重要なファイルを処理する部分は上記のリンク部分より少し進んだprocess_file関数(125行目)です。
このファイルを処理していると思われるprocess_file関数はopen_webui/routers/retrieval.pyで定義されています。
GitHubのコードはこの946行目です。
この関数を読み進めるとloaderを読み込み(1021行目)、そのload関数を使ってドキュメントを読み込んでいること(1030行目)がわかります。
そこでさらにLoaderクラスを確認していきますと、load関数の具体的な中身が見えてきます。
Loaderクラスを定義しているのはopen_webui/retrieval/loaders/main.pyであり、コードはこの169行目です。
_get_loader関数では、self.engineの値に分かれてその処理内容が記載されています。
デフォルトの設定だとself.engineにはtikaやdocument_intelligenceなどは入らないため、一番下のelseに分岐します。
このself.engineの値は管理者パネルの「ドキュメント」の「Content Extraction Engine」に当たります。
「Content Extraction Engine」に「デフォルト」を指定した場合は、一番下のelseに行くため、コードにあるように拡張子ごとに処理が分岐されています。
例えば拡張子が「.pdf」になっているPDFファイルの場合を見てみますと、「PyPDFLoader」つまりLangChainのpyPDFLoaderを使っていること(245行目)がわかります。
公式のget_loader関数のリンクは古くなっていると先程述べましたが、backend/open_webui/retriebal/loaders/main.pyの192行目の_get_loader関数が2025年4月のVer0.6.2時点では最新の箇所ということになります。
話をprocess_file関数に戻します。
添付したファイルをこのloader関数に従ってテキストにした後の処理です。
次に大事な処理としてsave_docs_to_vector_db関数があります。
GitHubのコードはこの1073行目です。
これは先ほどのログ(docker container logs open-webui)の2行目でも表示されていた関数になります。
(open_webui.routers.retrieval:save_docs_to_vector_dbの部分)
このsave_docs_to_vector_db関数が実際に定義されているのは、ログにもあるように
open_webui/routers/retrieval.pyです。
GitHubのコードだとこの785行目になります。
save_docs_to_vector_db関数を読み進めていきますと、テキスト分割において、チャンクの設定(chunk_sizeとchunk_overlap)が使われています。
管理者パネルの「ドキュメント」内で設定できる「チャンクサイズ」や「チャンクオーバーラップ」がここで使われていることが確認できます。
| 単語 | 意味 |
|---|---|
| チャンクサイズ |
Text SpliterがCharactorの場合は、RecursiveCharacterTextSplitterを使っており、文字数と同値 |
| チャンクオーバーラップ |
Text SpliterがCharactorの場合は、連続したチャンクで被る文字数。チャンクオーバーラップは文字列分割で大事な情報が落ちないように、文脈を保持するために使用します |
さらにsave_docs_to_vector_db関数を読み進めていくとembeddingsにtextsを与えています。
これは埋め込みモデル(embedding model)にチャンクサイズで分割したテキストを与えていることがわかります。
よく読むとreplace("¥n", " ")も実行しているので、改行は半角空白に置き換わっていることもわかります。
つまり改行はOpen WebUIの埋め込みモデルでは空白と同じになるということがここからわかります。
このベクトル化した結果をDBに入れて、save_docs_to_vector_db関数は終わります。
以上で、ファイルをアップロードした際にOpen WebUIでどのような処理が行われるのかを見てきました。
簡単にファイルアップロードの流れをまとめると
- 添付されたファイルを拡張子に沿ってパースする
- 埋め込みモデル(embedding model)を使ってベクトル化する
- ベクトル化したものをDBに保存する
という形になります。
RAGのコード実装
次にRAGを実施しているときのスクリプト部分を確認します。
「#」からすでに作成されたナレッジベース内のコレクションを選択し、RAGを動かした時のログを見ていきます。
例では「人物紹介」というコレクションを使っています。この中身は前編で解説しているので、詳細が気になる人はそちらを見てください。
ログは以下です。
docker container logs open-webui
大事なのは、ログの4行目に表示されているopen_webui.retrieval.utils:query_docです。
これはopen_webui/retrieval/utils.pyの76行目に当たります。
見るとわかりますが、この関数のメインの処理はVECTOR_DB_CLIENT.searchだけです。
この関数を追う前にquery_doc関数がどこから来たのかを先に確認します。
これはopen_webui/routers/retrieval.pyのquery_doc_handler関数から呼ばれています。
こちらのGitHubのコードはこの1535行目です。
このquery_doc_handler関数で大事なことは2点です。
(1) 管理者パネルの「ドキュメント」の「ブリッジ検索」がONになっている場合は1541行目のif文がtrueになるため、呼ばれる関数はquery_doc関数ではなく、query_doc_with_hybrid_search関数になります。
「ブリッジ検索」をONにした場合はBM25アルゴリズムを使ったり、リランキングしたりしてコレクションの検索をするようになります。記事が冗長になるのでこれ以上は踏み込みませんが、詳細が気になる人は下のquery_docと同じようにコードを読んでいけば分かると思います。
(2)query_doc関数に渡しているのは以下の内容です。
| 変数 | 解説 |
|---|---|
| collection_name | これはナレッジベースで作られたコレクションの名前です |
| query_embedding | 埋め込み関数(embedding function)にクエリを渡したもの、つまりクエリをベクトル化したものです |
| k | これは管理者パネルの「ドキュメント」内の「トップK」で指定できる数字で、検索の上位K件を結果に用います |
| user | これもそのままユーザです(一般ユーザと管理者ユーザでできることが異なるので、この値を渡していると思われますが要調査) |
この(1)(2)の情報を踏まえて、query_doc関数内のVECTOR_DB_CLIENT.searchを追っていきます。
VECTOR_DB_CLIENTはopen_webui/retrieval/vector/connector.pyです。
GitHubのコードだとここですが、そんなに中身があるわけではなく、VECTOR_DBで値を分岐させているだけです。
VECTOR_DBの値が大事ですが、これは一番上に書かれているようにopen_webui/config.pyに書かれているので、そちらにいきます。GitHubのコードはこの1616行目に書かれています。
これは記載の通りOSの環境変数のVECTOR_DBの値を使います。
設定されていない場合は第二引数のchromaが指定されるということなので、デフォルトだとDBとしては使うものはChromaになります。
なお、この環境変数の値は公式ドキュメントにも記載があり、コードと同じ挙動が書かれていることが確認できます。
それではVECTOR_DB_CLIENT.searchに話を戻しましょう。
デフォルトではDBはChromaを用いるのでconnector.pyの記載に従い、open_webui/retrieval/vector/dbs/chroma.pyを見にいきます。
GitHubのコードだとこの66行目にsearch関数が記載されています。
71行目では引数に与えられたコレクション名からDB内に保存されているコレクションを選び、73行目でそのコレクションの検索をしています。
Chroma DBのコードの詳細は別の記事に譲りますが、ここではクエリをベクトル化したものを渡して、近しい文書を検索しているだけです。
80行目にも記載がありますが、コサイン類似度を使ってクエリ(をベクトル化したもの)とコレクションに保存された文書(をベクトル化したもの)の類似度を出していることがわかります。1
以上で、Open WebUIでRAGがどのような処理が行われるのかを見てきました。
大きな流れとしては
- 埋め込み関数を使ってクエリをベクトル化する
- コサイン類似度を使って、クエリに近い文章を検索する
- 検索で得られた上位K件の文章を使い、LLMに渡して回答生成をする
という形になります。
終わりに
似たようなことをやっている方もいますが、UIが少し古くなっているのと個人的にはLoader以外も気になったので色々調べてみました。
Open WebUI+OllamaでローカルLLMを構築し、RAGをやりたい人の参考になれば幸いです。
もし記事の内容で誤りなどがあれば、コメント欄でご指摘ください。


























