Ollama: 第3段階総合実践
第3段階総合実践は「個別機能」から「システムレベル」への跳躍——フルスタックデプロイ、パイプライン構築、モデルオーケストレーションを一挙に。
💡 ヒント: 上級実践は前17レッスンのすべてのスキルを統合する——Dockerデプロイ(L15)+ RAGパイプライン(L14)+ マルチモデルルーティング(L17)+ 量子化選択(L16)+ LangChainオーケストレーション(L13)。「先にデプロイ → パイプライン構築 → ルーティング追加」の順序で段階的に統合し、一度にすべてを構築しようとしないこと。
📋 前提条件: まず以下を習得していること
- レッスン1: ローカルAIの概念と環境
- レッスン2: Ollamaのインストールと環境設定
- レッスン3: CLI基本操作
- レッスン4: モデル管理
- レッスン5: REST API基本
- レッスン6: 第1段階総合実践
- レッスン7: Python SDK統合
- レッスン8: Modelfileカスタムモデル
- レッスン9: GPUとCUDA設定
- レッスン10: マルチモーダルモデル
- レッスン11: OpenAI互換API
- レッスン12: 第2段階総合実践
- レッスン13: LangChain統合
- レッスン14: RAGパイプライン
- レッスン15: Dockerコンテナデプロイ
- レッスン16: モデル量子化と最適化
- レッスン17: マルチモデルオーケストレーション
1. 学べること
- Docker ComposeでのOllama + Chroma + WebUIフルスタックデプロイ
- RAGパイプライン構築とEmbeddingモデル比較
- マルチモデルルーター実装による自動トラフィック分割
- 量子化モデル品質比較実験
- SupportBot最終アーキテクチャ計画
2. SaaS起業家のリアルな事例
💡 ヒント: アーキテクチャ確定後、「能力マトリクス表」(モデル × シナリオ × 指標)で各コンポーネントの選定理由とパフォーマンスベースラインを文書化すること。将来の最適化やモデル変更のトレーサビリティが確保され、チームへの技術的決定の説明にも役立つ。
ℹ️ 情報: 第3段階総合実践は第4段階(セキュリティ/モニタリング/本番デプロイ)の直接の前提条件。この実践完了後、稼働するSupportBot V4(Dockerデプロイ + RAG + マルチモデルルーティング)が完成する。第4段階でさらに強化・最適化する。
(1) ペインポイント:ばらばらの機能が連携できない
AliceはLangChain、RAG、Docker、量子化、オーケストレーションを学んだが、スキルが散在している。統一されたSupportBotアーキテクチャに統合し、最終的な技術選定とデプロイ計画を決定する必要がある。
(2) ソリューション:最終アーキテクチャの決定
flowchart TD
A[ユーザー質問] --> B[ルーター<br/>3B分類器]
B -->|簡単| C[FAQ RAG<br/>3B + Chroma]
B -->|複雑| D[深い回答<br/>8B qwen2.5]
C --> E[応答]
D --> E
E --> F[Dockerデプロイ<br/>Ollama + FastAPI + Chroma]
3. 演習1:Docker Composeフルスタックデプロイ
⚠️ 注: Docker Composeフルスタックデプロイは4つ以上のサービスとGPUパススルーが関わり、初回起動時に様々な問題が発生する可能性がある。まず各サービスを個別にテスト(
docker run ollamaから始め、次にchroma、次にappを追加)、段階的にトラブルシューティングすること。一度にdocker compose upして大量のエラーに直面しないこと。
⚠️ 警告: Docker Composeフルスタックデプロイは4つ以上のサービスとGPUパススルーが関わる。初回起動時に様々な問題が発生する可能性がある。まず各サービスを個別にテスト(
docker run ollamaから始め、次にchroma、次にappを追加)、段階的にトラブルシューティングすること。一度にdocker compose upしないこと。
(1) フルスタックアーキテクチャ
| サービス | イメージ | ポート | 責務 |
|---|---|---|---|
| ollama | ollama/ollama | 11434 | モデル推論 |
| chroma | chromadb/chroma | 8001 | ベクトル保存 |
| webui | open-webui | 3000 | Webインターフェース |
| app | カスタムfastapi | 8000 | ビジネスロジック |
(2) ▶ サンプル:フルスタックデプロイと検証
YAML
# docker-compose.phase3.yml
version: "3.8"
services:
ollama:
image: ollama/ollama
ports: ["11434:11434"]
volumes: ["ollama_data:/root/.ollama"]
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:11434/api/tags"]
interval: 30s
retries: 5
chroma:
image: chromadb/chroma
ports: ["8001:8000"]
volumes: ["chroma_data:/chroma/chroma"]
environment: [ANONYMIZED_TELEMETRY=FALSE]
webui:
image: ghcr.io/open-webui/open-webui:main
ports: ["3000:8080"]
environment: [OLLAMA_BASE_URL=http://ollama:11434]
volumes: ["webui_data:/app/backend/data"]
depends_on:
ollama: {condition: service_healthy}
volumes:
ollama_data:
chroma_data:
webui_data:
BASH
# Deploy and verify
docker compose -f docker-compose.phase3.yml up -d
# Pull models
docker exec ollama ollama pull qwen2.5
docker exec ollama ollama pull nomic-embed-text
docker exec ollama ollama pull llama3.2:3b
# Verify all services
docker compose -f docker-compose.phase3.yml ps
curl http://localhost:11434/api/tags # Ollama
curl http://localhost:8001/api/v1/heartbeat # Chroma
curl http://localhost:3000 # WebUI
出力:
TEXT
Full-stack deployment verified successfully, all services running normally
4. 演習2:RAGパイプラインとEmbedding比較
(1) Embeddingモデル比較次元
| 次元 | nomic-embed-text | mxbai-embed-large | all-minilm |
|---|---|---|---|
| 次元数 | 768 | 1024 | 384 |
| サイズ | 274 MB | 670 MB | 46 MB |
| 英語品質 | 高 | 最高 | 中 |
| 中国語品質 | 中 | 中高 | 低 |
| 速度 | 高速 | 中程度 | 非常に高速 |
(2) ▶ サンプル:Embeddingモデル効果比較
PYTHON
from langchain_ollama import OllamaEmbeddings
from langchain_community.vectorstores import Chroma
import numpy as np
def compare_embeddings(query: str, docs: list[str],
models: list[str]) -> dict:
"""Compare retrieval results across embedding models."""
results = {}
for model_name in models:
embeddings = OllamaEmbeddings(model=model_name)
vectorstore = Chroma.from_texts(
texts=docs, embedding=embeddings,
collection_name=f"test_{model_name.replace('-','_')}"
)
retrieved = vectorstore.similarity_search_with_score(query, k=3)
results[model_name] = [
{"text": doc.page_content[:80], "score": round(score, 4)}
for doc, score in retrieved
]
return results
# Test documents
docs = [
"Return policy: 30 days from purchase, original condition required.",
"Shipping: Free for orders over $50, 3-5 business days delivery.",
"Warranty: 1-year manufacturer warranty for all electronics.",
"International shipping available to 50+ countries with import duties.",
"Refund process: 5-7 business days after return received.",
"Product exchange: Available within 14 days for different size/color."
]
# Compare
results = compare_embeddings(
query="How do I get my money back?",
docs=docs,
models=["nomic-embed-text", "all-minilm"]
)
for model, hits in results.items():
print(f"\n{model}:")
for hit in hits:
print(f" [{hit['score']}] {hit['text']}")
出力:
TEXT
# Function defined successfully
5. 演習3:マルチモデルルーター
(1) 完全ルーター実装
flowchart TD
A[顧客質問] --> B[意図分類器<br/>llama3.2:3b]
B --> C{意図カテゴリ}
C -->|FAQ/配送/注文| D[FAQ RAGパイプライン<br/>3B + Chroma]
C -->|製品/比較| E[深い回答<br/>8B qwen2.5]
C -->|苦情| F[共感応答<br/>8B qwen2.5 + 特殊プロンプト]
D --> G[応答]
E --> G
F --> G
(2) ▶ サンプル:完全ルーター + RAG
PYTHON
import ollama
import json
from langchain_ollama import ChatOllama, OllamaEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
class SmartRouter:
def __init__(self, chroma_path: str = "./chroma_kb"):
self.classifier = ChatOllama(model="llama3.2:3b", temperature=0)
self.small_model = ChatOllama(model="llama3.2:3b", temperature=0.3)
self.large_model = ChatOllama(model="qwen2.5", temperature=0.4)
self.embeddings = OllamaEmbeddings(model="nomic-embed-text")
try:
self.vectorstore = Chroma(
persist_directory=chroma_path,
embedding_function=self.embeddings
)
self.retriever = self.vectorstore.as_retriever(search_kwargs={"k": 3})
except Exception:
self.retriever = None
def classify(self, question: str) -> str:
response = self.classifier.invoke(
f"Classify as one word: faq, product, comparison, complaint, other.\nQuestion: {question}"
)
return response.content.strip().lower()
def answer(self, question: str) -> dict:
intent = self.classify(question)
if intent in ("faq", "other") and self.retriever:
# RAG pipeline for FAQ
docs = self.retriever.invoke(question)
context = "\n\n".join(d.page_content for d in docs)
response = self.small_model.invoke(
f"Context:\n{context}\n\nAnswer based on context: {question}"
)
model_used = "3B + RAG"
elif intent == "complaint":
response = self.large_model.invoke(
f"You are an empathetic customer service agent. "
f"Show understanding and offer solutions:\n{question}"
)
model_used = "8B (empathetic)"
else:
response = self.large_model.invoke(question)
model_used = "8B"
return {
"intent": intent,
"model": model_used,
"answer": response.content
}
# Usage
router = SmartRouter()
for q in ["What is the return policy?", "Compare 3 headphone models", "My order arrived damaged!"]:
result = router.answer(q)
print(f"[{result['intent']}|{result['model']}] {result['answer'][:80]}...")
出力:
TEXT
# Function defined successfully
6. 量子化実験
(1) 実験設計
| グループ | モデル | 量子化 | 容量 | 期待品質 |
|---|---|---|---|---|
| A | llama3.2:3b | Q4_K_M | 約2 GB | ベースライン |
| B | qwen2.5 | Q4_K_M | 約5 GB | より高い |
| C | qwen2.5 | Q8_0 | 約8 GB | 最高 |
(2) ▶ サンプル:量子化比較テスト
PYTHON
import ollama
import time
def quantization_benchmark(test_questions: list[str], models: list[str]):
"""Compare response quality across quantizations."""
results = {}
for model in models:
model_results = []
# Warm up
ollama.chat(model=model, messages=[{"role": "user", "content": "hi"}],
stream=False)
for q in test_questions:
start = time.time()
resp = ollama.chat(
model=model,
messages=[{"role": "user", "content": q}],
stream=False,
options={"temperature": 0.3}
)
elapsed = time.time() - start
model_results.append({
"question": q,
"answer": resp["message"]["content"][:200],
"latency_s": round(elapsed, 2)
})
results[model] = model_results
return results
# Run if models are available
# questions = [
# "What is the return policy for electronics?",
# "Translate to French: Free shipping on orders over $50",
# "Write a SQL query for top 5 customers"
# ]
# results = quantization_benchmark(questions, ["llama3.2:3b", "qwen2.5"])
出力:
TEXT
# Function defined successfully
7. 総合サンプル:SupportBot最終アーキテクチャ計画
PYTHON
# ============================================
# Comprehensive: SupportBot final architecture
# Integrates all Phase 3 capabilities
# ============================================
from dataclasses import dataclass, field
from typing import Optional
import json
@dataclass
class SupportBotArchitecture:
"""Final SupportBot architecture specification."""
name: str = "SupportBot V4"
version: str = "4.0"
components: dict = field(default_factory=lambda: {
"intent_classifier": {
"model": "llama3.2:3b",
"quantization": "Q4_K_M",
"purpose": "Classify customer intent in <1s",
"vram": "~2GB"
},
"faq_rag": {
"model": "llama3.2:3b",
"embedding": "nomic-embed-text",
"vector_db": "Chroma",
"purpose": "Answer FAQ from knowledge base",
"vram": "~2.3GB shared"
},
"deep_answer": {
"model": "qwen2.5:7b",
"quantization": "Q4_K_M",
"purpose": "Complex product questions and comparisons",
"vram": "~5GB"
},
"complaint_handler": {
"model": "qwen2.5:7b",
"system_prompt": "empathetic mode",
"purpose": "Handle complaints with empathy",
"vram": "shared with deep_answer"
},
"multimodal": {
"model": "llava",
"purpose": "Damage report image analysis",
"vram": "~5GB (on-demand)"
}
})
deployment: dict = field(default_factory=lambda: {
"platform": "Docker Compose",
"services": ["ollama", "chroma", "fastapi", "nginx"],
"vram_total": "8GB minimum, 12GB recommended",
"persistence": "Named volumes for models and Chroma"
})
routing_rules: dict = field(default_factory=lambda: {
"faq": "faq_rag (3B + RAG)",
"order_status": "faq_rag (3B + RAG)",
"shipping": "faq_rag (3B + RAG)",
"product_info": "deep_answer (8B)",
"comparison": "deep_answer (8B)",
"complaint": "complaint_handler (8B empathetic)",
"damage_report": "multimodal (llava)"
})
cost_analysis: dict = field(default_factory=lambda: {
"before_gpt4": "2,000 USD/month",
"after_local": "98 USD/month (hardware amortization + electricity)",
"saving": "1,902 USD/month (95% reduction)",
"payback_months": 1.1
})
def to_report(self) -> str:
"""Generate architecture report."""
report = [f"# {self.name} v{self.version} Architecture\n"]
report.append("## Components")
for name, spec in self.components.items():
report.append(f"- {name}: {spec['model']} - {spec['purpose']}")
report.append("\n## Deployment")
report.append(f"- Platform: {self.deployment['platform']}")
report.append(f"- Services: {', '.join(self.deployment['services'])}")
report.append(f"- VRAM: {self.deployment['vram_total']}")
report.append("\n## Routing Rules")
for intent, target in self.routing_rules.items():
report.append(f"- {intent} → {target}")
report.append("\n## Cost Analysis")
for key, value in self.cost_analysis.items():
report.append(f"- {key}: {value}")
return "\n".join(report)
# Generate report
arch = SupportBotArchitecture()
print(arch.to_report())
❓ よくある質問
Q フルスタックDockerデプロイのハードウェア要件は?
A 最低8GB VRAM + 16GB RAM + 50GBディスク。推奨12GB VRAM + 32GB RAM。CPUモードは32GB RAMが必要。
Q RAGパイプラインの検索品質を向上させるには?
A 1)より良いチャンキング戦略(意味的境界を保持);2)より良いEmbeddingモデル(mxbai-embed-large);3)より多くの文書カバレッジ;4)MMR検索で多様性を向上。
Q ルーターの分類精度が十分でない場合は?
A キーワード事前フィルタリングルールを追加してモデル負荷を軽減。Few-shotサンプルで分類一貫性を向上。分類器を8Bモデルにアップグレードも検討。
Q 量子化比較実験にどれくらいかかる?
A 3モデル × 3質問 × 3回 ≈ 約15分。必ずウォームアップを行い、コールドスタートのバイアスを回避すること。
Q SupportBot最終アーキテクチャに変更が必要な場合は?
A アーキテクチャは生きている。第4段階でパフォーマンスとセキュリティ要件に基づきさらに調整。第5段階のハンズオンプロジェクトでアーキテクチャを検証・洗練する。
Q 第4段階の前提知識は?
A Linuxサーバー運用の基礎、Nginxリバースプロキシ、Prometheus/Grafanaモニタリングの概念。レッスン19〜22でそれぞれ詳細に解説。
📖 まとめ
- Docker Composeフルスタックデプロイ:Ollama + Chroma + WebUI + FastAPI
- Embedding比較:nomic-embed-textが最もコスパが高い、mxbai-embed-largeが高精度
- Router + RAGの組み合わせ:FAQはRAG高速レーン、複雑な質問は8B深い回答へ
- 量子化実験が検証:Q4_K_Mの品質低下はカスタマーサービスシナリオで3%未満
- SupportBot最終アーキテクチャ:分類器 + FAQ RAG + 深い回答 + センチメント処理 + マルチモーダル
- 第3段階の全スキルを統一アーキテクチャに統合;第4段階は運用最適化フェーズへ
📝 練習問題
- 基本(難易度 ⭐):Docker ComposeでOllama + Chroma + WebUIフルスタックをデプロイし、3サービスが正常動作することを確認する。
- 中級(難易度 ⭐⭐):RAGパイプラインを構築し、2つのEmbeddingモデルで検索品質を比較し、Top-3ヒット率を記録する。
- 上級(難易度 ⭐⭐⭐):完全なSupportBotアーキテクチャを実装——ルーター + RAG + 深い回答——し、アーキテクチャ設計書とパフォーマンステストレポートを出力する。