OpenWebUI + SearXNG でローカルLLMにWeb検索を追加する手順 ― Docker構築から日本語設定・つまずきどころまで

ローカルLLMの最大の弱点は「最新情報を知らない」ことです。OpenWebUI にはWeb検索機能が組み込まれており、検索エンジンとしてセルフホストの SearXNG を指定すれば、外部の検索APIキー無しで、完全ローカル志向のまま「今日の天気」「最新リリース」に答えられるようになります。

この記事では、Docker で SearXNG を立てるところから、OpenWebUI との接続、日本語検索の設定、そして実機で踏んだつまずきどころまでを一本にまとめます。筆者は家庭内の複数マシンでローカルLLMの分散基盤を自作しているのですが、本記事はその環境固有の話を切り離し、素の OpenWebUI + Ollama 構成でそのまま再現できる手順に絞っています。

全体像と前提

構成は3つだけです。

  • Ollama ― ローカルLLMの実行(導入済み前提)
  • OpenWebUI ― チャットUI(導入済み前提)
  • SearXNG ― セルフホストのメタ検索エンジン。今回 Docker で新規に立てる

SearXNG はオープンソースのメタ検索エンジンで、Google や Bing など複数の検索エンジンに代理で問い合わせて結果をまとめて返します。自前でホストするので検索クエリが検索API事業者のアカウントに紐づかず、APIキーも課金も不要です。

手順1: SearXNG を Docker で立てる

docker-compose.yml を作ります。ポイントはコメントに書いたとおりです。

version: '3.8'
services:
  searxng:
    image: searxng/searxng:latest
    container_name: searxng
    restart: unless-stopped
    ports:
      - "192.168.0.10:8888:8080"   # LAN内のみにバインド(0.0.0.0公開は避ける)
    volumes:
      - ./searxng:/etc/searxng:rw
    environment:
      - SEARXNG_BASE_URL=http://192.168.0.10:8888/
      - GRANIAN_HOST=0.0.0.0       # IPv6無効カーネル環境では必須(後述)
    dns:
      - 8.8.8.8
      - 1.1.1.1

192.168.0.10 は SearXNG を動かすホストの LAN アドレスに読み替えてください。OpenWebUI と同居させるなら 127.0.0.1:8888:8080 でも構いません。SearXNG は誰でも使える検索の踏み台になり得るので、インターネットへ公開しないバインドにするのが大前提です。

Ubuntu 22.04 の素のサーバーで作業する場合、Docker まわりで2つ細かい注意があります。docker.io パッケージの Docker には docker compose サブコマンドが無いので、sudo apt install docker-compose(ハイフン付きの別パッケージ)が必要です。

手順2: formats: json を有効にする(最重要)

初回起動で ./searxng/settings.yml が生成されます。SearXNG はデフォルトで HTML しか返さず、OpenWebUI が使う JSON API は無効です。ここを直さない限り OpenWebUI からの検索は永遠に失敗します。

# searxng/settings.yml
search:
  formats:
    - html
    - json        # ← これを追加(OpenWebUI 連携の必須設定)

あわせて日本語検索を既定にしたい場合は同じ settings.yml に検索言語を指定します。

search:
  default_lang: "ja"    # 検索結果を日本語優先に

もうひとつ、SearXNG にはボット対策の limiter があり、環境によっては OpenWebUI からの機械的なアクセスがブロックされることがあります。LAN 内限定で運用するなら無効化してしまうのが簡単です。

# searxng/settings.yml
server:
  limiter: false        # LAN内限定運用なら無効化してよい

設定変更後は docker restart searxng で反映し、JSON API が生きているかを curl で確認します。

curl -s "http://192.168.0.10:8888/search?q=Python&format=json" \
  | python3 -m json.tool | head -10

検索結果の JSON が返ってくれば SearXNG 側は完成です。筆者環境では25件の結果が返りました。OpenWebUI に繋ぐ前に、必ずこの curl を先に通してください。後で動かないとき、SearXNG 側か OpenWebUI 側かの切り分けがこの一手で済みます。

手順3: OpenWebUI に SearXNG を登録する

OpenWebUI の管理者パネル → 設定 → Web検索 を開きます(バージョンにより表記が多少異なります)。

  • Web検索を有効にする: オン
  • 検索エンジン: searxng
  • Searxng クエリ URL: http://192.168.0.10:8888/search?q=<query>
  • 検索結果数・同時リクエスト数: まずは既定値のままで可

<query> はこのままのリテラルで書きます(OpenWebUI が実行時に置換します)。OpenWebUI を Docker で動かしている場合、127.0.0.1 はコンテナ自身を指してしまうので、ホストの LAN アドレスか host.docker.internal を使う点に注意してください。

設定後、チャット画面の入力欄にある Web検索トグル(地球アイコン/+メニュー)をオンにして「今日の東京の天気は?」のような質問を投げれば、検索結果のソース付きで回答が返ります。

つまずきどころ(実機で踏んだもの)

検索結果が返らない・403 になる → formats: json の漏れ

いちばん多いパターンです。手順2の formats: json を入れたか、変更後にコンテナを再起動したかを確認してください。curl に format=json を付けて直接叩くのが確実な切り分けです。

SearXNG コンテナが起動直後に落ちる → IPv6 無効カーネル

カーネルで IPv6 を無効化しているサーバーでは、SearXNG の WSGI サーバー(granian)がデフォルトで IPv6 ソケットを開こうとして起動に失敗します。compose に GRANIAN_HOST=0.0.0.0 を追加すれば解決します。筆者はこれで一度ハマりました。

検索はできるが遅い・止まる → limiter とアップストリームのブロック

limiter が有効なままだと機械アクセスが弾かれることがあります(手順2参照)。また SearXNG が代理で叩く上流エンジン側に一時的にブロックされることもあり、その場合は settings.yml で使用エンジンを調整します。

小さいローカルLLMだと検索が重くなる → 内部タスクを把握する

OpenWebUI の Web 検索は、裏で「検索クエリの生成」を選択中のモデルにやらせています。さらにタイトル自動生成・タグ自動生成も同じモデルに流れます。数BクラスのローカルLLMをCPUで動かしている環境では、この隠れた内部タスクが本命の推論と競合して極端に遅くなることがあります。重いと感じたら、管理者設定でタイトル/タグの自動生成をオフにする、クエリ生成用に軽量モデルを指定する(設定がある版の場合)、を検討してください。筆者はこの内部タスクが原因の誤動作を一度本格的に調査するはめになりました(詳細記事)。

回答の数字を鵜呑みにしない

検索が0件だったり失敗したりしたとき、小型のローカルLLMは「最高気温は27.7度です」のようなもっともらしい数字を平気で創作します(筆者が障害注入テストで実際に観測した挙動です)。検索ソースが表示されているかを確認する習慣をおすすめします。

さらに進めたい人へ ― 分散・自動判定・自作ルーティング

ここまでで「手動でWeb検索トグルを入れて使う」構成は完成です。筆者のサイトでは、この先の発展形として、家庭内の複数マシンにLLMを分散し、質問内容から「Web検索が要るか」を自動判定して専用Workerに振り分ける自作基盤を連載で記録しています。

「ローカルLLMに検索を持たせる」は、一枚の設定画面の裏に検索エンジン・クエリ生成・要約・フォールバックという結構な奥行きがあります。まずは本記事の最小構成で動かして、必要になったら奥へどうぞ。