ローカル LLM を社内業務に特化させる 4 段階カスタマイズ — Qwen3 を「より賢く」する仕組み
Claude Code で生成したコードをローカル LLM(Ollama + Qwen3)でレビューする構成を前回の記事で紹介しました。しかし、汎用モデルのままでは「受注ステータスの遷移ルール」や「金額計算に float を使ってはならない」といった社内固有のルールを知りません。
この記事では、Qwen3 を社内業務に特化させ、特定のコーディング規約・業務ルール・過去の障害パターンを踏まえたレビューができるようにする 4 段階のカスタマイズ手法を紹介します。
全体像:4 段階のカスタマイズ
| レベル | 手法 | 導入期間 | 効果 | 専門知識 |
|---|---|---|---|---|
| 1 | Modelfile(システムプロンプト) | 即日 | ルールベースの指摘 | 不要 |
| 2 | RAG(社内ドキュメント検索) | 1〜2 日 | 文脈を踏まえた指摘 | Docker の基本 |
| 3 | Few-shot(レビュー事例の学習) | 数日 | パターン認識の向上 | 不要 |
| 4 | LoRA ファインチューニング | 1〜2 週間 | モデル自体の精度向上 | Python・ML の基本 |
レベル 1:ルールを「教える」 ← すぐできる
レベル 2:資料を「渡す」 ← 1〜2日
レベル 3:お手本を「見せる」 ← 数日
レベル 4:頭脳を「鍛える」 ← 1〜2週間
推奨: レベル 1 から順に導入し、効果を確認しながらステップアップしてください。多くの場合、レベル 1 + 2 で十分な精度が得られます。
Modelfile とは?
レベル 1 に入る前に、Modelfile の仕組みを理解しておきましょう。
一言でいうと
Modelfile は、Ollama に「どのモデルを、どんな設定で、どんな役割で動かすか」を伝える設定ファイルです。Docker の Dockerfile に似た構文で書きます。
CLAUDE.md / AGENTS.md との違い
Claude Code を使っている方は「CLAUDE.md と何が違うの?」と思うかもしれません。両者は目的は似ていますが、読む相手が違います。
CLAUDE.md → Claude Code(エージェント)が読む指示書
Modelfile → Ollama(ランタイム)が読む設定ファイル
3 層構造で整理すると明快です。
| 設定ファイル | 対象の層 | 誰が読むか | 書き方 |
|---|---|---|---|
| CLAUDE.md / AGENTS.md | エージェント層 | Claude Code | Markdown |
| Modelfile | ランタイム + LLM 層 | Ollama | Dockerfile 風の独自構文 |
CLAUDE.md は「ドライバー(エージェント)への指示書」、Modelfile は「エンジン(ランタイム + LLM)の設定ファイル」です。
できることの違い
| CLAUDE.md | Modelfile | |
|---|---|---|
| ルール・規約を伝える | できる | できる |
| モデルを指定する | できない(Claude 固定) | できる(FROM qwen3:14b) |
| 温度・コンテキスト長を調整する | できない | できる |
| LoRA アダプタを適用する | できない | できる |
| ファイル形式 | Markdown | 独自構文 |
同じ「金額計算に float を使うな」というルールを伝える場合、書き方はこう変わります。
CLAUDE.md(Claude Code に伝える場合):
## コーディング規約
- 金額計算には Decimal 型を使用し、float は禁止
Modelfile(Ollama + Qwen3 に伝える場合):
FROM qwen3:14b
SYSTEM """
金額計算には Decimal 型を使用し、float は禁止する。
"""
PARAMETER temperature 0.1
Modelfile の基本構文
Modelfile で使える命令は以下の通りです。
| 命令 | 役割 | 例 |
|---|---|---|
FROM | ベースモデルを指定 | FROM qwen3:14b |
SYSTEM | システムプロンプト(役割・ルール) | SYSTEM "あなたはレビュアーです" |
PARAMETER | モデルのパラメータ調整 | PARAMETER temperature 0.1 |
TEMPLATE | プロンプトテンプレートの定義 | (上級者向け) |
ADAPTER | LoRA アダプタの適用 | ADAPTER ./lora_weights.gguf |
MESSAGE | 初期会話履歴の設定 | MESSAGE user "こんにちは" |
最小限の Modelfile はこれだけです。
FROM qwen3:14b
SYSTEM "あなたは親切な日本語アシスタントです。"
よく使う PARAMETER 一覧
| パラメータ | 効果 | 推奨値(レビュー用) |
|---|---|---|
temperature | 出力のランダム性。低いほど一貫した回答 | 0.1 |
num_ctx | コンテキストウィンドウの大きさ | 32768 |
repeat_penalty | 同じ表現の繰り返しを抑制 | 1.1 |
top_p | 出力の多様性を制御 | 0.9 |
top_k | 候補トークン数を制限 | 40 |
レビュー用途では temperature を低く設定するのがポイントです。毎回異なる指摘が出ると信頼性が下がるため、一貫性を重視します。
モデルの作成・管理コマンド
# Modelfile からカスタムモデルを作成
ollama create our-reviewer -f review-rules.Modelfile
# 作成済みモデルの一覧
ollama list
# モデルの詳細を確認
ollama show our-reviewer
# モデルの削除
ollama rm our-reviewer
# Modelfile を更新したら再作成(上書きされる)
ollama create our-reviewer -f review-rules.Modelfile
ルールの二重管理を避けるには
Claude Code(CLAUDE.md)とローカル LLM(Modelfile)の両方にルールを伝える場合、同じルールを 2 箇所に書く必要が出てきます。ルールの二重管理を避けるには、ルールを 1 つのファイルで管理し、両方から参照する方法が有効です。
# 1. ルールを単独ファイルで管理
cat rules.md
# ## コーディング規約
# - 金額計算には Decimal 型を使用し、float は禁止
# - N+1 クエリを検出したら必ず指摘する
# ...
# 2. CLAUDE.md から参照(手動でコピー or インクルード)
# 3. Modelfile を自動生成するスクリプト
cat << 'EOF' > generate_modelfile.sh
#!/bin/bash
RULES=$(cat rules.md)
cat > review-rules.Modelfile << MODELFILE
FROM qwen3:14b
SYSTEM """
あなたは当社のシニアエンジニアです。以下のルールに従ってコードレビューしてください。
${RULES}
"""
PARAMETER num_ctx 32768
PARAMETER temperature 0.1
MODELFILE
ollama create our-reviewer -f review-rules.Modelfile
echo "モデルを更新しました"
EOF
chmod +x generate_modelfile.sh
# ルール更新時に実行
./generate_modelfile.sh
この仕組みなら、rules.md を 1 箇所更新するだけで、CLAUDE.md と Modelfile の両方に反映できます。
レベル 1:Modelfile でルールを埋め込む(即日導入)
Modelfile の仕組みが分かったところで、実際にレビュー用のカスタムモデルを作成しましょう。
Modelfile の作成
# review-rules.Modelfile
FROM qwen3:14b
SYSTEM """
あなたは当社のシニアエンジニアとしてコードレビューを行います。
以下のルールに従い、違反を検出した場合は具体的な行番号と修正案を提示してください。
## 技術スタック
- バックエンド: Python (Django 5.x)
- API: GraphQL (Strawberry Django)
- DB: PostgreSQL 16
- インフラ: AWS ECS Fargate
## コーディング規約
- Django ORM のクエリでは select_related / prefetch_related を必ず使用する
- N+1 クエリを検出した場合は最優先で指摘する
- GraphQL リゾルバ内で同期 I/O(sync DB access)を使用してはならない
- 変数名・関数名はスネークケース、クラス名はパスカルケース
- 型ヒント(type hints)を必ず付与する
- マジックナンバーは定数として定義する
## セキュリティルール
- .env の値をコード内にハードコードしてはならない
- ユーザー入力は必ずバリデーション・サニタイズする
- SQL の直接組み立て(f-string, format, %演算子)は禁止。ORM またはパラメータバインドを使用する
- SECRET_KEY, API キー, パスワードをログに出力してはならない
- CORS の AllowAll は本番環境で使用禁止
## 業務ロジックルール
- 受注ステータスの遷移は「仮受注 → 確定 → 出荷 → 完了」の順序のみ許可する
- ステータスの逆行や飛び越しを許すコードは必ず指摘する
- 金額計算には Decimal 型を使用し、float は禁止する
- 税計算の端数処理は切り捨て(ROUND_DOWN)で統一する
- レコードの削除は論理削除(is_deleted フラグ)で行い、物理削除は禁止する
- 日時は必ず timezone-aware で扱う(naive datetime 禁止)
## レビュー出力フォーマット
各指摘は以下の形式で出力してください:
- **[重要度]** CRITICAL / WARNING / INFO
- **[カテゴリ]** セキュリティ / パフォーマンス / 業務ロジック / 規約
- **[場所]** ファイル名:行番号
- **[内容]** 問題の説明
- **[修正案]** 具体的な修正コード
"""
# コンテキスト長を拡張(レビュー対象のコードが長い場合に必要)
PARAMETER num_ctx 32768
# 温度を低く設定(一貫性のあるレビューのため)
PARAMETER temperature 0.1
# 繰り返しを抑制
PARAMETER repeat_penalty 1.1
モデルの作成と使用
# 専用レビューモデルを作成
ollama create our-reviewer -f review-rules.Modelfile
# テスト実行
ollama run our-reviewer "以下のコードをレビューしてください:
def calc_total(items):
total = 0.0
for item in items:
total += item.price * item.quantity
return total
"
期待される出力:
- [CRITICAL][業務ロジック] calc_total:2行目
金額計算に float (0.0) を使用しています。
Decimal 型を使用してください。
修正案:
from decimal import Decimal
total = Decimal('0')
Modelfile のメンテナンス
ルールは時間とともに変わります。Modelfile をリポジトリで管理し、ルール変更時にモデルを再作成します。
# ルール更新後
ollama create our-reviewer -f review-rules.Modelfile
# 古いモデルは自動的に上書きされる
レベル 1 の限界
- ルールが増えすぎるとシステムプロンプトが膨大になり、性能が低下する
- 「過去にこういう障害があった」といった文脈は教えられない
- ルールに書いていないパターンは検出できない
レベル 2:RAG で社内ドキュメントを検索させる(1〜2 日)
RAG(Retrieval-Augmented Generation)は、質問に関連するドキュメントをベクトル検索で取得し、LLM に渡す技術です。これにより、Modelfile に書ききれない大量の社内ドキュメントを参照したレビューが可能になります。
Open WebUI での RAG 構築
Open WebUI にはナレッジベース機能が内蔵されており、最も手軽に RAG を導入できます。
# Open WebUI + Ollama を起動(まだ導入していない場合)
docker run -d -p 3000:8080 \
-v ollama:/root/.ollama \
-v open-webui:/app/backend/data \
--name open-webui \
--restart always \
ghcr.io/open-webui/open-webui:ollama
# 埋め込みモデルをダウンロード
docker exec open-webui ollama pull nomic-embed-text:v1.5
リポジトリを丸ごと取り込んでいいのか?
社内のリポジトリにはソースコードと docs/ ディレクトリの両方が含まれています。「リポジトリを丸ごと RAG に入れてしまえばいいのでは?」と考えるのは自然ですが、丸ごと取り込むと検索精度が下がります。
丸ごと取り込みの問題点
| 問題 | 具体例 |
|---|---|
| ノイズが多すぎる | migrations/, node_modules/, .lock ファイルが検索にヒットし、本当に必要な情報が埋もれる |
| チャンク品質が低い | コードを機械的に 1000 文字で分割すると、関数の途中で切れて文脈が失われる |
| 検索精度の低下 | 似たコードが大量にあると、ベクトル検索が関連ドキュメントではなく類似コードを返してしまう |
| インデックスサイズの肥大化 | 不要なファイルで DB が膨らみ、検索速度が遅くなる |
推奨:フィルタリングして取り込む
リポジトリからレビューに役立つファイルだけを選別して取り込みます。
リポジトリ
├── docs/ ✅ 取り込む(設計書、仕様書)
├── README.md ✅ 取り込む
├── CLAUDE.md ✅ 取り込む(ルール)
├── models.py ✅ 取り込む(DB スキーマ定義)
├── serializers.py ✅ 取り込む(バリデーションルール)
├── tests/ ⚠️ 選択的に(テスト方針の理解用)
├── views.py ⚠️ 選択的に(主要なビジネスロジック)
├── migrations/ ❌ 除外
├── static/ ❌ 除外
├── node_modules/ ❌ 除外
├── .git/ ❌ 除外
├── *.pyc ❌ 除外
└── *.lock ❌ 除外
取り込みスクリプトの例
複数のリポジトリから有用なファイルだけを収集するスクリプトです。
#!/bin/bash
# index_repos.sh - 複数リポジトリからレビューに有用なファイルを収集
OUTPUT_DIR="./rag_docs"
mkdir -p "$OUTPUT_DIR"
for REPO_DIR in "$@"; do
REPO_NAME=$(basename "$REPO_DIR")
DEST="$OUTPUT_DIR/$REPO_NAME"
mkdir -p "$DEST"
# docs/ は丸ごと
[ -d "$REPO_DIR/docs" ] && cp -r "$REPO_DIR/docs/"* "$DEST/" 2>/dev/null
# 主要な設計ファイル
for f in README.md CLAUDE.md AGENTS.md CONTRIBUTING.md; do
[ -f "$REPO_DIR/$f" ] && cp "$REPO_DIR/$f" "$DEST/"
done
# Django のスキーマ・ロジック
find "$REPO_DIR" -name "models.py" -not -path "*/migrations/*" \
-not -path "*/.venv/*" \
-exec sh -c 'cp "$1" "'"$DEST"'/$(echo "$1" | tr "/" "_")"' _ {} \;
find "$REPO_DIR" -name "serializers.py" -not -path "*/.venv/*" \
-exec sh -c 'cp "$1" "'"$DEST"'/$(echo "$1" | tr "/" "_")"' _ {} \;
find "$REPO_DIR" -name "permissions.py" -not -path "*/.venv/*" \
-exec sh -c 'cp "$1" "'"$DEST"'/$(echo "$1" | tr "/" "_")"' _ {} \;
find "$REPO_DIR" -name "schema.py" -not -path "*/.venv/*" \
-exec sh -c 'cp "$1" "'"$DEST"'/$(echo "$1" | tr "/" "_")"' _ {} \;
echo "$REPO_NAME: $(ls "$DEST" | wc -l) 件のファイルを収集"
done
echo "合計: $(find "$OUTPUT_DIR" -type f | wc -l) 件"
# 使い方(複数リポジトリを一括収集)
./index_repos.sh ~/projects/order-system ~/projects/billing-api ~/projects/admin-portal
コード vs ドキュメントでチャンク戦略を変える
ファイル種別ごとにチャンクの切り方を変えると、さらに検索精度が上がります。
| ファイル種別 | チャンク戦略 | 理由 |
|---|---|---|
| Markdown(docs) | 見出し(##)単位で分割 | セクションごとに意味がまとまっている |
| Python(models.py) | クラス・関数単位で分割 | 途中で切ると文脈が失われる |
| YAML / JSON | ファイル丸ごと 1 チャンク | 構造が壊れると意味をなさない |
Open WebUI のデフォルト設定(チャンクサイズ 1000、オーバーラップ 200)はドキュメント向けです。Python コードを取り込む場合は、チャンクサイズを大きめ(2000〜4000)にするか、前処理で関数・クラス単位に分割してから取り込むのが効果的です。
実践的な運用:Claude Code でドキュメント化 → docs/ のみ RAG
ここまでフィルタリングやチャンク戦略を説明してきましたが、最も効果的な運用はもっとシンプルです。
普段 Claude Code で開発しているなら、ビジネスロジックやバリデーションルールを Claude Code にドキュメント化させ、docs/ ディレクトリに蓄積する。RAG にはその docs/ だけを取り込む。ソースコードは一切 RAG に入れません。
Claude Code(日常の開発作業中)
↓ ビジネスロジック・バリデーションルールをドキュメント化
docs/ に Markdown として蓄積
↓ docs/ のみを RAG に取り込み
Ollama + Qwen3(レビュー時に参照)
なぜソースコードより docs/ が優れるか
| 比較 | ソースコードを直接 RAG | docs/ のみ RAG |
|---|---|---|
| チャンク品質 | コードの途中で切れる | Markdown の見出し単位できれいに分割 |
| 検索精度 | 似たコードが大量ヒット | 意味のある説明文がヒット |
| メンテナンス | コード変更のたびに再インデックス | ドキュメント更新時のみ |
| ノイズ | import 文や migrations が混入 | 人間が読める説明のみ |
ソースコードは LLM にとって「読みにくい資料」ですが、Claude Code が書いた説明文は「読みやすい資料」です。RAG の検索精度はドキュメントの質に直結するので、この差は大きいです。
Claude Code にドキュメント化させる内容
docs/
├── business-logic/
│ ├── order-status-flow.md # 受注ステータス遷移ルール
│ ├── pricing-calculation.md # 金額計算・税計算ロジック
│ └── permission-rules.md # 権限・アクセス制御
├── validation/
│ ├── order-serializer.md # 受注のバリデーションルール
│ ├── user-form.md # ユーザー登録フォームのルール
│ └── payment-serializer.md # 決済のバリデーションルール
└── schema/
├── order-models.md # 受注関連テーブル定義と関係
└── user-models.md # ユーザー関連テーブル定義
Claude Code への指示例
開発作業の合間に、以下のようにドキュメント化を依頼します。
orders/serializers.py のバリデーションルールを
docs/validation/order-serializer.md にドキュメント化してください。
各フィールドのバリデーション条件、エラーメッセージ、
ビジネス上の理由を含めてください。
orders/models.py の Order モデルのステータス遷移ロジックを
docs/business-logic/order-status-flow.md にドキュメント化してください。
許可される遷移、禁止される遷移、遷移時の副作用(メール送信等)を
含めてください。
生成されたドキュメントは PR に含めてレビューし、コードと一緒にリポジトリに蓄積していきます。
この運用のメリット
- RAG の取り込みが簡単:
docs/をそのままナレッジベースにアップロードするだけ - チャンク品質が高い: Claude Code が構造化した Markdown なので、見出し単位で自然に分割できる
- 開発者にも役立つ: ドキュメント自体が新メンバーのオンボーディング資料になる
- フィルタリング不要: ソースコードを取り込まないので、除外ルールを考える必要がない
- メンテナンスが楽: ビジネスロジック変更時に Claude Code に「ドキュメントも更新して」と指示するだけ
取り込むべきドキュメント(優先度順)
上記の docs/ に加え、以下のドキュメントも RAG に取り込むと効果的です。
| ドキュメント | 効果 | 優先度 |
|---|---|---|
| docs/(Claude Code 生成) | ビジネスロジック・バリデーションの理解 | 最高 |
| コーディング規約 | ルール違反の検出 | 高 |
| 過去の PR レビューコメント | レビュー観点の学習 | 高 |
| 障害報告・ポストモーテム | 過去の失敗パターンの回避 | 中 |
| API 仕様書 | インターフェース整合性の確認 | 中 |
ナレッジベースの設定手順
- Open WebUI にログイン(
http://localhost:3000) - Workspace → Knowledge を開く
- New Knowledge をクリックし、名前を入力(例:
coding-standards) - ドキュメントをドラッグ & ドロップでアップロード
- チャット画面で
#coding-standardsと入力すると、ナレッジベースを参照しながら回答
埋め込みモデルの設定
デフォルトの埋め込みモデルでも動作しますが、日本語の精度を上げるには設定を調整します。
管理パネル → 設定 → ドキュメント で以下を設定:
| 設定項目 | 推奨値 |
|---|---|
| 埋め込みモデルエンジン | Ollama |
| 埋め込みモデル | nomic-embed-text:v1.5 |
| チャンクサイズ | 1000 |
| チャンクオーバーラップ | 200 |
コードレビュー用のプロンプト例
#coding-standards #incident-reports を参考にして、
以下のコード差分をレビューしてください。
過去の障害パターンに類似する箇所があれば特に重点的に指摘してください。
```diff
(ここに git diff を貼り付け)
### Python スクリプトで RAG レビューを自動化
Open WebUI を使わず、Python で直接 RAG パイプラインを組むこともできます。
```python
# review_with_rag.py
import chromadb
from chromadb.utils import embedding_functions
import subprocess
import requests
# ベクトル DB の初期化
ef = embedding_functions.OllamaEmbeddingFunction(
model_name="nomic-embed-text:v1.5",
url="http://localhost:11434"
)
client = chromadb.PersistentClient(path="./review_db")
collection = client.get_or_create_collection(
name="codebase_knowledge",
embedding_function=ef
)
def index_documents(docs_dir: str):
"""社内ドキュメントをベクトル DB に取り込む"""
import glob
files = glob.glob(f"{docs_dir}/**/*.md", recursive=True)
for i, filepath in enumerate(files):
with open(filepath) as f:
content = f.read()
collection.add(
documents=[content],
ids=[f"doc_{i}"],
metadatas=[{"source": filepath}]
)
print(f"{len(files)} 件のドキュメントを取り込みました")
def review_with_context(diff: str) -> str:
"""差分に関連するドキュメントを検索し、レビューを実行"""
# 関連ドキュメントを検索
results = collection.query(query_texts=[diff], n_results=5)
context = "\n---\n".join(results["documents"][0])
# Ollama API でレビュー実行
response = requests.post(
"http://localhost:11434/api/chat",
json={
"model": "our-reviewer",
"stream": False,
"messages": [{
"role": "user",
"content": f"""以下の社内ドキュメントを参考にしてコードレビューしてください。
## 関連ドキュメント
{context}
## レビュー対象の差分
```diff
{diff}
```"""
}]
}
)
return response.json()["message"]["content"]
if __name__ == "__main__":
# 初回: ドキュメントの取り込み
# index_documents("./docs")
# レビュー実行
diff = subprocess.run(
["git", "diff", "HEAD~1"],
capture_output=True, text=True
).stdout
print(review_with_context(diff))
レベル 2 の限界
- ドキュメントの品質に依存する(古い・不正確な資料があると逆効果)
- ベクトル検索の精度は完璧ではなく、関連ドキュメントを取りこぼすことがある
- ドキュメントの更新時に再インデックスが必要
レベル 3:Few-shot でレビューパターンを学習させる(数日)
Few-shot とは、「こういうコードにはこう指摘する」という具体例をプロンプトに含める手法です。レベル 1 のルール記述を、実例で補強します。
Few-shot 事例ファイルの作成
過去の PR レビューから「良い指摘」を集め、JSON ファイルにまとめます。
[
{
"bad_code": "total = sum(item.price * item.quantity for item in order.items.all())",
"review": "[CRITICAL][パフォーマンス] order.items.all() で N+1 クエリが発生します。prefetch_related を使用してください。",
"good_code": "# ビューまたはリゾルバで prefetch\norder = Order.objects.prefetch_related('items').get(id=order_id)\ntotal = sum(item.price * item.quantity for item in order.items.all())"
},
{
"bad_code": "if order.status == '出荷':\n order.status = '仮受注'\n order.save()",
"review": "[CRITICAL][業務ロジック] 受注ステータスが「出荷 → 仮受注」に逆行しています。許可される遷移は「仮受注 → 確定 → 出荷 → 完了」のみです。",
"good_code": "# ステータス遷移バリデーション\nSTATUS_FLOW = {'仮受注': '確定', '確定': '出荷', '出荷': '完了'}\nnext_status = STATUS_FLOW.get(order.status)\nif next_status:\n order.status = next_status\n order.save()"
},
{
"bad_code": "tax = int(price * 0.1)",
"review": "[CRITICAL][業務ロジック] float 演算と int() による切り捨てが混在しています。Decimal + ROUND_DOWN で統一してください。",
"good_code": "from decimal import Decimal, ROUND_DOWN\ntax = (Decimal(str(price)) * Decimal('0.1')).quantize(Decimal('1'), rounding=ROUND_DOWN)"
}
]
Few-shot を含む Modelfile
# review-fewshot.Modelfile
FROM qwen3:14b
SYSTEM """
あなたは当社のシニアエンジニアです。以下のルールとレビュー事例に基づいてコードレビューを行います。
## ルール
(レベル 1 と同じルールをここに記述)
## レビュー事例
### 事例 1: N+1 クエリ
悪い例:
```python
total = sum(item.price * item.quantity for item in order.items.all())
指摘: [CRITICAL][パフォーマンス] order.items.all() で N+1 クエリが発生します。 修正案:
order = Order.objects.prefetch_related('items').get(id=order_id)
事例 2: ステータス逆行
悪い例:
order.status = '仮受注' # 現在のステータスが「出荷」
指摘: [CRITICAL][業務ロジック] ステータスが逆行しています。 修正案: STATUS_FLOW 辞書でバリデーションを実装してください。
事例 3: 金額の float 計算
悪い例:
tax = int(price * 0.1)
指摘: [CRITICAL][業務ロジック] float 演算は金額計算で使用禁止です。 修正案: Decimal + ROUND_DOWN を使用してください。
上記の事例と同様のパターンを検出した場合、同じ形式で指摘してください。 """
PARAMETER num_ctx 32768 PARAMETER temperature 0.1
### Claude Code で Few-shot JSON を自動生成する
事例を手作業で 1 件ずつ書くのは大変です。**GitHub の PR レビュー履歴から Claude Code に自動生成させる**のが最も効率的です。
#### Step 1:PR レビューコメントの一括取得
```bash
# 過去の PR レビューコメントを取得(複数リポジトリ対応)
for repo in order-system billing-api admin-portal; do
gh api "repos/{owner}/$repo/pulls/comments" \
--paginate \
--jq '.[] | {
body: .body,
diff_hunk: .diff_hunk,
path: .path,
created_at: .created_at,
user: .user.login
}' >> pr_reviews_raw.json
done
取得される生データはこのような形式です。
{
"body": "ここ、float で計算してるけど Decimal にしないとまずいよ",
"diff_hunk": "@@ -12,3 +12,5 @@\n+ tax = int(price * 0.1)",
"path": "orders/services.py",
"created_at": "2025-11-15T09:30:00Z",
"user": "senior-dev"
}
Step 2:Claude Code に選別・構造化を依頼
pr_reviews_raw.json には過去の PR レビューコメントが入っています。
これを以下の形式の JSON に変換して docs/few-shot-reviews.json に
出力してください。
## 出力形式
[
{
"bad_code": "問題のあるコード(diff_hunk から抽出)",
"review": "[重要度][カテゴリ] 指摘内容(body を構造化)",
"good_code": "修正後のコード(body 内に含まれていれば抽出)",
"category": "セキュリティ | パフォーマンス | 業務ロジック | 規約"
}
]
## 選別基準
以下の条件を満たすコメントだけを採用してください:
- 具体的なコード修正案が含まれている
- 業務ルールやコーディング規約に言及している
- 「LGTM」「OK」「typo」だけのコメントは除外
- 曖昧な指摘(「もう少し考えて」「要検討」等)は除外
## 件数
30〜50 件に絞り込んでください。
カテゴリのバランスを考慮し、特定のカテゴリに偏らないようにしてください。
Claude Code はコメントの質を判断できるので、有用な事例だけを選別してくれます。手動で数百件のコメントをふるいにかけるより遥かに効率的です。
Step 3:生成結果の確認と調整
docs/few-shot-reviews.json を読み込んで、以下を確認してください:
1. カテゴリの分布(偏りがないか)
2. 重要度の分布(CRITICAL ばかりに偏っていないか)
3. 当社固有の業務ルールに関する事例が含まれているか
4. 不足しているパターンがあれば追加提案してください
Step 4:定期的な更新
新しい PR レビューが蓄積されたら、定期的に JSON を更新します。
# 前回更新以降のレビューコメントだけ追加取得
gh api "repos/{owner}/{repo}/pulls/comments" \
--paginate \
--jq '.[] | select(.created_at > "2026-03-01T00:00:00Z") | {
body: .body,
diff_hunk: .diff_hunk,
path: .path
}' >> pr_reviews_new.json
pr_reviews_new.json に新しい PR レビューコメントがあります。
docs/few-shot-reviews.json に追加すべき高品質な事例があれば
追加してください。合計 50 件を超える場合は、古い事例や
重複するパターンの事例を入れ替えてください。
Few-shot JSON から Modelfile を自動生成する
レベル 1 で紹介した generate_modelfile.sh と組み合わせ、JSON から Modelfile のレビュー事例セクションを自動生成できます。
#!/bin/bash
# generate_fewshot_modelfile.sh
RULES=$(cat rules.md)
# JSON から Markdown 形式の事例テキストを生成
EXAMPLES=$(python3 -c "
import json
with open('docs/few-shot-reviews.json') as f:
reviews = json.load(f)
for i, r in enumerate(reviews[:30], 1):
print(f'### 事例 {i}: {r[\"category\"]}')
print(f'悪い例:')
print(f'\`\`\`python')
print(r['bad_code'])
print(f'\`\`\`')
print(f'指摘: {r[\"review\"]}')
if r.get('good_code'):
print(f'修正案:')
print(f'\`\`\`python')
print(r['good_code'])
print(f'\`\`\`')
print()
")
cat > review-fewshot.Modelfile << MODELFILE
FROM qwen3:14b
SYSTEM """
あなたは当社のシニアエンジニアです。以下のルールとレビュー事例に基づいてコードレビューを行います。
## ルール
${RULES}
## レビュー事例
${EXAMPLES}
上記の事例と同様のパターンを検出した場合、同じ形式で指摘してください。
"""
PARAMETER num_ctx 32768
PARAMETER temperature 0.1
MODELFILE
ollama create our-reviewer-fewshot -f review-fewshot.Modelfile
echo "Few-shot モデルを更新しました"
レベル 3 の限界
- 事例が増えすぎるとコンテキストウィンドウを圧迫する(30〜50 件が目安)
- 事例にないパターンの検出力は上がらない
- JSON の定期更新が必要(ただし Claude Code で自動化可能)
レベル 4:LoRA ファインチューニング(1〜2 週間)
LoRA(Low-Rank Adaptation)は、モデルの重みの一部を少量のデータで再訓練する手法です。社内の PR レビュー履歴を学習データとして、Qwen3 自体のコードレビュー能力を向上させます。
レベル 1〜3 との違い
| レベル 1〜3 | レベル 4(LoRA) | |
|---|---|---|
| カスタマイズ対象 | プロンプト(入力) | モデルの重み(頭脳自体) |
| コンテキスト消費 | ルール・事例分だけ消費 | 消費しない(学習済み) |
| 未知のパターン | 検出困難 | 汎化により検出可能 |
| 更新コスト | ファイル編集のみ | 再訓練が必要 |
学習データの準備
PR レビュー履歴から「入力(コード差分)→ 出力(レビューコメント)」のペアを作成します。形式は Alpaca 形式(instruction / input / output の 3 フィールド)が標準的です。
[
{
"instruction": "以下のコード差分をレビューしてください。",
"input": "diff --git a/orders/views.py\n+ total = sum(float(i.price) * i.qty for i in items)",
"output": "[CRITICAL][業務ロジック] orders/views.py: 金額計算に float を使用しています。Decimal 型に変更してください。\n[WARNING][パフォーマンス] items の取得方法を確認してください。N+1 クエリの可能性があります。"
}
]
目安として 200〜500 件の学習データがあれば、ドメイン特化の効果が現れ始めます。
Few-shot JSON(レベル 3)との関係
レベル 3 で作成した docs/few-shot-reviews.json と LoRA の学習データは形式が似ていますが、量と目的が異なります。
| Few-shot(レベル 3) | LoRA 学習データ(レベル 4) | |
|---|---|---|
| 件数 | 30〜50 件 | 200〜500 件以上 |
| 使い方 | プロンプトに埋め込む | モデルの重みを更新する |
| 品質基準 | 厳選(代表的なパターンのみ) | 網羅的(バリエーションが多いほど良い) |
| 同じパターンの重複 | 不要(1 パターン 1 事例) | 必要(同じルール違反の異なるコード例が汎化を促す) |
| 形式 | 独自 JSON | Alpaca 形式(instruction / input / output) |
重要な違いは「重複」の扱いです。Few-shot では N+1 クエリの事例は 1 件で十分ですが、LoRA では「views.py での N+1」「serializers.py での N+1」「GraphQL リゾルバでの N+1」のように異なるコンテキストで同じ種類の問題が現れる事例を複数含めることで、モデルがパターンを汎化して学習します。
Few-shot JSON をベースに学習データを拡張する
ゼロから 300 件の学習データを作るのは大変です。レベル 3 で作った JSON を出発点にして、Claude Code に拡張させるのが効率的です。
レベル 3: 厳選 30〜50 件(Claude Code で選別済み)
↓ ベースとして再利用
レベル 4: 200〜500 件に拡張(Claude Code でバリエーション追加)
Step 1:既存の事例をバリエーション展開
docs/few-shot-reviews.json の 50 件をベースに、
LoRA ファインチューニング用の学習データを 300 件に拡張してください。
拡張方法:
- 既存の事例の「悪いコード」を別のバリエーションで書き直す
(変数名を変える、関数の構造を変える、別のモデルで同じ問題等)
- 同じルール違反でも異なるファイル・コンテキストの事例を追加する
- pr_reviews_raw.json から追加の事例を選別して含める
- 「問題なし(LGTM)」の事例も 20% 程度含める
(問題がないコードに誤って指摘しないように学習させるため)
出力形式(Alpaca 形式):
[
{
"instruction": "以下のコード差分をレビューしてください。",
"input": "差分コード",
"output": "レビューコメント"
}
]
出力先: docs/lora-training-data.json
Step 2:カテゴリバランスの確認
docs/lora-training-data.json のカテゴリ分布を確認してください。
以下のバランスが理想的です:
- 業務ロジック: 30%
- パフォーマンス: 20%
- セキュリティ: 20%
- コーディング規約: 15%
- LGTM(問題なし): 15%
偏りがあれば不足カテゴリの事例を追加してください。
「問題なし(LGTM)」の事例を含めるのがポイントです。これがないと、モデルが何にでも問題を指摘する「過剰検出」の傾向を持つようになります。
Step 3:形式変換
Few-shot JSON と LoRA 学習データは形式が異なるため、変換が必要です。
docs/few-shot-reviews.json(レベル 3 形式)を
docs/lora-training-data.json(Alpaca 形式)に変換するスクリプトを
書いてください。
変換ルール:
- bad_code → input(diff 形式に変換)
- review → output
- instruction は固定文「以下のコード差分をレビューしてください。」
- good_code は output に「修正案:」として追記
Unsloth によるファインチューニング
Unsloth は LoRA ファインチューニングを高速化するライブラリです。
# fine_tune.py(Google Colab または GPU マシンで実行)
from unsloth import FastLanguageModel
import json
# ベースモデルの読み込み
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="unsloth/Qwen3-14B",
max_seq_length=8192,
load_in_4bit=True,
)
# LoRA アダプタの設定
model = FastLanguageModel.get_peft_model(
model,
r=16, # LoRA のランク
lora_alpha=16,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
lora_dropout=0,
bias="none",
)
# 学習データの読み込み
with open("review_training_data.json") as f:
dataset = json.load(f)
# トレーニング実行
from trl import SFTTrainer
from transformers import TrainingArguments
trainer = SFTTrainer(
model=model,
train_dataset=dataset,
args=TrainingArguments(
output_dir="./review_model",
num_train_epochs=3,
per_device_train_batch_size=2,
learning_rate=2e-4,
),
)
trainer.train()
# GGUF 形式でエクスポート(Ollama 用)
model.save_pretrained_gguf(
"review_model_gguf",
tokenizer,
quantization_method="q4_k_m",
)
Ollama への取り込み
# fine-tuned.Modelfile
FROM ./review_model_gguf/model-q4_k_m.gguf
SYSTEM """
あなたは当社のコードレビュー専門 AI です。
(レベル 1 のルールをここにも記述)
"""
PARAMETER num_ctx 32768
PARAMETER temperature 0.1
ollama create our-reviewer-ft -f fine-tuned.Modelfile
レベル 4 の注意点
- GPU が必要: ファインチューニングには VRAM 16 GB 以上の GPU が必要(Google Colab の無料枠でも可能)
- データ品質が命: 不正確なレビューコメントを学習すると逆効果
- 定期的な再訓練: 新しいレビューパターンが蓄積されたら再訓練が必要
- 過学習に注意: 学習データが少なすぎると、特定パターンにしか反応しなくなる
実践的な導入ロードマップ
第 1 週:レベル 1(Modelfile)
├ 社内のコーディング規約を Modelfile に記述
├ 業務ロジックのルールを追加
└ Git フック(前回の記事パターン 1)と組み合わせて運用開始
第 2〜3 週:レベル 2(RAG)
├ Open WebUI を導入
├ 障害報告・設計書をナレッジベースに取り込み
└ レビュー精度を評価し、不足するドキュメントを追加
第 4 週:レベル 3(Few-shot)
├ PR レビュー履歴から良い指摘を 20〜30 件選別
├ Modelfile に事例を追加
└ 検出パターンの改善を確認
第 5〜6 週以降:レベル 4(LoRA)(必要に応じて)
├ PR レビュー履歴 200 件以上を学習データに整形
├ ファインチューニング実行
└ 既存モデルとの比較評価
各レベルの組み合わせ
4 つのレベルは排他的ではなく、組み合わせて使うのが最も効果的です。
レベル 4(LoRA)で鍛えたモデル
+ レベル 1(Modelfile)でルールを明示
+ レベル 2(RAG)で社内ドキュメントを参照
+ レベル 3(Few-shot)で事例を補強
= 社内業務に特化した高精度レビュー AI
最終的な構成図:
Claude Code → コード生成
↓
Git フック / OpenHands
↓
Ollama
↓
Qwen3(LoRA ファインチューニング済み)
+ システムプロンプト(業務ルール)
+ RAG(社内ドキュメント検索)
+ Few-shot(レビュー事例)
↓
レビュー結果(review.md)
まとめ
- レベル 1(Modelfile)から始める: 業務ルールをシステムプロンプトに書き込むだけで、即日導入可能
- レベル 2(RAG)で文脈を与える: 障害報告・設計書・コーディング規約をベクトル DB に格納し、レビュー時に参照させる
- レベル 3(Few-shot)でパターンを教える: 過去の良いレビュー事例を具体的に示すことで、検出精度が向上する
- レベル 4(LoRA)でモデル自体を鍛える: PR レビュー履歴を学習データとして Qwen3 を再訓練し、プロンプトに依存しない検出力を獲得
- 組み合わせが最強: 4 つのレベルは排他的ではなく、すべて組み合わせることで最高精度のレビュー AI が実現できる
- データ品質がすべての基盤: どのレベルでも、入力するルール・ドキュメント・事例・学習データの品質が精度を決定する
参考
- Claude Code 生成コードをローカル LLM でレビューする 3 つの構成パターン
- Ollama で Qwen3 を動かす初心者ガイド
- OpenHands 入門ガイド
- Ollama Modelfile リファレンス
- Ollama による完全ローカル RAG 構築ガイド - Saiteki AI
- Ollama と Open WebUI で自分専用の RAG をサッと構築する - Qiita
- 社内文書をセキュアに検索!Ollama と Open WebUI で構築する完全無料・RAG 環境 - Zenn
- Qwen2.5 LoRA ファインチューニング完全ガイド - リクリオ
- ローカル LLM をファインチューニングし RAG 構築するステップ - GPT Master
- Mac で LLM をファインチューニング|mlx-lm で LoRA を試す - Aru’s テクログ
- Mac mini M4 Pro 64GB はローカル LLM の夢を見るか? - mosuke5
- LoRA Fine-Tuning On Your Apple Silicon MacBook - Towards Data Science
- Explore large language models on Apple silicon with MLX - WWDC25
- Google Colab 無料 GPU で Qwen3 をファインチューニング - note
付録:レベル 4 に必要な PC スペックと推奨構成
LoRA ファインチューニングには GPU(VRAM 24GB 以上)が必要です。ただし、Unsloth フレームワークを使えば VRAM 使用量を最大 60% 削減でき、Google Colab の無料枠でも実行可能です。
必要スペック
| 項目 | 最低要件 | 推奨構成 |
|---|---|---|
| GPU | VRAM 24GB(RTX 3090) | VRAM 24GB(RTX 4090) |
| RAM | 32GB | 64GB |
| ストレージ | SSD 500GB | NVMe SSD 1TB |
| CPU | 8コア以上 | 12コア以上 |
3 つの選択肢
選択肢 1:Google Colab(初期投資ゼロ)— まずはここから
Unsloth を使えば、Google Colab の無料 T4 GPU(16GB VRAM)でも Qwen3 14B の LoRA ファインチューニングが可能です。4bit 量子化と LoRA を組み合わせることで、VRAM 使用量を大幅に削減します。
| プラン | GPU | VRAM | 月額 |
|---|---|---|---|
| 無料 | T4 | 16GB | ¥0 |
| Colab Pro | A100 | 40GB | ¥1,179 |
日本語の実践ガイド: Google Colab 無料 GPU で Qwen3 をファインチューニング
選択肢 2:RTX 3090 中古(コスパ重視)
既存の PC に GPU を増設する方法です。RTX 3090 は 24GB VRAM を搭載し、中古市場で 11〜13 万円で入手可能です。
| GPU | VRAM | 価格帯 | 備考 |
|---|---|---|---|
| RTX 3090(中古) | 24GB | 11〜13 万円 | コスパ最高。消費電力 350W、電源 850W 以上が必要 |
| RTX 4090(新品) | 24GB | 30〜40 万円 | 最高性能。推論速度も速い |
注意: GPU 増設には電源ユニットの容量確認が必要です。RTX 3090 は 350W、RTX 4090 は 450W を消費します。
選択肢 3:専用ワークステーション購入(本格運用向け)
継続的にファインチューニングを行う場合のみ検討してください。RTX 4090 搭載ワークステーションは 50〜80 万円です。
おすすめの導入順序
ステップ 1: Google Colab(無料)で Unsloth + Qwen3 14B の LoRA を試す
↓ 効果を確認できたら
ステップ 2: RTX 3090 中古(11〜13 万円)を既存 PC に増設
↓ 頻繁に再訓練するなら
ステップ 3: RTX 4090(30 万円〜)に移行
結論: まずは Google Colab + Unsloth で無料で試すのが最もリスクが低いです。Qwen3 14B の LoRA なら無料枠でも動作し、効果の検証にはこれで十分です。
Apple Silicon(Mac)で LoRA ファインチューニング
Mac ユーザーには Apple Silicon + MLX という選択肢もあります。Apple Silicon は統合メモリを採用しており、CPU と GPU がメモリを共有します。NVIDIA GPU のように VRAM が 24GB に制限されず、搭載メモリ全体を GPU 的に活用できます。
NVIDIA: CPU RAM 64GB ←→ GPU VRAM 24GB(別々)
Apple: 統合メモリ 64GB(CPU も GPU も共有)
Apple 製品の推奨モデル
| モデル | メモリ | 価格(税込) | LoRA 対応 |
|---|---|---|---|
| Mac mini M4 Pro 48GB | 48GB | 約 27 万円 | Qwen3 8B(余裕)/ 14B(ギリギリ) |
| Mac mini M4 Pro 64GB | 64GB | 約 33 万円 | Qwen3 14B(余裕)← おすすめ |
| Mac Studio M4 Max 128GB | 128GB | 約 50 万円〜 | Qwen3 30B まで対応 |
**最もお手頃は Mac mini M4 Pro 64GB(約 33 万円)**です。実際のベンチマークでも、Mac mini M4 Pro 64GB でローカル LLM が実用的に動作すると報告されています。普段は Mac として使い、LoRA 学習時に MLX を走らせる運用ができます。
MLX による LoRA ファインチューニング
Apple が開発した MLX フレームワークで、1 コマンドで LoRA ファインチューニングが実行可能です。
# MLX LM をインストール
pip install mlx-lm
# LoRA ファインチューニング
mlx_lm.lora \
--model mlx-community/Qwen3-14B-4bit \
--data ./lora-training-data \
--train \
--iters 1000
MLX は量子化が深く統合されており、4bit 量子化モデル上で LoRA アダプタを訓練できるため、メモリ使用量を大幅に削減できます。
NVIDIA GPU との比較
| Mac mini M4 Pro 64GB | RTX 3090 中古 + PC | |
|---|---|---|
| 価格 | 約 33 万円 | 11〜13 万円(GPU のみ) |
| 有効メモリ | 64GB(統合) | 24GB VRAM |
| 学習速度 | やや遅い | 速い |
| 推論速度 | 良好 | 良好 |
| 消費電力 | 低い(静音) | 高い(350W) |
| 日常利用 | Mac として普段使いも可能 | GPU 増設のみ |
| フレームワーク | MLX | PyTorch + Unsloth |
既に Mac ユーザーなら Mac mini M4 Pro 64GB が最もバランスの良い選択肢です。Windows PC を持っていて GPU を増設できるなら、RTX 3090 中古がコスパでは勝ります。