はじめに
本記事は、生成AIの良回答を 育てる ための 専門チャット検索機能 を作った話 の続編です。
なお、本記事の記載内容は、構成要素 と 処理フロー を設計として整理するにとどめ、実装コードの記載はしておりません。
理由はコードはライブラリのバージョンや実行環境で陳腐化しやすい一方、設計は新たな実装時にも再利用しやすいためです。
本記事で説明すること
- サーバー起動 と 検索 を以下の内容を中心に、
緩く説明 ※1します。- コンポーネント図
- サーバー起動時の処理フロー
- 質問から回答までの処理フロー
- RAGアプリとして見た場合の構成要素との対応
- 用語解説
- まとめ
※1. 要するに 本機能 の構成要素は サーバー起動 と 検索 だけではない、ということです。(笑)
コンポーネント図
補足説明
GCE上の構造
VMインスタンス に配置される 本機能 は、以下のレイヤで構成されます。
| レイヤ | 目的 |
|---|---|
| Guest OS (Debian) | ブートディスクのソースイメージとして指定する Guest OS(Debian) です。 |
| Nginx | 本機能 の フロントエンド を配置します。 |
| Docker Container (chat-search-app) | 本機能 の バックエンド を配置します。 |
レイヤ毎の構成
Nginx
WebUI
- インデックスHTML
| コンポーネント | 目的 |
|---|---|
| Search | 検索UI です。以下の 検索条件 を実装します。 - カテゴリ。 - サジェスト。 - 質問。 - 前提。 - 要件。 - 依頼。 |
| 回答 | 回答UI です。 質問に対する コーパスから回答 または Gemini API 連携 結果を表示します。 |
- メンテナンス中HTML
| コンポーネント | 目的 |
|---|---|
| メンテナンス中HTML |
AIモデル ロード中(準備中)、または ワーカースレッド 準備中に 本機能 にアクセスした場合当該HTMLを表示して、メンテナンス中 である旨をクライアントに知らせます。 メンテナンス中 とした理由は、準備中となる要因がデプロイ処理(メンテナンス)であるためです。 |
Docker Container (chat-search-app)
ExpressApp (Node.js + Express)
エンドポイント
- サーバー
| コンポーネント | 目的 |
|---|---|
| サーバー | 初期化(Top-level処理) サーバー起動 |
- API
| コンポーネント | 目的 |
|---|---|
| API | WebUIからのリクエストを元に コーパス を検索対象として サービス を呼び出します。検索結果を元に ルーティング判定 を呼び出して 判定結果 を取得します。 判定結果 を元に回答(コーパスから回答 または Gemini API 連携)を WebUI に返却します。 |
サービス
ビジネスロジックを担当する層です。
| コンポーネント | 目的 |
|---|---|
| サービス | サービスとして以下の機能を実装します。 ・設定ファイルアクセスサービス ・カテゴリ別ナレッジベースアクセスサービス ・代表クエリアクセスサービス ・コーパスアクセスサービス ・評価レポートアクセスサービス ・検索サービス 1. BM25検索。 2. ベクトル検索。 3. RRF統合。 |
リポジトリ
データアクセスを担当する層です。
| コンポーネント | 目的 |
|---|---|
| リポジトリ | リポジトリとして以下の機能を実装します。 - 設定ファイル の読み込み。- カテゴリ別ナレッジベース の読み込み。- 代表クエリ の読み込み。- コーパス の 読み込み と 書き込み。- 評価レポート の書き込み。 |
ワーカースレッド
| コンポーネント | 目的 |
|---|---|
| ワーカークライアント | ベクトル検索 から呼び出され、ワーカー に 推論 を委譲します。 |
| ワーカー | ワーカークライアントからの POSTメッセージ受信 をトリガーに、推論 を呼び出します。 |
推論
| コンポーネント | 目的 |
|---|---|
エンベディング |
質問に対する ベクトル値 を取得します。 |
ルーティング
| コンポーネント | 目的 |
|---|---|
| ルーティング判定 | RRF統合スコアから コーパスから回答 または Gemini API 連携 を判定して判定結果を返却します。 |
CLIツール
運用スクリプト
| コンポーネント | 目的 |
|---|---|
コーパス作成スクリプト |
カテゴリ別ナレッジベース を読み込みトークナイズ(分かち書き) と ベクトル を埋め込んだ コーパス を出力します。 |
保守スクリプト
| コンポーネント | 目的 |
|---|---|
評価レポート作成スクリプト |
代表クエリ と コーパス を読み込み BM25検索、ベクトル検索、RRF統合 を実行して推奨しきい値 を埋め込んだ 評価レポート を出力します。 |
Guest OS (Debian)
スクリプト
導入
| コンポーネント | 目的 |
|---|---|
ディレクトリ構成構築スクリプト |
本機能 の ディレクトリ構成の構築 と設定ファイル のテンプレートを出力します。 |
運用
| コンポーネント | 目的 |
|---|---|
カテゴリ別ナレッジベース作成スクリプト |
カテゴリ別ナレッジベース のテンプレートを Markdown で出力します。 |
デプロイ処理スクリプト |
GCE 上で動作する Docker Container のデプロイを自動化します。 |
データモデル
前記事の データモデル を参照してください。
AIモデル
前記事の AIモデル を参照してください。
サーバー起動時の処理フロー
補足説明
この処理フローは、Docker Container 内で Node.js プロセスが起動され Express がポート 3000 を listen する状態になるまでを表しており
大きく以下の 4 フェーズに分かれます。
- フェーズ1: Top-level処理
- フェーズ2: 初期データの読み込み と Gemini API クライアント生成
- フェーズ3: EagerLoad
- フェーズ4: サーバー起動
フェーズ1: Top-level処理
-
dotenvライブラリにより.envの内容を取得します。dotenv.config() -
プロキシ環境下でのクライアントIPアドレス取得を有効化します。
app.set("trust proxy", 1) -
ミドルウェア適用
-
morgan
- HTTP リクエストロギング用のミドルウェア morgan を組込みます。
-
express.json()
- JSON ボディパーサーミドルウェアの組込みを組込みます。
-
-
APIエンドポイント登録呼び出し
- API側の初期処理
- ミドルウェア適用
- limiter ミドルウェアを 検索API に設定します。
-
express-rate-limitにより 1分間に10回までのリクエスト を許可します。設定内容は以下の通りです。- ウィンドウ: 1分間
- 最大リクエスト数: 10回
-
- ミドルウェア readiness判定を 検索API に設定します。
- readiness判定とは
AIモデルや ワーカースレッド が準備できていない場合、検索API を受け付けない仕組みです。
- readiness判定とは
- limiter ミドルウェアを 検索API に設定します。
- ミドルウェア適用
- API側の初期処理
フェーズ2: 初期データの読み込み と Gemini API クライアント生成
- 初期データの読み込み
設定ファイルコーパス代表クエリ
- Gemini API クライアント生成
- Google が提供している Node.js/TypeScript 向けの公式 Gen AI SDK (@google/genai パッケージ) を使用します。
フェーズ3: EagerLoad
-
EagerLoadを非同期に実行 ※1
※1.
EagerLoad の処理内容は 検索サービス と同じです。
検索サービス の詳細は「質問から回答までの処理フロー」の フェーズ2: 検索サービス(BM25検索・ベクトル検索・RRF統合)呼び出し を参照して下さい。
技術的制約
- リソース制約
- e2-micro インスタンスは CPU/メモリ が限られています。
- e2-micro(2個の vCPU, 1GBメモリ) + Swap領域 4GB
- このため、
ONNXモデルを使用した パイプラインロード と推論に大きな負荷が掛かり処理速度に影響を与えます。 -
EagerLoadを実行することにより、初回リクエスト時の待ち時間を短縮しています。
- e2-micro インスタンスは CPU/メモリ が限られています。
フェーズ4: サーバー起動
- 3000番ポートで HTTP リクエストのリスニングを開始します。
質問から回答までの処理フロー
補足説明
この処理フローは、利用者 が WebUI から検索を実行した際に、
ExpressApp上でどのように処理が進み、コーパスから回答 または Gemini API 連携 により回答が返却されるまでを
API を中心に表しており、大きく以下の 4 フェーズに分かれます。
- フェーズ1: リクエストパラメータ取得
- フェーズ2: サービス(BM25検索・ベクトル検索・RRF統合)呼び出し
- フェーズ3: ルーティング判定(コーパスから回答 or Gemini API 連携)呼び出し
- フェーズ4: 結果作成とレスポンス返却
フェーズ1: リクエストパラメータ取得
- 以下のリクエストパラメータを取得します。
- カテゴリ。
- 質問。
- 前提。
- 要件。
- 依頼。
フェーズ2: 検索サービス(BM25検索・ベクトル検索・RRF統合)呼び出し
検索は Precision@5 を指標として、BM25検索 と ベクトル検索 を実行し
BM25検索結果(スコア) と ベクトル検索結果(スコア) を RRF統合 により統合して最終スコアの順位付けをします。
1. BM25検索
- スコア算出
- カテゴリで絞り込んだ
コーパスを対象に、質問とコーパスのBM25関連度をスコア算出します。
- カテゴリで絞り込んだ
- スコア降順ソート
- 算出したスコアを降順にソートし、上位5件 を抽出して返却します。
2. ベクトル検索
- ワーカースレッド
- ワーカークライアント から ワーカー を呼び出します。
- ワーカー から
推論を呼び出します。-
推論- 量子化された
ONNXモデルをxenova/transformersによって パイプラインロード し パイプラインインスタンス を取得します。 - 取得した パイプラインインスタンス によって、質問に対するベクトル値を取得します。
-
onnxruntime-nodeによって質問を量子化されたONNXモデルに入力します。 - 量子化された
ONNXモデルがエンベディングを実行して質問のベクトル値を取得します。
-
- 量子化された
-
- スコア算出
- カテゴリで絞り込んだ
コーパスを対象に、質問とコーパスのベクトルがどの程度近いかをコサイン類似度でスコア算出します。
- カテゴリで絞り込んだ
- スコア降順ソート
- 算出したスコアを降順にソートし、上位5件 を抽出して返却します。
3. RRF統合
- BM25検索結果(スコア) と ベクトル検索結果(スコア) を RRFで統合 し、RRF統合スコア を算出してスコア降順にソートします。
フェーズ3: ルーティング判定(コーパスから回答 or Gemini API 連携)呼び出し
- 最上位RRF統合スコア(RRF統合スコアの先頭) と
設定ファイルの しきい値 を比較し、以下の判定結果を返却します。- 最上位RRF統合スコア が しきい値以上
- コーパスから回答
- 最上位RRF統合スコア が しきい値未満
- Gemini API 連携
- 最上位RRF統合スコア が しきい値以上
フェーズ4: 結果作成とレスポンス返却
コーパスから回答
- コーパスから回答 を作成して API から WebUI にレスポンスを返却します。
Gemini API 連携
- Gemini API 連携 を実行、連携結果から回答を作成して API から WebUI にレスポンスを返却します。
RAGアプリとして見た場合の 構成要素 との対応
本機能、一般的なRAG構成に当てはめると次のようになります。
| RAGの要素 | 本機能での構成要素 | 設計上の意味 |
|---|---|---|
| Knowledge Source | カテゴリ別ナレッジベース |
外部知識の元になるデータです。 |
| Index | コーパス |
検索しやすい形に構造化した外部知識です。 BM25検索用情報とベクトル検索用情報を、あらかじめ埋め込みます。 |
| Retriever | サービス |
外部知識を検索するための仕組みです。 本機能ではBM25検索、ベクトル検索、RRF統合で外部知識を取得します。 |
| Embedder | 推論 |
質問や外部知識の埋め込みベクトルを取得する AIモデル です。 |
| Orchestrator | API + サービス + ルーティング判定 |
システム全体のデータの流れを制御するコントローラです。 検索、判定、生成AIフォールバックを制御します。 |
| Generator | Gemini API |
質問が外部知識に無い場合、生成AIで回答生成を行います。 |
| UI | WebUI |
利用者の質問入力と回答表示を担当します。 |
用語解説
| 用語 | 役割と意味 |
|---|---|
| RAG | Retrieval-Augmented Generation の略です。 生成AIに直接すべてを答えさせるのではなく 検索で取得した外部知識を根拠として回答生成に利用する仕組みです。 |
コーパス |
検索対象として整形された文書集合です。 本機能ではカテゴリ別ナレッジベースを検索用JSONに変換したものを指します。 |
EagerLoad |
処理時間を抑える目的のため 関連データを予め読み込む方式です。 |
| readiness判定 | アプリケーションがリクエストを受け付けられる状態かを示すことです。 本機能では AIモデル のロード状態、ワーカースレッド の状態を見て システム利用可能/システム利用不可 を判定します。 |
| Precision@5 | 上位5件の検索結果のうち、正解がどれだけ含まれているかを表す評価指標です。 |
| トークナイズ(分かち書き) | 文章を意味のある単位(単語・形態素)に分割します。 例: 質問が "DAO 設計 JPA SQL" の場合 "dao", "設計", "jpa", "sql" に分割。 BM25検索の入力パラメータになります。 |
| BM25検索 | キーワード一致に強い検索方式です。 単語の出現頻度と文書長をもとに関連度を計算します。 |
BM25関連度 |
質問と文書に含まれる単語を比較し、重要な単語がどの程度一致しているかを数値化します。 |
| ベクトル検索 | 文章のベクトルに対して、意味の近さで検索する方式です。 表現が違っても意味が近い文書を拾いやすくします。 |
コサイン類似度 |
質問と文書をそれぞれベクトルとして表し、2つのベクトルの方向がどの程度近いかを数値化します。 方向が近いほど、質問と文書の意味が似ていると判断します。 |
| RRF統合 | Reciprocal Rank Fusion の略です。 複数の検索結果を順位ベースで統合し、検索方式ごとの偏りを抑え順位付けする方式です。 |
しきい値 |
コーパスから回答 するか Gemini API 連携 するかを判定する 最小の境界値 です。 |
| ワーカースレッド | Node.jsでCPU負荷の高い処理をメインスレッドから分離して実行する仕組みです。 |
AIモデル |
文章をベクトルに変換するモデルです。 |
ONNXモデル |
Open Neural Network Exchange の略です。AIモデル を ONNXという共通形式 で保存したものです。本機能では ONNXモデル を 推論 のために使用します。 |
| onnxruntime-node |
ONNXモデル を Node.js環境 からネイティブ実行する 推論基盤 です。 |
| @xenova/transformers |
ONNXモデル で エンベディング するためのコントローラーで、以下を実行します。- 文章のトークナイズ(単語ID化)。 - onnxruntime-node 呼び出し。 - ONNXモデル が出力した単語ごとのベクトルを平均化して、文章に対する384次元の数値配列を出力します。 |
推論 |
onnxruntime-node によって文章を ONNXモデル に入力し、ベクトル値を取得することです。 |
エンベディング |
文章をベクトルに変換する処理で、ベクトル検索の入力パラメータになります。 本機能では文章をベクトル化するために - @xenova/transformers - model_quantized.onnx を使って、日本語を含む多言語文章を検索用のベクトルに変換しています。 |
| 量子化モデル | 精度の低下を最小限に抑えて、INT8などの 重みパラメータの精度を縮小・離散化 する技術を用いて軽量化した AIモデル です。メリットはメモリ消費量が少なく、ファイルサイズも小さいので高速に 推論 する事が可能となります。推論精度は非量子化モデルに比べ、95% 〜 99%(実用上ほぼ同等) です。 |
| 非量子化モデル | 高精度(FP32)な重みパラメータを持つ精度の高い AIモデル です。メリットは推論精度が高く、モデル本来の性能を 100%(基準値として) 発揮できる事です。 |
まとめ
第二段では、専門チャット検索機能の現在構成を、コードではなく設計として整理しました。
第三段記事は、ホスティング環境 とした理由と、コンポーネント図 とした理由を、課題一覧 の 課題No.1 プラットフォームの選定 と紐付けて 説明 します。


