1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Opus vs Sonnet ── AIの「安い方」でコード書かせたら、たった1テスト差だった件

1
Posted at

前回のあらすじ

前回の記事で、Claude Code(Opus 4.6)に4言語×5回 = 20回コードを書かせた。

結果:ほぼ全言語100%。 退屈なくらい動いた。

で、当然こう思うわけ:

「じゃあ安い方(Sonnet)でも同じ結果出るの?」

Opus 4.6 はClaude最上位モデル。Sonnet 4.6 はその下位互換。速くて安い。

同じプロンプト、同じテスト、同じ言語。モデルだけ変える。

やってみた。


検証条件:前回と完全に同一

条件 内容
言語 TypeScript / Python / Rust / Ruby(4言語)
回数 各5回(計20回 × 2モデル = 40回)
プロンプト 同一の PROMPT.md
テスト 同一の test_api.sh(7項目)
差分 モデルだけ(Opus 4.6 vs Sonnet 4.6)

変数はモデルだけ。これでモデル差が測れる。


結果:差、ほぼなし。

総合スコア

133 対 132。差はたった1テスト。

モデル 合計 合格率
Opus 4.6 133/140 95.0%
Sonnet 4.6 132/140 94.3%

正直、誤差じゃない?

言語別の内訳

言語 Opus 4.6 Sonnet 4.6
TypeScript 35/35(100%) 35/35(100%) ±0
Python 35/35(100%) 32/35(91.4%) -3
Rust 35/35(100%) 35/35(100%) ±0
Ruby 28/35(80%) 30/35(85.7%) +2

ちょっと待って。Ruby は Sonnet のほうがスコア高い?

そう、ここが面白いところ。


バグの「質」が全然違う

総合スコアは僅差。でも 失敗の仕方 が全然違った。

Opus 4.6 の失敗パターン

Opus は 1回だけ派手にコケた。Ruby Run-1 で rackup gem を Gemfile に入れ忘れてビルド失敗。残り19回は完璧。

失敗パターン:0か100か。 動くか動かないか。

Sonnet 4.6 の失敗パターン

Sonnet は 毎回ちょっとずつコケた。致命的じゃないけど、微妙なバグが散在。

失敗パターン:6/7 が通るけど1つだけ落ちる。 一見動くけど、実は問題がある。

               Opus 4.6          Sonnet 4.6
失敗の仕方     ████████░░░░      ░░░░░░░░░░░░
               1回 大失敗         8回 小失敗
ビルド失敗     1回               0回
テスト失敗     0テスト           8テスト

Opus は「動くか動かないか」。Sonnet は「動くけど微妙に壊れてる」。

実務だとどっちが怖い? 正直 Sonnet のほうが怖い。ビルド失敗は即気づくけど、6/7 通るバグは見逃す。


Sonnet のバグを深掘りする

バグ①:Flask の Unicode エスケープ(Python run-4)

Opus は5回とも FastAPI を選んだ。Sonnet は4回 FastAPI、1回だけ Flask を選んだ。

その1回が事故った。

# Flask のデフォルト動作
>>> from flask import jsonify
>>> jsonify({"title": "テスト買い物"})
# → {"title": "\u30c6\u30b9\u30c8\u8cb7\u3044\u7269"}

Flask は JSON_AS_ASCII=True がデフォルト。日本語が \uXXXX にエスケープされる。

テストスクリプトは grep "テスト買い物" で検証してるから、エスケープされた文字列はマッチしない。

# テスト結果
PASS: GET /todos returns 200          # ← ASCII、OK
PASS: GET /todos returns array        # ← "[" を検索、OK
PASS: POST /todos returns 201         # ← ステータスコード、OK
FAIL: POST /todos returns todo with title  # ← "テスト買い物" がない!
FAIL: GET /todos after POST has item       # ← 同上
FAIL: PUT /todos/1 returns updated todo    # ← "更新済み" がない!
PASS: DELETE /todos/1 returns deleted      # ← "deleted" は ASCII、OK

HTTP ステータスは全部正しい。JSON の中身も正しい。でも日本語の表現方法が違う。

Flask の問題であって、Sonnet のコーディング能力の問題ではない。ただし「Flask を選んだ」のは Sonnet の判断。

教訓:AI のフレームワーク選択は安定してるようで、たまにブレる。そのブレが事故を生む。

バグ②:sqlite3 gem 2.0 の API 変更(Ruby 全5 run)

こっちのほうが深刻。全5回で同じバグ

# Sonnet が書いたコード(全5 run 共通パターン)
DB.execute(
  'UPDATE todos SET title = ?, completed = ? WHERE id = ?',
  title, completed, id    # ← 引数を個別に渡してる
)
ArgumentError: wrong number of arguments (given 4, expected 1..2)

sqlite3 gem 2.0 で API が変わった。

# sqlite3 gem 1.x(旧API)── 動く
DB.execute(sql, param1, param2, param3)

# sqlite3 gem 2.0(新API)── これが正解
DB.execute(sql, [param1, param2, param3])

