コンテンツにスキップ

開発ドキュメント統合

開発ドキュメント統合

このドキュメントは、RAGシステム開発に関する技術的な実装詳細、環境構築、デプロイ手順などを統合したものです。

1. RAGシステム開発 構成案

システム構成

  • クライアントアプリ:Flutter
  • クライアント側API:Cloud Run(Python)
  • ベクトルデータベース:Qdrant
  • ベクトル変換:Cloud Functions(Python)
  • データベース:Firestore
  • ファイルストレージ:Firestorage
  • ユーザー認証:Firebase Auth
graph TD
    A[クライアントアプリ(Flutter)]
    B[ユーザー認証
(Firebase Auth)] C[データベース
(Firestore)] D[ファイルストレージ
(Firestorage)] E[API
(Cloud Run)] F[ベクトル変換
(Cloud Functions)] G[ベクトルデータベース
(Qdrant)] A --> B A --> C A --> D A <--> E E <--> G D --> F F --> G

システムフロー

  1. 管理者アカウントは開発者が発行し、カスタムクレームにtenantId(企業ID)・roleのtenantOwner(企業アカウント管理者)を付与
  2. 管理者アカウントは同じtenantIdを持つユーザーを作成可能で、カスタムクレームに同じtenantIdをセットしroleにはtenantEditor(編集者)かtenantViewer(閲覧者)を付与
  3. ユーザーはクライアントアプリからFirestorageにファイルをアップロード
  4. アップロードされたファイルの情報(id・tenantId・name・url・isConverted・uploadedUserId)をfirestoreに保存
  5. CloudFunctionsまたはユーザーの操作でファイルをベクトル変換しQdrantに保存
  6. QdrantへのベクトルデータにはtenantId・docId・pageNumberなどをペイロードとして付与
  7. ユーザーはログイン後getIdTokenResultを実行してFirebaseAuthのJWTを取得
  8. ユーザーはこのJWTと質問の文字列をリクエストに入れてAPIを叩く
  9. APIではトークンを検証して認証ユーザーからのリクエストであることを確認
  10. トークンをデコードしてtenantIdを取得し、ペイロードのtenantIdでフィルターしたうえで、類似ベクトルを検索し、回答を生成してユーザーに返す

2. Agentic RAG開発 構成案

flowchart TD
    U[ユーザー(Flutter)]
    F[Firebase Auth]
    A[Cloud Run API]
    C[意図分類処理
(**LangChain等**)] V[ベクトルDB
(Qdrant等)] S[構造化DB
(SQL・Firestore等)] X[外部API / 外部MCPサーバー] L[LLM
(回答生成)] U -->|JWT取得| F U -->|質問+JWT| A A -->|トークン検証| F A --> C C --> V V --> L C --> S S --> L C --> X X --> L L --> U

追加機能候補

  • ソースごとの重みづけや優先度制御
  • ログ収集/評価メトリクス追加
  • LangChain / LangGraphを使ってAIエージェント寄りにする

構造化DBへのアクセスもMCPサーバーでいい感じに問い合わせ可能:

  • PDF等のファイル ▶︎ 読み取ってベクトルDBで保存してクエリ投げる(もしくはAdobeやOfficeが提供するMCPサーバーを使うか作る)
  • 既存DBのデータ ▶︎ MCPサーバー作って問い合わせる
  • その他の非構造化データ ▶︎ ベクトルDBに保存してクエリ投げる

3. 複数企業データ保存方法

Qdrantのクラスターにはコレクションが存在するが、コレクションを契約企業ごとに増やすのは推奨されておらず、推奨されているのはベクトルデータにペイロードを付与し、そのペイロードに企業IDなどを入れて検索対象を絞る手法。

認証の流れ

  1. FirebaseAuthのユーザーが作成される時にAdminSDKを使用してカスタムクレームでtenantIdを付与する
  2. クライアントアプリ側からログイン後にgetIdTokenでJWTを取得する
  3. このJWTと質問文字列のみ使用してAPIリクエストを投げる
  4. API側でこのトークンを検証してOKならQdrantにアクセス
  5. QdrantにはペイロードにtenetIdが保存されてベクトルを保存しておく
  6. ペイロードのtenantIdが一致する文書のみを検索対象にする

4. 回答の情報ソースの表示について

セクション分割戦略

  • PDFを読み取った後、自動判定 and ルールベース判定で内容をセクションごとに区切る(例:2条・3条)
  • 1セクションを1つのベクトルデータとして保存する
  • 情報源の表示のために以下のデータをベクトルデータのペイロードに保存する
{
"tenant_id": "tenant_abc123",
"document_id": "manual_2025",
"document_title": "製品マニュアル2025年版",
"page": 18,
"section_name": "第8条 損害賠償",
"section_meanings": ["損害賠償", "責任範囲"],
"content": "本契約に違反した場合の損害賠償責任について定める...",
"updated_at": "2025-04-26T10:00:00Z"
}

