セルフホスト型の Retrieval-Augmented Generation プラットフォーム。
Markdown / PDF / テキストを投入し、ローカルLLM(Ollama)またはクラウドAPI(Gemini)で質問応答できる。 ポートフォリオサイトに埋め込むチャットBotとして実運用中。
LangChain / LlamaIndex は一切使わず、各SDKを直接呼ぶ自前実装。
| レイヤー | 技術 |
|---|---|
| バックエンド | Python 3.12 + FastAPI |
| パッケージ管理 | uv |
| ベクトルDB | ChromaDB(コサイン距離) |
| LLM(Primary) | Ollama(qwen3.5:9b) |
| LLM(Fallback) | Gemini API(gemini-2.0-flash) |
| Embedding | Ollama(nomic-embed-text デフォルト / bge-m3 推奨) |
| メタデータDB | SQLite(aiosqlite) |
| フロントエンド | React + TypeScript + Vite + Tailwind CSS |
| コンテナ | Docker Compose |
| デプロイ | 自宅サーバー + Cloudflare Tunnel |
Frontend (React + Tailwind)
|
API (FastAPI)
|- /chat 質問応答(SSEストリーミング対応)
|- /documents ドキュメント管理(APIキー認証)
+- /collections コレクション・システムプロンプト管理
|
Core (RAGパイプライン)
|- Ingestion チャンキング (Markdown/PDF/Text)
|- Providers LLM・Embedding抽象化 (ABC -> Ollama/Gemini)
|- VectorStore ChromaDB ラッパー
+- RAG Retriever -> Generator
|
Backends
|- ChromaDB ベクトル検索(組み込みモード)
|- SQLite メタデータ・システムプロンプト管理
|- Ollama ローカルLLM + Embedding
+- Gemini API クラウドLLM(フォールバック)
- 複数形式のドキュメント投入 — Markdown(見出しベースチャンキング + heading_path)、PDF(ページ単位)、プレーンテキスト(段落単位)
- ベクトル検索 — ChromaDB でコサイン距離検索(Embedding モデルは環境変数で切り替え可)
- SSEストリーミング — トークン単位のリアルタイム応答
- LLMプロバイダ差し替え — ABCパターンで Ollama / Gemini を透過的に切り替え
- コレクション別システムプロンプト — サーバーサイドで管理し、クライアントからの改ざんを防止
- API認証 —
X-API-Keyヘッダーで書き込みAPIを保護 - マルチGPU対応 — 環境変数で GPU 割り当て・並列数を制御
- Docker & Docker Compose
- NVIDIA GPU + nvidia-container-toolkit(Ollama用)
git clone https://github.com/luy869/rag-platform.git
cd rag-platform
# 環境変数を設定
cp .env.example .env # 必要に応じて編集
# 起動(初回はモデルのダウンロードに時間がかかる)
docker compose up --build起動後:
- Backend: http://localhost:4000
- Frontend: http://localhost:3000
- Ollama: http://localhost:11434
# ヘルスチェック
curl http://localhost:4000/health
# ドキュメント投入
curl -X POST http://localhost:4000/documents/upload \
-H "X-API-Key: your_api_key" \
-F "file=@example.md" \
-F "collection_name=default"
# 質問(非ストリーミング)
curl -X POST http://localhost:4000/chat/ \
-H "Content-Type: application/json" \
-d '{"question": "RAGとは何ですか?", "collection_name": "default"}'
# 質問(ストリーミング)
curl -N -X POST http://localhost:4000/chat/ \
-H "Content-Type: application/json" \
-d '{"question": "RAGとは何ですか?", "collection_name": "default", "stream": true}'portfolio-bot/ にプロフィールデータ投入スクリプトがある。
# プロフィールデータを投入
cd portfolio-bot
python ingest.py
# システムプロンプトを設定
curl -X PUT http://localhost:4000/collections/portfolio/system-prompt \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{"system_prompt": "'"$(cat system_prompt.md)"'"}'質問応答。stream: true で SSE ストリーミング。
Request: {"question": "...", "collection_name": "default", "stream": false}
Response: {"answer": "...", "source_files": ["example.md"]}
ストリーミング時は text/event-stream で以下のイベントを返す:
data: {"type": "chunk", "content": "こん"}
data: {"type": "complete", "answer": "...", "source_files": [...]}
ドキュメント投入。multipart/form-data で送信。
Form: file=@example.md, collection_name=default
Response: {"status": "success", "document_id": "uuid-xxx", "chunk_count": 5}
ドキュメント削除。ChromaDB とメタデータ DB の両方からクリーンアップ。
コレクション一覧取得。
コレクション作成。
コレクション削除。
コレクション別システムプロンプト設定。
ヘルスチェック。
rag-platform/
├── README.md
├── CLAUDE.md プロジェクト指針
├── PLAN.md 開発計画書
├── DEVELOPMENT_LOG.md 開発ログ(面接用)
├── RAG_TEXTBOOK.md 技術解説教科書
├── docker-compose.yaml
│
├── backend/
│ ├── dockerfile
│ ├── pyproject.toml uv 依存管理
│ ├── uv.lock
│ ├── app/
│ │ ├── main.py FastAPI エントリポイント
│ │ ├── config.py 設定管理
│ │ ├── api/
│ │ │ ├── auth.py API キー認証
│ │ │ └── routes/
│ │ │ ├── chat.py チャット(SSE対応)
│ │ │ ├── documents.py ドキュメント管理
│ │ │ └── collections.py コレクション管理
│ │ ├── core/
│ │ │ ├── ingestion/
│ │ │ │ ├── markdown.py 見出しベースチャンキング
│ │ │ │ ├── pdf.py ページ単位チャンキング
│ │ │ │ └── text.py 段落単位チャンキング
│ │ │ ├── providers/
│ │ │ │ ├── base.py ABC(LLMProvider / EmbeddingProvider)
│ │ │ │ ├── ollama.py Ollama 実装
│ │ │ │ └── gemini.py Gemini 実装
│ │ │ ├── vectorstore/
│ │ │ │ └── chroma.py ChromaDB ラッパー
│ │ │ └── rag/
│ │ │ ├── pipeline.py RAG パイプライン
│ │ │ ├── retriever.py ベクトル検索
│ │ │ └── generator.py LLM 回答生成
│ │ ├── db/
│ │ │ └── metadata.py SQLite メタデータ管理
│ │ └── models/
│ │ └── chunk.py Chunk データクラス
│ └── tests/ ユニットテスト
│
├── frontend/ React UI
├── portfolio-bot/ プロフィール投入スクリプト
│ ├── profile.md プロフィールデータ
│ ├── system_prompt.md ポートフォリオBot用プロンプト
│ ├── ingest.py 投入スクリプト
│ └── ingest_default.py デフォルトコレクション用
│
└── docker-compose.yaml
.env を作成して設定:
# Ollama
OLLAMA_HOST=http://ollama:11434
OLLAMA_EMBED_MODEL=bge-m3 # デフォルト: nomic-embed-text(日本語精度を上げる場合は bge-m3 を推奨)
# bge-m3 を使う場合は事前に: ollama pull bge-m3
# GPU 設定
GPU_DEVICES=0 # 使用する GPU(マルチ GPU: 0,1)
OLLAMA_PARALLEL=1 # Ollama 同時リクエスト数
# API 認証
API_KEY=your_secret_key # 未設定で認証スキップ(開発用)
# CORS
CORS_ORIGINS=http://localhost:3000 # カンマ区切りで複数指定可
# Gemini(オプション)
GEMINI_API_KEY=your_api_keycd backend
uv run pytest -vcd backend
uv add <package>.venvは anonymous volume で除外する(ホストマウントとの競合を防ぐ)pyproject.tomlのrequires-python、.python-version、Dockerfile の Python バージョンを必ず統一する
| 判断 | 理由 |
|---|---|
| 自前実装 > LangChain | ブラックボックス排除、面接で全部説明可能、依存最小化 |
| FastAPI > Flask | ネイティブ非同期、SSE、Pydantic 統合 |
| ChromaDB > Qdrant | 組み込みモード、追加サービス不要 |
| SSE > WebSocket | 一方向通信で十分、実装がシンプル |
| 見出しベース > 固定長チャンキング | 文書構造を活用、heading_path でコンテキスト保持 |
| bge-m3 > nomic-embed-text | 日本語セマンティック検索の精度(OLLAMA_EMBED_MODEL で切り替え) |
| SQLite > PostgreSQL | ゼロ設定、メタデータ規模に十分 |
| chat() > generate() | メッセージロール分離でプロンプトインジェクション対策 |
MIT