引数を配列で渡す必要がある。Sonnet はこの Breaking Change を知らなかった。

面白いのは、INSERT と SELECT は動いてる こと。パラメータが1つなら execute(sql, param) で引数2つだから、新APIでもそのまま動く。3つ以上になった瞬間に壊れる。

だから GET、POST、DELETE は通って、PUT だけ落ちる。6/7 通る「微妙なバグ」の正体がこれ。

テスト結果(Ruby 全5 run 共通)
PASS: GET /todos returns 200             ← SELECT(1param)OK
PASS: GET /todos returns array           ← SELECT OK
PASS: POST /todos returns 201            ← INSERT(1param)OK
PASS: POST /todos returns todo with title ← INSERT OK
PASS: GET /todos after POST has item     ← SELECT OK
FAIL: PUT /todos/1 returns updated todo  ← UPDATE(3params)エラー!
PASS: DELETE /todos/1 returns deleted    ← DELETE(1param)OK

Sonnet は sqlite3 gem 2.0 の Breaking Change を知らなかった。Opus は知っていた(配列で渡していた)。

ここが 最上位モデルと高速モデルの差 として表れた。学習データの鮮度か、推論能力の差か。どちらにせよ、Ruby の最新 gem に対する知識で Opus に軍配。


Opus が Ruby で負けてるのにスコアが高い?

混乱するよね。整理する。

Opus Sonnet
Ruby ビルド失敗 1回(Run-1、0/7) 0回
Ruby テスト失敗 0テスト(他4 run は 28/28) 5テスト(5 run × 1テスト)
Ruby 合計 28/35 30/35

Ruby だけ見れば Sonnet の勝ち。 全部ビルドは通った。Opus は1回ビルドで死んだ。

でもトータルで見ると:

Opus Sonnet
TypeScript 35 35
Python 35 32
Rust 35 35
Ruby 28 30
合計 133 132

Python の3テスト差で Opus が逆転してる。


フレームワーク選択の比較

前回の記事で「AI が選ぶフレームワーク、面白いくらい偏ってる」と書いた。Sonnet でも検証。

言語 Opus 4.6 Sonnet 4.6
TypeScript Express 5/5 Express 4/5, Fastify 1/5
Python FastAPI 5/5 FastAPI 4/5, Flask 1/5
Rust actix-web 5/5 actix-web 5/5
Ruby Sinatra 5/5 Sinatra 5/5

Opus は「鉄板フレームワーク」から絶対ブレない。 Sonnet はたまに別の選択をする。

その「たまに」が事故の原因になったのが今回の Python Flask 事件。

安定性を取るなら Opus。多様性がほしいなら Sonnet。

……いや、多様性いらないか。テスト落ちたし。


じゃあ Sonnet で十分?

結論から言うと、タスクによる。

ユースケース おすすめ 理由
CRUD / プロトタイプ Sonnet 94% の成功率で十分。コスト1/5
本番コード生成 Opus フレームワーク選択が安定。最新API対応力が高い
多言語ベンチマーク Opus Ruby の微妙なバグを避けたい
TypeScript / Rust どちらでも 両モデルとも100%。差なし
Python Opus Flask 事故のリスクを避ける
Ruby 引き分け Opus はビルド失敗、Sonnet はAPI不整合

CRUDなら Sonnet で十分

今回のベンチマーク(Todo API)レベルなら、Sonnet 4.6 で94.3%

20回中19回は完動するコードが出てくる。残り1回も「PUT だけ落ちる」レベル。致命的じゃない。

でも「微妙なバグ」は怖い

6/7 テストが通って1つだけ落ちるバグ。これ、テストがなかったら 気づかない

前回の記事でも書いたけど、テスト駆動が AI 時代にこそ効く。今回の Sonnet のバグは全部テストで検出できた。テストなかったら「動いてるっぽい」で本番に行くところだった。


まとめ

知見 一言
たった1テスト差 Opus 133/140 vs Sonnet 132/140
TS / Rust は差なし どちらも100%。安い方で十分
バグの質が違う Opus = 大失敗、Sonnet = 微妙なバグ
Flask 事故 Sonnet がフレームワーク選択でブレた
sqlite3 gem 2.0 Sonnet は最新API変更を知らなかった
フレームワーク安定性 Opus は鉄板から絶対ブレない
テストが命綱 Sonnet のバグは全部テストで検出

「高いモデルと安いモデル、どっち使うべき?」

「CRUDなら安い方でいい。ただしテストは絶対書け。」

これが40回ベンチマーク回した人間の結論。

前回 + 今回で計40回 AI にコード書かせて分かったのは、モデル選択より大事なのはテストの存在 だってこと。

Opus だろうが Sonnet だろうが、テストがなければバグは見逃す。テストがあれば、どっちのモデルでもバグを検出できる。

いいねとストックしてくれると次の検証のモチベになります。


検証コード:GitHub リポジトリ

前回の記事:TypeScript vs Python vs Rust vs Ruby ── AIに20回コード書かせて分かった「本当の差」

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?