インデックス設定

フィルタに使用する項目ならindexを張る必要がある(schema明示するとindexが作成される)

client.recreate_collection(
collection_name="documents",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
payload_schema={
"tenant_id": PayloadSchemaType.keyword,
"section_meanings": PayloadSchemaType.keyword,
"document_id": PayloadSchemaType.keyword
}
)

5. 文書チャンク戦略

概要

RAGシステムで実装されているPDFテキストのチャンク化戦略について説明します。日本語テキストの特性(縦書き・横書き)を考慮した高度なチャンク戦略を採用し、検索精度と文脈保持のバランスを最適化しています。

テキスト方向の判定(縦書き/横書き)

PDFから抽出したテキストが縦書きか横書きかを自動的に判定します。

判定方法:

  • 一文字ごとの改行パターンの検出
  • 特定の記号(「|」など)の使用頻度の分析
  • 行の長さの統計分析

縦書きの特徴:

  • 平均行長が短い(通常10文字未満)
  • 一文字ごとの改行が多い
  • 「|」などの縦書き特有の記号が多い

方向別の処理フロー

横書きテキストの処理フロー:

  1. ページ区切りによる分割
  2. ルールベースの区切り
  3. 意味ベースの区切り

縦書きテキストの処理フロー:

  1. ルールベースの区切り
  2. 意味ベースの区切り

ルールベース分割

テキストを構造的特徴に基づいて分割します。

分割ルール:

  • 日本語の文末表現(。.!?など)での分割
  • 段落、見出し、箇条書きなどの構造的特徴での分割
  • 目標チャンクサイズ(約300文字)に最適化
  • 文の途中で切れないように調整

意味ベース分割

テキストの意味的なまとまりに基づいて分割します。

分割方法:

  • SemanticChunkerを使用した意味的なまとまりでの分割
  • エンベディング間の差異に基づいて分割ポイントを決定
  • 上位10%の差異で分割(percentile=90)

階層的チャンク化

異なるサイズのチャンクを生成し、階層構造を作成します。

チャンクサイズ:

  • 大チャンク(約1000文字):広い文脈を保持
  • 中チャンク(約300文字):標準的な検索用
  • 小チャンク(約100文字):高精度の検索用

6. API側実装方針

ディレクトリ構成例

.
├── Dockerfile
├── .dockerignore
└── src
└── main.py

FastAPI実装サンプル

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
import os
import requests
from pathlib import Path
from dotenv import load_dotenv
app = FastAPI()
# 環境変数から現在の環境を取得
env = os.getenv("ENV", "development")
# APIディレクトリのパスを取得
api_dir = Path(__file__).parent.parent
# 環境に応じた.envファイルを読み込む
load_dotenv(api_dir / f".env.{env}")
# 環境変数から各種キー・URLを取得
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
QDRANT_URL = os.environ.get("QDRANT_URL")
QDRANT_API_KEY = os.environ.get("QDRANT_API_KEY")
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.post("/query")
async def query(request: Request):
try:
data = await request.json()
if not data or "query" not in data:
raise HTTPException(status_code=400, detail="Missing 'query' in request data")
query_text = data["query"]
# Qdrant API にリクエストを転送する例
headers = {"Content-Type": "application/json"}
if QDRANT_API_KEY:
headers["Authorization"] = f"Bearer {QDRANT_API_KEY}"
# ※ Qdrant のエンドポイント例。実際の仕様に合わせて URL やパラメータを調整してください。
qdrant_search_url = f"{QDRANT_URL}/search"
payload = {
"query": query_text,
# Qdrant API に必要な他のパラメータがあれば追加してください
}
response = requests.post(qdrant_search_url, json=payload, headers=headers)
response.raise_for_status()
result = response.json()
return JSONResponse(content=result)
except HTTPException as http_exc:
return JSONResponse(content={"error": http_exc.detail}, status_code=http_exc.status_code)
except Exception as e:
return JSONResponse(content={"error": "Failed to query Qdrant", "details": str(e)}, status_code=500)

Dockerfile

