Open WebUI で日本語ウェブ検索付き生成AIチャットを動かす

## はじめに

Open WebUI は Ollama などの生成 AI に Web の UI を付加し、使いやすくしてくれます

https://openwebui.com/

更に、 ChatGPT などと同じように、文書に対する質問やウェブ検索などの機能も付いています

しかしながら、デフォルトで文書の埋め込み(Embedding)に使っているモデル(sentence-transformers/all-MiniLM-L6-v2)が英語用であるなど、日本語にはあまり適応できていない面があります

本記事では Open WebUI をコンテナで起動し、起動時の環境変数指定で日本語に対応できるように設定します

実装したコンテナ設定はこちら

https://github.com/RyoWakabayashi/open-webui-container-for-jp/tree/main/ollama-on-host

## Ollama の準備

Ollama 自体もコンテナで動かす、という方法もありますが、 Ollama はローカル（ホストマシン）で直接動かした方が速いです

本記事ではコンテナ上の Open WebUI からローカルの Ollama を呼び出すようにします

### インストール

Ollama をローカルにインストールしておきます

https://ollama.com/

インストール手順は公式に従ってください

### Gemma 2 JPN のダウンロード

日本語チャット用のモデルとして Google の Gemma 2 JPN を Ollama でダウンロードしておきます

https://huggingface.co/google/gemma-2-2b-jpn-it

ローカルのコンソールで以下のコマンドを実行してください

```bash
ollama pull hf.co/alfredplpl/gemma-2-2b-jpn-it-gguf
```

試しにローカルの CLI でチャットしてみましょう

```bash
ollama run hf.co/alfredplpl/gemma-2-2b-jpn-it-gguf
```

![スクリーンショット 2025-03-01 11.57.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/e8eb1333-277a-47a8-805a-7ec9f5423137.png)

軽量なモデルなので知識は少ないですが、日本語の応答は十分です

試し終わったら Ctrl + d でチャットを終了します

### Ruri のダウンロード

もう一つ、テキスト埋め込みのために日本語特化の Ruri をダウンロードしておきます

https://huggingface.co/cl-nagoya/ruri-base

```bash
ollama pull kun432/cl-nagoya-ruri-base
```

## コンテナ定義

以下の内容で docker-compose.yml ファイルを作成します

また、 searxng ディレクトリーを作成しておきます

```yml
---
services:
  openwebui:
    container_name: openwebui_host
    image: ghcr.io/open-webui/open-webui:main
    environment:
      GLOBAL_LOG_LEVEL: "debug"
      ENABLE_RAG_LOCAL_WEB_FETCH: True
      ENABLE_RAG_WEB_SEARCH: True
      RAG_EMBEDDING_ENGINE: "ollama"
      RAG_EMBEDDING_MODEL: "kun432/cl-nagoya-ruri-base:latest"
      RAG_EMBEDDING_BATCH_SIZE: 1
      RAG_OLLAMA_BASE_URL: "http://host.docker.internal:11434"
      CHUNK_SIZE: 500
      CHUNK_OVERLAP: 50
      RAG_WEB_SEARCH_ENGINE: "searxng"
      RAG_WEB_SEARCH_RESULT_COUNT: 3
      RAG_WEB_SEARCH_CONCURRENT_REQUESTS: 10
      SEARXNG_QUERY_URL: "http://searxng:8080/search?lang=ja&q=<query>"
      QUERY_GENERATION_PROMPT_TEMPLATE: |-
        ### Task:
        Analyze the chat history to determine the necessity of generating search queries, in the given language. By default, **prioritize generating 1-3 broad and relevant search queries** unless it is absolutely certain that no additional information is required. The aim is to retrieve comprehensive, updated, and valuable information even with minimal uncertainty. If no search is unequivocally needed, return an empty list.

        ### Guidelines:
        - クエリは必ず **日本語** にしてください
        - Respond **EXCLUSIVELY** with a JSON object. Any form of extra commentary, explanation, or additional text is strictly prohibited.
        - When generating search queries, respond in the format: { "queries": ["クエリ1", "クエリ2"] }, ensuring each query is distinct, concise, and relevant to the topic.
        - If and only if it is entirely certain that no useful results can be retrieved by a search, return: { "queries": [] }.
        - Err on the side of suggesting search queries if there is **any chance** they might provide useful or updated information.
        - Be concise and focused on composing high-quality search queries, avoiding unnecessary elaboration, commentary, or assumptions.
        - Today's date is: {{CURRENT_DATE}}.
        - Always prioritize providing actionable and broad queries that maximize informational coverage.

        ### Output:
        Strictly return in JSON format: 
        {
          "queries": ["クエリ1", "クエリ2"]
        }

        ### Chat History:
        <chat_history>
        {{MESSAGES:END:6}}
        </chat_history>
    ports:
      - "3000:8080"
    volumes:
      - open-webui:/app/backend/data

  searxng:
    container_name: searxng_host
    image: searxng/searxng:latest
    ports:
      - "8080:8080"
    volumes:
      - ./searxng:/etc/searxng:rw
    env_file:
      - .env
    restart: unless-stopped
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
      - DAC_OVERRIDE
    logging:
      driver: "json-file"
      options:
        max-size: "1m"
        max-file: "1"

volumes:
  open-webui:
```

Open WebUI と SearXNG のコンテナを起動しています

SearXNG はメタ検索エンジンで、様々な検索エンジンの検索結果を集約してくれます

Open WebUI では SearXNG を API として呼び出し、ウェブ上の情報を収集します

https://github.com/searxng/searxng

以下、何点か重要な点を補足します

