こちらの記事で Web サイトによくある『よくある質問』の内容を参照して回答する簡単な RAG を作ってみました。次は、これをチャットボット化してみようと思います。
今回目指すのは、 Wordpress で使うようなチャットボットをイメージしています。
なお、以下の関連記事がありますので、こちらもよろしく。
- OpenAI API で RAG をやってみた
- RAG をチャットボットに仕立てる (1/2): FastAPI コンテナ作成 <--- ここ
- RAG をチャットボットに仕立てる (2/2):チャットボットの実装
API にする
いままでは、コマンドラインでループ内で質問を受け付けていたので、まずはこれを質問を投げたら答えが返ってくるような API に仕立てようと思います。
ChatGPT に聞いたところ FastAPI がおすすめとのこと。ざっと、 FastAPI について調べてみると、めっちゃ簡単に REST API を作ることができるライブラリっぽいです。
FastAPIを用いたAPI開発テンプレート #Python - Qiita
ということで、早速やってみます。
準備は、
pip install fastapi uvicorn
でいいみたいです。
FastAPI を使って、APIを作るのは、メソッド名のデコレータを書いていけばよいようです。その際、デコレータの引数に API パスを書いておけば、そのパスでアクセスしたときに応答してくれるとのことです。
from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException, Query from pydantic import BaseModel import vector_index query_engine = None # POST のペイロードを定義 class FaqQuery(BaseModel): question: str = Query( min_length=1, max_length=1000, description="The question to query the FAQ system.", ) app = FastAPI(lifespan=lifespan) # 問い合わせ エンドポイント @app.post("/query/") def query(payload: FaqQuery): """ Handle the FAQ query. """ global query_engine if not query_engine: return {"error": "Query engine is not initialized."} try: response = query_engine.query(payload.question) # レスポンスの整形 result = { "answer": str(response), "references": [ {"title": d.get("title", ""), "url": d.get("url", "")} for d in response.source_nodes[0].metadata.get("references", []) ], } return result except Exception as e: print(f"error: {str(e)}") raise HTTPException(status_code=500, detail=str(e))
POST で受け取るデータは、 BaseModel というのを継承してクラスを定義すればいいそうです。このとき、API呼び出しを処理する関数(この例だと query )に型ヒントがあると、送られてきたデータのバリデーションもやってっくれるそうです。めっちゃ便利。
毎回の API 呼び出しのたびに、 RAG のデータを読み込む(こちらの記事で作った、 vector_index.load_query_engine を呼びだす)のはいかにも効率が悪いので、実行時に一度だけ読ませるようにします。
ChatGPT によると @app.on_event("startup") というデコレータで定義すればいい、と出てきたんですが、すでに非推奨になっているらしくて、いまは、
# 起動前と後に実行されるのはこうやって定義するらしい @asynccontextmanager async def lifespan(app: FastAPI): """ Application lifespan event to initialize the query engine. """ global query_engine try: # Initialize the query engine query_engine = vector_index.load_query_engine() yield finally: # Cleanup if necessary query_engine = None
のように定義するっぽいです。
これらを1つのファイル(rag_fastapi.py)にまとめておきます。
テスト
これを呼び出して動かしてみます。コマンドラインから
.venvmor@DESKTOP-DE7IL4F:~/tmp/openai/samples/ragSample/app$ uvicorn rag_fastapi:app --reload --port 8000 INFO: Will watch for changes in these directories: ['/home/mor/tmp/openai/samples/ragSample/app'] INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) INFO: Started reloader process [52743] using StatReload INFO: Started server process [52745] INFO: Waiting for application startup. INFO: Application startup complete.
こんな感じに起動します。引数の rag_fastapi:app の部分は、実行したいファイル(rag_fastapi.py)のappオブジェクト、を指しています。 --reload はファイルに変更があった際に自動的に再読み込みしてくれるオプションだそうです。--port はいわずもがなポート番号です。
実際に API をたたいてみます。 VSCode の REST Client
という拡張機能を入れると POST でも GET でも(別のメソッドでも)簡単にリクエストを投げられるとのことなので、これで試してみます。
拡張子を .http にしたファイルを作成し、
という感じにリクエストを書きます。先頭行に、メソッド名(GETは省略可)とURL、続く行にヘッダ、1行開けて、POSTのペイロードという感じですね。
このファイルの拡張子が .http だと、URLの上側に Send Request と表示されるので、これをクリックすると
こんな感じに応答が返ってきます。いい感じやね。
(参考)別の実行方法
さきほどは uvicorn というサーバーをコマンドラインから実行しましたが、他の方法もあります。先ほどのファイルの最後に、
if __name__ == "__main__": import uvicorn # Start the FastAPI application uvicorn.run(app, host="127.0.0.1", port=8000)
とつけておけば、
.venvmor@DESKTOP-DE7IL4F:~/tmp/openai/samples/ragSample/container/app$ python rag_fastapi.py INFO: Started server process [62108] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
とするだけでも呼び出すことができます。ご参考までに。
docker コンテナとして起動
さて、次は、 Wordpress からこの API を呼べるようにしてやる必要があります。テストなんでお手軽に試すため QNAP の Container Station を利用してコンテナを立てます。
Wordpress のコンテナのサンプルはいっぱいあるので後回しにして、まず先にさきほど作った FastAPI をdocker コンテナに仕立ててやります。
ちょっと調べてみると、こちらの記事がほぼやりたいことを実現してくれてます。
ということで、これに従って作業します。
なお、今回作業したフォルダはこんな感じにしました。
. ├── .env ├── Dockerfile ├── app │ ├── __index__.py │ ├── faiss_index │ │ ├── default__vector_store.json │ │ ├── (略) │ │ └── index_store.json │ ├── faq_dict.py │ ├── rag_fastapi.py │ └── vector_index.py ├── docker-compose.yml └── requirements.txt
docker-compose.yml
コンテナを起動するための docker-compose.yml は次のようにします。
services: fastapi: container_name: fastapi build: . restart: always ports: - 8000:8000 environment: - OPENAI_API_KEY
ここで、 OpenAI の API キーは .env に書いておいてこれを渡すようにします。
# added by Junichi MORI, 2025/6/5, for openai api OPENAI_API_KEY="API キー"
Dockerfile
docker-compose.yml では Dockerfile からイメージを作っています。この Dockerfile は次のようにしました。
FROM python:3.13-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY ./app . CMD ["uvicorn", "rag_fastapi:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]
requirements.txt
LlamaIndex の処理と FastAPI を実行するために必要なものを書いておきます。
llama-index faiss-cpu llama-index-vector-stores-faiss openai fastapi uvicorn
ビルドとコンテナの起動
最初は、 Contaienr Station の『アプリケーション』から docker-compose.yml を与えて、コンテナを起動させます。もちろんこの段階では、起動に失敗します。
次に、上記の一式をローカルで作成して、これらを QNAP NAS の Container Station 所定のアプリケーションフォルダに ssh でログインしてコピーします。
ssh でアプリケーションフォルダにいる状態で、
[user1@nas01 rag-test]$ docker compose build [+] Building 7.5s (11/11) FINISHED docker:default (略)
のようにして、イメージをビルドしておきます。
次に、 Contaienr Station の画面に戻って、 アプリケーション全体を再起動します。問題がなければ、 FastAPI がコンテナで起動されます。
Wordpress コンテナを追加
ここまでできたら、この FastAPI のコンテナを呼び出すため、 docker-compose.yml に Wordpress のコンテナを追加します。
services: fastapi: container_name: fastapi build: . restart: always ports: - 8000:8000 environment: - OPENAI_API_KEY wordpress: image: wordpress restart: always ports: - 8080:80 environment: WORDPRESS_DB_HOST: db WORDPRESS_DB_USER: exampleuser WORDPRESS_DB_PASSWORD: examplepass WORDPRESS_DB_NAME: exampledb volumes: - wordpress:/var/www/html db: image: mysql:8.0 restart: always environment: MYSQL_DATABASE: exampledb MYSQL_USER: exampleuser MYSQL_PASSWORD: examplepass MYSQL_ROOT_PASSWORD: wordpress volumes: - db:/var/lib/mysql volumes: wordpress: db:
Wordpress に関する部分は、 DockerHub の Wordpress のサンプルから持ってきました。
QNAP NAS の ContainerStation の画面から、さきほど動かした FastAPI のアプリケーションを選択して、歯車アイコンをクリックして、『再作成』を選びます。
すると、docker-compose.yml を記入する画面が表示されるので、上記の Wordpresss を追加した内容に書き換えます。
このまま『更新』をクリックすると、3つのコンテナからなるアプリケーションが起動します。
ちょっと面倒なのが、この方法で再作成すると、 fastapi コンテナを作ったときのファイル等はいったん削除されてしまいます(フォルダごと削除されて、再作成されます)。fastapi イメージが残っているので、 fastapi コンテナの起動はできているのですが、 .env ファイルも消されているので、 docker-compose.yml に書いている OpenAI API キー が引き渡されないため正しく動作しません。
実際、 fastapi コンテナにリクエストしても、こんな感じに
Connection Error となって返ってきます。
なので、 .env ファイルを再度コピーしておきます。faiss インデックスのファイルや Dockerfile はすぐには不要なのですが、イメージの再ビルドに備えて、一緒に他のファイルもコピーしておきます。
ということで、 .env ファイルコピー後に、もう一度 fastapi のコンテナを作り直しておきます。
[user1@nas01 rag-test]$ docker compose up -d --no-deps --force-recreate fastapi
Wordpress のセットアップ
コンテナが問題なく起動したら、指定したポート番号(この例だと 8001)へアクセスしてみます。すると、 こんな感じの
言語選択画面が表示されます。『日本語』を選択して、進むと、引き続いて、
こんな感じの初期設定画面(管理者ユーザー名とパスワードの指定画面)が表示されるので、必要な項目を入力して、一番下の『WordPress をインストール』ボタンをクリックしてしばらく待つと、
と画面が表示され、セットアップ完了です。
この状態で、問題なくサイトを表示したり、管理画面にログインできることを確認しておきます。
まとめ
さて、これで準備ができました。次はチャットボットを実装していきます。