# ベースイメージとして Python 3.11-slim を利用
FROM python:3.11-slim
# Poetryインストール
ENV POETRY_VERSION=1.5.1
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
RUN curl -sSL https://install.python-poetry.org | python -
ENV PATH="/root/.local/bin:${PATH}"
# 作業ディレクトリを設定
WORKDIR /app
# 依存関係のファイルをコピーし、インストール
COPY pyproject.toml poetry.lock /app/
RUN poetry install --no-root --no-interaction --no-ansi --no-cache
# アプリケーションコードを全てコピー
COPY . .
# Cloud Run はコンテナのポート 8080 を利用するため、EXPOSE 8080
EXPOSE 8080
# コンテナ起動時に実行するコマンド
CMD ["poetry", "run", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8080"]

7. Cloud Runデプロイ

config作成

これはSDK内に保存されるだけなので各PC環境で対応が必要

Terminal window
gcloud config configurations create spella-rag-api-dev
gcloud config configurations create spella-rag-api-stg
gcloud config configurations create spella-rag-api-prod

紐づけ

Terminal window
gcloud config configurations activate spella-rag-api-dev
gcloud config set account n.miyagawa@spella.tech
gcloud config set project spella-rag-dev
gcloud config configurations activate spella-rag-api-stg
gcloud config set account n.miyagawa@spella.tech
gcloud config set project spella-rag-stg
gcloud config configurations activate spella-rag-api-prod
gcloud config set account n.miyagawa@spella.tech
gcloud config set project spella-rag-prod

確認

Terminal window
gcloud config configurations list

で以下のように表示されればOK

Terminal window
spella-rag-api-dev False n.miyagawa@spella.tech spella-rag-dev
spella-rag-api-prod True n.miyagawa@spella.tech spella-rag-prod
spella-rag-api-stg False n.miyagawa@spella.tech spella-rag-stg

Dockerイメージ作成とデプロイ

  • .dockerignoreを作成して .venv を無視
  • Dockerイメージを作成してデプロイ
Terminal window
make deploy-dev

8. Cloud Functionsのデプロイ

FunctionsはPoetryを直接使用できないため通常のrequirements.txtを使用する

手順

  1. brewでpythonをインストール
Terminal window
brew install python@3.12
  1. firebaseディレクトリでエディタを開いて以下を実行
Terminal window
python3.13 -m venv functions-py/venv
source functions-py/venv/bin/activate
pip install -r functions-py/requirements.txt
  1. エディタ開き直してmain.pyを開き右下のバーで'venv': venvのインタプリタを選択 (出てこなければ Enter interpreter pathfunctions-py/venv を入力 )

  2. IAMのcompute@developer.gserviceaccount.com 的なアカウントに「ストレージバケット閲覧者」を付与する

  3. Functionsの関数側でリージョンを指定する

@storage_fn.on_object_finalized(region="asia-northeast1")
def test_gcs_trigger(event: storage_fn.CloudEvent[storage_fn.StorageObjectData]) -> None:
print(f"📦 ファイル: {event.data.name} バケット: {event.data.bucket}")
  1. makeコマンドをfirebaseディレクトリで実行してデプロイ(例:dev)
Terminal window
make deploy-dev

9. 環境構築(example)

pyenvのインストール

Terminal window
brew install pyenv

pyenvの環境変数をzshrcに追加

Terminal window
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init --path)"' >> ~/.zshrc
source ~/.zshrc

pyenvで任意のバージョンを追加

Terminal window
pyenv install 3.11.5

poetryのインストール

Terminal window
curl -sSL https://install.python-poetry.org | python3 -
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

プロジェクト設定

エディタでexampleディレクトリを開いて以下を実行

Terminal window
poetry config virtualenvs.in-project true
poetry install

Poetry関連操作

誤って無駄に仮想環境を作成してしまった場合は、

Terminal window
poetry env list

で削除対象を探して

Terminal window
poetry env remove $対象

で消す

各プロジェクトに.venvがあり、かつインタプリタに.venv: PoetryがあればOK

10. FlutterアプリFlutterFireコマンド

Adminアプリ

Dev

Terminal window
flutterfire configure \
--project=spella-rag-dev \
--out=lib/firebase_options_dev.dart \
--android-app-id=tech.spella.rag.admin.dev \
--ios-bundle-id=tech.spella.rag.admin.dev \
--android-out=android/app/src/dev/google-services.json \
--ios-out=ios/dev/GoogleService-Info.plist \
--macos-bundle-id=tech.spella.rag.admin.dev \
--macos-out=macos/dev/GoogleService-Info.plist

Stg

Terminal window
flutterfire configure \
--project=spella-rag-stg \
--out=lib/firebase_options_stg.dart \
--android-app-id=tech.spella.rag.admin.stg \
--ios-bundle-id=tech.spella.rag.admin.stg \
--android-out=android/app/src/stg/google-services.json \
--ios-out=ios/stg/GoogleService-Info.plist \
--macos-bundle-id=tech.spella.rag.admin.stg \
--macos-out=macos/stg/GoogleService-Info.plist

