メインコンテンツへスキップ
見出し画像

【AI実測 #2】Claude Codeの「合格」は6割しか当たらない

    AIが直したコード60件を、別のAIに「本当に直ったか」採点させました。3モデルとも「合格」と答えた38件のうち、実際に直っていたのは23件。6割です。

    残りの4割は、見えているテストを全部通ったまま壊れていました。AIにコードを書かせて、AIにレビューさせている人には、そのまま効く数字だと思います。

    採点役が何を見落としたのか、そしてAIの採点のどこなら信じてよかったのかまで書きます。先に言ってしまうと、信じてよかったのは「合格」ではないほうでした。

    先に結論

    先に、60件を数え終えたあとの結論だけ書きます。

    • 採点役が「合格」と言った回のうち、本当に直っていたのは Opus 5 で6割・Sonnet 5 で6割・Haiku 4.5 で4割

    • 逆に「不合格」と言った回は、180回のうち一度も外れなかった

    • 3モデルの判定が割れた20件は、20件とも実際に壊れていた。割れること自体が、いちばん当てになる信号でした


    手元に「正解つきの60件」がある、という話

    前回の「安いモデルも1行足せば直すのか」を測った回と、その前の回で、同じ課題をClaude Codeの3モデルに何度も直させていました。手元のMacで実際に走らせたもので、合計60件の修正が残っています。

    この60件には、ちょっと珍しい性質があります。

    1. 60件とも、渡したテストは全部通っている(つまり、普通に見れば「直った」)

    2. それとは別に、モデルに一度も見せていないテストを後から当ててある

    2つめを、ここでは伏せテストと呼びます。「バグ報告に書いてある症状が、報告に出てきた入力以外でも直っているか」だけを確かめる短いテストです。

    この伏せテストの結果が、そのまま合否の正解になります。今回あらためて60件すべてに当て直したところ、直っていたのは23件、直っていなかったのは37件でした。過去2回の記録と1件の食い違いもありません。

    見えているテストは60件とも通る。落ちるのは、後から当てた伏せテストだけ。

    つまり「答えが分かっている採点問題」が60問ある状態です。これに採点役をぶつけます。

    採点役に渡したもの、渡さなかったもの

    採点役にはClaude Codeの3モデル(Opus 5 / Sonnet 5 / Haiku 4.5)を使い、60件すべてを3モデルに採点させました。合計180回です。

    渡したのは4つだけ。

    • 修正後のコード一式

    • 見えているテスト(実行してよい、と伝えてあります)

    • 元のバグ報告

    • その回の差分

    渡さなかったものが大事です。伏せテスト・正解ラベル・他の回の結果・どのモデルが直したかは、一切渡していません。どれか1つでも混ざれば答えを見ながら採点することになるので、混入していないかをスクリプト側でも検査しています。

    採点役に聞いたことは、これ1つだけです。

    この修正は、見えているテストだけでなく、すべての妥当な入力に対して、報告されたバグを正しく直しているか。見えているテストが全部通ることは前提です。それだけを理由に合格にしないでください。

    答えは「合格」か「不合格」の一言と、理由1行。採点役が実装を書き換えていないことは、実行前後のファイルのハッシュを突き合わせて確認しました(180回とも書き換えゼロ)。

    画像
    図1 60件の修正に、3つの採点役をぶつけた手順(筆者作成)

    結果:「直った」は当てにならず、「直っていない」は外さない


    画像
    図2 採点役3モデルの成績。正解は伏せテストの合否(筆者作成)

    表の言葉だけ補足します。見逃しは「合格と言ったのに、実は壊れていた」。空振りは「不合格と言ったのに、実は直っていた」。品質の観点でまずいのは前者です。

    数字を本文にも書いておきます。

    • 判定が当たった: Opus 5 が45/60、Sonnet 5 が44/60、Haiku 4.5 が25/60

    • 見逃し: 15件・16件・35件

    • 空振り: 3モデルとも0件

    そして、いちばん意外だったのがここです。

    空振りは、180回の判定で1件も出なかった。

    3モデルとも、実際に直っていた23件は全部「合格」と言い当てています。間違えるのは必ず一方向 —— 壊れているものを「直った」と言う側にだけ寄っていました。

    裏を返せば、採点役が「直っていません」と言ったときは、毎回それが正しかったということになります。正直、ここまできれいに片側へ寄るとは思っていませんでした。

    なぜ見逃すのか——教科書のバグは消えて、その先が残る

    見逃しの理由を読んでいくと、ほとんどが同じ形をしていました。経費の集計で出た見逃し51件のうち19件が、「可変デフォルト引数」あるいは「可変既定引数」という言葉をそのまま使って「正しく直っている」と述べています。

    3箇所とも根本原因を修正し、境界値・丸め・可変デフォルト引数の一般ケースでも正しく動作する

    可変デフォルト引数というのは、Pythonを勉強すると必ず出てくる有名な落とし穴です。関数の初期値に空のリストを書くと、そのリストが呼び出しのたびに使い回されて中身が溜まっていく、というもの。

    安いモデルたちは、これをきちんと直しています。初期値を空にするのをやめて、中で作り直すようにした。だから採点役も「教科書どおりに直っている」と判断しました。

    • 消えたもの: 関数の初期値が呼び出しをまたいで使い回される問題

    • 残ったもの: 呼び出す側から渡されたリストを、そのまま書き換えてしまう問題

    ところが、残った穴があります。

    画像
    図3 同じ一行の差で、見えないテストの結果が割れる。左が穴の残った直し方、右が残らなかった直し方(筆者作成)

    呼び出す側がリストを渡してきた場合、そのリストに直接書き足してしまうのは変わっていないのです。Opus 5 だけが、渡されたリストを複製してから使っていました。差はほぼ一行です。

    もう1つの課題も同じ形でした。鍵に「年」を足して年またぎの取り違えだけ直し、「日」は足さない。3月15日と3月20日の請求額が同じになります。

    • Haiku 4.5: 10回とも「日」を足さなかった

    • Sonnet 5: 10回中7回。残り3回は「日」まで入れて正しく直している

    同じモデルでも回ごとに揺れます。1回動かして「このモデルはこう直す」と決めつけないほうがよさそうです。

    2つの課題に共通していたのは、これです。

    「有名なバグの形」が消えたところで、採点が止まっている。

    教科書に名前が載っている問題は見つけられるのに、その修正が作り残した穴は見に行かない。

    数字にも出ています。可変デフォルト引数のほうの課題は判定90件のうち51件が見逃し。日割り計算のほうは15件でした。名前のついた定番バグほど、採点役は安心してしまうようです。

    判定が割れた20件は、20件とも壊れていた

    3モデルの判定を並べて、揃ったかどうかで分けてみました。

    • 3モデル全員が「合格」…… 38件(うち15件は実際には壊れていた)

    • 3モデル全員が「不合格」…… 2件

    • 判定が割れた …………………… 20件(20件とも実際に壊れていた)


    画像
    図4 3つの採点役の答えが揃ったかで分ける(筆者作成)

    全員一致の「合格」ですら4割が外れているのに、割れた20件は100%壊れている。つまり、採点役の多数決を取って「2対1で合格」としていたら、その20件を全部通していたことになります。

    ついでに分かったことがあります。3モデルの判定はきれいに入れ子になっていました。Opus 5 が合格にしたものは Sonnet 5 も必ず合格にし、Sonnet 5 が合格にしたものは Haiku 4.5 も必ず合格にする。厳しさの順に一列に並んでいたわけです。

    つまり判定が割れたとき、多数決の結果は「真ん中のモデルの意見」にしかなりません。3体に聞く意味が消えます。合議で決めるのではなく、割れた時点で赤信号として扱うほうが正しい使い方でした。

    自分の修正を自分で採点させるとどうなるか

    採点役と修正役の組み合わせ別にも出しました。ここも見事でした。

    画像
    図5 誰が誰を採点したか別の的中(筆者作成)
    • Haiku 4.5 が Haiku 4.5 の修正を採点した20件は、20件すべて「合格」。実際には20件とも壊れていました(的中0%)

    • 同じ20件を Opus 5 に採点させると、14件は「不合格」と見抜きます

    • Sonnet 5 が Sonnet 5 の修正を採点した20件も、的中は11件

    一方、Opus 5 の修正20件は、3つの採点役すべてが20/20で正しく合格と判定しています。直っているものを見抜くのは簡単なのです。難しいのは、もっともらしく壊れているものを見抜くことでした。

    自己採点が甘くなるというより、自分が見落とした観点は、採点する側に回っても同じように見落とす、という当たり前のことが起きているだけだと思います。見落としの癖はモデルごとに固有なので、採点役は必ず別のモデル、できれば一段賢いモデルにする——ここは実務でそのまま使える結論になりました。

    明日から使えると思ったこと

    60件を数え終えて、自分の手順を3つ変えました。

    画像
    図6 この結果から決めた3つの運用(筆者作成)
    1. 採点役は、修正させたモデルと別にする。 同じモデルの自己採点は、いちばん当たらなかった条件です

    2. 「不合格」だけを信号として使う。 「合格」は情報がほぼゼロ(6割・4割)ですが、「不合格」は180回外れませんでした

    3. 判定が割れたら通さない。 多数決で押し切らない。割れた20件は全部壊れていました

    そして、いちばん効くのは身も蓋もない話でした。AIの採点をテストの代わりにしない。 60件とも見えているテストは通っていて、それでも37件が壊れていたのです。症状の周辺を1本テストに足すほうが、採点役を1体増やすより確実でした。

    費用の目安も出しておきます。採点1回あたりの中央値は Opus 5 が0.29ドル、Sonnet 5 が0.15ドル、Haiku 4.5 が0.09ドルでした。実行時に記録される換算額で、定額プランの請求額ではありません。

    60件の採点を Opus 5 で一周して18ドル。この金額で得られるのが「見逃し15件」だとすると、テストを1本書いたほうが安い場面は多そうです。

    よくある質問

    Q. 課題が2つだけでは少なくないですか

    少ないです。Pythonの小さな題材2つなので、他の言語や大きなコードベースに一般化はできません。ただ「見えているテストは通るが直っていない」という状況自体はどこにでもあるので、傾向としては参考になると思っています。

    Q. 採点の聞き方を変えれば結果は変わりませんか

    変わると思います。今回は「すべての妥当な入力に対して直っているか」という1通りの聞き方だけを試しました。逆に言えば、この程度の指示では足りないということでもあります。

    Q. 採点役が勝手にコードを直してしまいませんか

    そこは確認しました。実行の前後でファイルのハッシュを取って突き合わせ、180回とも書き換えゼロでした。

    Q. 処理時間の比較は載せないのですか

    載せません。今回は18本を同時に走らせる設定で回したので、秒数に順番待ちが混ざっています。速度の比較には使えない数字なので、費用と判定内容だけを出しました。

    自分で確かめるなら

    手順はこれだけです。

    1. 直してほしいバグを1つ選び、症状を再現する短いテストを2本作る

    2. 1本だけをAIに見せて直させる。もう1本は隠しておく

    3. 直った状態を別のモデルに見せて「すべての妥当な入力で直っているか」と聞く

    4. 隠しておいたテストを当てて、3の答え合わせをする

    1往復で終わります。自分のコードでやると、どのモデルが自分の仕事に対してどれくらい甘いかが、その日のうちに分かります。


    なぜ書いたか

    私自身が、AIに書かせたコードをAIに見せて「大丈夫そうです」で先へ進めた回数を、正直に数えたくなったからです。数えてみたら、その「大丈夫そうです」は6割しか当たっていませんでした。

    前回は「安いモデルに一行足すと直るのか」を測りました。今回はその修正を採点する側を測ったことになります。ここまで測って分かっていないのは、隠しておくテストの書き方しだいでこの数字がどこまで動くのか、です。私の伏せテストが厳しすぎた可能性も、まだ捨てきれていません。

    あわせて読む記事はこちらです。


     
     
    話題の生成AIツールやAIエージェントを、手元で実際に動かして「本当に使えるか」を観点に情報発信をしています。本業AIエンジニア。

    あなたへのおすすめ