Ollama: 第2段階総合練習
第2段階総合練習は「使える」から「熟練」への跳躍——Modelfileカスタマイズ、GPU高速化、API移行を網羅。
💡 ヒント: これらの総合練習は第2段階のコアスキル(Modelfileカスタマイズ、Python SDK統合、GPU高速化、OpenAI互換API、マルチモーダルモデル)の定着を目的とする。練習1→2→3→4の順に完了することを推奨、各練習は前の練習の知識と成果物に基づいている。すべての練習を完了すると、完全なSupportBot V2アーキテクチャ計画が完成し、第3段階のDockerデプロイとRAG統合への確固たる基盤となる。
📋 前提条件: まず以下を習得していること
- レッスン1: ローカルAIの概念と環境概要
- レッスン2: Ollamaのインストールと環境設定
- レッスン3: CLI基本操作
- レッスン4: モデル管理
- レッスン5: REST APIの基礎
- レッスン6: 第1段階総合練習
- レッスン7: Python SDK統合
- レッスン8: Modelfileカスタムモデル
- レッスン9: GPUとCUDA設定
- レッスン10: マルチモーダルモデル
- レッスン11: OpenAI互換API
1. 学べること
- Modelfile + Python SDKの組み合わせ実践
- CPU対GPU推論速度ベンチマーク
- 完全なOpenAIアプリケーションのOllamaへの移行
- マルチモーダル画像記述マイクロサービスの実装
- SupportBot V2アーキテクチャ計画
2. SaaS創業者のリアルな事例
ℹ️ 情報: 第2段階総合練習の成果物はSupportBot V2アーキテクチャ計画——これは単なる練習ではなく、後続の第3段階(Docker/量子化/マルチモデルオーケストレーション)と第4段階(セキュリティ/モニタリング/本番デプロイ)への入力でもある。今、計画に時間を投資すれば、後で恩恵を受ける。
⚠️ 警告: OpenAI互換APIへの移行後、必ず回帰テストを行うこと——ローカルモデルの動作はGPT-4と異なり、出力フォーマットの逸脱、システムプロンプトの遵守度の違い、マルチターン対話のコンテキスト理解の差異が生じる可能性がある。
(1) ペインポイント:プロトタイプの機能が限定的すぎる
AliceのSupportBot V1は単純なテキスト対話しかできず、ロールカスタマイズ、GPU高速化、画像分析、API互換性が欠けている。第2段階で学んだすべての能力を統合して、より強力なV2版を構築する必要がある。
(2) ソリューション:第2段階の全機能統合
PYTHON
# SupportBot V2 leverages all Phase 2 skills:
# - Custom Modelfile for role specialization
# - GPU acceleration for speed
# - OpenAI-compatible API for easy integration
# - Multimodal for image analysis
3. 練習1:Modelfile + Python SDK組み合わせ実践
💡 ヒント: Modelfile + Python SDKは最良の組み合わせ——Modelfileがロールとパラメータを固定し、SDKがビジネスロジックと会話管理を担当。コードにシステムプロンプトをハードコードしないこと;Modelfileを「設定ファイル」とし、コードはそれを呼び出すだけにする。
(1) カスタムモデルの作成とSDKからの呼び出し
flowchart TD
A[Modelfile作成] --> B[ollama create]
B --> C[Python SDKインポート]
C --> D[アプリケーション構築]
| ステップ | アクション | ポイント |
|---|---|---|
| 1 | SupportBot Modelfile作成 | SYSTEM + PARAMETER + MESSAGE |
| 2 | ollama create |
カスタムモデルをビルド |
| 3 | Pythonでollama.chat(model='supportbot')呼び出し |
システムプロンプトの指定不要 |
| 4 | Modelfileあり/なしで出力を比較 | ロール一貫性の改善を確認 |
(2) ▶ サンプル:Modelfile + SDK完全ワークフロー
TEXT
# supportbot.Modelfile
FROM qwen2.5:7b
SYSTEM You are SupportBot for GlobalShop e-commerce. Be polite, concise (2-3 sentences). For order queries ask for order number. If unsure, say 'Let me connect you with a human agent.'
PARAMETER temperature 0.4
PARAMETER num_ctx 4096
PARAMETER num_predict 256
MESSAGE user What is your return policy?
MESSAGE assistant Our return policy allows returns within 30 days in original condition. Free return shipping included.
PYTHON
import ollama
# Create model (run once)
# ollama.create(model='supportbot', from_='./supportbot.Modelfile')
# Use the custom model - no system prompt needed!
def test_supportbot():
questions = [
"I want to return order #12345",
"Do you ship to Germany?",
"What's the price of the iPhone 15?"
]
for q in questions:
response = ollama.chat(
model='supportbot',
messages=[{'role': 'user', 'content': q}],
stream=False
)
print(f"Q: {q}")
print(f"A: {response['message']['content']}\n")
test_supportbot()
4. 練習2:CPU対GPUベンチマーク
⚠️ 注: ベンチマーク時、必ず2〜3回のウォームアップ呼び出し(結果は破棄)を行ってから正式な計測を開始すること。最初のリクエストにはコールドスタートのモデルロード時間(5〜30秒)が含まれ、平均値を著しく水増しする。また、テスト中は他のGPU集約的タスクを実行しないこと。
(1) ベンチマーク指標
| 指標 | CPU期待値 | GPU期待値 | 差 |
|---|---|---|---|
| 初回トークンレイテンシ(TTFT) | 2-5s | 0.2-0.5s | 5-10倍 |
| 生成速度(tok/s) | 5-10 | 30-60 | 5-10倍 |
| 総レイテンシ(200トークン) | 20-40s | 3-7s | 5-10倍 |
| メモリ使用量 | RAM | VRAM | 異なる種類 |
(2) ▶ サンプル:CPU対GPU比較スクリプト
PYTHON
import ollama
import time
from statistics import mean
def benchmark(model: str, prompt: str, runs: int = 5) -> dict:
"""Benchmark model inference speed."""
latencies = []
token_speeds = []
# Warm up (first call includes model loading)
ollama.chat(model=model, messages=[{'role': 'user', 'content': 'warm up'}],
stream=False)
for _ in range(runs):
start = time.time()
response = ollama.chat(
model=model,
messages=[{'role': 'user', 'content': prompt}],
stream=False
)
elapsed = time.time() - start
# Estimate tokens (rough: ~4 chars per token for English)
content = response['message']['content']
token_count = len(content) // 4
latencies.append(elapsed)
token_speeds.append(token_count / elapsed if elapsed > 0 else 0)
return {
"model": model,
"avg_latency_s": round(mean(latencies), 2),
"avg_tokens_per_s": round(mean(token_speeds), 1),
"runs": runs
}
# Run benchmark
print("=== CPU vs GPU Benchmark ===")
result = benchmark("qwen2.5", "Explain machine learning in 100 words")
print(f"Model: {result['model']}")
print(f"Avg latency: {result['avg_latency_s']}s")
print(f"Avg speed: {result['avg_tokens_per_s']} tokens/s")
# Compare different models
for model in ["llama3.2:3b", "qwen2.5", "mistral"]:
result = benchmark(model, "Explain AI in 50 words", runs=3)
print(f"{model}: {result['avg_tokens_per_s']} tok/s, {result['avg_latency_s']}s")
出力:
TEXT
=== CPU vs GPU Benchmark ===
5. 練習3:OpenAIアプリケーション移行
(1) 移行ワークフロー
flowchart TD
A[元のOpenAIアプリ] --> B{API呼び出しを特定}
B --> C[base_urlをOllamaに変更]
C --> D[モデル名を変更]
D --> E[各機能をテスト]
E --> F{全機能動作?}
F -->|はい| G[移行完了]
F -->|いいえ| H[非互換を修正]
H --> E
| チェックステップ | アクション | 検証 |
|---|---|---|
| 1 | base_url変更 | curl localhost:11434/v1/modelsがモデルリストを返す |
| 2 | モデル名変更 | qwen2.5が正常応答 |
| 3 | ストリーミング出力テスト | stream=Trueが正常動作 |
| 4 | JSONモードテスト | response_formatが正常動作 |
| 5 | Embeddingsテスト | nomic-embed-textが正常動作 |
| 6 | Function Callingテスト | 非対応の場合はJSONモード代替に切替 |
(2) ▶ サンプル:完全移行実践
PYTHON
from openai import OpenAI
import json
# Before migration
# client = OpenAI(api_key="sk-xxx")
# model = "gpt-4"
# After migration - only these 3 lines changed
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
model = "qwen2.5"
embed_model = "nomic-embed-text"
# Test 1: Chat
print("=== Test 1: Chat ===")
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Hello, who are you?"}],
temperature=0.3
)
print(response.choices[0].message.content)
# Test 2: Streaming
print("\n=== Test 2: Streaming ===")
stream = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Count from 1 to 5"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
print()
# Test 3: JSON mode
print("\n=== Test 3: JSON Mode ===")
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "List 3 colors as JSON array"}],
response_format={"type": "json_object"},
temperature=0.1
)
print(response.choices[0].message.content)
# Test 4: Embeddings
print("\n=== Test 4: Embeddings ===")
response = client.embeddings.create(model=embed_model, input="Hello world")
print(f"Embedding dimension: {len(response.data[0].embedding)}")
print("\n=== All tests passed! ===")
出力:
TEXT
=== Test 1: Chat ===
\n=== Test 2: Streaming ===
\n=== Test 3: JSON Mode ===
\n=== Test 4: Embeddings ===
\n=== All tests passed! ===
6. マルチモーダルチャレンジ:画像記述マイクロサービス
(1) マクロサービスアーキテクチャ
flowchart TD
A[HTTPリクエスト<br/>image + prompt] --> B[FastAPIエンドポイント]
B --> C[Base64画像エンコード]
C --> D[Ollama llavaモデル]
D --> E[画像記述]
E --> F[JSONレスポンス]
(2) ▶ サンプル:FastAPI画像記述マイクロサービス
PYTHON
from fastapi import FastAPI, UploadFile, File, Form
from fastapi.responses import JSONResponse
import ollama
import base64
app = FastAPI(title="Image Description Service")
@app.post("/describe")
async def describe_image(
file: UploadFile = File(...),
prompt: str = Form("Describe this image in detail")
):
"""Describe an uploaded image using llava."""
image_data = base64.b64encode(await file.read()).decode("utf-8")
response = ollama.chat(
model="llava",
messages=[{
"role": "user",
"content": prompt,
"images": [image_data]
}],
stream=False,
options={"temperature": 0.3}
)
return JSONResponse({
"description": response["message"]["content"],
"model": "llava",
"filename": file.filename
})
@app.post("/extract-text")
async def extract_text(file: UploadFile = File(...)):
"""OCR-like text extraction from image."""
image_data = base64.b64encode(await file.read()).decode("utf-8")
response = ollama.chat(
model="llava",
messages=[{
"role": "user",
"content": "Extract all visible text from this image verbatim.",
"images": [image_data]
}],
stream=False,
options={"temperature": 0.1}
)
return JSONResponse({
"text": response["message"]["content"],
"model": "llava"
})
# Run: uvicorn service:app --host 0.0.0.0 --port 8000
出力:
TEXT
# Function defined successfully
(3) ▶ サンプル:SupportBot V2アーキテクチャ計画
PYTHON
# SupportBot V2 Architecture Blueprint
SUPPORTBOT_V2_SPEC = {
"name": "SupportBot V2",
"components": {
"custom_model": {
"tool": "Modelfile",
"purpose": "Role-specialized customer service model",
"config": "qwen2.5:7b + custom SYSTEM + PARAMETER"
},
"api_gateway": {
"tool": "OpenAI compatible API",
"purpose": "Easy integration with existing tools",
"config": "/v1/chat/completions endpoint"
},
"image_service": {
"tool": "llava multimodal",
"purpose": "Damage report analysis, product photo description",
"config": "FastAPI + llava + Base64 encoding"
},
"embedding_service": {
"tool": "nomic-embed-text",
"purpose": "Product knowledge base (RAG ready for Phase 3)",
"config": "/v1/embeddings endpoint"
},
"gpu_acceleration": {
"tool": "CUDA / Metal",
"purpose": "Fast inference for real-time responses",
"config": "Auto-detected by Ollama"
}
},
"cost_comparison": {
"before": "GPT-4 API: ~2,000 USD/month",
"after": "Local Ollama: ~100 USD/month (hardware + electricity)",
"saving": "1,900 USD/month (95% reduction)"
}
}
print("SupportBot V2 Architecture:")
for name, spec in SUPPORTBOT_V2_SPEC["components"].items():
print(f" {name}: {spec['tool']} - {spec['purpose']}")
出力:
TEXT
SupportBot V2 Architecture:
7. 総合サンプル:第2段階フルスキル検証
PYTHON
# ============================================
# Comprehensive: Phase 2 full skills verification
# Modelfile + SDK + GPU + OpenAI compat + Multimodal
# ============================================
import ollama
from openai import OpenAI
import base64
import time
import json
from pathlib import Path
def phase2_verify():
results = {}
# Test 1: Custom Modelfile model
print("=== Test 1: Custom Modelfile Model ===")
try:
resp = ollama.chat(
model="supportbot",
messages=[{"role": "user", "content": "I want a refund for order #12345"}],
stream=False
)
results["modelfile"] = "PASS"
print(f" Response: {resp['message']['content'][:80]}...")
except Exception as e:
results["modelfile"] = f"FAIL: {e}"
# Test 2: GPU acceleration
print("\n=== Test 2: GPU Acceleration ===")
start = time.time()
resp = ollama.chat(
model="qwen2.5",
messages=[{"role": "user", "content": "Hello"}],
stream=False
)
elapsed = time.time() - start
results["gpu_speed"] = f"{elapsed:.2f}s"
print(f" Response time: {elapsed:.2f}s ({'GPU' if elapsed < 3 else 'CPU'})")
# Test 3: OpenAI compatible API
print("\n=== Test 3: OpenAI Compatible API ===")
try:
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
resp = client.chat.completions.create(
model="qwen2.5",
messages=[{"role": "user", "content": "Say OK"}],
temperature=0.1
)
results["openai_compat"] = "PASS"
print(f" Response: {resp.choices[0].message.content}")
except Exception as e:
results["openai_compat"] = f"FAIL: {e}"
# Test 4: Embeddings
print("\n=== Test 4: Embeddings ===")
try:
resp = client.embeddings.create(model="nomic-embed-text", input="test")
dim = len(resp.data[0].embedding)
results["embeddings"] = f"PASS ({dim}d)"
print(f" Embedding dimension: {dim}")
except Exception as e:
results["embeddings"] = f"FAIL: {e}"
# Test 5: Streaming
print("\n=== Test 5: Streaming ===")
try:
stream = client.chat.completions.create(
model="qwen2.5",
messages=[{"role": "user", "content": "Count 1 to 3"}],
stream=True
)
chunks = 0
for chunk in stream:
if chunk.choices[0].delta.content:
chunks += 1
results["streaming"] = f"PASS ({chunks} chunks)"
print(f" Received {chunks} chunks")
except Exception as e:
results["streaming"] = f"FAIL: {e}"
# Summary
print("\n" + "=" * 40)
print("Phase 2 Verification Results:")
for test, status in results.items():
print(f" {test}: {status}")
passed = sum(1 for v in results.values() if "PASS" in str(v))
print(f"\n Total: {passed}/{len(results)} passed")
if passed >= 4:
print(" ✅ Ready for Phase 3!")
else:
print(" ⚠️ Review failed tests.")
if __name__ == "__main__":
phase2_verify()
❓ よくある質問
Q Modelfileで作成したモデルがPython SDKから見つからない場合は?
A
ollama createが成功した(エラーなし)ことを確認。ollama listでモデルが存在することを確認。Python SDKのモデル名はollama createで指定した名前と完全に一致する必要がある。Q ベンチマーク結果が大きくばらつく場合は?
A 最初の呼び出しにはモデルロード時間(コールドスタート)が含まれる。計測前にウォームアップ呼び出しを2回行う。テスト中は他のプログラムを実行しない。5回以上の実行の平均を取る。
Q OpenAI移行後に一部機能が動かなくなったら?
A 1つずつトラブルシュート——まず基本Chat、次にストリーミング、次にJSONモードをテスト。各機能を個別に検証して具体的な非互換箇所を特定する。
Q llavaモデルがまだプルされていない場合は?
A 練習4には事前に
ollama pull llavaが必要。ディスク容量が不足する場合、練習4はスキップ可能——第3段階のRAGにはマルチモーダルは不要。Q SupportBot V2とV1の核心的な違いは?
A V1はPythonスクリプトレベルのラッパー;V2はModelfileロールカスタマイズ、OpenAI互換APIゲートウェイ、マルチモーダル画像分析を統合したマイクロサービスアーキテクチャ。
Q 第3段階に進むために必要な前提知識は?
A LangChainの基礎(プロンプトテンプレート、チェーン、エージェント)、RAGの概念(文書ロード、ベクトル検索)、Dockerの基礎。レッスン13〜18で順次カバーする。
📖 まとめ
- Modelfile + Python SDKの組み合わせで、コード内でのカスタムモデルのゼロ設定使用が可能
- ベンチマークにはウォームアップ+複数回平均が必要;CPU対GPUの差は5〜10倍
- OpenAI移行はbase_url/api_key/modelの3箇所の変更のみ
- マルチモーダルマイクロサービスはFastAPI + Base64エンコード + llavaで実装
- SupportBot V2はModelfile + OpenAI互換 + マルチモーダル + 埋め込みを統合
- 第2段階フルスキル検証:5テストに合格すれば第3段階へ進行可能
📝 練習問題
- 基本(難易度 ⭐):SupportBot Modelfileを作成し、Python SDKで呼び出して3つの異なる質問をテストし、ロール一貫性を記録する。
- 中級(難易度 ⭐⭐):CPU対GPUベンチマークを実行し、3つの異なるモデルで速度を比較し、パフォーマンスレポート表を出力する。
- 上級(難易度 ⭐⭐⭐):SupportBot V2の完全実装を完了——Modelfileロールカスタマイズ + OpenAI互換API + マルチモーダル画像分析(オプション)、移行ドキュメントを作成する。