はじめに
GMOコネクトの永田です。
同じLLM、同じプロンプトで30件のIssueを2回分類・要約して、完全一致は66.7%でした。
LLMの出力精度を測ろうとして最初にぶつかったのがこの壁です。正解データ自体がLLMの生成物だと、文字列の完全一致では精度を測れません。プロンプトを改善したくても、数百件の出力を目視で「良くなったか」を比べるのは無理があります。しかもチューニングの変数はプロンプトだけではなく、モデルの種類やサイズ、Thinkingなどのモデル設定も絡みます。掛け算で組み合わせが膨れ上がるので、全パターンを人手で確認するのは現実的ではないです😇
今回、LLM-as-judgeという手法でLLMの出力を別のLLMに自動採点させ、その結果をClaude Codeに渡してプロンプトや構成を自律的に改善させる仕組みを作りました。Redmineチケットからのレビューコメント自動抽出タスクで16構成のA/Bテストを回し、プロンプトを3世代にわたって改善した結果、妥当率が90.4%から98.6%まで上がっています。
まとめ
30件のサンプル、Gemini Flash、Claude Code。この3つで16構成のA/Bテストを自動評価し、LLM出力の妥当性(本記事では妥当率と呼びます)を90.4%から98.6%まで引き上げました。
仕組みは単純です。LLM-as-judgeで出力の妥当性を自動採点し、その結果をClaude Codeに渡してプロンプトや構成を改善させる。このループを3回回した結果が下の表です。
| 改善ステップ | 妥当率 | 改善幅 |
|---|---|---|
| v1(初版プロンプト) | 90.4% | — |
| v2(過剰分類の抑制) | 98.2% | +7.8pt |
| v3(見落とし対策) | 98.6% | +0.4pt |
あわせてThinking ON/OFFやモデルサイズの影響も定量化しています。一番効いたのはThinking ON/OFFで+20pt、次がプロンプト改善で+7〜10pt、モデルサイズは+2ptでした。
背景
前回の記事「機密データも安全に。Ollamaで16,000件→496観点のレビューチェックリストを自動生成」では、ローカルLLMのOllamaを使ってRedmineチケットからレビューチェックリストを自動生成しました。
あの処理の最初のステップであるPhase 1が、チケットの本文やコメントからソフトウェアレビューの指摘を抜き出して、design(設計)、coding(実装)、testing(試験)、none(指摘なし)の4カテゴリに振り分けるタスクです。今回はこのPhase 1の精度改善と速度向上に取り組みました。
1つのチケットから複数の指摘が出てくることもあります。たとえばdesignとcodingの両方に該当する指摘が含まれるケースがあり、単純なテキスト分類より手強いタスクです。
分類に使うモデルはgpt-oss(OpenAI公開のオープンモデル)で、ローカルGPUのDGX Sparkで推論しています。
チューニング用サンプルの準備
今回の手法では、精度判定のLLM-as-judgeにクラウドLLMであるGemini API(gemini-3-flash-preview)を使いました。judgeに送るデータ、つまりチケットの内容と分類結果はクラウドに送られることになります。
そのため、本番の機密データ全件をそのまま評価にかけるわけにはいきません。クラウドに送っても問題ないサンプルを事前に用意する必要があります。
今回は次の手順で進めました。本番1,184件からカテゴリ分布を考慮して30件を抽出し、機密情報が含まれないことを確認。この30件でプロンプトのチューニングサイクルを回し、改善が済んだプロンプトを本番のローカルLLM環境で全件に適用する、という流れです。
機密データはローカルから出さず、チューニング用のサンプルだけクラウドを使う運用です。
精度判定の課題
同じモデルで再実行しても33%が合わない
精度を測る一番素朴な方法は、正解データとの完全一致、いわゆるExact Matchです。ところが正解データ自体がLLMの出力だと、この方法はうまく機能しません。
同一の120bモデル、同一プロンプトで30件のチケットを2回処理したところ、カテゴリの完全一致は20/30件、66.7%でした。temperature=0.3に絞っていてもこの結果です。
なぜずれるのか
1回目: category=design, review_comments="メモリ上限を超えるファイルでOOMリスク"
2回目: category=design, review_comments="Podのメモリサイズ以上のファイル処理で障害発生の懸念"
カテゴリは同じでも、review_commentsの言い回しが違えば不一致になります。1回目にひとつにまとめていた指摘が2回目では2つに分かれる、といった粒度の揺れも起きます。
文字列の一致ではなく、内容として妥当かどうかを見る仕組みが要ります。
LLM-as-judge
設計
LLM-as-judgeは、対象LLMの出力を別のLLMに採点させる手法です。今回は3つの要素を設計しました。
まず判定基準。正解か不正解かの2択ではなく、valid(妥当)、borderline(やや拡大解釈)、invalid(不適切)の3段階にしました。おおむね妥当だが厳密には微妙、というケースを拾うためです。
次に見落とし検出。judgeには分類結果の評価に加えて、LLMが見落としたカテゴリも指摘させます。noneと判定されたチケットに実は技術的指摘が埋まっていた、という「noneの誤判定」などを検出できます。
最後に指標の定義です。妥当率はvalidとborderlineの合計割合。正確率はvalidだけの割合。Ticket妥当率はチケット単位で妥当と判定された割合。見落とし数はjudgeが指摘した見落としカテゴリの総数です。
実装
judgeのシステムプロンプトの抜粋です。
JUDGE_SYSTEM_PROMPT = """\
あなたは Redmine チケットのレビュー分類を評価する判定者です。
チケットの内容(subject, description, journals)を読み、
LLM が出力したレビュー分類結果が妥当かどうかを判定してください。
## 判定基準
各レビュー項目について、以下の3段階で判定してください:
- valid: チケット内容から明確に裏付けられる妥当な分類
- borderline: 解釈次第では裏付けられるが、やや拡大解釈
- invalid: チケット内容からは裏付けられない不適切な分類
## 出力形式
{
"judgments": [
{"category": "分類カテゴリ", "verdict": "valid/borderline/invalid",
"reason": "判定理由を1文で"}
],
"missed_categories": ["見落としたカテゴリがあれば記載"],
"overall_verdict": "valid/borderline/invalid"
}"""
judgeモデルの呼び出し部分です。
def call_judge_gemini(client, model_name, ticket_content, items):
"""チケット内容と分類結果を Gemini に送り、妥当性を判定する"""
user_prompt = f"""## チケット内容
{ticket_content}
## LLM の分類結果
詳細:
{items_text}
## 判定
上記のチケット内容に対して、この分類結果は妥当ですか?"""
config = types.GenerateContentConfig(
system_instruction=JUDGE_SYSTEM_PROMPT,
thinking_config=types.ThinkingConfig(thinking_level="HIGH"),
temperature=0.1,
max_output_tokens=8192,
)
response = client.models.generate_content(
model=model_name, contents=contents, config=config,
)
return response.text, usage, wall_time
judgeにはthinking_level="HIGH"を設定して判定精度を上げ、temperature=0.1で揺れを抑えています。チケットの全文をjudgeに渡すので、原文に基づいた判定になります。
judgeの信頼性検証
Gemini 3 Flash(gemini-3-flash-preview)の判定がどこまで信頼できるか、10チケット×全構成=108判定をClaude Codeでも独立に評価して突き合わせました。
| 指標 | 値 |
|---|---|
| 二値一致(妥当か不適切か) | 103/108 (95.4%) |
| 判定一致(valid/borderline/invalidが同一) | 75/108 (69.4%) |
妥当か不適切かの判断は95.4%一致。3段階の完全一致が69.4%にとどまるのは、validとborderlineの境界をどこに置くかのLLMでの解釈差です。
今回のタスクではFlashクラスのモデルで十分な判定精度が出ましたが、より複雑なタスクではjudgeにも上位モデルが必要になる可能性があります。judgeモデルの選定もタスクに応じて検証するのが望ましいです。
Claude Codeによるプロンプト・構成の自律チューニング
ワークフロー
人が手を動かしたのは、judge結果を確認してClaude Codeに「改善して」と伝えたところだけです。エラーパターンの特定、原因の分析、改善案の作成、プロンプトへの反映はすべてClaude Codeが自律的にやっています。
プロンプト改善だけでなく、モデルサイズの切り替え(120b→20b)やThinking ON/OFFの比較実験もClaude Codeが自律的に実行・評価しました。「次は20bで試して」「Think OFFで速度を測って」といった指示を出すと、スクリプトの実行からjudge評価、結果の比較分析まで一貫して回します。
v1からv2へ:過剰分類の抑制
judge結果を渡されたClaude Codeは、こんなパターンを見つけました。チケットに書かれていない改善提案をLLMが勝手に作り出して、レビュー指摘として分類している。作業報告やリリース連絡しかないチケットに対して、LLMが独自にdesignやcodingの指摘を生成していたのです。
Claude Codeはプロンプトに次の制約を追加しました。
## 重要な制約(v2で追加)
**抽出の原則: チケットに書かれていることだけを抽出する**
- 「こうすべき」「この実装は問題がある」など、明示的なレビュー指摘が必要
- あなた自身がレビュー指摘を創作してはいけない
迷った場合は none を選んでください。過剰に分類するよりも、見逃す方が望ましい。
あわせてnoneの判定例を3つ追加しています。作業連絡、障害対応報告、リリース連絡の例です。
この変更で妥当率は90.4%から98.2%に上がりました。+7.8ptです。
v2からv3へ:見落とし対策
v2のjudge結果をClaude Codeに渡したところ、3つのエラーパターンが見つかりました。
1つ目は長いチケットのover-none。コメントが10件以上あるチケットで、技術的指摘を含むコメントが埋もれてnoneと判定されていました。2つ目はdesignの範囲が広すぎる問題。証明書更新やディスク拡張といったインフラ運用手順をdesignに誤分類していました。3つ目はtestingの見落とし。テスト環境変更に伴うテスト失敗の指摘を拾えていませんでした。
Claude Codeはこれらに対応する指示をプロンプトに追加しました。
## コメントが多いチケットの処理(v3で追加)
チケットのコメント数が多い場合でも、全てのコメントを注意深く読んでから判断してください。
- コメントが10件以上あっても、その中に1件でも技術的指摘が含まれていれば抽出する
- コメント数が多いことだけを理由に none にしてはいけない
## 分類カテゴリ
- design: ソフトウェアのアーキテクチャ・設計方針・機能仕様に対する指摘
- 注意: 以下は design ではない
- インフラの運用手順(証明書更新、ディスク拡張、環境設定変更)
- 設定値の変更報告
妥当率は98.2%から98.6%に改善し、見落としも5件から4件に減りました。
自律改善の経過
| バージョン | 主な変更 | 妥当率 | 見落とし |
|---|---|---|---|
| v1(初版) | — | 90.4% | 2 |
| v2 | 過剰分類抑制、none例追加 | 98.2% | 5 |
| v3 | 長文チケット対策、design範囲明確化、testing例追加 | 98.6% | 4 |
v2で見落としが2から5に増えたのは、過剰分類を抑制した副作用です。noneに寄せたぶん、本来拾うべきものまで落としていました。v3でその対処を入れて、精度と見落としの両方を改善しています。
Thinking ON/OFF
Thinkingとは
gpt-ossモデルは推論時に内部でreasoning tokensと呼ばれる思考過程を生成してから最終回答を出力します。この思考過程のON/OFFを切り替えることで、速度と精度のバランスを調整できます。Think OFFの具体的な設定手順(chat_templateの書き換え方やreasoning_effortの指定方法など)は別記事で改めて紹介します。
| 方式 | 仕組み | 速度への影響 |
|---|---|---|
| Think ON(デフォルト) | 内部で reasoning tokens を生成してから回答 | 基準 |
| Think OFF | chat_template で reasoning を無効化 | 高速化 |
| reasoning_effort=low | reasoning 量を短縮 | 中間 |
実験結果
16構成をテストしました。主な結果です。
| 構成 | 速度 | 妥当率 | 見落とし |
|---|---|---|---|
| 120b Think ON 改善後プロンプト | 18.9s/件 | 98.6% | 4 |
| 20b Think ON 改善後プロンプト | 10.4s/件 | 96.5% | 5 |
| 20b Think OFF 改善後プロンプト | 2.1s/件 | 76.3% | 18 |
| 20b Think OFF 改善後+明示的推論プロンプト | 13.4s/件 | 87.5% | 11 |
| 20b reasoning_effort=low 改善後プロンプト | 8.1s/件 | 78.0% | 14 |
Think OFFは5倍速いが精度は20pt落ちる
20bモデルで同一の改善後プロンプトを使った比較です。速度はThink ONの10.4s/件からThink OFFで2.1s/件と5.0倍速くなりました。120bベースラインの18.5s/件からの比較では8.8倍です。
ただし妥当率は96.5%から76.3%に20pt下がり、見落としは5件から18件に跳ね上がりました。reasoningを省略すると、複雑なチケットを安易にnoneと判定してしまう傾向が出ます。
reasoning_effort=lowもThink OFFと同水準
reasoningを短くする設定(effort=low)では妥当率78.0%。完全に省略するThink OFFの76.3%とほぼ変わりません。reasoningは短くしても省略しても同じだけ精度が落ちます。このタスクでは十分な量のreasoningが必要でした。
Think OFFにexplicit CoT(明示的推論)を入れると速度が消える
Think OFFの精度を補おうとして、プロンプト側にexplicit chain-of-thought(明示的推論)を組み込みました。コメントを1件ずつスキャンしてから分類結果を出力する2段階構成です。
精度は76.3%から87.5%に11pt改善しましたが、速度が2.1sから13.4sまで落ちました。Think ONの12.4sとほぼ同じです。精度を取り戻そうとすると、速度の恩恵が消えます。
Ollamaのnothinkオプションに注意
Ollamaではモデル定義ファイル(Modelfile)にPARAMETER nothink trueを設定することで、APIレスポンスからreasoningを非表示にできます。今回gpt-ossモデルでこの設定を試したところ、レスポンスからreasoningは消えるものの、内部ではreasoningを生成・Decodeしていることが分かりました。
| 指標 | Think ON | Ollama nothink | 本当のThink OFF |
|---|---|---|---|
| completion_tokens | 524 | 503 | 73 |
| 速度 | 12.4s | 10.4s | 2.1s |
completion_tokensがThink ONとほぼ同じ503トークンで、本当のThink OFFの73トークンの7倍です。少なくともgpt-ossモデルでは、Ollamaのnothink設定は見かけ上の非表示にとどまり、実際の高速化にはつながりませんでした。モデルによって動作が異なる可能性はありますが、nothinkを使う場合はcompletion_tokensや速度を確認して、本当にreasoningが省略されているか検証することをおすすめします。
精度への影響はタスク次第
Think OFFの影響はタスクの複雑さで大きく変わります。
| タスク | Think OFFの精度影響 | 理由 |
|---|---|---|
| メタデータ抽出(単純な構造化、別件タスク) | 精度維持、7.6x高速化を達成 | 判断が単純でreasoning不要 |
| レビューコメント分類(今回のタスク) | -20ptの精度低下 | 複数コメントの文脈理解や指摘の有無判断に多段階の推論が要る |
パターンマッチで済むタスクなど軽量なタスクならThink OFFが効きます。文脈理解や推論を伴うタスクではThink ONが必須です。
因子分析
全16構成の結果から、Think、プロンプト、モデルサイズの3因子の影響を切り分けました。
| 因子 | 影響幅 | 具体例 |
|---|---|---|
| 1. Think ON/OFF | +20pt | 改善後プロンプトでThink ON 96.5% vs Think OFF 76.3% |
| 2. プロンプト改善 | +7〜10pt | 120b Think ONで初版 90.4%から改善後 98.6% |
| 3. モデルサイズ | +2pt | 改善後プロンプト Think ONで20b 96.5%から120b 98.6% |
一番効くのはThink ON/OFFで+20pt。次がプロンプト改善で+7〜10pt。モデルサイズの差は+2ptにとどまり、しかもThink ONのときだけ効きます。モデルを大きくするよりプロンプトを直すほうが精度に利く、というのが今回の実験で見えた結果です。ただし、これはあくまで今回のタスクでの傾向であり、タスクによってはモデルサイズが大きく効く可能性もあります。どの因子が支配的かはタスクごとに異なるので、LLM-as-judgeで都度計測して判断するのが確実です。
| 目的 | 推奨構成 | 妥当率 | 速度 |
|---|---|---|---|
| 精度最優先 | 120b Think ON + 改善後プロンプト | 98.6% | 18.9s/件 |
| 速度と精度のバランス | 20b Think ON + 改善後プロンプト | 96.5% | 10.4s/件 |
| 速度最優先(精度犠牲) | 20b Think OFF + 改善後プロンプト | 76.3% | 2.1s/件 |
まとめ
今回はRedmineチケットの分類タスクで検証しましたが、LLM-as-judgeとAI Agentの改善ループはテキスト分類、情報抽出、文書要約といったLLM活用全般に転用できます。ローカルLLMに限った話でもなく、OpenAIやAnthropicのクラウドAPIでも同じパターンが使えます。
自分のタスクに適用する場合、ステップは3つです。まずタスクに応じた評価基準を決めてjudgeを作る。次に初版プロンプトを計測して改善ループを回す。最後にThinking ON/OFFやモデルサイズを変えて因子分析し、最適構成を選ぶ。
今後はサンプル数を30件から100件以上に拡大して評価の信頼性を上げることや、judge基準そのものをAI Agentで改善すること、といった発展が考えられます。
最後に、GMOコネクトではサービス開発支援や技術支援をはじめ、幅広い支援を行っておりますので、何かありましたらお気軽にお問合せください。