Ollamaとの接続設定が終わった時点では、「動いた!」という達成感があります。でも本番プロジェクトで実際に使い続けていくと、すぐにいくつかの壁が見えてきます。
「補完が遅い」「新しいAndroidのAPIを知らない」「チームメンバーの環境で同じ設定を再現するのが面倒」——こういった問題を乗り越えるには、もう少し踏み込んだ設定と設計が必要です。
ここでは私が実際のAndroid開発プロジェクトでGemma 4ローカル環境を本番運用するにあたって構築したアーキテクチャをお伝えします。
モデル選択の戦略:用途別スイッチング
最初の大きな判断は「どのGemmaモデルを使うか」です。答えは「一つに固定しない」です。
私が実践している方法は、タスクの種類によってモデルを自動的に切り替える構成です:
# .ollama-profiles(プロジェクトルートに置く)
COMPLETION_MODEL=gemma4:9b # コード補完:速度優先
REVIEW_MODEL=gemma4:26b # コードレビュー:品質優先
EXPLAIN_MODEL=gemma4:9b # コード説明:中間
GENERATE_MODEL=gemma4:26b # ゼロから生成:品質優先Android Studioはこのプロファイルを直接読む機能はありませんが、カスタムOllamaプロキシを挟むことで実現できます。
Ollamaプロキシの実装
# ollama_router.py — タスク種別に応じてモデルを振り分けるプロキシ
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import httpx
import json
app = FastAPI()
# タスク種別とモデルのマッピング
MODEL_ROUTING = {
"completion": "gemma4:9b", # 速度優先
"review": "gemma4:26b", # 品質優先
"generate": "gemma4:26b", # ゼロから生成
"default": "gemma4:9b",
}
def detect_task_type(prompt: str) -> str:
"""プロンプトの内容からタスク種別を判定"""
prompt_lower = prompt.lower()
if any(word in prompt_lower for word in ["review", "レビュー", "問題", "bug", "error"]):
return "review"
if any(word in prompt_lower for word in ["generate", "作成", "create", "write", "実装"]):
return "generate"
return "default"
@app.post("/api/generate")
async def route_generation(request: Request):
body = await request.json()
prompt = body.get("prompt", "")
task_type = detect_task_type(prompt)
target_model = MODEL_ROUTING.get(task_type, MODEL_ROUTING["default"])
# 実際のOllamaに転送(モデルを置き換えて)
body["model"] = target_model
async with httpx.AsyncClient(timeout=120.0) as client:
async with client.stream(
"POST",
"http://localhost:11434/api/generate",
json=body
) as response:
return StreamingResponse(
response.aiter_bytes(),
media_type="application/x-ndjson"
)
# プロキシを起動
# uvicorn ollama_router:app --host 0.0.0.0 --port 11435Android Studioのエンドポイントを http://localhost:11434 から http://localhost:11435(プロキシ)に変更すると、タスクに応じたモデルルーティングが自動的に機能します。
Gemma 4のファインチューニング:プロジェクト固有パターンの学習
Ollamaで動くGemma 4はベースモデルです。プロジェクト固有のパターン(独自のアーキテクチャ規約、社内ライブラリの使い方、チームのコーディングスタイル)を学習させると、補完の精度が大きく向上します。
トレーニングデータの準備
既存のコードベースから訓練データを作ります。「入力(不完全なコード)→出力(完成形)」のペアを用意します。
# prepare_training_data.py
import json
import os
from pathlib import Path
def extract_kotlin_pairs(repo_path: str) -> list[dict]:
"""Kotlinファイルから訓練データを抽出"""
pairs = []
repo = Path(repo_path)
for kotlin_file in repo.rglob("*.kt"):
content = kotlin_file.read_text(encoding="utf-8")
# クラス定義のパターンを抽出
# 実際には正規表現でメソッドシグネチャ+実装を分離する
pairs.append({
"instruction": f"以下のシグネチャのKotlinメソッドを実装してください:\n{extract_signature(content)}",
"output": extract_implementation(content),
"source": str(kotlin_file.relative_to(repo))
})
return pairs
def create_training_jsonl(output_path: str, pairs: list[dict]):
"""Ollama/HuggingFace形式のJSONLファイルを生成"""
with open(output_path, "w", encoding="utf-8") as f:
for pair in pairs:
training_example = {
"prompt": pair["instruction"],
"completion": pair["output"]
}
f.write(json.dumps(training_example, ensure_ascii=False) + "\n")
print(f"訓練データ {len(pairs)} 件を {output_path} に保存しました")
# 実行
pairs = extract_kotlin_pairs("/path/to/your/android/project")
create_training_jsonl("training_data.jsonl", pairs)LoRAによる軽量ファインチューニング
フルモデルのファインチューニングは計算コストが高いため、LoRA(Low-Rank Adaptation)を使います。
# ファインチューニング環境の準備
pip install transformers peft trl bitsandbytes
# fine_tune_gemma.py の実行
python fine_tune_gemma.py \
--base_model "google/gemma-4-9b" \
--training_data "training_data.jsonl" \
--output_dir "./gemma4-android-finetuned" \
--lora_r 16 \
--lora_alpha 32 \
--num_epochs 3 \
--batch_size 4# fine_tune_gemma.py
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer
import torch
def fine_tune_gemma(base_model: str, training_data: str, output_dir: str, **kwargs):
# モデルとトークナイザーのロード
tokenizer = AutoTokenizer.from_pretrained(base_model)
model = AutoModelForCausalLM.from_pretrained(
base_model,
torch_dtype=torch.bfloat16,
device_map="auto",
load_in_4bit=True # 4bit量子化でVRAMを節約
)
# LoRA設定
lora_config = LoraConfig(
r=kwargs.get("lora_r", 16),
lora_alpha=kwargs.get("lora_alpha", 32),
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 訓練設定
training_args = TrainingArguments(
output_dir=output_dir,
num_train_epochs=kwargs.get("num_epochs", 3),
per_device_train_batch_size=kwargs.get("batch_size", 4),
warmup_steps=100,
learning_rate=2e-4,
fp16=True,
save_strategy="epoch",
logging_steps=50,
)
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=load_dataset("json", data_files=training_data)["train"],
tokenizer=tokenizer,
max_seq_length=2048,
)
trainer.train()
trainer.save_model(output_dir)
print(f"✅ ファインチューニング完了: {output_dir}")ファインチューニング後、OllamaでカスタムModelfileを作成して登録します:
# Modelfile(ファインチューニング済みモデル用)
FROM ./gemma4-android-finetuned
SYSTEM """
あなたはAndroid/Kotlinの専門家AIアシスタントです。
このプロジェクトでは以下の規約を守ってください:
- ViewModelにはAndroid ViewModelを使用する
- Repository層を必ず経由してデータアクセスする
- Kotlinコルーチンを使用する(RxJavaは使わない)
- DIにはHiltを使用する
"""
PARAMETER temperature 0.3
PARAMETER top_p 0.9
ollama create gemma4-android-project -f Modelfileチーム展開:Docker Composeによる環境再現
個人環境で動いても、チーム全員が同じ設定を再現できなければ意味がありません。Docker Composeを使って、Ollama環境をコード化します。
# docker-compose.yml
version: "3.8"
services:
ollama:
image: ollama/ollama:latest
ports:
- "11434:11434"
volumes:
- ollama_models:/root/.ollama
- ./models:/models # カスタムModelfileの場所
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
restart: unless-stopped
ollama-router:
build: ./ollama-router
ports:
- "11435:11435"
environment:
- OLLAMA_URL=http://ollama:11434
depends_on:
- ollama
restart: unless-stopped
model-initializer:
image: ollama/ollama:latest
volumes:
- ollama_models:/root/.ollama
- ./scripts:/scripts
entrypoint: ["/scripts/init-models.sh"]
depends_on:
- ollama
volumes:
ollama_models:# scripts/init-models.sh
#!/bin/bash
# 必要なモデルを自動ダウンロード
ollama pull gemma4:9b
ollama pull gemma4:26b
# カスタムモデルの登録
if [ -f /models/Modelfile.android ]; then
ollama create gemma4-android-project -f /models/Modelfile.android
fi
echo "✅ モデルの初期化完了"新しいチームメンバーのオンボーディング手順はシンプルになります:
git clone <project-repo>
docker compose up -d
# → Ollamaが自動起動し、必要なモデルが自動ダウンロードされる
# Android Studioのエンドポイントを http://localhost:11435 に設定するだけCI/CDパイプラインでのローカルLLM活用
コードレビューをCI/CDに組み込むことで、Pull Request時に自動的にAIレビューを実行できます。
# .github/workflows/ai-review.yml
name: AI Code Review
on:
pull_request:
paths:
- 'app/src/**/*.kt'
jobs:
ai-review:
runs-on: self-hosted # ローカルLLMを持つセルフホステッドランナー
steps:
- uses: actions/checkout@v4
- name: Start Ollama
run: docker compose up -d ollama
- name: Wait for Ollama ready
run: |
until curl -sf http://localhost:11434/api/version; do
sleep 2
done
- name: Run AI code review
run: |
# 変更されたKotlinファイルを取得
CHANGED_FILES=$(git diff --name-only origin/main -- '*.kt')
for file in $CHANGED_FILES; do
echo "## Reviewing $file" >> review_results.md
python scripts/ai_review.py "$file" >> review_results.md
echo "" >> review_results.md
done
- name: Post review as PR comment
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const review = fs.readFileSync('review_results.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## 🤖 AI Code Review (Gemma 4 ローカル)\n\n${review}`
});# scripts/ai_review.py
import sys
import httpx
import json
def review_kotlin_file(file_path: str) -> str:
with open(file_path) as f:
code = f.read()
prompt = f"""以下のKotlinコードをAndroid開発のベストプラクティスの観点からレビューしてください:
1. アーキテクチャの問題点
2. パフォーマンスの懸念事項
3. テスタビリティ
4. メモリリークの可能性
コード:
```kotlin
{code}具体的な改善提案を3点以内で日本語で簡潔に述べてください。"""
response = httpx.post(
"http://localhost:11434/api/generate",
json={
"model": "gemma4:26b",
"prompt": prompt,
"stream": False
},
timeout=120.0
)
return response.json()["response"]
if name == "main": result = review_kotlin_file(sys.argv[1]) print(result)
## 実運用で学んだこと
半年ほどこの構成で開発してみて、予想外に良かったのは「AIの提案が"社内文化"を反映するようになる」という点です。クラウドのGemini APIは汎用的な提案をしますが、ファインチューニングしたモデルは「このプロジェクトではこう書くべき」という提案をしてきます。
一方で、継続的な更新が課題です。AndroidのAPIやJetpack Composeは進化が速く、ファインチューニングモデルの知識は作った時点で止まります。重要なAPIアップデート後には再訓練を検討する必要があります。
クラウドAPIとローカルLLMを用途で使い分けるハイブリッド構成(機密コードはローカル、新APIの調査はクラウド)が現実的なバランスだと感じています。
## 個人開発12年の視点で見る Gemini 運用
### 運用時に毎月見ているメトリクス
- API 呼び出し成功率と平均レイテンシ
- 1リクエストあたりの平均トークン消費
- AdMob 売上との比較で見た ROI