Prod

Terminal window
flutterfire configure \
--project=spella-rag \
--out=lib/firebase_options_prod.dart \
--android-app-id=tech.spella.rag.admin \
--ios-bundle-id=tech.spella.rag.admin \
--android-out=android/app/src/prod/google-services.json \
--ios-out=ios/prod/GoogleService-Info.plist \
--macos-bundle-id=tech.spella.rag.admin \
--macos-out=macos/prod/GoogleService-Info.plist

Userアプリ

Dev

Terminal window
flutterfire configure \
--project=spella-rag-dev \
--out=lib/firebase_options_dev.dart \
--android-app-id=tech.spella.rag.user.dev \
--ios-bundle-id=tech.spella.rag.user.dev \
--android-out=android/app/src/dev/google-services.json \
--ios-out=ios/dev/GoogleService-Info.plist \
--macos-bundle-id=tech.spella.rag.user.dev \
--macos-out=macos/dev/GoogleService-Info.plist

Stg

Terminal window
flutterfire configure \
--project=spella-rag-stg \
--out=lib/firebase_options_stg.dart \
--android-app-id=tech.spella.rag.user.stg \
--ios-bundle-id=tech.spella.rag.user.stg \
--android-out=android/app/src/stg/google-services.json \
--ios-out=ios/stg/GoogleService-Info.plist \
--macos-bundle-id=tech.spella.rag.user.stg \
--macos-out=macos/stg/GoogleService-Info.plist

Prod

Terminal window
flutterfire configure \
--project=spella-rag \
--out=lib/firebase_options_prod.dart \
--android-app-id=tech.spella.rag.user \
--ios-bundle-id=tech.spella.rag.user \
--android-out=android/app/src/prod/google-services.json \
--ios-out=ios/prod/GoogleService-Info.plist \
--macos-bundle-id=tech.spella.rag.user \
--macos-out=macos/prod/GoogleService-Info.plist

11. 社内RAG開発案

先日櫻井さんから聞いた「社員評価用の参考情報を取得したい」という用途については、MCPサーバーを文脈ハブとして使用して

  • DBへの直接データアクセス
  • 外部MCPサーバーへのアクセス
  • ベクトル化したデータへのアクセス

の処理を内容に応じて処理を分岐または併用させるのが良さそう。

[フロントエンド or アプリ]
[MCPサーバ]
├─ 📊 構造化DBアクセス(例:PostgreSQL)
├─ 🧠 ベクトルDBアクセス(例:Qdrant)
🔧 文脈整形 + プロンプト構築
[LLM(OpenAIなど)]
[回答 + 出典 + 引用元など返却]

MCPサーバ内の主要処理ロジック

  1. ユーザーからの質問を受け取る
  2. 分類または意図理解(質問の種類を判断)
  3. 構造化DBから情報を取得(例:社員名、所属、スキルなど)
  4. ベクトルDBから文脈を取得(例:PR・日報・Slackなど)
  5. それらをまとめてテンプレートに落とし込む
  6. LLMに渡すプロンプトを構築
  7. (必要なら)LLMに問い合わせて回答生成
  8. 回答+出典+データを返す

12. RAGシステム概要

構成概要

  1. ドキュメント取り込み・前処理

    • データソース(ファイル、Webページ、DBなど)から原文を取り込み、適切に前処理(クレンジング、テキスト分割、正規化)を行います。
  2. 埋め込み生成

    • 前処理済みテキスト(文書、チャンク)に対して、事前学習済みの埋め込みモデル(例:OpenAI、SentenceTransformers等)を使い、各テキストの**ベクトル表現(埋め込み)**を生成します。
  3. ベクトルDBへの格納

    • 生成したベクトルとともに、元のテキストやメタデータ(例:ソース、チャンク番号、作成日時など)をベクトルDB(例:ChromaDB、Milvus、Pinecone、PostgreSQL+pgvector等)に保存します。
  4. クエリ受付・前処理

    • ユーザからの問い合わせを受け取ったら、同様に問い合わせテキストを埋め込み生成し、ベクトル表現に変換します。
  5. ベクトル類似検索

    • クエリのベクトルを用いて、ベクターDBに対して類似検索を実行し、関連性の高い文書(または文書のチャンク)を上位K件抽出します。
  6. LLMによる回答生成

    • 取得した関連文書(コンテキスト)とユーザの問い合わせを組み合わせ、LLM(Large Language Model)へ入力します。
  7. レスポンス返却

    • LLMの生成結果をユーザに返却します。