### RAG 用の設定

docker-compose.yml の以下の部分でテキスト埋め込みに関する設定をしています

```yml
...
      RAG_EMBEDDING_ENGINE: "ollama"
      RAG_EMBEDDING_MODEL: "kun432/cl-nagoya-ruri-base:latest"
      RAG_EMBEDDING_BATCH_SIZE: 1
      RAG_OLLAMA_BASE_URL: "http://host.docker.internal:11434"
      CHUNK_SIZE: 500
...
```

- `RAG_EMBEDDING_ENGINE: "ollama"`: テキスト埋め込みに Ollama を使うように指定します
- `RAG_EMBEDDING_MODEL: "kun432/cl-nagoya-ruri-base:latest"`: テキスト埋め込みに Ruri モデルを使うように指定します
- `RAG_OLLAMA_BASE_URL: "http://host.docker.internal:11434"`: Ollama はホスト側にいるので、 `host.docker.internal` に対してアクセスするよう指定しています
- `CHUNK_SIZE: 500`: ドキュメントを分割するサイズです。デフォルトだと 1000 ですが、そのままでは Ruri の最大サイズを超えているのでエラーになります

### ウェブ検索の設定

docker-compose.yml の以下の部分でウェブ検索に関する設定をしています

```yml
...
      ENABLE_RAG_WEB_SEARCH: True
      ...
      RAG_WEB_SEARCH_ENGINE: "searxng"
      ...
      SEARXNG_QUERY_URL: "http://searxng:8080/search?lang=ja&q=<query>"
      QUERY_GENERATION_PROMPT_TEMPLATE: |-
...
```

- `ENABLE_RAG_WEB_SEARCH: True`: ウェブ検索を有効化します
- `RAG_WEB_SEARCH_ENGINE: "searxng"`: ウェブ検索に SearXNG を使用します
- `SEARXNG_QUERY_URL: "http://searxng:8080/search?lang=ja&q=<query>"`: コンテナのサービス名 `searxng` にアクセスするよう指定しています。また、 `lang=ja` で検索時の言語を日本語に固定しています
- `QUERY_GENERATION_PROMPT_TEMPLATE: |-`: 次行以降でウェブの検索クエリを生成するためのプロンプトを指定しています。デフォルトのプロンプトに `クエリは必ず **日本語** にしてください` の文言を加え、出力例も `クエリ` と日本語にしています。この指定がないと、 `in the given language` と指定していても `What is 〇〇` のような英語混じりのクエリを生成してしまいます

## コンテナの起動

以下のコマンドでコンテナを起動します

```bash
docker compose up
```

しばらくして初回起動が終わると、それぞれ以下の URL でブラウザからアクセスできるようになっています

- Open WebUI: <http://localhost:3000>

  ![スクリーンショット 2025-03-01 9.41.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/bde54cb6-65a4-4d0a-8d93-30d0316701af.png)

- SearXNG: <http://localhost:8080>

  ![スクリーンショット 2025-03-01 17.16.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/3003aa0d-c347-41fa-8cdb-0a1ee0d604be.png)

### SearXNG の設定変更

searxng/settings.yml に SearXNG の設定ファイルが作成されています

検索結果を JSON 形式で返せるように `formats` の値として `json` を追加します

```diff_yaml
...
  formats:
    - html
+   - json
...
```

変更を保存したら `Ctrl + c` で一度コンテナを停止し、再度コンテナを起動します

```bash
docker compose up
```

この状態で別ターミナルから以下のコマンドを実行すると、 JSON 形式の検索結果が取得できます

```bash
curl 'http://0.0.0.0:8080/search?lang=ja&q=やせうま&format=json'
```

## Open WebUI の管理者ユーザー作成

<http://localhost:3000> にアクセスし、 Ollama の初期画面を開きます

"Get started" の上の `→` アイコンをクリックしてください

![スクリーンショット 2025-03-01 9.41.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/09a1b872-4675-4b6c-8bed-025d8ed33429.png)

管理者ユーザー作成の画面が表示されるので、適当な値を入力し、 "Create Admin Account" をクリックしてください

![スクリーンショット 2025-03-01 9.41.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/3c9610ac-9f60-4bd2-bdbb-18d898c76d17.png)

リリースノートが表示されるので、右下の "OK、始めましょう！"　をクリックして閉じます

![スクリーンショット 2025-03-01 9.41.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/cb3a7d04-ed43-4c9f-8e10-7066fab99086.png)

検索画面が表示されます

![スクリーンショット 2025-03-01 9.42.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/c9b752f5-992d-4560-b1a2-68647492d9da.png)

## ウェブ検索なしのチャット

左上のモデルに `hf.co/alfredplpl/gemma-2-2b-jpn-it-gguf:latest` を選択します

![スクリーンショット 2025-03-01 9.42.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/c9b752f5-992d-4560-b1a2-68647492d9da.png)

大分県の郷土菓子「やせうま」に関する質問をしてみましょう

![スクリーンショット 2025-03-01 9.43.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/03450521-5794-4c99-8747-ec726fb1c08d.png)

全くのデタラメです

では、新しいチャットでウェブ検索を有効にして同じ質問をしてみます

![スクリーンショット 2025-03-01 9.43.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/4148bd6d-ac5c-4ffb-8bae-a517b8295c38.png)

ちゃんと正しい答えを返してくれました

## まとめ

ウェブ検索やテキスト埋め込みを日本語に特化させることで、日本語での生成AIチャットを実装できました

情報源をウェブにすることで、軽量なモデルでも多くの知識を扱えるのが良いですね