第1章 「諦めない9B」を、もう一度試してみた
以前、Ornith 1.5 9Bという小さなLLMを、実際のAgent環境で働かせてみた。
結果は、かなり印象的なものだった。
Ornith 1.5 9Bは、とにかく諦めない。
コードを書かせれば、エラーが出ても修正する。
修正して別のエラーが出れば、また原因を探す。
Contextが膨れ上がっても、コンパクションを挟みながら作業を続ける。
その粘り強さは、9Bというパラメータ数から想像するものとは、かなり違っていた。
実際、Ornith 1.5 9Bの公称ベンチマークは、小型モデルとしては異様に高い。
SWE-bench Verifiedは 70.6。
Terminal-Bench 2.1は 46.2、Claude Codeをハーネスとして使った評価では 47.0 を記録している。
9Bのモデルが、ソフトウェア開発やTerminal操作を伴うAgentic Benchmarkでこれだけの数字を出す。
前回の記事では、その数字が単なるベンチマーク上の飾りなのか、それとも本当に「働ける9B」なのかを確かめた。
そして私が実際に見たOrnithは、確かによく働いた。
ただし、万能ではなかった。
デバッグはできる。だが、設計力まで9Bの壁を越えるわけではない
前回の実験で特に強く感じたのは、Ornithの能力には妙な偏りがあることだった。
すでに存在するコードに問題があれば、かなり粘り強く原因を探す。
Toolを使い、ファイルを読み、修正し、また実行する。
この反復作業は得意だった。
ところが、そもそもの設計が悪い場合、その壁を越えるのは難しい。
間違った設計を前提に、延々とデバッグを続けてしまうこともあった。
当然といえば当然だ。
9Bのモデルが、突然70Bや100B級の設計能力を手に入れるわけではない。
私は前回の記事を、
「諦めない」だけでは、優秀なAgentになれない。
という結論で締めた。
ところが、その後もOrnithを触っていて、少し気になることがあった。
同じくらいのサイズ、あるいはOrnithより大きなモデルと比べても、どうもOrnithの「仕事の仕方」が違うのである。
Gemma 4 12Bは、ちゃんとコードを書ける
比較対象として面白かったのが、Gemma 4 12B QATだった。
Gemma 4 12Bは約12BパラメータのDenseモデルで、Google自身もreasoning、coding、agentic workflowを用途として掲げている。
実際、私がClaude Code上で使った印象も悪くない。
むしろコードの設計や初期実装では、Ornithより感心させられる場面すらあった。
つまり、
Ornithだけがコードを書けるから、Toolを使った開発で強い。
という単純な話ではない。
Gemmaもコードを書ける。
設計もできる。
実装もできる。
それなのに、ある非常に小さな実験をしてみると、両者の挙動には驚くほど大きな差が現れた。
使ったのは、複雑なプログラミング課題ではない。
小学生でもルールを理解できる、数字遊びだった。
4つの数字から「24」を作る
今回使ったのは「24 Game」と呼ばれる古典的な算数パズルである。
ルールは単純だ。
4つの数字をそれぞれ1回だけ使い、
+、−、×、÷と括弧だけで24を作る。
たとえば、
1, 1, 4, 8
なら、
(1 + 1) × (4 + 8) = 24
である。
このパズルには、1〜13の数字からなる1,362種類の問題を収録した公開データセットも存在する。
しかも単に問題と正解が入っているだけではない。
2012年以降に集められた640万回以上の人間による挑戦結果をもとに、解答時間や正答率などの難易度指標まで収録されている。
その中でも、
3, 3, 8, 8
は有名な難問のひとつだ。
答えは、
8 ÷ (3 − 8 ÷ 3) = 24
途中で 1/3 を作らなければならないため、人間が頭の中だけで試行錯誤すると意外に見つけにくい。
今回、私はこの24 Gameを使って、Ornith 1.5 9BとGemma 4 12B QATに同じ問題を解かせてみることにした。
ただし、暗算能力を比較したかったわけではない。
プロンプトには、こう書いた。
「必要なら利用可能なToolを使ってよい。」
つまり、Pythonを書いてもいい。
総当たりしてもいい。
自分で検証プログラムを作ってもいい。
目的はただひとつ。
正しい答えを見つけること。
そこで見えてきたのは、「9Bと12Bのどちらが数学に強いか」という単純な話ではなかった。
もっとAgentらしい能力の差だった。
コードが書けることと、コードを使って問題を解けることは、どうやら同じではない。
今回の実験環境と24 Gameの出典
今回の比較は、単純なチャット画面で行ったものではない。
AgentハーネスにはBionicを使用した。
Ornith 1.5 9BとGemma 4 12B QATを同じAgent環境で動かし、必要に応じてコードを書き、Toolを実行できる状態で比較している。
実行環境はWindows 11、GPUはGeForce RTX 3060 12GB。
モデルはいずれもローカルで動作させた。
今回使った「24 Game」は、4つの数字をそれぞれ1回ずつ使い、四則演算と括弧によって24を作る古典的な算数パズルである。
公開データセット nlile/24-game には1,362種類の問題が収録されており、2012年以降に集められた640万回以上の人間による挑戦結果から、正答率や解答時間などの難易度情報も付与されている。
今回の3問は、この24 Gameのルールを使って難易度に幅が出るよう選び、あらかじめプログラムでも解の存在を確認した。
特に、
3, 3, 8, 8
は同データセットでもHardの例として紹介されている有名な問題である。
なお、モデル自体の仕様と公開ベンチマークについては、Ornith公式の Ornith 1.5 モデルページ、およびGoogle公式の Gemma 4 Model Card を参照した。
今回の目的は、24 Gameそのものの数学能力を競わせることではない。
同じAgent環境とToolを与えたとき、モデルが問題をどう解こうとするのか。
その「仕事の仕方」を観察することにある。
nlile/24-game dataset
Ornith 1.5 official model page
Gemma 4 official Model Card
第2章 Gemma 4 12Bは「考え続ける」──そして無限回廊に落ちた
まず、Gemma 4 12B QATに3つの問題を与えた。
プロンプトは次の通りだ。
「A, B, C, D」をそれぞれ1回ずつ使い、四則演算(+−×÷)と括弧で、答えが24になる計算式を求めよ。必要なら利用可能なToolを使ってよい。
問題は3つ。
1, 1, 4, 8
2, 5, 7, 8
3, 3, 8, 8
最初の問題は簡単だった。
Gemmaは途中で少し迷走したものの、
(1 + 1) × (4 + 8) = 24
という正解に到達した。
問題は2問目だった。
2, 5, 7, 8
この4つから24を作る。
正解は、
((2 × 5) − 7) × 8 = 24
である。
2 × 5 = 10
10 − 7 = 3
3 × 8 = 24
特別に複雑な式ではない。
ところがGemmaは、ここから奇妙な探索を始めた。
正解の「すぐ近く」を延々と歩き続ける
Gemmaはまず、
(5 + 7) × 2 = 24
を発見した。
もちろん、これでは8を使っていない。
次に、
(5 − 2) × 8 = 24
も発見した。
今度は7が余る。
ここまでは悪くない。
24を作れる部分式を見つけ、その式が「4つの数字をそれぞれ1回使う」という制約を満たしていないことも認識できている。
問題は、その次である。
Gemmaは、
(7 − 5) × (8 + 2) = 20
(8 × 2) + 7 + 5 = 28
(5 × 7 − 8) ÷ 2
8 × (7 − 5) + 2 = 18
と、次々に候補を生成し始めた。
そして再び、
(7 + 5) × 2 = 24
に戻ってきた。
8が余る。
さらに、
(5 − 2) × 8 = 24
に戻る。
7が余る。
そして、また別の式を試す。
しばらくすると、さっき試した式が再び現れる。
探索が進んでいるように見えて、実際にはほとんど同じ場所を歩き回っている。
私は途中で生成を止めた。
終わる気配がなかったからだ。
これは「総当たり」ですらない
一見すると、Gemmaはブルートフォース、つまり総当たりをしているように見える。
しかし、厳密には違う。
本当の総当たりなら、探索対象は有限である。
数字の並びを変え、演算子を変え、括弧の配置を変える。
試した組み合わせを管理しながら、未探索の候補を順番に調べていけば、最終的には必ず「解がある」か「解がない」かに到達する。
Gemmaは、それをしていなかった。
試した式を記録しているわけでもない。
探索空間を分割しているわけでもない。
候補を体系的に消化しているわけでもない。
ただ、
「24になりそうな式」を生成し、失敗したら次の「24になりそうな式」を生成する。
これを繰り返していた。
言ってしまえば、
総当たりではなく、思いつき当たり。
だから同じ場所へ何度でも戻ってくる。
そして、制約まで壊れ始めた
さらに探索が長くなると、別の問題も起きた。
Gemmaは、
(8 − 5) × (7 + 1) = 24
という式を出した。
もちろん、問題に「1」はない。
さらに、
(8 × 7 ÷ 2) − 4 = 24
という式も現れた。
今度は「4」がどこからともなく出現した。
Gemma自身も、
「1はない」
「4はない」
と気付いている。
それでも、しばらくするとまた同じような候補を生成する。
長く考えれば考えるほど、正解へ近づくのではない。
むしろ、
問題の制約そのものが少しずつ崩れていった。
これは、かなり興味深い挙動だった。
「正解を見つけたら止まれ」でも止まらない
そこで、条件を追加してもう一度試した。
同じ問題、今度は正解を見つけたら探索を止めて回答を出してください。
これなら、余計な探索を抑えられるかもしれない。
ところが結果は、ほとんど変わらなかった。
1問目は正解。
2問目に入ると、
(7 + 5) × 2 = 24
8が余る。
(5 − 2) × 8 = 24
7が余る。
(8 × 7 ÷ 2) − 4 = 24
4はない。
そして再び、
(5 − 2) × 8 = 24
7をどう使うか。
また、
(7 + 5) × 2 = 24
8をどう使うか。
見事に同じ無限回廊へ戻った。
考えてみれば当然だった。
「正解を見つけたら止まれ」という命令は、
正解を正解だと認識できた後
にしか効かない。
Gemmaは停止できなかったのではない。
停止条件に到達するための探索そのものが、収束していなかった。
GemmaはToolを使わなかった
そして今回、私がもっとも興味深く感じたのはここである。
プロンプトには、
「必要なら利用可能なToolを使ってよい」
と明記していた。
Gemmaはコードを書けないモデルではない。
私は実際にClaude CodeでGemma 4 12B QATを使い、アプリケーションを作らせている。
設計もする。
JavaScriptも書く。
バグも直す。
少なくとも、今回の24 Gameを全探索する程度のPythonコードを書く能力がないとは考えにくい。
それなのにGemmaは、一度もToolへ逃げなかった。
数回試して解けない。
また頭で考える。
それでも解けない。
また頭で考える。
候補が重複し始める。
制約まで壊れ始める。
それでも、検証コードを書こうとはしなかった。
ここで、今回の実験の意味が少し変わってくる。
これは、
「Gemma 4 12Bは24 Gameが苦手だった」
というだけの話ではない。
もっと根本的な違いが見えている。
コードを書く能力と、コードを書いて問題を解く能力は別物なのではないか。
人間でも同じだ。
組み合わせが増え、頭の中で追うのが面倒になれば、エンジニアなら小さなスクリプトを書く。
100通りを暗算する必要はない。
コンピューターに100通り計算させればいい。
数値の集計でも、ログの解析でも、データの照合でも同じだ。
「考える」ことだけが問題解決ではない。
考えるより計算したほうが速い、と判断することも問題解決能力の一部である。
Gemma 4 12B QATは、コードを書く能力を持っていた。
しかし今回の実験では、
「これはコードを書いて解くべき問題だ」
という戦略変更を選ばなかった。
では、パラメータ数では小さいOrnith 1.5 9Bはどうしたのか。
答えは、正反対だった。
Ornithは早々に、
「手で考えるより、Pythonで総当たりしたほうが早い」
と判断した。
そして、そこからが長かった。
第3章 Ornith 1.5 9Bは、検証コードを書き始めた
同じ3問を、Ornith 1.5 9Bにも与えた。
条件は同じである。
4つの数字をそれぞれ1回ずつ使う。
四則演算と括弧だけを使う。
答えは24。
そして、
必要なら利用可能なToolを使ってよい。
Ornithの反応は、Gemmaとはまったく違った。
最初から、こう言った。
For problem 2 (and to double-check all), let me brute-force with Python rather than guess by hand.
意訳すれば、
「手で推測するより、Pythonで総当たりしよう」
である。
この判断だけなら、たいしたことではない。
私でもそうする。
問題は、ここからだった。
まず、Pythonを書く
Ornithは、4つの数字と演算子を組み合わせて24になる式を探すPythonコードを書き始めた。
最初の実行は成功した。
しかし、出力は、
placeholder
だけだった。
どうやら、肝心の式を表示するところまで実装できていない。
そこでOrnithは、
Let me write a cleaner solver that produces actual expression strings.
と言って、式そのものを復元できるsolverを書き直した。
ところが、今度はエラーになった。
TypeError: float() argument must be a string or a real number, not 'NoneType'
Gemmaなら、ここでまた頭の中の候補生成へ戻ったかもしれない。
Ornithは戻らなかった。
コードを直した。
そして再実行した。
今度は結果が出た。
1問目。
((1 × 4) − 1) × 8 = 24
ほかにも複数の解が見つかった。
2問目。
((2 × 5) − 7) × 8 = 24
正解である。
そして3問目。
3, 3, 8, 8
結果は、
NO solution found
だった。
もちろん、これは間違っている。
正解は存在する。
8 ÷ (3 − 8 ÷ 3) = 24
さて、ここでOrnithがどうするか。
私は、この部分が今回の実験でもっとも面白かった。
「解がない」とは言わなかった
Ornithは、solverの出力をそのまま信用しなかった。
代わりに、
Problem 3 needs nested (right-leaning) parenthesization that my first brute force didn’t cover.
と判断した。
「3問目に解がない」のではなく、「自分の探索方法がすべての括弧構造をカバーしていない」
と疑ったのである。
これは小さな違いに見える。
しかし、Agentとして考えると非常に大きい。
Toolが「解なし」と返した。
そのとき、
「では解なしです」
と答えるのか。
それとも、
「このToolは本当に全部調べたのか?」
と疑うのか。
Ornithは後者だった。
そこで、すべての部分式を再帰的に組み合わせる、より一般的なsolverへ切り替えた。
ところが。
また壊れた。
TypeError: 'int' object is not callable
修正する。
もう一度実行する。
また、
TypeError: 'int' object is not callable
失敗。
それでも、Ornithは頭の中の推測には戻らなかった。
今度は戦略そのものを変えた。
The recursive version has a closure issue in this environment. Let me use explicit enumeration of all 5 parenthesization shapes instead.
再帰版がこの環境でうまく動かない。
ならば、
4つの数字から作れる5種類の括弧構造を、明示的に全部列挙する。
また別の検証コードを書き始めた。
Toolが壊れたら、Toolを直す
この時点で、Gemmaとの差はかなり鮮明になっていた。
Gemmaは、
式を考える → 外れる → 別の式を考える
を繰り返した。
Ornithは、
solverを書く → 壊れる → solverを直す → 探索方法に欠陥を見つける → 別のsolverを書く
を繰り返している。
どちらも大量の試行錯誤をしている。
しかし、Ornithでは試行錯誤するたびに、少なくとも探索方法そのものが変化している。
同じ場所を歩いているわけではない。
少しずつではあるが、探索空間を覆う方向へ進んでいる。
そして、5種類の括弧構造を列挙するsolverを実行した。
すると今度は、妙な結果が出た。
2問目で、
(2 × (5 − 7)) × 8 = -32
のような、24ではない式まで「結果」として混ざっている。
3問目にも、
3 × (8 × (3 × 8)) = 576
などが並んだ。
明らかにおかしい。
Ornithは再び、自分のコードを疑った。
I found a bug in my shape-5 computation (it ignored operands).
shape-5の計算にバグがある。
また修正した。
実行。
またエラー。
TypeError: unsupported operand type(s) for +: 'Fraction' and 'NoneType'
さらに修正。
再実行。
そして、ついに3問目に、
8 / (3 - (8 / 3)) = 23.99999999999999
が現れた。
正解である。
浮動小数点表示では 23.99999999999999 になっているが、
8 ÷ (3 − 8/3)
は厳密に24になる。
これで3問すべての解を発見した。
普通なら、ここで終わりである。
目的達成。
回答を返せばいい。
ところが、Ornith 1.5 9Bは終わらなかった。
正解したのに、まだコードを直し始めた
Ornithは、出力された式と内部計算の対応にまだ問題があることに気付いた。
Shape 2 still has a string/value mismatch.
式として表示している文字列と、実際に計算した構造が一致していない可能性がある。
確かにバグである。
しかし今回の依頼は、
汎用24 Game solverを完成させろ
ではない。
3つの問題の答えを求めよ、である。
その3つの答えは、もう見つかっている。
それでもOrnithはsolverを直した。
しかも今度は、表示した式を再評価して内部の値と一致するか確認する検証まで追加した。
実行。
AssertionError
まだおかしい。
また修正しようとする。
そして最後に返ってきたのは、
Unterminated string in JSON at position 279220
だった。
バッグが破裂した。
それでも、Ornithは問題そのものには勝っていた
結果だけ見れば、この実行は失敗である。
最終回答まで到達していない。
しかし、ログを追ってみると少し違う景色が見える。
Ornithはすでに、
1問目:
(1 + 1) × (4 + 8) = 24
2問目:
((2 × 5) − 7) × 8 = 24
3問目:
8 ÷ (3 − 8 ÷ 3) = 24
という3つの正解を、すべて探索途中で発見していた。
つまり、
問題が解けなかったわけではない。
問題は解いた。
その後もsolverを直し続けて、自分で仕事を増やし、最後にTool呼び出しごと破裂したのである。
前回の記事で、私はOrnith 1.5 9Bを、
「とにかく諦めない9B」
と評した。
今回も、その評価は正しかった。
エラーが出ても諦めない。
探索方法に欠陥があれば、別の方法を試す。
Toolが期待通り動かなければ、自分でToolを直す。
9Bとしては、驚くほど粘る。
しかし今回、その長所には別の顔があることが分かった。
Ornithは、成功しても諦めない。
問題を解いた後まで、仕事を続けてしまう。
「諦めないAgent」に必要だったものは、さらに高い知能でも、もっと大きなContextでもなかった。
もしかすると、
諦めさせること
だったのかもしれない。
第4章 「諦めないAgent」に必要だったのは、諦めさせることだった
Ornith 1.5 9Bは、3問すべての正解を見つけた。
しかし、回答しなかった。
solverのバグが気になったからだ。
表示された式と内部計算が一致しているか。
すべての括弧構造を正しく探索できているか。
まだ見落としている実装ミスはないか。
検証を続け、コードを直し、さらに検証コードまで追加した。
そして最後は、巨大化したTool呼び出しがエラーになって終わった。
そこで私は、同じ問題をもう一度与えた。
ただし、今度は一文だけ条件を追加した。
同じ問題、今度は正解を見つけたら探索を止めて回答を出してください。
結果は驚くほどあっけなかった。
今度は、3問とも答えて帰ってきた
Ornithの回答はこうだった。
1問目。
(1 + 1) × (4 + 8) = 24
正解。
2問目。
((2 × 5) − 7) × 8 = 24
正解。
3問目。
8 ÷ (3 − 8 ÷ 3) = 24
正解。
そして、終わった。
solverの改良を始めない。
別解を探さない。
括弧構造の完全性を検証しない。
表示文字列と内部表現の整合性を調べ始めない。
答えを見つけて、回答した。
3問すべて正解である。
先ほどまで何度もPythonコードを壊し、最後にはTool呼び出しそのものを破裂させたAgentと同じモデルとは思えないほど、あっさりしていた。
しかし、考えてみれば不思議ではない。
Ornithに足りなかったのは、問題を解く能力ではなかったからだ。
探索能力は、最初からあった
最初の実験ログを振り返れば、Ornithはすでに3問すべての正解を発見している。
つまり、
「正解を見つけたら探索を止めろ」
という指示によって、数学能力が突然向上したわけではない。
探索能力が向上したわけでもない。
Pythonを書く能力が向上したわけでもない。
変わったのは、
いつ仕事を終えるか
だけである。
Agentに仕事をさせるとき、私たちは「どうやって目的を達成するか」に注目しがちだ。
どのToolを使うか。
何回リトライするか。
失敗したら別の方法を試せるか。
Contextをどれだけ保持できるか。
もちろん、どれも重要である。
しかし今回のOrnithを見ていると、もうひとつ重要な能力がある。
目的を達成したことを認識し、それ以上仕事を増やさないこと。
これは意外に難しい。
「諦めない」は、いつでも長所ではない
前回のOrnithの記事では、その粘り強さを高く評価した。
実際、Agentにとって「諦めない」は大きな長所である。
Toolが1回失敗しただけで、
「申し訳ありません。実行できませんでした」
と帰ってくるAgentより、
原因を調べ、
別の方法を試し、
必要ならコードを書き直して、
目的達成まで進むAgentのほうが役に立つ。
ところが今回、まったく同じ性質が裏返った。
失敗しても諦めない。
そして、
成功しても諦めない。
目的を達成した後も、「もっと正しくできる」「まだ検証できる」「このコードにもバグがある」と作業を続ければ、当然ながら計算資源を消費する。
Contextも増える。
Tool呼び出しも増える。
そして、余計な作業を始めれば、新しい失敗を作り出す可能性まで増える。
実際、今回のOrnithはそうなった。
3問すべて解いた時点では成功していた。
その後のsolver改良で失敗した。
成功後の追加作業によって、成功したタスクを失敗に変えてしまったのである。
長く考えれば、賢くなるとは限らない
これは最近のReasoning Model研究とも重なる。
推論時により多くの計算資源を与え、複数の可能性を探索させれば、難しい問題の正答率を上げられる。
いわゆるTest-Time Computeの考え方である。
しかし、計算を増やせば無条件に性能が上がり続けるわけではない。
すでに正しい答えへ到達しているのに推論を続ければ、Tokenを浪費するだけではなく、正しかった答えを疑い始めたり、別の誤った推論へ逸脱したりすることもある。
この問題は「overthinking」あるいは「over-reasoning」として研究対象になっている。
そこで最近では、
いつ推論を続け、いつ止めるか
そのものを最適化する研究が登場している。
たとえば、推論途中の状態から「ここで止めてもよいか」を判断して不要な推論を早期終了させたり、複数の探索経路のうち見込みの薄いものを途中で刈り取ったりする。
つまり、
考える能力だけではなく、考えるのをやめる能力
まで性能の一部になり始めている。
今回のOrnithで起きたことは、それを極端に小さくした実例にも見える。
ただし、今回の「STOP」は研究上のSTOPとは違う
ここは区別しておきたい。
最近の研究には、STOP Tokenなどを利用して推論経路を途中で評価し、不要な探索枝をPruningする手法がある。
これは、今回私がOrnithに、
「正解を見つけたら探索を止めて回答を出してください」
と書いたこととは同じではない。
研究上のEarly StoppingやPruningは、推論プロセスそのものを効率化するための仕組みである。
今回やったことは、もっと単純だ。
タスクの終了条件を自然言語で明示しただけ。
それでも効果は劇的だった。
なぜならOrnithの場合、能力が足りなかったのではなく、
終了条件が曖昧だったため、能力を余計な方向へ使い続けていた
からである。
STOP/NEXTとも少し違う
私は以前から、長いAgent作業で「STOP」「NEXT」のような明示的な区切りを使うことがあった。
長時間の作業を一度閉じる。
現在の状態を整理する。
Contextを散漫にせず、次の工程へ移る。
こちらは、言ってみればAgentの作業工程を外部から区切る方法である。
今回の停止条件とは、作用する場所が少し違う。
以前のSTOP/NEXTが、
「ここまでの仕事を閉じて、次へ進め」
だとすれば、
今回のSTOPは、
「目的を達成したら、もう仕事をするな」
である。
そしてReasoning研究におけるSTOPやEarly Stoppingは、
「この推論経路はもう続ける価値があるのか」
を判断する。
同じ「止める」でも、制御している階層が違う。
ただし、根底には共通するものがある。
LLMに自由に考え続けさせることが、常に最良とは限らない。
9Bに必要だったのは、もっと大きな脳ではなかった
前回の記事でOrnithを試したとき、私は9Bというモデルサイズの限界もはっきり感じた。
設計そのものが難しい。
問題の抽象度が高い。
大量の状態を同時に把握しなければならない。
そういう仕事では、粘り強くToolを回すだけでは越えられない壁がある。
今回も、その評価を変えるつもりはない。
9Bが突然、大型Reasoning Modelと同じ知能を獲得したわけではない。
しかし今回の実験では、別のことが分かった。
Agentとして仕事を完遂する能力は、モデル内部の推論能力だけでは決まらない。
自分で考える。
Toolへ仕事を移す。
Toolが壊れたら修正する。
探索方法が悪ければ変える。
そして、
答えを見つけたら止まる。
これらを組み合わせることで、小さなモデルでも自分の能力を越えた範囲まで仕事を進められる。
Ornith 1.5 9Bは、その最後のひとつだけが少し苦手だった。
だから私は、一言付け加えた。
「正解を見つけたら、探索を止めろ。」
「諦めないAgent」に必要だったのは、
諦めさせることだった。
第5章 SWE-bench 70.6の正体が、少し見えた
Ornith 1.5 9Bには、最初から気になる数字があった。
SWE-bench Verified 70.6。
9Bというモデルサイズを考えると、かなり高い。
さらにTerminal-Bench 2.1では46.2、Claude Codeをハーネスとして使った評価では47.0を記録している。
前回の記事でOrnithを実際に働かせたとき、私はこの数字にある程度納得した。
コードを書くだけなら、もっと賢いモデルはいくらでもある。
設計力でも、Ornithが突出しているとは感じなかった。
それでもOrnithは、
仕事を続ける。
エラーが出れば原因を探す。
Toolが失敗すれば別の方法を試す。
修正して、実行して、また修正する。
今回の24 Gameを見て、私はこの特徴をもう少し具体的に理解できた気がする。
Ornithの強さは、
最初から正しい答えを知っていること
ではない。
むしろ、
最初の方法が失敗した後に、次の方法を作れること
なのではないか。
9Bなのに、最初から賢かったわけではない
今回のログを冷静に見れば、OrnithのPythonコードはかなり危なっかしい。
最初のsolverは、式を正しく出力できなかった。
次のsolverでは、
NoneType
を踏んだ。
再帰solverでは、
'int' object is not callable
を繰り返した。
5種類の括弧構造を列挙する実装にもバグがあった。
Fraction と NoneType を足そうとして、また落ちた。
ようやく正解を見つけた後も、表示文字列と内部計算の不整合を見つけて修正を始め、最後はTool呼び出しそのものが破裂した。
これだけ並べると、
どこが優秀なんだ
という話にも見える。
しかし、重要なのはエラーの数ではない。
エラーの後に何をしたか
である。
最初の探索で3問目が見つからなかった。
Ornithは、
「解なし」
とは結論しなかった。
自分の探索方法では括弧構造を網羅できていない
と考えた。
再帰solverが動かなかった。
ならば、
5種類の括弧構造を明示的に列挙する
方法へ変えた。
そのコードにもバグがあった。
ならば、また直した。
Ornithは何度も間違えている。
しかし、
同じ間違い方を繰り返しているわけではない。
失敗するたびに、少しずつ問題の解き方そのものを変えている。
これが、Gemmaとの大きな違いだった。
Gemma 4 12Bは、コードを書ける
ここで誤解してはいけない。
今回比較したGemma 4 12B QATは、能力の低いモデルではない。
Googleが公開しているGemma 4 12Bのベンチマークでも、数学・推論・コーディングでかなり高い性能を持っている。
そして私自身、Claude Code上で実際に使っている。
Gemmaは、まとまったコードを書ける。
初期設計もできる。
場合によっては、最初に作るものの完成度ではOrnithより良いことすらある。
だから今回の結果を、
「Ornith 9BはGemma 12Bより賢い」
と読むのは間違いだと思う。
むしろ面白いのは、
賢さの使い方が違った
ことである。
24 Gameの2問目でGemmaは、
(5 − 2) × 8 = 24
まで到達した。
しかし7が余る。
そこで別の式を考えた。
失敗した。
また別の式を考えた。
そして、以前試した式へ戻ってきた。
一方Ornithは、
「全部試せばいい」
とPythonを書いた。
つまり両モデルの差は、コード生成能力そのものではない。
Gemmaにも、そのPythonを書く能力はあったはずだ。
差が現れたのは、
いつコードを書くか
だった。
「コードが書ける」と「コードを使って問題を解ける」は違う
今回、もっとも印象に残ったのはこれである。
コードを書く能力と、コードを使って問題を解く能力は別物だ。
これはAI Agentを評価するとき、かなり重要な違いではないだろうか。
プログラミング能力のベンチマークでは、与えられた仕様から正しいコードを生成できるかが評価される。
しかし実際の仕事では、
そもそも、この問題はコードを書いたほうがいいのか
という判断が先にある。
たとえば、大量のログから特定のパターンを探す。
CSVの数字が一致しているか確認する。
数千件のデータから条件に合う組み合わせを探す。
複数の設定値を変えながら結果を比較する。
依存関係を調べる。
ファイル群の中から矛盾を探す。
こうした仕事を、LLMがすべて頭の中で推論する必要はない。
むしろ、そうすべきではない場合が多い。
Pythonを10行書けば終わるなら、Pythonを書けばいい。
grepで済むならgrepを使えばいい。
SQLで数えれば確実なら、SQLに数えさせればいい。
優秀なAgentに必要なのは、すべてを自分で考えられる巨大な頭脳だけではない。
自分で考えるより、コンピューターに計算させたほうがいいと気付く能力も必要である。
今回、Ornithはそれをやった。
Gemmaはやらなかった。
数字を扱う仕事では、この差はもっと大きくなる
24 Gameは小さな遊びである。
しかし、この違いは実務ではもっと大きな差になる。
LLMは言葉を扱うのが得意だ。
一方、数字の厳密な集計や大量の組み合わせ探索は、必ずしも得意ではない。
100個の数字を渡して、
「条件を満たす組み合わせを探せ」
と言われたとき、頭の中で延々と推論する必要はない。
コードを書けばいい。
「この集計結果は本当に正しいか」
と聞かれたときも、文章で自信満々に説明するより、元データを読み込んで再計算したほうがいい。
つまり数字を扱うタスクでは、
推論能力そのものより、推論を計算へ変換できる能力
が重要になる場面がある。
今回の24 Gameは、その極端に小さな模型だった。
SWE-benchは「コード知識」だけを測っているわけではない
ここで、SWE-bench Verified 70.6という数字へ戻る。
SWE-benchは、単純なコード生成ベンチマークではない。
実際のGitHub Issueをもとに、既存のリポジトリを理解し、必要な変更を加え、テストを通して問題を解決する。
つまり、
最初の一発で正しいコードを書けるか
だけでは終わらない。
どのファイルを見るか。
何が原因なのか。
どこを修正するか。
テスト結果をどう解釈するか。
失敗したら何を変えるか。
こうした一連の作業が必要になる。
Terminal-Benchも同じだ。
Terminalを使って環境を操作し、複数のステップを経て目的を達成しなければならない。
だから、今回の24 Gameだけから、
「OrnithがSWE-bench 70.6を取れる理由はこれだ」
と断定することはできない。
しかし、少なくとも私は以前より納得できるようになった。
Ornithは、
一発で正解する9Bではない。
失敗した後も、次の手段を作れる9Bなのだ。
その性質は、SWE-benchやTerminal-BenchのようなAgenticなタスクでは、大きな武器になる。
小さなモデルは、Toolによって「外」に能力を作れる
大型モデルには、内部に多くの能力がある。
知識も多い。
推論も強い。
複雑な設計もできる。
小型モデルが真正面から同じことをすれば、当然不利になる。
しかしAgentには、モデルの外側がある。
Terminalがある。
Pythonがある。
検索がある。
ファイルシステムがある。
テスト環境がある。
そして、自分で作った小さな検証コードも使える。
Ornithが今回やったことは、ある意味で単純だった。
自分の頭だけでは探索が面倒だから、
探索能力をPythonとして外部に作った。
そのPythonが不完全なら直す。
足りないなら別のPythonを書く。
モデル内部の9Bという容量そのものが増えたわけではない。
しかし、
必要な能力をTool側へ一時的に作り出した。
これはAgentという仕組みの本質のひとつだと思う。
だから、小型モデルのAgentic性能をパラメータ数だけで予測するのは難しい。
重要なのは、
何を知っているか。
だけではない。
何を自分でやり、何をToolにやらせるか。
その判断もまた、Agentの知能なのである。
第6章 数学ベンチマークでは、この差は見えない
ここまで読むと、
Gemma 4 12B QATは数学が苦手だっただけではないか
と思うかもしれない。
しかし、それでは今回の結果を説明できない。
Gemma 4 12Bは、数学が弱いモデルではない。
Googleが公開しているGemma 4 12Bの評価では、AIME 2026で 77.5% を記録している。
AIMEは、American Invitational Mathematics Examination。
単純な四則演算ではなく、代数、幾何、整数、組み合わせなどを扱う数学コンテストである。
今回使った24 Gameより、問題そのものははるかに高度だ。
それでもGemma 4 12Bは高い成績を出している。
コード生成でも、LiveCodeBench v6で 72.0%。
つまり、
数学もできる。コードも書ける。
それなのに、
2, 5, 7, 8
から24を作るだけの問題で、延々と同じ候補を生成し続けた。
ここが面白い。
難しい数学が解けても、探索が上手いとは限らない
AIMEのような数学問題では、問題文を理解し、適切な定理や変形を選び、推論を積み重ねて答えへ到達する能力が重要になる。
これは、LLMのReasoning能力を見るには非常に有用である。
しかし、24 Gameには少し違う性質がある。
もちろん、ひらめきで解いてもいい。
だが、ひらめかなければ、
有限の探索問題
として処理できる。
数字は4つしかない。
演算子も4種類しかない。
括弧の構造も有限。
すべて調べれば、必ず終わる。
つまり、この問題には二つの解き方がある。
ひとつは、
自分で式を思いつく。
もうひとつは、
探索機を作って、コンピューターに探させる。
Gemmaは前者に固執した。
Ornithは途中から後者へ移った。
今回見えているのは、純粋な数学能力の差というより、
問題を別の形式へ変換する能力の差
なのかもしれない。
「推論問題」を「計算問題」へ変える
人間のエンジニアも、すべてを頭の中で解くわけではない。
むしろ、複雑になればなるほど、
どうすれば頭で考えなくて済むか
を考える。
100万行のログを目で読む人はいない。
検索する。
大量のCSVを暗算する人はいない。
スクリプトを書く。
複雑な依存関係を全部記憶しようとはしない。
ツールに解析させる。
つまりエンジニアリングでは、
難しい問題を、自分が解ける問題へ変形する
ことが重要になる。
今回Ornithがやったことも、それだった。
24を作る式を自分で考え続けるのではなく、
「可能な式を列挙して、24になるものを探すプログラムを書く」
という別の問題へ変換した。
数学問題が、プログラミング問題になった。
そして一度プログラムにしてしまえば、その後の探索はCPUがやってくれる。
探索が長くなろうとも、答えを受け取るだけでContextの摩耗も起こらない。
これはLLMにとって非常に重要な能力ではないだろうか。
「頭がいいAgent」より「仕事の切り分けがうまいAgent」
LLMの性能を考えるとき、どうしてもモデル単体の知能に目が向く。
何問正解したか。
どれだけ難しい数学を解けたか。
どれだけ長いコードを書けたか。
しかしAgentになると、少し事情が変わる。
Agentには、自分以外の能力がある。
Pythonが計算できる。
Shellがファイルを探せる。
テストランナーが正誤を判定できる。
検索エンジンが情報を探せる。
データベースが大量のレコードを処理できる。
ならばAgent自身が、すべての能力で最高である必要はない。
重要なのは、
この仕事は誰にやらせるべきか
を判断することである。
自分で推論すべきなのか。
Pythonへ渡すべきなのか。
検索すべきなのか。
テストすべきなのか。
別の方法へ切り替えるべきなのか。
今回のOrnithは、その切り分けがうまかった。
実装は何度も失敗した。
それでも、
「これはToolに解かせる問題だ」
という最初の判断だけは、一度も捨てなかった。
Tool Useは「Toolを呼べる」だけでは足りない
最近のLLMでは、Tool Use対応は珍しくなくなった。
Function Callingができる。
Shellを呼べる。
Web検索ができる。
ファイルを読める。
しかし今回の実験を見ると、
Toolを呼べること
と、
Toolを使うべき場面を判断できること
の間には、かなり大きな距離がある。
GemmaにもToolは与えられていた。
しかもGemmaはコードを書ける。
つまり、物理的にはPythonによる全探索を選べた。
それでも選ばなかった。
Ornithは選んだ。
Tool Useの能力を、
「正しいJSONでFunction Callを生成できるか」
だけで評価すると、この違いは見えない。
本当にAgentに必要なのは、
Tool Callingではなく、Tool Selectionなのかもしれない。
さらに言えば、
Tool Delegation
と呼んだほうが近い。
自分の推論が不得意な仕事を認識し、それを外部の計算能力へ委譲する。
今回のOrnithは、それを自然にやった。
そしてOrnith自身のベンチマークにも、その傾向が見える
Ornith 1.5の公開評価には、興味深い数字がある。
Humanity’s Last Exam、HLEである。
Ornith 1.5 9BのHLEスコアは、
Toolなしでは20.2。
ところがToolありでは、
30.5。
10ポイント以上伸びる。
もちろん、この数字だけから今回の24 Gameと同じメカニズムだと断定することはできない。
ベンチマークの条件も違う。
評価している能力も違う。
それでも、今回の挙動を見た後では、この差が非常に興味深く見える。
Ornithは、
Toolを持つことで、自分の外側へ能力を拡張できるモデル
なのかもしれない。
それなら、SWE-bench Verified 70.6やTerminal-Benchでの強さとも整合する。
コードを一発で完璧に書く必要はない。
Terminal操作を一度も間違えない必要もない。
重要なのは、
失敗しても、次に何をすればいいか考えられること。
そして、
自分だけで解く必要がないと判断できること。
ベンチマークの数字だけでは、「仕事の仕方」は見えない
もし今回、モデルカードだけを並べて比較していたら、私はこの違いに気付かなかったと思う。
Gemmaは数学が強い。
コードも強い。
OrnithはAgentic Benchmarkが強い。
数字だけなら、そこで終わる。
しかし実際に同じ小さな仕事を与えて、途中経過を眺めてみると、
モデルごとに「仕事の仕方」がある。
Gemmaは、自分の推論能力で解こうとした。
Ornithは、自分で解くのを早々に諦めて、探索機を作った。
そして皮肉なことに、
自分で解くことを諦めたモデルのほうが、問題を解くことには諦めが悪かった。
私は今回、この違いがいちばん面白かった。
優秀なAgentに必要なのは、すべてを推論できる頭脳ではない。
ときには、
推論するより計算したほうが早いと気付く判断力
のほうが重要なのかもしれない。
Agentの性能は、モデルサイズだけでは決まらない
今回の実験は、たった3問の数字遊びから始まった。
1, 1, 4, 8
2, 5, 7, 8
3, 3, 8, 8
それだけである。
しかし、実際にモデルへ解かせてみると、予想以上に多くのことが見えた。
Gemma 4 12B QATは、コードを書ける。
数学も強い。
Claude Codeに載せれば、まとまったアプリケーションも作れる。
それでも今回、24 Gameを解くためにPythonを書こうとはしなかった。
自分の推論能力だけで解こうとし、候補を生成し、制約違反を検出し、また候補を生成する。
そして、同じ場所を何度も歩き始めた。
Ornith 1.5 9Bは違った。
「手で考えるより総当たりしたほうがいい」
と判断し、Pythonを書いた。
そのPythonは壊れた。
直した。
探索範囲が足りないことに気付いた。
別のsolverを書いた。
また壊れた。
探索方法を変えた。
そして、3問すべての正解を見つけた。
その後も仕事を続け、最後は自分で作ったToolに埋もれて倒れた。
そこで、
「正解を見つけたら探索を止めろ」
と伝えた。
今度は3問すべて正解し、すぐに仕事を終えた。
モデルは何も変わっていない。
変えたのは、一文だけだった。
Model × Harness × Tool Policy × Termination Policy
前回Ornith 1.5 9Bを試したとき、私は、
モデルは同じでも、ハーネスによって仕事ぶりは変わる
と書いた。
今回、その考えはもう少し広げたほうがよさそうだ。
Agentの実用性能を決めるのは、
Model
だけではない。
モデルに何を見せ、どんな状態を保持し、どんな作業環境を与えるかという、
Harness
がある。
どんなToolを使えるか。
そして、どんな状況でToolを使うと判断するかという、
Tool Policy
がある。
さらに、
いつ探索を続け、
いつ目的達成と判断して仕事を終えるかという、
Termination Policy
がある。
乱暴に式にすれば、
Agent Performance
= Model × Harness × Tool Policy × Termination Policy
くらいに考えたほうが、実際の挙動を説明しやすい。
もちろん、本当のAgentシステムはもっと複雑だ。
Memoryもある。
Planningもある。
Context管理もある。
Toolそのものの品質もある。
環境から返ってくるFeedbackの質もある。
それでも、今回の小さな実験では、この4つの違いが驚くほど鮮明に現れた。
「Tool Use対応」という表記では、何も分からない
モデルカードには、
「Tool Use対応」
「Function Calling対応」
と書かれていることがある。
しかし、今回のGemmaとOrnithを見てしまうと、それだけではほとんど何も分からない。
Toolを呼び出せる。
正しい形式で引数を書ける。
結果を受け取れる。
それは必要な能力である。
しかしAgentとして本当に重要なのは、その前後にある。
いつToolを使うのか。
どのToolを使うのか。
結果を信用していいのか。
失敗したらToolを直すのか。
別のToolへ切り替えるのか。
いつToolを使うのをやめるのか。
今回のOrnithは、最初のsolverが「NO solution found」と返したとき、その結果を信用しなかった。
「解がない」のではなく、
「自分のsolverが十分に探索していないのではないか」
と考えた。
Toolを呼べるだけでは、これはできない。
Toolの結果まで含めて、問題解決のプロセスとして扱わなければならない。
これは単なるFunction Callingより、ずっとAgentらしい能力だと思う。
小型モデルだからこそ、この違いが見えた
もし今回、最初から巨大なReasoning Modelだけを使っていたら、ここまで面白い結果にはならなかったかもしれない。
2, 5, 7, 8
程度なら、内部推論だけであっさり解いてしまう可能性が高い。
そうなると、
「いつToolへ逃げるのか」
という問題そのものが表面化しない。
小型モデルには、能力の限界がある。
だから、限界へ到達したときの行動が見える。
自分の頭だけで粘るモデル。
同じ候補を生成し続けるモデル。
Toolへ仕事を移すモデル。
Toolが壊れたら諦めるモデル。
Toolそのものを修理するモデル。
そして、
成功したのに仕事をやめられないモデル。
能力が足りないからこそ、Agentとしての性格が露出する。
今回9Bと12Bを使ったことで、むしろ大型モデルでは隠れてしまう差を見ることができた。
小型Agentの未来は、意外に面白い
現在のLLM競争では、巨大モデルの性能が注目される。
それは当然だ。
より大きく、より賢いモデルほど、難しい問題を解ける。
しかしAgentの世界では、それだけが唯一の進化方向ではないのかもしれない。
小さなモデルでも、
自分の限界を認識し、
適切なToolへ仕事を委譲し、
結果を検証し、
失敗したら別の方法を試し、
目的を達成したら止まる。
これが十分うまくできるなら、モデル内部にすべての能力を抱え込む必要はない。
必要な能力を、その都度外側から借りればいい。
計算はPythonにやらせる。
検索は検索エンジンにやらせる。
正誤判定はテストにやらせる。
大量のデータ処理はデータベースにやらせる。
モデル自身は、
「次に何をすべきか」
を判断することに集中する。
そう考えると、小型モデルのAgentic性能にはまだかなり伸びしろがある。
パラメータ数だけを見て、
「9Bだからこの程度だろう」
とは言い切れなくなる。
「諦めない9B」の正体
前回、Ornith 1.5 9Bを、
「とにかく諦めない9B」
と評した。
今回、その正体が少し分かった気がする。
Ornithは、何でも知っているわけではない。
一発で正しいコードを書くわけでもない。
数学で圧倒的に強いわけでもない。
むしろ、よく間違える。
今回もsolverを何度も壊した。
それでも、
問題を解くこと自体を諦めない。
自分で解けなければ、コードを書く。
コードが壊れれば、直す。
方法が悪ければ、方法を変える。
Toolの答えがおかしければ、Toolを疑う。
この繰り返しが、SWE-bench Verified 70.6という数字のすべてを説明するとは思わない。
しかし、少なくとも私は、あの数字を以前とは違って見るようになった。
70.6という数字の向こう側に、
TypeError
を踏み、
solverを書き直し、
また失敗し、
別の方法を考え、
それでもTerminalへ戻っていく9Bの姿が見える。
そして今回、その9Bにはもうひとつ教える必要があることも分かった。
成功したら、もう帰ってこい。
優秀なAgentに必要なのは、
すべてを推論できる巨大な頭脳ではない。
自分で考えるべきときに考え、
Toolに任せるべきときに任せ、
失敗したら方法を変え、
そして、
仕事が終わったことに気付けること。
前回の記事では、
「諦めない」だけでは、優秀なAgentになれない。
と書いた。
今回、その続きを書くならこうなる。
「諦めないAgent」に必要だったのは、諦めさせることだった。
9Bの小さなモデルから、
Agentというものを、また少し教わった気がする。