13. スモールRAGの作成手順

初期環境設定

pyenvのインストール

Terminal window
brew install pyenv

pyenvの環境変数をzshrcに追加

Terminal window
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init --path)"' >> ~/.zshrc
source ~/.zshrc

pyenvで任意のバージョンを追加

Terminal window
pyenv install 3.11.5

開発を行う箇所でディレクトリを作成

Terminal window
pyenv local 3.11.5

.gitignore作成

Terminal window
echo "
# Python caches
__pycache__/
*.pyc
# Env
.env
# Mac metadata
.DS_Store
" > .gitignore

poetryのインストールと環境変数追加

Terminal window
curl -sSL https://install.python-poetry.org | python3 -
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

プロジェクト初期化

Terminal window
poetry init

ライブラリ追加

Terminal window
poetry add "langchain>=0.3.15,<0.4.0"
poetry add "openai>=1.60.1,<2.0.0"
poetry add "chromadb>=0.6.3,<0.7.0"
poetry add "unstructured[md]>=0.16.15,<0.17.0"
poetry add "langchain-community>=0.3.15,<0.4.0"
poetry add "python-dotenv>=1.0.1,<2.0.0"
poetry add "tiktoken>=0.6.0"
poetry add "langchain-openai>=0.0.5"
poetry add "sentence-transformers>=2.5.1"

関連ファイル作成

Terminal window
mkdir docs src
touch docs/sample_manual.md
touch src/main.py

実行方法

通常のビルド(Dockerではない)

Terminal window
poetry run python src/main.py
// env導入・FastAPI化後なら
poetry run uvicorn src.main:app --host 0.0.0.0 --port 8000

Dockerビルド

Terminal window
docker build -t spella_rag:latest .

Docker実行

Terminal window
docker run --rm -it \
--env-file .env \
spella_rag:latest

FastAPI化

Terminal window
docker build -t spella_rag .
Terminal window
docker run -p 8000:8000 -v $(pwd)/docs:/app/docs --env-file .env spella_rag

http://localhost:8000/docs にアクセスしてFastAPIのUI内でTry it Outで試せる

14. RAG実務メモ

RAGシステムの精度改善フロー

生成AIを企業で本格的にシステム導入する場合、継続的に精度を測定・改善する運用設計が重要。これからのITエンジニアは簡単なRAGを実装するだけでなく、生成AIシステムの運用保守まで考慮したアーキテクチャの設計力が求められる。

RAGは簡単だけど、RAGシステムを本番運用するのは難しい。AIシステムは精度という不確実でブラックボックスな指標を扱わなければならない。具体的には、業務知見者が定期的にAIの出力をチェックしたり、業務基準が変わった場合は精度基準を見直して学習・評価データを再定義したりする必要がある。

AWSが提唱したRAGの手法

LLMに文書データのジャンル分けとジャンル毎の想定質問の要約をさせておいて、ユーザーがジャンルを指定して質問文を入力すると、LLMが該当ジャンルの要約に基づいて質問文を複数の質問に分けて並列に検索するアーキテクチャ。

クエリ拡張は例えば検索ワードを言い換えて並列検索させる方法(Multi-Query Retriever)やダミー文書を検索ワードにする方法(HyDE)が有名だけど、文書の情報を加味せずクエリを生成するため空振りが多い。

今回の手法は文書のサマリを参考にクエリを生成するため、文書にフィットしやすい利点がある。

RAGの評価方法

RAGは検索部分と回答部分それぞれについて精度を分析してボトルネックを特定するのが定石だけど、必要な評価指標が一通り網羅されている。

少し難しそうに見えるけど中身は単純で、ユーザが入力した質問文に対する検索結果 (Retrieved Chunks) と生成AIの回答 (Model Response) が、模範解答 (GroundTruth Answer) と比べてどの程度項目 (Claim) が合ってるかを生成AI自身に判定させているだけ。

15. 個別プロファイルについて

ユーザーごとに「何を優先すべきか・何にアクセスすべきか」の個別プロファイルを持つ

16. シークレット情報

Qdrant

Dev

URL

[削除済み - セキュリティ上の理由により非表示]

API Key

[削除済み - セキュリティ上の理由により非表示]

Stg・Prod(環境未作成)

SendGrid

dev

Terminal window
[削除済み - セキュリティ上の理由により非表示]

その他のコマンド

gcloudコマンド

  • 削除: gcloud config configurations delete (config名)
  • 確認: gcloud config configurations list
  • アクティベート: gcloud config configurations activate 環境名