「完全無料で、データが外部に出ないAIチャットボットを作りたい」そう思い立って、いろいろ試してみた結果、なんとか動くものは出来上がりましたが、想像以上に大変でした。
本記事は、実際にチャットボットを構築・検証した作業記録をもとに執筆しています。外部記事や調査レポートからの引用はなく、掲載している設定値・エラー内容・応答速度はすべて検証時の実測値です。
【目次】
- なぜローカル環境で作ろうと思ったのか
- 環境準備:Docker と Dify のインストール
- Ollama でローカルモデルを動かす
- Dify と Ollama の接続
- Web 検索との連携:DuckDuckGo から SearXNG へ
- 日本語対応のためのシステムプロンプトの工夫
- 実際に動かしてみた結果
- まとめ:それでも得られたもの
なぜローカル環境で作ろうと思ったのか
ChatGPT や Claude は非常に便利ですが、毎月の利用料金と「データがどこかのサーバーに送られている」という懸念が常につきまといます。特に社内情報や個人情報を扱う業務では、クラウドAIの利用に慎重にならざるを得ません。そこで今回は、自分のMacの中だけで完結するAIチャットボットの構築に挑戦しました。使用したのは Ollama(ローカルLLM実行環境)と Dify(オープンソースのAIアプリ開発プラットフォーム)の組み合わせです。
環境準備:Docker と Dify のインストール
まずは必要なツールの準備をしていきます。
Docker Desktop のインストール
まず Docker Desktop を公式サイトからダウンロードしてインストールします。Apple Silicon(M1/M2/M3)の場合は「Mac with Apple Chip」版を選んでください。インストール後、Docker Desktop を起動しておきます。
Dify のセットアップ
Dify はオープンソースの LLM アプリ開発プラットフォームです。以下のコマンドを実行することで、Docker Compose で一発起動できます。
git clone <https://github.com/langgenius/dify.git> cd dify/docker cp .env.example .env docker compose up -d
初回はイメージのダウンロードに数分かかります。完了したら「 http://localhost:8080 」からアクセスできます。管理画面が表示されればセットアップ完了です。
Ollama でローカルモデルを動かす
Ollama のインストール
以下のコマンドを実行します。
brew install ollama
モデルの選択
モデル選びには意外な落とし穴がありました。最初に試した Qwen 2.5(7B) は高性能ですが、モデルの出所やガバナンスの観点から、よりオープンな選択肢として Meta 製の Llama 3.1に切り替えました。
以下のコマンドを実行します。
ollama pull llama3.1:8b
GPU を有効にして起動
Apple M3 の Metal GPU をフル活用するため、全インターフェースで起動します。
OLLAMA_HOST=0.0.0.0:11434 ollama serve
上記のコマンドを実行後、ollama ps コマンドで 100% GPU と表示されれば成功です。
Dify と Ollama の接続
ここが最初の山場でした。Dify は Docker コンテナ上で動いているため、localhost:11434 では Ollama に届きませんでした。コンテナの中から見ると localhost は「自分自身(コンテナ)」を指してしまうからです。macOS では host.docker.internal というホスト名を使うことで、コンテナからホストの Mac に接続できます。
Dify → 設定 → モデルプロバイダー → Ollama で Base URL を以下のように設定します。
http://host.docker.internal:11434
背景: Ollama を Docker コンテナ内で動かすと、macOS の GPU(Metal)にアクセスできず CPU のみの動作になるようです。処理速度が大幅に低下するため、Ollama はホストの Mac で動かすことにしました。
Web 検索との連携:DuckDuckGo から SearXNG へ
Llama 3 は 2023年12月頃までしか学習データを保持していないようでした。そこで、チャットボットにリアルタイムの情報を持たせるため、Web 検索ツールを追加しました。
DuckDuckGo の限界
最初は DuckDuckGo を選びました。しかし短時間に複数回検索するとレート制限に引っかかり、安定して使えない場面が出てきました。
SearXNG の自己ホスト
そこでヨーロッパ発のオープンソース検索エンジンで、完全無料・制限なし・プライバシー保護の三拍子が揃って SearXNG の自己ホストに切り替えました。
Dify の docker-compose.yaml に以下を追加して同じ Docker ネットワークに入れる形で設定します。
searxng:
image: searxng/searxng:latest
restart: always
ports:
- "8888:8080" #※1
volumes:
- ./volumes/searxng:/etc/searxng
networks:
- default #※2
Dify からは http://searxng:8080 でアクセスできます。同じ Docker ネットワーク内ではコンテナ名でそのまま通信できるのが便利です。
設定ファイル(settings.yml)には以下の記述が必須です。
server:
secret_key: "your-custom-key" #※3
limiter: false bind_address: "0.0.0.0"
port: 8080
search:
formats:
- html
- json #※4
補足
- ※1:ホスト側のポート8888でアクセス可能にする設定
- ※2:Difyと同じnetworkに所属させることでコンテナ名で通信可能に
- ※3:デフォルト値のままだと起動が拒否されるため、必ず独自の値に変更すること
- ※4:この設定がないとAPIから利用できない
エージェントモードの選択が成否を分ける
Dify のエージェントには ReAct と Function Calling の2種類があります。
ReAct モード
モデルに「決まった書き方で答えてください」とお願いするイメージです。ただし、これは人間に「必ずこの書式で書いて」と頼むのと同じで、約束を守ってもらえるとは限りません。実際、モデルが指定した形式を守らなかったり、途中で崩れたりすることがあります。Llama 3.1(8B)は比較的小さいモデルなので、この「書き方のルール」を守り続けるのが苦手です。
Function Calling モード
「書き方」をモデルではなくシステム側が管理します。モデルは「何を検索したいか」だけ考えればよく、フォーマットはDifyが自動的に整えてくれます。
料理に例えると以下のようなイメージです。
- ReAct モード:シェフに「必ずこのレシピ通りに作って」とお願いする(守れないこともある)
- Function Calling モード:シェフは食材を選ぶだけ、調理手順はロボットが自動でやる(確実)
最初は ReAct モードを使っていましたが、Llama 3.1 が決められた JSON フォーマットを正しく出力できず、検索を何度も繰り返してループから抜け出せない事態が続きました。
結果として、Function Calling モードに切り替えることで安定しました。 モデル設定で「Function call support: Yes」を有効にすることが前提条件です。また、最大反復回数はデフォルトの10から 3に下げることをおすすめします。無駄なループを防ぎ、応答速度が改善されることが想定されるためです。
日本語対応のためのシステムプロンプトの工夫
日本語で質問しても英語で返してくる問題が発生しました。その問題に対しては、以下のシステムプロンプトが効果的でした。
"You are a helpful Japanese assistant.
・Always respond in Japanese only. Never use English.
・Use search tools to find current information.
・Summarize search results clearly in Japanese.
・Be concise and accurate."
ローカルモデルへの日本語指示は、日本語よりも英語で書いた方が効きやすいという点は興味深い発見でした。英語が苦手でも、Google Translate などで英訳してシステムプロンプトに記載しておくことをおすすめします。
実際に動かしてみた結果
最終的には、日本語で質問すると SearXNG で検索し、結果を日本語でまとめて返すチャットボットが完成しました。「今日の MLB の結果を教えてください。大谷と村上の結果も」という質問に対して、検索結果を元にある程度日本語で回答できることを確認しました。
ただし、設定が不足しているのか、以下の制限がありました。
項目 | 実態 |
|---|---|
応答速度 | 1回の返答に30〜50秒かかる |
日本語精度 | 指示が100%守られるわけではない。回答に英語が混ざることがある |
情報の正確性 | 古い情報が混ざることがある |

これがローカルモデルの現状の限界なのか、設定の最適化不足によるものなのかは判断できていません。ただし、GPT-5やClaudeと同等の品質を期待するのは現実的ではないでしょう。
まとめ:それでも得られたもの
「完全ローカル・完全無料・データ外部送信ゼロ」これらの条件を満たすAIチャットボットを、ローカル環境(Mac)で動かすことができました。社内の機密情報を扱う用途や、クラウドAIのコストを抑えたい場面では十分に実用的だと考えれます。また、何より、AIの裏側の仕組み(モデル、エンドポイント、エージェント、ツール連携)を手を動かしながら理解できたことが大きな収穫でした。「完全無料のローカルAI」は夢ではなさそうです。ただし、設定作業にはそれなりに時間がかかることを覚悟しておく必要があります。
