はじめに:AIの出力、"合ってる"ってどうやって判断してます?
最近、コードのかなりの部分をAIに書いてもらってる人、増えてきましたよね。
Claude CodeにしてもCursorにしても、関数1個どころか、機能まるごと「いい感じに書いといて」で返ってくる時代です。生産量はびっくりするくらい増えました。
でも、ここでちょっと正直に考えてみたいんです。
そのAIの出力、"合ってる"ってどうやって判断してます?
たぶん多くの現場の答えは、こうじゃないでしょうか。「目で見て、なんとなく大丈夫そうだったらマージ」。
これ、責めてるわけじゃないんです。僕も最初はそうでした。むしろ、AIが書いた量が少ないうちは、目視で十分まわります。
ただ、AIの生産量が増えていくと、ある日ふと気づくんですよね。自分が、AIの出力をひたすら採点し続けるだけの人になってる、という事実に。
実装はAIがやってくれるようになったのに、レビューだけは全部こっちに残ってる。しかも量は増える一方。これって、めっちゃもったいなくないですか。
今日はこの「毎回ぜんぶ目視レビュー問題」を、評価(Eval)を仕組みにするという考え方で抜ける話をします。抽象論で終わらせず、コピペで動く最小ハーネスとプロンプト例まで持って帰れるように書きました。
なぜ「毎回ぜんぶ目視レビュー」は破綻するのか
まず、なぜ目視レビューが限界を迎えるのか。ここを腹落ちさせないと、仕組みの話に進めません。理由は大きく4つあります。
1. スケールしない。
AIの出力は、人間の数十倍の速さで増えます。でも人間の目は1日8時間しかありません。出力が10倍になっても、目は10倍速くなりません。ここで需要と供給が壊れる。
2. 属人化する。
「あの人がレビューすれば大丈夫」は、その人がボトルネックになるということでもあります。レビュー基準がその人の頭の中にしかないと、チームに広がりません。
3. 静かな回帰(リグレッション)に気づけない。
ここが一番こわいところです。AIの出力品質は、自分のコードを変えてなくても勝手に変わることがあります。モデルがアップデートされた、プロンプトをちょっといじった、RAGに入れる文書を差し替えた。こういう変化で、昨日まで通っていたケースが今日は崩れる。目視だと、これに気づくのは「本番でバグが出てから」になりがちです。
4. 再現できない。
「前にこういう壊れ方したよね」を、人間の記憶だけで管理するのは無理があります。同じ失敗を、何度でも踏み直すことになる。
ちょっと具体的な事故を想像してみましょう。
あるチームが、AIに「ユーザー入力をJSONに整形する」処理を任せていた。半年間、目視レビューで問題なく回っていた。ある日、使っているモデルが新バージョンに切り替わった。すると、ごくまれに
nullを文字列の"null"として返すようになった。誰もコードを変えていない。レビューもすり抜けた。気づいたのは、3週間後、決済データの集計がズレた本番障害として、だった。
これ、AIが悪いわけじゃないんです。「出力が合っているかを、毎回・自動で・同じ基準で確かめる仕組み」が無かっただけ。情報を生成することと、それが正しいと保証することは、全然別の仕事なんですよね。
そして大事なのはここです。AI時代にエンジニアの仕事がなくなるんじゃなくて、重心が「実装する人」から「評価を設計する人」へ移る。電卓ができても数学者の仕事は消えなかった。計算じゃなく「何を計算すべきか」が仕事になっただけ。それと同じ流れだと思うんです。
結論:レビューを「評価(Eval)」という仕組みに変える
先に結論を置きます。
やることは、毎回の目視レビューを、一度作れば自動で回り続ける"評価(Eval)システム"に置き換えることです。
ここで言う評価(Eval)とは、おおげさなものじゃありません。ざっくり言うと、こういう部品の組み合わせです。
- ゴールデンセット … 「この入力には、こう答えてほしい(あるいは、こういう基準を満たしてほしい)」を集めた、お手本の問題集。回帰テストの土台。
- 決定論チェック … 形式・スキーマ・lint・既存テストなど、プログラムで白黒つけられる速くて安い検査。
- LLM-as-judge … 「読みやすさ」「意図に合ってるか」みたいに、機械的には測りにくい品質を、別のAIに採点させる仕組み。
- CIゲート … 評価スコアが基準を下回ったら、マージやデプロイを止める関所。
- 人間サンプリング監査 … 全部は見ない。5〜10%だけ人間が見て、採点の精度そのものを保つ。
ポイントは役割分担です。人間が設計するのは「合否の基準(ルーブリック)」。採点という単純作業をスケールさせるのはAI。そして、その採点が信用できるかを一部だけ人間が監査する。
「全部を目で見る」から「基準を設計して、採点を任せて、一部だけ確かめる」へ。これが今日の主題です。
評価ピラミッド4階層 — 実務の地図
いきなりコードに行く前に、全体の地図を見せます。評価は4階層のピラミッドで考えると整理しやすいです。下にいくほど「速くて安い」、上にいくほど「賢いけど高い」。
┌───────────────────────────┐
高い・遅い │ L3: 人間サンプリング監査 │ ← 5〜10%だけ。判定の精度を守る
├───────────────────────────┤
│ L2: LLM-as-judge (意味) │ ← 読みやすさ・意図適合などを採点
├───────────────────────────┤
│ L1: ゴールデンセット回帰 │ ← 過去の成功/失敗を固定して再発防止
├───────────────────────────┤
安い・速い │ L0: 決定論チェック │ ← format/schema/lint/test。まずここ
└───────────────────────────┘
順番がめちゃくちゃ大事です。いきなりLLM-as-judgeから作らないこと。
まずL0の決定論チェックで、機械的に弾けるものは全部弾く。「JSONとして壊れてる」「必須フィールドが無い」「lintが落ちる」みたいなのは、AIに採点させるまでもありません。速いし、安いし、ブレません。ここで9割の事故は止まります。
その上で、L0では測れない「意味の質」だけをL2のjudgeに回す。そしてjudge自体が信用できるかを、L3の人間監査で定期的に確かめる。
この順番を守るだけで、評価のコストは一気に下がります。怠惰なエンジニアほど、まず一番安い層を厚くするんです。
手を動かす:最小Evalハーネスを作る
ここからは実際に動くものを作ります。題材は「自然文の住所を、決まったJSON構造に整形するAI処理」とします。汎用的なダミー題材です。
Step 1. ゴールデンセットを持つ
まず、お手本の問題集をファイルで持ちます。最初は25〜50件で十分。ハッピーケースだけじゃなく、過去にやらかしたケースと、意地悪なエッジケースを必ず入れるのがコツです。
[
{
"id": "addr-001",
"input": "東京都千代田区1-2-3 サンプルビル4F",
"must_include": ["千代田区"],
"expect_schema": ["prefecture", "city", "rest"],
"note": "標準ケース"
},
{
"id": "addr-002",
"input": "住所不明",
"must_include": [],
"expect_schema": ["prefecture", "city", "rest"],
"note": "回帰: nullを文字列nullで返した事故の再発防止"
},
{
"id": "addr-003",
"input": "Tokyo, Chiyoda 1-2-3",
"must_include": [],
"expect_schema": ["prefecture", "city", "rest"],
"note": "エッジ: 英語表記でも壊れないか"
}
]
ここで大事なのは、addr-002 のような前に壊れたケースを、必ず1件ずつ資産として残していくこと。これが回帰テストの本体です。本番でヒヤッとするたびに1行足す。これだけで、明日の自分が同じ事故を踏まなくて済みます。
Step 2. 決定論チェック(L0)を書く
次に、機械で白黒つく検査。AIを呼ぶ前に、まずこれで弾きます。
import json
def deterministic_checks(raw_output: str, case: dict) -> list[str]:
"""機械的に判定できる失敗を返す。空リストなら合格。"""
failures = []
# 1) そもそもJSONとして妥当か
try:
data = json.loads(raw_output)
except json.JSONDecodeError:
return ["JSONとしてパースできない"]
# 2) 必須キーが揃っているか
for key in case["expect_schema"]:
if key not in data:
failures.append(f"必須キー欠落: {key}")
# 3) 含まれているべき文字列があるか
for needle in case["must_include"]:
if needle not in raw_output:
failures.append(f"必須語が含まれない: {needle}")
# 4) "null"という文字列で誤魔化していないか(過去事故の番人)
if data.get("prefecture") == "null":
failures.append('prefectureが文字列"null"になっている')
return failures
地味ですよね。でもこの地味な関数が、L2のjudgeを呼ぶコストの大半を節約してくれます。判定が速くてブレない検査を、できるだけ下の層に積む。これが評価設計の基本姿勢です。
Step 3. LLM-as-judge(L2)を呼ぶ
L0を通った出力だけ、意味の質をAIに採点させます。judgeを呼ぶコードは、驚くほど薄くて大丈夫です。
def judge(input_text: str, model_output: str, llm) -> dict:
"""別のLLMに採点させ、JSONの判定を受け取る。llmは任意のクライアント。"""
prompt = JUDGE_PROMPT.format(
input_text=input_text,
model_output=model_output,
)
raw = llm.complete(prompt, temperature=0) # 採点はブレを抑えるため温度0
verdict = json.loads(raw)
return verdict # {"pass": bool, "score": int, "reason": str}
判定の温度は0に寄せます。採点者がコロコロ気分で点を変えたら、回帰テストになりませんからね。
肝心なのは、薄いコードの中身じゃなくて、採点基準(ルーブリック)を書いたプロンプトです。ここが今回の山場なので、章を分けます。
Step 4. CIゲートで関所を作る
最後に、これをCIに組み込みます。スコアが基準を割ったら、マージを止める。pytestでもいいし、GitHub Actionsならこんな形です。
name: ai-eval-gate
on: [pull_request]
jobs:
eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run eval suite
run: python run_eval.py --golden golden.json --min-pass-rate 0.95
# run_eval.py 内で合格率が 0.95 を下回ったら exit 1 にしておく
# → CIが赤くなり、マージがブロックされる
ここまで来ると、「AIの出力が静かに劣化したら、人間が気づく前にCIが赤くなる」という状態になります。本番障害として顕在化する前に、関所で止まる。3週間後に決済データのズレで気づく、みたいな未来が消えるわけです。
LLM-as-judgeを"信頼できる採点者"にする
さて、ここが心配性な人ほど気にしてほしいところです。
LLM-as-judgeは便利ですが、judge自身も間違えます。しかも、わりとクセのある間違え方をする。だから「AIに採点させてるから安心」と丸投げするのは危険です。信頼できる採点者に育てる手順がいります。
まず、ちゃんとしたルーブリックを書く
採点プロンプトは、曖昧な5段階評価じゃなく、観点ごとに白黒つく形で書きます。出力はJSON固定にして、機械で集計できるようにします。
あなたはコード出力の厳格なレビュアーです。
以下の「入力」と「モデル出力」を、次の3観点で評価してください。
甘い点はつけないこと。少しでも疑わしいものは fail にすること。
観点:
1. 正確性: 入力の情報を取りこぼさず、捏造もしていないか
2. スキーマ適合: 指定キー(prefecture, city, rest)に正しく割り当てているか
3. 安全性: 不明な値を、それと分かる形(null)で表現できているか
入力:
{input_text}
モデル出力:
{model_output}
次のJSONだけを返すこと。前置きや説明は禁止:
{"pass": true/false, "score": 0-10, "reason": "30字以内で根拠"}
「甘い点はつけるな」「疑わしきは fail」と明示するのがポイントです。judgeは放っておくと優しすぎる先生になりがちなので、最初から厳しめに躾けます。
バイアスを知っておく
judgeには、知られたクセがあります。代表的なのはこの3つです。
- 位置バイアス … 2つの出力を比べさせると、先に出したほうを高く評価しやすい。
- 冗長性バイアス … 長くて説明が多い出力を、中身に関係なく高く評価しやすい。
- 自己優遇バイアス … 自分(同じモデル)が書いた文章を、好意的に採点しやすい。
対策はシンプルで、判定の根拠(reason)を必ず書かせること。そして比較させるときは順番を入れ替えて2回聞く。これだけでだいぶマシになります。
jury(多数決)で安定させる
一番効くのが、1人の採点者に頼らず、複数のjudgeに採点させて多数決を取るやり方です。jury-of-judges と呼ばれます。
このタスクを、3人の独立したレビュアーとして評価してください。
それぞれ別々に pass/fail を判定し、最後に多数決の結論を出してください。
- レビュアーA(正確性重視): {観点1だけを厳しく見る}
- レビュアーB(安全性重視): {観点3だけを厳しく見る}
- レビュアーC(全体最適): {3観点をバランスで見る}
出力:
{"verdicts": ["pass"/"fail", ...], "final": "pass"/"fail", "reason": "..."}
観点をわざと分けるのがミソです。同じ視点の3人より、違う視点の3人のほうが、見落としを潰せます。1人なら見逃した安全性の穴を、安全性担当のBが拾う、みたいに。
最後に、人間がキャリブレーションする
そして、これを忘れちゃいけません。judgeが正しく採点できているかを、人間が確かめる工程です。
やり方は地味です。人間が30〜50件、自分の手で pass/fail のラベルを付ける。さらに「なぜそう判定したか」を一言メモする。そのうえで、judgeの判定とどれだけ一致するかを見ます。
# キャリブレーション用の聞き方(人間ラベルと突き合わせる)
次の出力を pass/fail で判定し、判断理由を1文で書いてください。
そのあと、人間のラベル「{human_label}」と、あなたの判定が
一致したか不一致かを答えてください。
不一致だった場合、ルーブリックのどの表現が曖昧だったかを指摘してください。
ここで不一致が多ければ、judgeが悪いんじゃなくて、たいていルーブリック(基準)の書き方が曖昧なんです。人間の判断基準を、もう一段だけ言語化してプロンプトに足す。これを何回か回すと、judgeの判定が人間の感覚にそろってきます。
この「人間が基準を磨き、AIが採点を回す」往復こそが、評価を仕組みにするってことの中身です。
人間が設計し、AIが採点し、人間が監査する
ここまでの役割分担を、1枚に整理しておきます。AI時代の開発って、結局この線引きをどこに引くかの勝負だと思うんですよね。
| 工程 | 誰がやる | 中身 |
|---|---|---|
| 合否基準(ルーブリック)の設計 | 人間 | 何を正解とするか。ここが一番の知的作業 |
| ゴールデンセットの選定 | 人間 | どのケースを資産として残すか。事故を1件ずつ追加 |
| 決定論チェックの実装 | 人間→以後自動 | 一度書けば、あとは機械が回す |
| 大量の出力の採点 | AI | スケールする単純作業はAIに渡す |
| 採点の監査(5〜10%) | 人間 | judgeが暴走してないかを定期チェック |
| 基準の更新 | 人間 | 不一致やすり抜けを見て、基準を磨き直す |
見てのとおり、人間の仕事は消えてません。むしろ難しくて面白いところ(基準の設計と監査)に集中できるようになる。採点という消耗戦から解放されて、設計に頭を使えるようになるんです。
これが、僕がよく言う「実装者から、設計・評価・文脈設計者へ」の正体です。役割が奪われるんじゃなくて、一段上に引き上げられる感覚に近いかなと。
明日から始める3ステップ
「全部やるのは大変そう」と思った人へ。一気にやらなくて大丈夫です。小さく始めるのが正解です。
-
ゴールデンセットを1ファイル、5件だけ作る。
いま一番AIに任せてる処理について、「これは絶対こう返ってほしい」を5件、JSONで書く。次にヒヤッとしたら1件足す。これだけで回帰テストの種ができます。 -
決定論チェックを1個だけ書く。
JSONが壊れてないか、必須キーがあるか。一番安い検査を1個、CIに入れる。judgeはまだいりません。これだけで事故の体感は減ります。 -
judgeのルーブリックを1枚書いて、10件だけ人間と突き合わせる。
厳しめのルーブリックを書いて、人間の判定と一致するか10件で確かめる。ズレたら基準の言葉を直す。judgeを"育てる"感覚をつかめば、あとは広げるだけです。
3つとも、たぶん1〜2時間で着手できます。完璧じゃなくていい。65点でいい。今日5件のゴールデンセットを書いておけば、明日の自分が回帰で救われます。
おわりに:採点を毎回手でやるのは、もう卒業
AIにコードを書かせる時代、本当のボトルネックは「生成」じゃなくて「それが合ってるとどう保証するか」に移りました。
そして、その保証を毎回・人間が・目視でやるのは、もう量的に無理がある。これは気合いの問題じゃなくて、設計の問題なんですよね。
だから、レビューを「仕組み」に変える。
基準は人間が設計し、採点はAIに任せ、その採点を人間が一部だけ監査する。一度組めば、AIの出力が静かに劣化しても、本番に出る前に関所が止めてくれます。
これって、めちゃくちゃ怠惰な発想なんです。でも、いい怠惰です。汗をかくんじゃなくて、冷や汗をかく前に仕組みで止める。手を動かし続けるんじゃなくて、頭とAIで一回設計しておく。
毎回の採点に消耗してる今日の自分から、ちょっとだけ卒業して、評価を設計する側にまわってみる。
今日ゴールデンセットを5件書いた自分に、明日の自分はたぶん「あざっす」って言ってくれます。そのくらい軽い一歩から、始めてみませんか。
最後まで読んでくれて、ありがとうございました。