開発ドキュメント統合
開発ドキュメント統合
このドキュメントは、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
システムフロー
- 管理者アカウントは開発者が発行し、カスタムクレームにtenantId(企業ID)・roleのtenantOwner(企業アカウント管理者)を付与
- 管理者アカウントは同じtenantIdを持つユーザーを作成可能で、カスタムクレームに同じtenantIdをセットしroleにはtenantEditor(編集者)かtenantViewer(閲覧者)を付与
- ユーザーはクライアントアプリからFirestorageにファイルをアップロード
- アップロードされたファイルの情報(id・tenantId・name・url・isConverted・uploadedUserId)をfirestoreに保存
- CloudFunctionsまたはユーザーの操作でファイルをベクトル変換しQdrantに保存
- QdrantへのベクトルデータにはtenantId・docId・pageNumberなどをペイロードとして付与
- ユーザーはログイン後getIdTokenResultを実行してFirebaseAuthのJWTを取得
- ユーザーはこのJWTと質問の文字列をリクエストに入れてAPIを叩く
- APIではトークンを検証して認証ユーザーからのリクエストであることを確認
- トークンをデコードして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などを入れて検索対象を絞る手法。
認証の流れ
- FirebaseAuthのユーザーが作成される時にAdminSDKを使用してカスタムクレームでtenantIdを付与する
- クライアントアプリ側からログイン後にgetIdTokenでJWTを取得する
- このJWTと質問文字列のみ使用してAPIリクエストを投げる
- API側でこのトークンを検証してOKならQdrantにアクセス
- QdrantにはペイロードにtenetIdが保存されてベクトルを保存しておく
- ペイロードの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文字未満)
- 一文字ごとの改行が多い
- 「|」などの縦書き特有の記号が多い
方向別の処理フロー
横書きテキストの処理フロー:
- ページ区切りによる分割
- ルールベースの区切り
- 意味ベースの区切り
縦書きテキストの処理フロー:
- ルールベースの区切り
- 意味ベースの区切り
ルールベース分割
テキストを構造的特徴に基づいて分割します。
分割ルール:
- 日本語の文末表現(。.!?など)での分割
- 段落、見出し、箇条書きなどの構造的特徴での分割
- 目標チャンクサイズ(約300文字)に最適化
- 文の途中で切れないように調整
意味ベース分割
テキストの意味的なまとまりに基づいて分割します。
分割方法:
SemanticChunkerを使用した意味的なまとまりでの分割- エンベディング間の差異に基づいて分割ポイントを決定
- 上位10%の差異で分割(percentile=90)
階層的チャンク化
異なるサイズのチャンクを生成し、階層構造を作成します。
チャンクサイズ:
- 大チャンク(約1000文字):広い文脈を保持
- 中チャンク(約300文字):標準的な検索用
- 小チャンク(約100文字):高精度の検索用
6. API側実装方針
ディレクトリ構成例
.├── Dockerfile├── .dockerignore└── src └── main.pyFastAPI実装サンプル
from fastapi import FastAPI, Request, HTTPExceptionfrom fastapi.responses import JSONResponseimport osimport requestsfrom pathlib import Pathfrom 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.1RUN 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 8080EXPOSE 8080
# コンテナ起動時に実行するコマンドCMD ["poetry", "run", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8080"]7. Cloud Runデプロイ
config作成
これはSDK内に保存されるだけなので各PC環境で対応が必要
gcloud config configurations create spella-rag-api-devgcloud config configurations create spella-rag-api-stggcloud config configurations create spella-rag-api-prod紐づけ
gcloud config configurations activate spella-rag-api-devgcloud config set account n.miyagawa@spella.techgcloud config set project spella-rag-dev
gcloud config configurations activate spella-rag-api-stggcloud config set account n.miyagawa@spella.techgcloud config set project spella-rag-stg
gcloud config configurations activate spella-rag-api-prodgcloud config set account n.miyagawa@spella.techgcloud config set project spella-rag-prod確認
gcloud config configurations listで以下のように表示されればOK
spella-rag-api-dev False n.miyagawa@spella.tech spella-rag-devspella-rag-api-prod True n.miyagawa@spella.tech spella-rag-prodspella-rag-api-stg False n.miyagawa@spella.tech spella-rag-stgDockerイメージ作成とデプロイ
.dockerignoreを作成して.venvを無視- Dockerイメージを作成してデプロイ
make deploy-dev8. Cloud Functionsのデプロイ
FunctionsはPoetryを直接使用できないため通常のrequirements.txtを使用する
手順
- brewでpythonをインストール
brew install python@3.12- firebaseディレクトリでエディタを開いて以下を実行
python3.13 -m venv functions-py/venvsource functions-py/venv/bin/activatepip install -r functions-py/requirements.txt-
エディタ開き直してmain.pyを開き右下のバーで
'venv': venvのインタプリタを選択 (出てこなければEnter interpreter pathでfunctions-py/venvを入力 ) -
IAMの
compute@developer.gserviceaccount.com的なアカウントに「ストレージバケット閲覧者」を付与する -
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}")- makeコマンドをfirebaseディレクトリで実行してデプロイ(例:dev)
make deploy-dev9. 環境構築(example)
pyenvのインストール
brew install pyenvpyenvの環境変数をzshrcに追加
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrcecho 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrcecho 'eval "$(pyenv init --path)"' >> ~/.zshrcsource ~/.zshrcpyenvで任意のバージョンを追加
pyenv install 3.11.5poetryのインストール
curl -sSL https://install.python-poetry.org | python3 -echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrcsource ~/.zshrcプロジェクト設定
エディタでexampleディレクトリを開いて以下を実行
poetry config virtualenvs.in-project truepoetry installPoetry関連操作
誤って無駄に仮想環境を作成してしまった場合は、
poetry env listで削除対象を探して
poetry env remove $対象で消す
各プロジェクトに.venvがあり、かつインタプリタに.venv: PoetryがあればOK
10. FlutterアプリFlutterFireコマンド
Adminアプリ
Dev
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.plistStg
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.plistProd
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.plistUserアプリ
Dev
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.plistStg
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.plistProd
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.plist11. 社内RAG開発案
先日櫻井さんから聞いた「社員評価用の参考情報を取得したい」という用途については、MCPサーバーを文脈ハブとして使用して
- DBへの直接データアクセス
- 外部MCPサーバーへのアクセス
- ベクトル化したデータへのアクセス
の処理を内容に応じて処理を分岐または併用させるのが良さそう。
[フロントエンド or アプリ]↓[MCPサーバ]├─ 📊 構造化DBアクセス(例:PostgreSQL)├─ 🧠 ベクトルDBアクセス(例:Qdrant)↓🔧 文脈整形 + プロンプト構築↓[LLM(OpenAIなど)]↓[回答 + 出典 + 引用元など返却]MCPサーバ内の主要処理ロジック
- ユーザーからの質問を受け取る
- 分類または意図理解(質問の種類を判断)
- 構造化DBから情報を取得(例:社員名、所属、スキルなど)
- ベクトルDBから文脈を取得(例:PR・日報・Slackなど)
- それらをまとめてテンプレートに落とし込む
- LLMに渡すプロンプトを構築
- (必要なら)LLMに問い合わせて回答生成
- 回答+出典+データを返す
12. RAGシステム概要
構成概要
-
ドキュメント取り込み・前処理
- データソース(ファイル、Webページ、DBなど)から原文を取り込み、適切に前処理(クレンジング、テキスト分割、正規化)を行います。
-
埋め込み生成
- 前処理済みテキスト(文書、チャンク)に対して、事前学習済みの埋め込みモデル(例:OpenAI、SentenceTransformers等)を使い、各テキストの**ベクトル表現(埋め込み)**を生成します。
-
ベクトルDBへの格納
- 生成したベクトルとともに、元のテキストやメタデータ(例:ソース、チャンク番号、作成日時など)をベクトルDB(例:ChromaDB、Milvus、Pinecone、PostgreSQL+pgvector等)に保存します。
-
クエリ受付・前処理
- ユーザからの問い合わせを受け取ったら、同様に問い合わせテキストを埋め込み生成し、ベクトル表現に変換します。
-
ベクトル類似検索
- クエリのベクトルを用いて、ベクターDBに対して類似検索を実行し、関連性の高い文書(または文書のチャンク)を上位K件抽出します。
-
LLMによる回答生成
- 取得した関連文書(コンテキスト)とユーザの問い合わせを組み合わせ、LLM(Large Language Model)へ入力します。
-
レスポンス返却
- LLMの生成結果をユーザに返却します。
13. スモールRAGの作成手順
初期環境設定
pyenvのインストール
brew install pyenvpyenvの環境変数をzshrcに追加
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrcecho 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrcecho 'eval "$(pyenv init --path)"' >> ~/.zshrcsource ~/.zshrcpyenvで任意のバージョンを追加
pyenv install 3.11.5開発を行う箇所でディレクトリを作成
pyenv local 3.11.5.gitignore作成
echo "# Python caches__pycache__/*.pyc
# Env.env
# Mac metadata.DS_Store" > .gitignorepoetryのインストールと環境変数追加
curl -sSL https://install.python-poetry.org | python3 -echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrcsource ~/.zshrcプロジェクト初期化
poetry initライブラリ追加
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"関連ファイル作成
mkdir docs srctouch docs/sample_manual.mdtouch src/main.py実行方法
通常のビルド(Dockerではない)
poetry run python src/main.py
// env導入・FastAPI化後ならpoetry run uvicorn src.main:app --host 0.0.0.0 --port 8000Dockerビルド
docker build -t spella_rag:latest .Docker実行
docker run --rm -it \--env-file .env \spella_rag:latestFastAPI化
docker build -t spella_rag .docker run -p 8000:8000 -v $(pwd)/docs:/app/docs --env-file .env spella_raghttp://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
[削除済み - セキュリティ上の理由により非表示]その他のコマンド
gcloudコマンド
- 削除:
gcloud config configurations delete (config名) - 確認:
gcloud config configurations list - アクティベート:
gcloud config configurations activate 環境名