概要(3行)
- 自作16課題(アルゴ/バグ修正/機能実装/日本語仕様+難問6)を、テストフィードバック付きの自走ループで解かせたら Sakana Fugu・Fugu Ultra・Claude(Opus 4.8)の3者とも 16/16 を初回一発正解(pass@1 100%)。正答率では差がつかなかった。
- 唯一はっきり差が出たのは 速度。Fugu Mini 平均 10.1 秒 / Fugu Ultra 平均 44.1 秒。Ultra の重いオーケストレーションは、このタスク帯では正答率を1ミリも上げず、レイテンシだけ約4.4倍。
- これは Sakana 自身の公表ベンチとも整合する。オーケストレーションの優位は「最難関の SE 課題」でのみ現れ、よく定義された個人規模タスクではどのフロンティアモデルでも解けてしまい差が消える。
本記事の検証は全証跡(実行コマンド・コンソールログ・全試行の生出力・全テスト結果・コスト)を保存して行いました。総コストは $0.83 / 上限 $40。再現手順は最後に記載しています。
目次
1. はじめに — なぜ検証したか
2026年6月、Sakana AI により「Sakana Fugu / Fugu Ultra」が公開され、「一部で Claude Fable 越え」と話題になりました。
そら当然「本当にそうなのか?」って疑問を持ったので、個人ができる範囲でAIを使って多角的に・実データで確かめたのがこの記事です。
ここで重要な構造的事実があります。
Fugu は単独モデルではありません。 7B の "Conductor(指揮者)" が複数のフロンティア LLM(GPT-5.5 / Claude Opus / Gemini 3.1 Pro / 自分自身の再帰呼び出し)をタスクに応じて束ねる オーケストレーションモデル です。
つまり「Fugu vs Claude」は、構造的には 「Claude を含むオーケストラ vs 単体の Claude」 という問いでもあります。
そこで本記事の問いはシンプルな優劣ではなく、
「束ねる」アプローチ(Fugu)は、素の Claude に対して コーディングでどれだけ上乗せ/劣化するのか
としました。
2. 検証対象
| Sakana Fugu / Fugu Ultra | Claude (Opus 4.8) | |
|---|---|---|
| 位置づけ | 複数 LLM を束ねるオーケストレーション | 単体フロンティアモデル |
| アクセス | OpenAI 互換 API(https://api.sakana.ai/v1) |
Claude Code / エージェント |
| 課金 | 従量(入力$5 / 出力$30 per 1M) | サブスク |
| モデルID |
fugu / fugu-ultra
|
claude-opus-4-8 |
日本語特化チャット「Sakana Chat (Namazu)」も存在しますが、API 非提供のため本記事の自動ベンチ対象外としています。
3. 検証方法(どう測ったか)
3.1 AIによる自作16課題(学習データ汚染を避けてオリジナル作問)
既存ベンチは学習済みの可能性があるため、すべてAIに自作してもらいました。全課題 hidden unittest で機械採点(主観を排除)。
| カテゴリ | 課題数 | 例 |
|---|---|---|
| algo(アルゴリズム) | 3 | 式評価器、区間マージ、LRUキャッシュ |
| bugfix(バグ修正) | 3 | ローマ数字の減算記法、辞書の再帰マージ、営業日計算 |
| feature(機能実装) | 2 | RFC4180風CSVパーサ、テンプレートエンジン |
| ja-spec(日本語仕様) | 2 | 漢数字→整数、和暦→西暦変換 |
| hard-algo(難アルゴ) | 2 | 正規表現マッチ(DP)、テキスト両端揃え |
| hard-perf(性能制約) | 2 | スライディングウィンドウ最大値、部分和カウント |
| hard-trap(規格の罠) | 2 | SemVer 2.0比較、表示幅トランケート(全角=2) |
各課題には参照解 _reference.py を同梱し、「参照解で全テスト PASS」を事前確認(採点バグと性能の取り違え防止)。
性能制約課題の工夫:ナイーブ O(n²) 解は60秒で timeout = 不合格、O(n) 解のみ通過するよう設計。実機で「ナイーブ解は timeout、O(n) 解は PASS」を確認済みです。アルゴリズムの計算量まで採点に反映しています。
課題プロンプトの例(和暦変換):
`solution.py` に、和暦の文字列を西暦(整数 int)へ変換する関数
`wareki_to_seireki(s)` を実装してください。
- 令和元年 = 2019(令和N年 = 2018 + N)/ 平成元年 = 1989 / 昭和元年 = 1926
- 「元」は元年(1年) として扱う
- 対応していない元号が来た場合は ValueError を送出する
例: wareki_to_seireki("平成31年") -> 2019 / wareki_to_seireki("大正5年") -> ValueError
3.2 評価プロトコル(両者をエージェント化して公平に)
両者とも テストフィードバック付きの自走ループ(最大4反復) で揃えました。失敗したら unittest 出力をモデルに返して再挑戦させます。
-
Fugu 側:OpenAI 互換 API(
fugu/fugu-ultraeffort=high)を実測。 -
Claude 側:作問した本人が解くと「答えを知っていて無効」になるため、作問文脈を持たない独立した Claude(Opus 4.8)サブエージェント16体に
prompt.mdのみ(テスト非公開)を渡して解かせ、同一ハーネスで採点。
3.3 実行コマンド
ハーネスは Python 標準ライブラリのみ(追加 pip 不要)。実際に叩いたコマンドは以下です。
# 1) 採点系の健全性チェック(全16課題が参照解でPASSすることを確認)
python3 harness/validate_tasks.py
# 2) Fugu Mini:全課題を自走ループで実測(最大4反復・各1試行)
bash harness/run_suite.sh fugu fugu - 4
# run_suite.sh の中身(抜粋):
# for d in benchmarks/*/*/; do
# python3 harness/agent_bench.py run "$d" --provider fugu --model fugu \
# --max-iters 4 --trials 1
# done
# 3) Fugu Ultra:reasoning_effort=high で実測
bash harness/run_suite.sh fugu fugu-ultra high 4
# 4) Claude 側:独立サブエージェントが書いた solution.py を同一ハーネスで採点
python3 harness/score_claude.py
# 5) 3者を横並びで集計
python3 harness/aggregate.py
全コマンドの入力と出力は harness/runlog.sh でタイムスタンプ付きで logs/ に保存しています。
3.4 ハーネスの中身(実行するPythonの要点)
ハーネスは4本の Python で構成されます。すべて標準ライブラリのみで、依存パッケージはありません。
| ファイル | 役割 |
|---|---|
harness/fugu_bench.py |
API呼び出し・コスト算出・コード抽出・unittest採点の土台 |
harness/agent_bench.py |
テスト失敗を返して再挑戦させる自走ループ本体 |
harness/score_claude.py |
Claudeサブエージェントの solution.py を同一採点系で採点
|
harness/aggregate.py |
全試行JSONをモデル別に集計 |
① コスト算出と予算ガード(fugu_bench.py)
従量課金単価から usage をコストへ変換し、累積を台帳に記録。$40 を超えそうなら実行前に停止します。
BUDGET_CAP_USD = 40.0 # ハード上限
RATE_IN, RATE_OUT, RATE_CACHED = 5.0, 30.0, 0.50 # per 1M tokens (<=272K)
def compute_cost(usage):
pin = usage.get("prompt_tokens", 0)
pout = usage.get("completion_tokens", 0)
cached = (usage.get("prompt_tokens_details") or {}).get("cached_tokens", 0)
non_cached = max(0, pin - cached)
return round(non_cached/1e6*RATE_IN + cached/1e6*RATE_CACHED + pout/1e6*RATE_OUT, 6)
② OpenAI互換API呼び出し(fugu_bench.py)
urllib だけで /chat/completions を叩きます。reasoning_effort で Ultra の思考深度を指定。
def call_fugu(env, model, messages, effort=None, timeout=180):
url = env["SAKANA_BASE_URL"].rstrip("/") + "/chat/completions"
body = {"model": model, "messages": messages}
if effort:
body["reasoning_effort"] = effort # high / xhigh(max)
req = urllib.request.Request(
url, data=json.dumps(body).encode(),
headers={"Authorization": "Bearer " + env["SAKANA_API_KEY"],
"Content-Type": "application/json"}, method="POST")
t0 = time.time()
with urllib.request.urlopen(req, timeout=timeout) as resp:
payload = json.loads(resp.read().decode())
return (payload["choices"][0]["message"]["content"],
payload.get("usage", {}), round(time.time() - t0, 2))
③ サンドボックス採点(fugu_bench.py)
応答から最長のコードブロックを抜き出し、一時ディレクトリで tests.py を unittest 実行。60秒で timeout=不合格(性能制約課題の判定はここで効く)。
CODE_RE = re.compile(r"```(?:python|py)?\s*\n(.*?)```", re.DOTALL)
def extract_code(text):
blocks = CODE_RE.findall(text)
return max(blocks, key=len).strip() if blocks else None # 説明用の小片に釣られない
def run_tests(task_dir, solution_file, code):
with tempfile.TemporaryDirectory() as sb:
# solution と tests.py を sandbox に置いて unittest を別プロセス実行
...
try:
out = subprocess.run([sys.executable, "_run.py"], cwd=sb,
capture_output=True, text=True, timeout=60)
except subprocess.TimeoutExpired:
return {"passed": False, "note": "timeout"} # ナイーブ解はここで落ちる
r = json.loads(re.search(r"RESULT_JSON=(\{.*\})", out.stdout).group(1))
r["passed"] = (r["ran"] > 0 and r["failures"] == 0 and r["errors"] == 0)
return r
④ 自走ループの心臓部(agent_bench.py)
最大N反復。失敗したら unittest 出力をそのまま user メッセージで返して再挑戦させ、全ターンを記録します。Fugu / Claude は PROVIDERS で差し替えるだけ。
PROVIDERS = {"fugu": call_fugu, "claude": call_claude} # endpointだけ差し替えて公平化
for it in range(max_iters):
content, usage, latency = call(env, model, messages, effort)
cost = compute_cost(usage) if provider == "fugu" else 0.0 # Claudeは別請求
code = extract_code(content)
score = run_tests_verbose(task_dir, sol_file, code)
iterations.append({"iter": it, "latency_sec": latency, "cost_usd": cost,
"extracted_code": code, "test_result": score})
messages.append({"role": "assistant", "content": content})
if score["passed"]:
final["passed"] = True
break
# ★失敗トレースを返して直させる(テストフィードバック)
messages.append({"role": "user", "content":
f"Your solution failed. unittest output:\n\n{score['report']}\n\n"
f"Fix the bug and output the complete corrected {sol_file} ..."})
公平性の肝:Fugu も Claude も「同じ system プロンプト・同じ prompt.md・同じ最大4反復・同じ unittest 採点」を通ります。違うのは call_fugu / call_claude のエンドポイントだけです。
⑤ 集計(aggregate.py)
results/*/*/*.json を読み、モデル別に pass@1・平均反復・レイテンシ・コストを算出。§4.1 の表はこの出力です。
p1 = sum(1 for r in rs if r["passed"] and r["iters"] == 1) / n # pass@1
ai = sum(r["iters"] for r in rs) / n # 平均反復
al = sum(r["latency"] for r in rs) / n # 平均レイテンシ
採点系自体の健全性は
validate_tasks.pyが担保します(各課題の_reference.pyをsolution.pyとして流し込み、参照解で全テストPASSすることを事前確認=「採点バグ」と「モデルの実力」を取り違えない)。
4. 結果
4.1 正答率 — 三者とも完全飽和 🎯
python3 harness/aggregate.py の出力(一次ログ: logs/20260624-032137_aggregate-all-three.log):
MODEL runs pass@1 avg_iter avg_lat(s) avg_cost($) sum_cost($)
------------------------------------------------------------------------
fugu 16 100% 1.00 10.1 0.0207 0.3313
fugu-ultra 16 100% 1.00 44.1 0.0278 0.4455
opus-subagent 16 100% 1.00 0.0 0.0000 0.0000
| モデル | pass@1 | 平均反復 | 平均レイテンシ | 平均コスト | 合計コスト |
|---|---|---|---|---|---|
| Fugu (Mini) | 100% (16/16) | 1.00 | 10.1s | $0.021 | $0.33 |
| Fugu Ultra (high) | 100% (16/16) | 1.00 | 44.1s | $0.028 | $0.45 |
| Claude (Opus 4.8) | 100% (16/16) | 1.00 | (比較不可※) | — | — |
※ Claude 側はエージェント実行のオーバーヘッドを含むため、Fugu の API 応答時間と直接比較せず正答率の比較に使用。
難問(正規表現DP・性能制約の O(n) 実装・SemVer の優先順位・East Asian Width)を含めても、全員が初回1発で正解。易しめ10問だけでなく難問6問を足しても天井効果は破れませんでした。
4.2 唯一はっきり差が出た軸 — レイテンシ ⏱️
カテゴリ別の平均レイテンシ(秒)。全カテゴリで Ultra が大幅に遅い。
| category | fugu | fugu-ultra | Ultra/fugu 倍率 |
|---|---|---|---|
| algo | 7.8 | 39.0 | ×5.0 |
| bugfix | 6.7 | 20.8 | ×3.1 |
| feature | 33.8 | 104.2 | ×3.1 |
| hard-algo | 4.9 | 31.1 | ×6.3 |
| hard-perf | 4.0 | 36.6 | ×9.2 |
| hard-trap | 13.1 | 41.9 | ×3.2 |
| ja-spec | 3.3 | 49.1 | ×14.9 |
平均レイテンシ(全課題)
fugu ██████ 10.1s
fugu-ultra ███████████████████████████ 44.1s ← 同じ正答率で約4.4倍
正答率が同じなのだから、このタスク帯では Ultra を選ぶ理由がない、というのが素直な読みです。
4.3 課題別の生データ(実測16課題ぶん)
| 課題 | category | fugu lat(s) | fugu $ | ultra lat(s) | ultra $ |
|---|---|---|---|---|---|
| algo_expr_eval | algo | 13.78 | 0.0268 | 56.12 | 0.0252 |
| algo_interval_merge | algo | 5.16 | 0.0070 | 30.78 | 0.0197 |
| algo_lru_cache | algo | 4.33 | 0.0082 | 30.02 | 0.0142 |
| bugfix_business_days | bugfix | 3.24 | 0.0073 | 14.56 | 0.0108 |
| bugfix_deep_merge | bugfix | 11.59 | 0.0211 | 29.39 | 0.0129 |
| bugfix_roman | bugfix | 5.30 | 0.0065 | 18.54 | 0.0124 |
| feature_csv_parse | feature | 53.46 | 0.1134 | 146.40 | 0.1161 |
| feature_template | feature | 14.22 | 0.0364 | 61.92 | 0.0379 |
| hard_regex_match | hard-algo | 2.84 | 0.0073 | 31.64 | 0.0161 |
| hard_semver | hard-trap | 18.19 | 0.0317 | 53.31 | 0.0336 |
| hard_sliding_window_max | hard-perf | 4.98 | 0.0085 | 32.20 | 0.0132 |
| hard_subarray_sum | hard-perf | 3.00 | 0.0052 | 41.06 | 0.0097 |
| hard_text_justify | hard-algo | 7.02 | 0.0183 | 30.48 | 0.0173 |
| hard_truncate_width | hard-trap | 8.00 | 0.0142 | 30.54 | 0.0209 |
| ja_kansuji | ja-spec | 3.89 | 0.0115 | 62.95 | 0.0607 |
| ja_wareki | ja-spec | 2.70 | 0.0079 | 35.21 | 0.0247 |
全16課題 pass=True / iters=1(初回成功)。最も重かったのは CSV パーサ(引用符内のカンマ・改行・二重引用符の扱い)。
4.4 コード簡潔さ(LOC)も差なし
非空・非コメント行の平均:Fugu 28.1 / Ultra 27.6 / Claude 28.6。正答率だけでなくコード量でも有意差はありませんでした。
4.5 各AIが書いたコードを覗く(実出力)
「3者とも正解」だけでは中身が見えないので、実際に各AIが書き出した solution.py を並べます(results/ 配下の生データ。空行・辞書の改行など体裁のみ軽く統一し、ロジックは未改変)。
例1:和暦→西暦変換(ja_wareki / 日本語仕様)
§3.1 で示した例題への回答です。3者ともロジックはほぼ同型でした。
🐡 Fugu Mini
def wareki_to_seireki(s):
base = {"令和": 2018, "平成": 1988, "昭和": 1925}
gengo = s[:2]
if gengo not in base:
raise ValueError(f"未対応の元号: {gengo}")
rest = s[2:]
if rest.endswith("年"):
rest = rest[:-1]
if rest == "元":
n = 1
else:
n = int(rest)
return base[gengo] + n
🐡 Fugu Ultra
def wareki_to_seireki(s):
bases = {"令和": 2018, "平成": 1988, "昭和": 1925}
era = s[:2]
if era not in bases:
raise ValueError(f"Unsupported era: {era}")
if not s.endswith("年"):
raise ValueError(f"Invalid format: {s}") # ← Ultraだけ入力検証が1段厚い
year_text = s[2:-1]
if year_text == "元":
year = 1
else:
year = int(year_text)
return bases[era] + year
🤖 Claude (Opus 4.8)
def wareki_to_seireki(s):
base = {"令和": 2018, "平成": 1988, "昭和": 1925}
gengo = s[:2]
if gengo not in base:
raise ValueError("対応していない元号です: {}".format(gengo))
rest = s[2:]
if rest.endswith("年"):
rest = rest[:-1]
if rest == "元":
n = 1
else:
n = int(rest)
return base[gengo] + n
読みどころ:辞書ベースの元号→基準年マッピングという発想は3者一致。違いは些細で、Ultra だけ「『年』で終わらなければ
ValueError」という入力検証を1段追加していました(テスト通過には不要だが堅牢)。日本語仕様の解釈に差は出ていません。
例2:和が k の連続部分配列の数(hard_subarray_sum / 性能制約)
ナイーブな O(n²) 二重ループだと60秒 timeout で不合格になる課題。3者とも prefix-sum + ハッシュの O(n) 解に一発で到達しました。
🐡 Fugu Mini
def subarray_sum(nums, k):
counts = {0: 1}
prefix = 0
result = 0
for num in nums:
prefix += num
result += counts.get(prefix - k, 0)
counts[prefix] = counts.get(prefix, 0) + 1
return result
🐡 Fugu Ultra
def subarray_sum(nums, k):
count = 0
prefix_sum = 0
prefix_counts = {0: 1}
for num in nums:
prefix_sum += num
count += prefix_counts.get(prefix_sum - k, 0)
prefix_counts[prefix_sum] = prefix_counts.get(prefix_sum, 0) + 1
return count
🤖 Claude (Opus 4.8)
from collections import defaultdict
def subarray_sum(nums, k):
counts = defaultdict(int)
counts[0] = 1
prefix = 0
result = 0
for x in nums:
prefix += x
result += counts[prefix - k]
counts[prefix] += 1
return result
読みどころ:アルゴリズムの骨格(累積和をキーにした出現回数カウント)は完全に一致。唯一の違いは Claude が
collections.defaultdictを使い、Fugu系は素のdict.get(...)を使った点だけ。正答率だけでなく「書きぶり」まで収束していることが見て取れます。
例3:RFC4180風CSVパーサ(feature_csv_parse / 機能実装)
逆に、最も書きぶりが分かれたのがこの課題です。引用符内のカンマ・改行、""によるエスケープ、末尾改行の扱いなどを、csvモジュール禁止で自前パースさせます(本検証で最重・最長のタスク)。3者とも全テストPASSですが、設計が三者三様でした。
| 状態管理の方針 | 行数(空行込み) | |
|---|---|---|
| Fugu Mini |
明示的な4状態FSM(START_FIELD/IN_UNQUOTED/IN_QUOTED/AFTER_QUOTE の整数定数で分岐) |
106 |
| Fugu Ultra | bool フラグ+ネストした補助関数(end_field() / end_record())で整理 |
88 |
| Claude (Opus 4.8) |
in_quotes と pending の2フラグだけに絞った最小構成 |
72 |
🐡 Fugu Mini — 明示的ステートマシン(クリックで展開)
def parse_csv(text):
if text == "":
return []
text = text.replace("\r\n", "\n").replace("\r", "\n")
rows = []
record = []
field = []
START_FIELD = 0
IN_UNQUOTED = 1
IN_QUOTED = 2
AFTER_QUOTE = 3
state = START_FIELD
ended_with_newline = False
i = 0
n = len(text)
while i < n:
ch = text[i]
if state == START_FIELD:
if ch == '"':
state = IN_QUOTED
ended_with_newline = False
elif ch == ",":
record.append("")
ended_with_newline = False
elif ch == "\n":
record.append("")
rows.append(record)
record = []
field = []
state = START_FIELD
ended_with_newline = True
else:
field.append(ch)
state = IN_UNQUOTED
ended_with_newline = False
elif state == IN_UNQUOTED:
if ch == ",":
record.append("".join(field))
field = []
state = START_FIELD
ended_with_newline = False
elif ch == "\n":
record.append("".join(field))
rows.append(record)
record = []
field = []
state = START_FIELD
ended_with_newline = True
else:
field.append(ch)
ended_with_newline = False
elif state == IN_QUOTED:
if ch == '"':
if i + 1 < n and text[i + 1] == '"':
field.append('"')
i += 1
ended_with_newline = False
else:
state = AFTER_QUOTE
ended_with_newline = False
else:
field.append(ch)
ended_with_newline = False
elif state == AFTER_QUOTE:
if ch == ",":
record.append("".join(field))
field = []
state = START_FIELD
ended_with_newline = False
elif ch == "\n":
record.append("".join(field))
rows.append(record)
record = []
field = []
state = START_FIELD
ended_with_newline = True
else:
field.append(ch)
state = IN_UNQUOTED
ended_with_newline = False
i += 1
if not ended_with_newline:
record.append("".join(field))
rows.append(record)
return rows
def to_dicts(rows):
if not rows:
return []
header = rows[0]
return [dict(zip(header, row)) for row in rows[1:]]
🐡 Fugu Ultra — bool フラグ+ネスト補助関数(クリックで展開)
def parse_csv(text):
if text == "":
return []
text = text.replace("\r\n", "\n").replace("\r", "\n")
records = []
record = []
field = []
in_quotes = False
after_quote = False
at_field_start = True
def end_field():
nonlocal field, at_field_start, after_quote
record.append("".join(field))
field = []
at_field_start = True
after_quote = False
def end_record():
nonlocal record, field, at_field_start, after_quote
record.append("".join(field))
records.append(record)
record = []
field = []
at_field_start = True
after_quote = False
i = 0
n = len(text)
while i < n:
ch = text[i]
if in_quotes:
if ch == '"':
if i + 1 < n and text[i + 1] == '"':
field.append('"')
i += 2
else:
in_quotes = False
after_quote = True
i += 1
else:
field.append(ch)
i += 1
continue
if after_quote:
if ch == ",":
end_field()
elif ch == "\n":
end_record()
else:
field.append(ch)
at_field_start = False
after_quote = False
i += 1
continue
if ch == '"' and at_field_start:
in_quotes = True
at_field_start = False
elif ch == ",":
end_field()
elif ch == "\n":
end_record()
else:
field.append(ch)
at_field_start = False
i += 1
if record or field or after_quote or in_quotes or not at_field_start:
record.append("".join(field))
records.append(record)
return records
def to_dicts(rows):
if not rows:
return []
header = rows[0]
return [dict(zip(header, row)) for row in rows[1:]]
🤖 Claude (Opus 4.8) — 2フラグの最小構成(クリックで展開)
def parse_csv(text):
if text == "":
return []
# Normalize line endings.
text = text.replace("\r\n", "\n").replace("\r", "\n")
records = []
field = []
record = []
in_quotes = False
pending = False # True if a field/record is in progress and must be flushed.
i = 0
n = len(text)
while i < n:
ch = text[i]
pending = True
if in_quotes:
if ch == '"':
if i + 1 < n and text[i + 1] == '"':
field.append('"')
i += 2
continue
else:
in_quotes = False
i += 1
continue
else:
field.append(ch)
i += 1
continue
else:
if ch == '"':
in_quotes = True
i += 1
continue
elif ch == ',':
record.append("".join(field))
field = []
i += 1
continue
elif ch == '\n':
record.append("".join(field))
field = []
records.append(record)
record = []
pending = False
i += 1
continue
else:
field.append(ch)
i += 1
continue
# Flush the final field/record only if something is pending. A trailing
# newline at the very end leaves pending=False, avoiding an extra record.
if pending:
record.append("".join(field))
records.append(record)
return records
def to_dicts(rows):
if not rows:
return []
header = rows[0]
result = []
for row in rows[1:]:
result.append(dict(zip(header, row)))
return result
読みどころ:同じ仕様・同じ全テスト通過でも、設計思想がくっきり分かれました。Fugu Mini は教科書的な明示ステートマシン(状態を整数定数で命名)、Fugu Ultra は補助関数でDRYに整理、Claude は状態を
in_quotes+pendingの2フラグまで削ぎ落とした最小実装。特に「末尾改行で空レコードを作らない」という罠を、Fugu Mini はended_with_newline、Claude はpendingフラグで処理しており、**最も難しい課題ほど“正解は1つでも書き方は分かれる”**ことがよく表れた例です。
4.6 Sakana 公表ベンチとの対比(ベンダー主張・未検証)
| ベンチ | Fugu Ultra | 比較 |
|---|---|---|
| SWE-Bench Pro | 73.7 | Opus 4.8 69.2 を上回るが Fable 5 には及ばず |
| TerminalBench 2.1 | 82.1 | Fable 5 が上 |
| LiveCodeBench | 93.2 | Fugu が上 |
これらの数値はすべて Sakana 自身の公表値で、第三者の独立検証はされていません(ベンダー主張として扱う)。対 Fable 5 は「最難関 SE 課題では僅差で Fable 5 が上、種目により入れ替わり、差はいずれも一桁」というのが実態です。
5. 考察
5.1 「オーケストレーションの価値」はどこに現れるか
Sakana の数値では Fugu Ultra は最難関 SWE-Bench Pro で単体 Opus を上回りますが、今回私の行った個人規模ベンチでは3者とも100%で差が消えました。これは矛盾ではありません。よく定義された中〜難程度のタスクは、いまやどのフロンティアモデルでも解けてしまうため、オーケストレーションの上乗せ分は観測できないのです。束ねる価値は「単体モデルが失敗し始める、最難関・長文脈・多段の SE 課題」でのみ顕在化します。
5.2 Ultra は(このタスク帯では)報われない
Fugu Ultra は Mini と同じ正答率・同じコード量を、約4.4倍のレイテンシで出しました。深い「集合知」は易しい問題に対してはオーバーキルで、待ち時間という形でコストだけが残ります。
5.3 構造:Fugu は内部で Claude を使う
Conductor は Claude Opus / Sonnet を planner として呼ぶと報じられています。つまり Fugu の出力品質の一部は Claude 由来でありうる。「Fugu が Claude に勝つ」という見出しは、厳密には 「Claude を含むオーケストラが、単体 Claude を(最難関で)わずかに上回る」 と読むべきです。
5.4 日本語タスク
漢数字変換・和暦変換(規格の罠込み)でも3者とも正解。少なくとも本検証の範囲では、Sakana の日本語特化が Claude に対して明確な優位を示す場面は観測できませんでした。
6. 実務的な結論(どう選ぶか)
- 💡 日常のコーディング/個人開発:速くて安い単体モデル、または Fugu Mini で十分。正答率は頭打ち。
- 🔥 Fugu Ultra を使う価値:単体モデルが詰まる最難関・多段・長時間の SE 課題に限る。日常使いではレイテンシ増がそのまま不利益。
- 💰 コスト:一度きりの検証なら従量課金が明朗(本検証の総額は $0.83 / 上限 $40 = 桁違いに安い)。継続利用ならサブスクとの損益分岐を要計算。
- 🧩 Fugu の本質的価値:単一 API で複数フロンティアを束ね、ベンダーロックインや個別最適を肩代わりする「運用の手軽さ」にある。生の知能の伸びしろより、統合レイヤーの利便性で評価すべきプロダクト。
7. 限界と再現方法
正直な限界の開示
- N=16・個人規模で統計的厳密性に限界。
- Claude 側レイテンシはエージェント実行のオーバーヘッドを含み、Fugu の API 応答時間と直接比較不可(→正答率比較に使用)。
- 作問者と Claude が同じモデル系列(親近性バイアスの可能性)。
- Fugu のモデルプールは可変で測定時点依存。
- 本検証は「飽和の確認」までを示すもの。最難関領域(SWE-Bench Pro 帯)の優劣は個人では再現困難で、Sakana 公表値(未検証)に依拠している。
再現手順:
python3 harness/validate_tasks.py # ① 採点系の検証
bash harness/run_suite.sh fugu fugu - 4 # ② Fugu Mini 実測
bash harness/run_suite.sh fugu fugu-ultra high 4 # ③ Fugu Ultra 実測
python3 harness/score_claude.py # ④ Claude 側(独立サブエージェント)採点
python3 harness/aggregate.py # ⑤ 集計
全ての入力・出力・コンソールログ・全試行の会話/コード/採点/コスト/レイテンシを benchmarks/ harness/ results/ logs/ sources/ JOURNAL.md に保存しています。
まとめ
「Sakana Fugu は Claude を超えたのか?」への私の答えは——よく定義されたコーディングタスクでは、超えても劣ってもいない。3者とも満点で、差は速度とコストにしか出なかった。Fugu の真価は生の知能ではなく、複数フロンティアを1本の API に束ねる統合レイヤーの利便性にあります。そして「Fable 越え」の見出しは、最難関の一部種目に限った、しかも僅差の話——というのが実測と公表値の両方から見えた結論でした。
本記事の数値は手元の実測です。Fugu はモデルプールが可変なため、時期によって結果は変わりえます。気になった方はぜひ自分の課題で測ってみてください 🐡
個人レベルの検証ですし、参考までに。
個人プロジェクトでしばらく残ったクレジット使ってコーディングさせてみようと思います。
とはいえ、これを国産AIといってニュースに載せるのは個人的に違うのでは?って思っている。