エージェント向けテスト・検証技術の水準は実際どうなのか?

著者はZstd実装の評価で26のテスト/検証プロンプト条件と4つのskillを試したが、ほぼどれも平均を上回らず、Defaultの方が平均より高かった。agentは技術を表面的に使い、無関係な性質を証明し、ランダム入力で同じエラーパスにぶつかり、同じバグを2つの「差分」実装に書き込むことさえある。

日本語
コピー

エージェント向けのテスト・検証技術は、実際どれほどのものか?

以前指摘したとおり、コーディングエージェントに有効なテスト技術を使わせることは、ある品質水準に到達する手段としてはかつてなく容易になっているのに、ソフトウェアの品質は悪化しているように見える。これは、開発者が使っているデフォルトのやり方があまり効いていない可能性を示している。ここで検証するのは、特定の技術やライブラリを使うようエージェントに簡単に指示するだけで、実装の正しさが向上するのかどうかだ。テストの専門知識はなく、使うべきだと「聞いたことがある」だけの人がエージェントを導いたとき、どれだけ効果があるかを確かめることになる。

プログラミング言語ごとのエージェント有効性比較で扱った Zstd 実装の評価を再利用し、Zstd を実装させるプロンプトにさまざまな補足指示を付け足して、テスト技術やテストライブラリを比較した。「テスト駆動開発を使え」「Lean 4 を使え」「QuickCheck を使え」「プロパティベーステストを使え」などである。IMAP RFC を対象にしたものなど、他の評価もいくつか走らせたが、それらは簡単に触れる。

実装はすべて Rust。テストした 26 のプロンプト条件は、ACL2、Alloy、「リスクの高い領域を監査してファズせよ」、「まず監査」、Creusot、Default(追加指示なし)、差分テスト、ファジング、Hegel、Insta、Judgement(最良の技術を使うようエージェントに指示)、Kani、Lean 4、「間違えるな」、メタモルフィックテスト、ミューテーションテスト、プロパティベーステスト、Proptest、QuickCheck、rstest、Rust 標準のテストフレームワーク、SMT ソルバ(Z3、cvc5、Yices が利用可能)、Spin、TDD、TLA+、Verus。加えて 4 つの skill も試した。公式 Hegel skill を使う Hegel、ECC の Rust テスト skill(ECC は GitHub 25 万 star、3.8 万 fork の skill 集)、Trail of Bits のプロパティベーステスト skill、そして自分で書いたテスト skill(私はラッダイトで、skill ではなくプロンプトを使う。良い skill の書き方などまるで分からない)。自分のものを除けば、これらは codex に関連 skill を探させたときに上位に出てきたものだから選んだ。

予測

各条件の成績についていくつか予想を事前登録した。

  • TDD は振るわない(確信度 55%)

TDD をわざわざ入れたのは、振るわないと思ったからだ

  • ここは確信度が低い。TDD を指示されたエージェントが何をするか分からないからだ。TDD をやらず、振るわない原因にならない何かをするかもしれない(そもそも TDD が振るわないという判断自体が間違っているかもしれない)

形式的手法は過剰に好成績を出さない(確信度 52%)

  • 考え方はこうだ。形式的手法は有効で役に立つ(今は以前よりなおさら)。良いテスト手法も同じく有効で役に立つ。そして簡単な問題では、同程度の習熟度で使うなら、形式的手法が過剰に好成績を出すはずがない
  • 上と同じで、それ以上に確信度が低い。何かを指示されたエージェントが何をするか分からないからだ。しかも形式的手法は、有効なテスト技術よりも agentic coding の文脈で大きく喧伝されている。ラボが合成データの RL 環境でエージェントを訓練し、形式的手法は非常に得意にさせたが、良いテスト技術は得意にさせなかった、という可能性は十分ある(後者のほうが簡単なはずで、ただ流行っていないから誰もやっていないのだと思っていた)

「間違えるな」は指示なしより良い成績を出さない(確信度 95%)

  • これは冗談で、多くの人が試している。効くなら誰か気づくだろう?

ECC のテスト skill(25 万 star、3.8 万 fork)は過剰に好成績を出さない(確信度 65%)

  • やや大きく、役に立つと思える情報が中身に一切ない。エージェントに TDD を使うよう指示しており、それが TDD を使わせる方向に働く程度には、状況を悪くすると予想する(しかも TDD の条件より指示が強く、成功しやすいかもしれない。ただし私の知る限り、それが成功しにくくする可能性もある)。残りの情報は役に立たなそうで、それなりのコストもある
  • skill 全般について予測の確信度が低いのは、skill をあまり使わないし、どう評価すればいいか本当に分からないからだ。考え方はこうだ。「このテキストをプロンプトとして渡し、この物体が LLM のコンテキストウィンドウを漂うとしたら、どれだけ効果があるか」

Hegel の skill は過剰に好成績を出さない(確信度 65%)

  • 非常に大きい(SKILL.md とリンクされた Rust リファレンスで 2 万トークンを超える)。しかもエージェントへの指示というよりチュートリアルのように読める

Trail of Bits のテスト skill は過剰に好成績を出さない(確信度 55%)

  • 役に立ちそうな情報がいくつかあるが、これもかなり大きい

全体の結果

以下の非常に雑然とした図が、各テスト条件の結果を示している(codex に GPT-5.6 Sol、effort は medium と xhigh の 2 段階)。データを見るとき、私は大抵の人より密集して雑然とした図のほうを好む。たとえばここの最初の図のようなものである。大抵の人はこうした図を雑然として読みづらいと感じるので、他人に情報を提示するときは、情報を一連の図に分割し、1 枚あたりの情報量を減らすほうを好む。以下で述べる理由により、ここではそうせず、この極度に雑然とした図だけを示す。横軸がコスト、縦軸が(隠された)テストを 100% 通過した実行の割合で、各条件・各 effort あたり平均 80 回の実行(マウスオーバーで bootstrap 共分散、50% 不確実性が表示される。同種のものを近い色にする試みもしてあり、たとえば形式的手法は青系、プロパティベーステストは緑系、など):

はっきり言えるのは、何かが飛び抜けて優れていたわけではないということだ。ただ、Default(追加指示なし)は明らかに平均より上だった。xhigh を見ると、平均的にはファジングと PBT 関連の条件が形式手法よりやや良く、medium はもっと混沌としていた。codex が勧めてきたテスト関連の skill は振るわず、我々がその場で書いたカスタム skill はそこそこだった(重要な違いが1つある。我々の skill は agent をデフォルトの挙動からより生産的な挙動へ押し出すよう設計されていたのに対し、他の skill はどちらかというとチュートリアルだった)。TDD は予想どおり振るわなかった(ある skill も agent に TDD を使うよう勧めており、その skill も agent がその指示に従おうとしたケースでは同じようにひどかった)。

agent が実際に何をしたかを見れば、全体として agent がこれらのツールや技術をうまく使えていないことはすぐ明らかになる。ここで指摘したとおり、また私が話した全員が指摘したとおり、agent はテストが本当に苦手で、「デフォルトで」まともなテストをどう書くのか理解していないように見える。たとえば、これは Gary Bernhardt のコメントだ:

AI agent のテストへの向き合い方は、おおむねこうだ:

  1. 15年前に mock 反対派が妄想した病的なケースを持ち出してくる。しかも mock を実際に使ったことは一度もない。過剰な mock に関する素朴な妄想。
  2. その病的なケースをテスト戦略の骨格にしてしまう。

結局のところ、agent に特定のテスト技術やテストライブラリを使わせても、あなたが期待するほどには変わらない。各条件で何が起きたかは後で詳しく見るが、総じてテスト技術に関しては、agent は普段書くテストを別のテスト技術の枠組みに流し込むか、その技術を表面的に使うだけで、その技術から価値を引き出すようなことを実際にはしない。多くの場合、ある技術が名指しされると、agent は Gary が描写したことをその技術向けにやるだけだ(たとえば形式手法では、たいてい無関係な性質を証明する。性質ベースのテストでは、agent は完全にランダムな入力に大きく依存し、無効・却下されるケースに大量にぶつかるか、自明な性質を1つ見つけてそれを低価値なランダムケースで走らせる)。IMAP RFC(各条件40回ずつ実行した)でも他のランダムな RFC(それぞれ数回の単発実行)でも、結果に実質的な差はなかった。総じて、問題がどの種類であれ——Zstd のようなビット操作の問題でも、IMAP1 のようなプロトコルでも、その他何であれ——agent は形式手法、テストライブラリ、テスト技術を有効な形では使えていない。

xhigh では、agent は概して自分が書いたテストを通せるが、書いているのはひどいテストだ(たとえば4本のビットストリームを使う機能のテストに同一のビットストリームを4本投入し、ビットストリームの順序が入れ替わることで生じるバグをすべて見逃す)。以前 Zstd 評価で言語について指摘したとおり、素朴なループでより低い effort で走らせると結果は悪くなる(agent はこれをより多くやり、より低い正確性で行き詰まる)。

なぜ AI ラボが agent にテストの仕方を学ばせる RL 環境を作らないのか不思議だ。ソフトウェアがまともに動かないことはコーディング agent の普及にとって重要そうだし、これは RL に向いた種類の問題に見える。以前見たとおり、agent は有界実行時間の最適化問題ではかなり得意になっている。これは当然で、まさにそこが安価に大量の RL 環境を作って訓練できる領域だからだ。試すのは見た目より難しい類の話かもしれないが、有効なテストとテスト技術のための RL 環境を作るのも同じ種類の問題に見える。制約は単に有効なテスト技術の知識が普及していないせいで誰も試そうと思わないだけかもしれず、そのせいで人々は agent に非効率なテスト(たとえば標準的な単体テスト)をさせている2。あるいは、この問題は何らかの理由で実行時間最適化よりずっとパッケージ化しにくいのだろうか? agent が十分に優れ、テストや検証なしで普遍的に正しいコードを書けるようになれば、これはすぐ無意味になるかもしれない。だが少なくとも公開されている agent が登場してから今(2026年9月)まで、agent がテストの専門家の導きなしにテストの仕方について多少なりとも見当をつけられれば、agentic coding の有効性は大きく上がりそうだ。

以下、各条件で agent がどうしたかを、正確性の悪い順から良い順に見ていく。ただし、この順位から強い結論を引き出さないよう警告しておく。

ここでの失敗の多くは、プログラミング言語がトークン使用量と正確性に与える影響を調べたときに見た失敗と似ている。つまり、しばしば特異的なのだ。たとえば言語では、agent が Clojure でバイト変換のセマンティクスを間違える割合がかなり高く、Java ではそうではない。agent は Java のバイト変換セマンティクスを得るのに unchecked-byte ではなく byte を使えると「知っている」はずで(そしておそらくある程度は実際に知っている)にもかかわらず、だ。

なぜ特定の言語が agent に向くのかについては漠然とした説明がいろいろあるが、agent が実際に何をし、失敗モードが何かを見ると、Elixir であれ OCaml であれ J であれ、誰かのお気に入りの言語が agentic coding に向く理由として私が聞いた説明はどれも当てはまらない(Rust のメモリ安全性に関するコメントは別で、これは我々が試した多言語 pandoc 評価で、agent が書いた C、C++、Rust の間のメモリ安全性問題の比較によって裏づけられている)。代わりに見えたのは、理由不明の特異的な失敗の山だった3。言語については、言語の人気と性能の間に中程度の相関(コストが低く正確性が高い)が観察できるので、人気のある言語のほうが訓練データが多い(人間が書いたコードだけでなく、合成データも)からだと推測するのは妥当だ。ここではっきりしたパターンはない。あるのは、agent が手にしているのがライブラリや技術の名前だけのとき、テストや検証技術を有効に応用できることがほとんどない、ということだけだ(何がより効くかは後で論じる)。各条件で何が起きたかを読みたくない人は、ここをクリックして最後の項目まで飛ばしてほしい。

Verus

Verus は SMT ソルバーと各種の推論を用いて、コードが仕様を満たすことを証明する。

Verus はコードが仕様を満たすことを証明できるのに、agent はそれをしなかった。代わりに証明したのは、Zstd に関するさまざまな抽象的な性質だ。Verus のようなツールを自分で使ったことがないので、専門家や初心者のユーザーが普段何をするのかは分からない。ただ、チュートリアルを読んだ限りでは少し奇妙に思える。agent は Verus で実際のコードを検証しようとせず、抽象的な推論にしか使っていない。このツールは「実際のコードの性質を証明する」ことを容易にするために設計されているように見えるのに。

さらに、証明した性質を見ると、たいてい数が少なく、内容も退屈だ。たとえば「有効な cursor/index/distance が与えられたとき、結果の操作は境界内に収まる」といったものを証明する。それ自体は悪くないが、バグの発生源ではない。しかも agent はしばしば空虚な証明、実質 A => A を書く。本物の Verus の証明はこういうものだ:

  requires                                                                                                                                                                                         
      0 < a <= window,                                                                           
      0 < b <= window,
      0 < c <= window,
  ensures
      0 < c <= window,
      0 < a <= window,
      0 < b <= window,

agent が実際に何かを証明した場合、それはたいてい比較的単純なもので、バグがありそうな部分は避けている(たとえば agent はエンコードとデコードでビットストリームの順序を反転させることにしばしば失敗するが、書いたテストではそれを検出できない。テストが回文の入力を使っているからだ。ここで何らかの「反転」の証明をしていれば、agent は違う形でこのことに「気づいて」いたかもしれない)。

Verus とそのドキュメントだけを与えられた状況で、agent は Verus から価値を引き出せなかったようだ。

結果を見ると、xhigh 档の Verus は全体としては悪くない(正確性は平均よりやや低いが、はるかに安い)。medium 档はコストが平均並みで、正しく実行できた割合が最も低く、平均の正解テスト数も最も低い。agent は Verus から実際には価値を得ていないので、正確性のためにやっていることの中心は依然として従来型のテスト(Rust 組み込みの #[test] 関数による単体テスト)だ。medium から xhigh にかけて、agent は従来型のテストにはるかに多くの労力を費やし、Verus には少ししか費やしていない。それで xhigh の結果がそこそこになった。

実際のテストを見ると、Verus 条件が明らかに不利な二つの機能の一方(四路ストリームジャンプテーブル)で、Verus の agent は 160 回中 89 回テストを書いた。偶然にも Default 条件とまったく同じ回数だが、Verus の agent のほうがひどいテストを書きやすい。誤った結果をテストにエンコードしがちで、通りやすくカバレッジの悪いテスト——たとえば四つのストリームをまったく同じにする——も書きがちだ。こういうものを私は「失敗が特異的」と呼んでいる。Verus そのものに、使わない場合にひどいテストを書く原因になる要素は何もないし、一般に Verus を使った人がひどい単体テストを書くと予想したりはしない。Clojure を使った人がバイト変換のミスを多く犯すと予想しないのと同じだ。それでもここでは、何らかの理由でそれが起きている(偶然かもしれない)。

AI ラボの内部にいる人間なら、こうしたことがなぜ起きるのか、もっと良い情報を得られるのかもしれない。だが外からでは、この種のことの原因を判断するのはたいていかなり難しい(言語の問題についてもっともらしい仮説を一つ立てたとしても、複数の言語で多くのサンプルを走らせる必要がある。我々が見た、同じ問題を扱った論文では、言語の普及度と agent の有効性の相関は観察されていない。扱った言語が少なすぎてこうした弱い相関について推論できないか、扱った問題が小さすぎて些末すぎるかのどちらかだ)。

Alloy

Alloy はしばしば有界モデル検査器と呼ばれる。Alloy 6 では追加の機能が入ったので、この呼び方はあまり正確でないかもしれないが、それは私の専門の範囲をはるかに超えている。私の理解では、Alloy を使うときは普通、自分のモデルに関する性質を証明する(コードが動くことを証明するのではない)。

Alloy は正確性のスコアが下から二番目で、珍しいことに medium と xhigh の両档で概して振るわなかった。図には示していないが(示しても情報が増えないように思えた)、全体として max と xhigh の結果は強く相関し、medium の結果とはかなり異なる。

Verus で見たのと同じで、Alloy を使う agent は正確性のほぼすべてを標準の Rust #[test] に頼り、Alloy はただいじくり回しているだけだった。ここでも、形式的手法のまずい使い方は正確性の役に立たなかった。

個別には、問題やリスクの発見に近づいた Alloy の使用例もある。だがそうした例は少ない。ある例では、Alloy が反例を見つけ、それを受けて agent がその潜在バグに対する緩和策を Rust 実装に加えた。あいにくその反例は 8 ビットのオーバーフローに依存していて、実際には起こり得ない。実際の実装は 64 ビットの usize を使っており、与えられた入力ではオーバーフローしないからだ。つまりコードが複雑になっただけで、実際のバグは防げていない。

別の例では、Alloy の仕様自体が誤っていて、関連するテストが失敗した。テストが失敗した後、agent は Alloy の仕様を直した。その仕様が最初から正しければ、agent は失敗を経ずに正しいコードを書けたかもしれない。同じような良いことが起きた例はほかにもありそうだが、潜在バグを実際に防げたかどうかは分からない。

Alloy の agent モデリングは Verus の agent より Zstd のアルゴリズムに近い(後者はほとんど算術の検査などに終始している)が、それでもモデリングは間違っている。

差分テスト

差分テストとは、同じ入力を複数の実装に与えて結果を比較し、問題を見つける手法だ。原理的には LLM を使うときに試す価値のあるやり方に見える。異なるサイコロの目はしばしば異なる結果を生むし、ここで指摘したように、agent をある実装の上で何度も反復させる方が(それが全体の一部、あるいは一つの関数の一部でしかなくても)、agent に一からやり直させるよりたいてい結果が悪い。

だがこれがもたらしたのは下から三番目の結果だった。この条件は xhigh では平均よりやや上、medium では平均を大きく下回る。完全な実装を二つ作って比較した agent は一つもいなかった。160 回の実行のうち 135 回は、差分テストと呼べなくもない何かをやっていたが、これまで見てきた他の条件と同じで、概してありきたりで実際には役に立っていない。しかも差分テストならバグを捕まえられたはずの場面で、agent は独立に実装するのではなく同じものを二度書いて、同じバグを両方のバージョンに埋め込んでいた。

私は agent に独立して作業させ、それぞれ別のコンテキストで起動することがあるが、差分テストではそれが効果的にできていなかった。agent はたいてい同じものを二度書くだけだった。

Hegel Skill

公式の Hegel skill が Hegel の挙動をどう変えるかを論じるのは意味があるが、正確さの悪い順から良い順に並べると、Hegel Skill は Hegel より上に来る。正確さの結果がより悪いからだ。この skill についての議論は下の Hegel の節を参照。

Lean 4

Lean 4 は対話型定理証明器と言えるかもしれない。

Lean についての予想を事前登録していたわけではないが、どの形式化ツールが良い成績を出すかを事前登録していたら、Lean は期待できる側に入れていただろう。比較的人気で流行っているため、RL 環境の合成データのおかげで良い成績を出す可能性が相対的に高いからだ。

Lean の agent は実際に性質を証明していたが、Verus 条件と同じで、agent がやっていたのはほとんど算術の証明で、バグが出やすかったり危険だったりする部分には触れていなかった。

他の形式化条件と同様、Lean の agent は標準的な Rust テストに大きく依存していた。ここまでの形式化条件と同じく、取るに足らないことをいくつか証明しても正確さの助けにはならなかった。

QuickCheck

QuickCheck は性質ベースのテストライブラリで、長らくこの種のライブラリとして最も有名だった。今はその座は Hypothesis のものかもしれないが。

残念ながら、agent が性質ベースのテストを活用できた度合いは、これまで見てきた形式化ツールとほぼ同じだった。QuickCheck を使うとき、agent はほとんどがごく単純な「スモークテスト」を書き、何も検査できていなかった。ランダム入力も使っていたが、完全にランダムな入力は Zstd のようなもののテストにはかなりひどい(ランダム入力は失敗・拒否されるコードパスをわずかしか通らないからだ)。

さらに、検査される性質は比較的少なかった。すべての agent が QuickCheck を使ったが、160 回の実行のうち 63 回は性質を一つしか検査していない。agent は技術的には確かに QuickCheck を使っていたものの、またしても主に従来型のテストに頼っていた。なぜか、agent が実際に書いた従来型テストは Default 条件や他のほとんどの条件より多かったのに、テストと修正の反復は少なかった(そのためこの条件のコストは平均を下回る)。

TDD

TDD はここでは振るわなかった。IMAP RFC の評価でも同じだった。

TDD のプロンプトは agent の挙動を大きく変えたようだ。agent が生成するテストの数は倍になり、はるかに反復的な「テスト-コード-テスト-コード」のワークフローで作業していた。ただし TDD の擁護者なら、agent は本当に TDD を使っていたわけではないと言うだろう。細かい粒度の反復的な TDD をやった例はごくわずかしかない。

全体として、agent は事前に書くテストが増えた。たとえば、実質的な(スタブでない)実装に取りかかる前に失敗するテストを一つ以上持っていたケースは 160 回中 67 回で、Default 条件は 160 回中 0 回だった。

大まかなテスト種別で見ると、TDD はどの種別でもテストが多かった。小さくありきたりなテストも、統合テストやエンドツーエンドテストも増えている。「agent が何かをやりすぎ、あるいはやらなさすぎ」というような高レベルな判断は、どうやらデータに合わない。具体的な失敗と、テストがそれをどう見逃したかを見ると、TDD 条件にはそうした例がかなりある。たとえば、Huffman ストリームが四つあるとき、Zstd はジャンプテーブルと呼ばれるものを使う。

TDD の agent は、一般的なケースをカバーするテストをより多く書いていたのに、この機能の評価テストでは失敗しやすかった。なぜか TDD の agent は、難しいケースをカバーしないテストを書きがちだった(たとえば四つのストリームをまったく同じにし、しかもそれをありきたりなものにする。Verus で見たのと同じだ)。これもまた、AI ラボの中の人たちがどんな視野を持っているのか気になる事例だ。TDD で agent を事前に誘導すると、なぜテストも実装もより悪くなるのか、外からでは分からない。

TDD と少数のテスト条件しか見なければ、こんな仮説が立つかもしれない。TDD で書かれたコードには、あまり良くない小さなテストがたくさん付きがちだから、TDD で agent を導けば、そうした無効なテストをより多く書かせることになるのではないか。ただ、なぜ Verus でも同じパターンが見えるはずなのかは分からない。Verus(あるいは他のもの)の挙動に共通の原因があるのか確かめられれば、それが TDD にも当てはまるか判断できるだろう——たとえばオープンソースモデルでこの実験をやり直し、何らかのレベルでモデル内部で実際に何が起きているかを調べるとか。

2 つの skill も agent をより反復的に働かせる。頻繁に実行するほど良い結果が得られるという理論が背後にあるのかもしれないが、この 2 つも同じく振るわなかった。全体として、どの条件でも agent は自分で書いたテストを xhigh と max で通すことができる(max は示していないが、正確性は xhigh よりわずかに良く、コストはずっと安い)。自分で書いたテストをより反復的に通すと、agent は誤ったテストをより多く書きがちになり、誤った挙動が固定化される。

Yossi Kreinin は TDD がなぜより悪いテストにつながりうるかについてこう考えている。

ちなみに、コードを書く前にテストを書くと、テストの難所はより難しくなると思う。何が難しくなるかについて、より少ししか知らないからだ。ランダムテストをするにしても(「tdd」がランダムテストと関係あるとは思わないが)、分布をバグのある方向へ導くことはより難しくなる。コードを書くか、少なくともそれを見ていれば、何が明らかに正しく見えるか、何がうまくいくかどうか分からないかが分かる。その挙動は容易には理解できないからだ。言い換えれば、tdd は君をブラックボックステストへと導く。複雑な機械にとって、僕の見るところブラックボックステストはホワイトボックステストほど効果的ではない。人間については間違いなくそうだ。agent についてはあまり確信がない。

TDD が振るわないという私の推測は当たっていたのか。結果だけを見れば、答えはイエスだ。私の推論については(明示的に文書で事前登録したわけではないが、当時何を考えていたかは分かっている)、よく分からないと思う。私の考えはおおよそこうだった。最近さまざまな文脈議論したように、agent に本当に正しいことをさせる——単に過学習させるのではなく——ことは、agent で良い性能や正確性を得るための鍵の一つだ。方法論一般として、この指示が agent の挙動をどう変えるかという話ではなく、TDD は生まれつき過学習を招きやすいように見える。

agent は実際により悪いテストを書き、時に比較的高コストで無効な反復ワークフローを使った。だが、私が予想していた「人間が TDD を使い、agent に実装を指示する」という失敗モードがここでの本当の問題なのかは分からない。私の直感はまさにその問題から来ている。ここでの推論は「おそらく正しい方向の近く、おそらくそうではない」と評価する。これを判断するにはもっと多くの評価と調査が必要で、データが増えれば私の元の推論は間違いだったという結果になるだろうと推測している。

Spin

Spin はモデル検査器だ。

ここからは結果が平均からあまり離れない範囲に入る。Spin は medium と xhigh の両方で平均よりわずかに悪く、コストは平均より低い。他の形式化ツールで見たのと同じく、Spin の使用は概して無効だった。この事例では、ある種の挙動を Spin でモデル化することは、その挙動をカバーする隠しテストの合否とまったく相関しなかった。Spin の使用は表面的で、何も生み出さなかった。

Hegel

Hegel は Hypothesis を基にしたプロパティベーステストライブラリだ。

ここまで来ればもう予想がつくように、agent は Hegel を有効には使わなかった。使った範囲でも表面的で、たいていは普通のテストに大量に頼った後だった。「agent はこれを本当に意味のある形ではやっていない」と繰り返すのは冗長なので、この節は短くして、特に奇妙な点だけを拾う。

agent が実際に使うワークフローは概ねこうだ。

  1. RFC と API/契約を読む
  2. Zstd を実装する
  3. 普通のテストを走らせる
  4. Hegel のドキュメントを読む
  5. Hegel で 1 から 4 個の簡単なプロパティベーステストを書く
  6. 普通の組み込み Rust テストを使い続ける

前述のとおり、Hegel skill は正確性を高めなかった。正確性はむしろ悪化した(差はランダムかもしれないほど小さいが)。より目立つのはコストの高さで(medium で 26% 増、xhigh で 41% 増)、その原因は因果的と思われる。

この skill は agent にテストをより多く生成させる。増えたテストの大半は、不正な入力で panic しないことの確認と、往復(round-trip)テストだ。前者はプロパティベースやファジングの条件で agent がもともと過剰にやりがちなことなので、そこに力を割いても無駄だ。後者自体は悪い考えではなさそうに見える(私は特定の性質を確認するのに有用そうなので、往復テストを作るよう agent に明示的に指示することもよくある)。だが、最もバグが出やすい場所には効いていない。追加の指示がなければ、agent はもともと正しい可能性が高い平凡な性質のために往復テストを書きたがる。

コストには複数の原因がある。一つはこの skill がかなり大きいことだ(skill 自体が 3.4 万文字、さらに 4.5 万文字の Rust 専用リファレンスを読み込み、最終的に 2 万トークンを超える)。実行開始時に読み込まれ、その後も多くの操作で繰り返し読まれる。これにより平均の追加費用は medium で 16%、xhigh で 18% 増える(生のトークン数では medium が平均 90 万、xhigh が 180 万増。ただしこれらの内容はキャッシュヒット率が非常に高く——初回読み込み後は 99.85%——繰り返し読まれる回数が多いため、総コストのかなりの部分を占める)。

もうひとつ、乗法的なコストがある(この乗数はすでに前の数字に含まれている)。この skill は構造化された操作手順も定めており、そのせいで作業量がかなり増える。この作業は正しさを高めないので、コストだけが増えて見合う利益がない。

ひとつ注意しておきたいのは、この skill が使われたのは 160 回中 157 回「しか」なかったことだ。LLM を使うときの一般論と同じで、挙動も結果もランダムだ。agent が使うべきだとあなたが考える skill があっても、使うかどうかは AI ラボの外にいる人には不透明な要因に左右されうる。

ToB skill

このケースでは、160 回の実行のうち実際にその skillを開いて読んだのは 108 回だけだった。この skill は Rust で proptest を使うよう勧めているが、依存を追加するには承認が必要だとも勧めており、これらはすべて単一ターンの自律実行なので、それは行われなかった。

ここまで見てきた他のプロパティベーステストのケースと同様、プロパティテストはごく初歩的で、役に立つ形では行われていない。

Rstest

Rstest はフィクスチャベースのテストライブラリだ。

agent は実際には rstest を使っていない。技術的には使っているが、実質的には rstest の中で標準的なユニットテストを書いているだけで、rstest が意図する形では使っておらず、rstest の意味を無にしている。ある高次の意味では、ここまで見てきたどの技術にも当てはまるが、agent は他の技術については少なくとも表面的には使っていた(たとえば Hegel で低価値なプロパティテストをいくつか書いた)。それに対してここでは、agent は Rstest を Rstest たらしめているものを使っていない(プロパティベースのテストライブラリに置き換えれば、同種の失敗は、それらを使って非プロパティベースのユニットテストだけを書くことにあたる)。

Rust test

ここで指すのは標準の Rust 組み込みテストフレームワークで、agent は Default 条件でこれを使い、他の条件でも非常に多く依存した。

agent に組み込みテストフレームワークを使うよう明示的に要求すると、テストは増えた(medium では通常の 2 倍、xhigh では 25% 増)が、正しさは向上しなかった。agent が間違えるときは、重要な挙動をテストしていないか、誤ったテスト挙動を実装しているかのどちらかであることが多い。テストを増やしても、リスクのある挙動のカバレッジが実質的に上がるわけでも、テストに誤った挙動が埋め込まれた実行の割合が減るわけでもなかった。

Yossi Kreinin はこう補足している。

固定入力/固定出力型のテストは、機械にも人間にもこの傾向を助長すると思う。入力を生成するなら、出力が正しいかどうかを判定するコードが別に必要になる。そのコード自体にバグがありうるが、それでも「コードを走らせて、その出力を正解とみなす」よりは、何が正しいか、あるものが正しいかどうかをどう判定するかを考えることを促す。固定出力だと、コードの出力をそのまま書き留めて、自分に筋が通っていると納得させるだけになりがちだ。

Creusot

Creusot は Verus と同じ領域にある。

ここまで見てきた他の形式的手法の条件と同様、Creusot は有効には使われなかった。

ミューテーションテスト

ミューテーションテストは、コードを改変してテストがどれだけ有効かを判断し、良いカバレッジが得られるようテストを補う手法だ。ミューテーションテストは標準的なプログラミング用語だが、agent は一般に本当のミューテーションテストをしておらず、普通のテストに加えて、ミューテーションテストとは言えないちょっとした変更を少し加えるだけだった。TDD の指示が挙動を変えても agent に TDD をさせられなかったのと同じだ。

実際にミューテーションテストが起きた例は少数あるが、量はごく少なく、まれだった。

Judgement

この条件は、agent が自分の判断で適切なテスト手法を適応的に使うことを求める。ここまで見てきたことを踏まえれば当然だが、agent はほとんど標準の Rust ユニットテストを使った。少数の agent が限定的なファジングをした。他のテストライブラリや形式的手法のライブラリも使えたが、使われなかった。

ファジング

ファジングは、テスト入力を何らかの形でランダム化する手法だ。

agent はランダムなバイトを投げ込むことに大きく依存したが、これはほとんど同じコードパス(無効な入力)を通るだけだった。有効な入力のランダムな変種を投げ込むことも試したが、これもほとんど入力が拒否されるパスを繰り返し通るだけだった。

agent がランダムな構造化入力を生成したまれなケース(160 回中 10 回)では、半数が実際のバグを発見し、そのうちいくつかは非自明なケースだった。160 回中 5 回はファジングが比較的有効に使われた。良いとは言えないが、ここまで見てきた技術の中では比較的有効な使い方のひとつだ。これは、agent が訓練を変えずとも、この点をより上手くやれるよう誘導できることも示唆している。ある意味ではやり方を知っている。ただ、後押しされなければ、たいてい実際にはやらないだけだ。

Insta

Insta はスナップショットテスト(golden testing とも呼ばれる)を行うライブラリだ。結果を「スナップショット」または「golden file」の形の正解と比較する。一般にスナップショットは何らかのシリアライズされたデータで、たとえばあるデータ構造の JSON オブジェクトや、CLI 出力のログなどだ。

予想どおり、スナップショットテストはほとんど使われず、agent は主に従来型のテストに頼った。agent は Insta を使いはしたが、多くの場合 Insta の中で普通のユニットテストを書いていただけだった。

SMT

agent には SMT ソルバーを使うよう指示してあり、Z3、cvc5、Yices はすべてインストール済みだった。

agent のほとんどは SMT ソルバーをある種の計算用紙として扱い、FSE の状態範囲やヘッダの算術などを計算させていた。agent がモデルを組んだとしても、たいていは正しい対象をモデル化しておらず、よくある間違いを避けられていなかった。

たとえばある計算は本来 byte1 + (byte2 << 8) + 0x7F00 であるべきだった。多くの agent は byte1 + (byte2 << 8) | 0x7F00 と実装した。agent は SMT ソルバーでその計算に関する性質を証明し、それでもコードを間違って書いた。その結果、SMT の使用は Default(指示なし)より良いようには見えなかった。

TLA+

TLA+ は振る舞いをモデル化するための言語とツールだ。

平均より上のグループに入った(それでも Default よりは悪い)が、前述のとおり実際の順位はあまり真に受けていない。意味があるとは限らないが、TLA+ は medium では平均よりわずかに上、xhigh ではもう少し上だった。

160 個の agent のうち 159 個が何らかの TLA+ モデルを作成し、たいていは Zstd の状態機械モデルだった。具体的なカバレッジとしては、30 個が Huffman/FSE/エントロピー符号化(バグが出やすい領域)をモデル化した。他の形式手法のケースと同様、TLA+ のモデリングはフローの比較的後半(大量の標準テストと実装の後)で行われた。agent が TLA+ モデル内の誤りを見つけて直すことはあったが、TLA+ の問題が最終的に Rust コードの実際の変更につながった例は見つからなかった。

本物らしく見える TLA+ モデリングが起きてはいたが、それが正確性を高めたとしても、観察が難しいほどわずかな改善だった。全体として、TLA+ モデリングをより精緻に行った実行でも、正確性は良くなっていない。

メタモルフィックテスト

メタモルフィックテストでは、関連する入力から得られる出力の間に期待される関係があるかを確認する。たとえば、ソート関数なら等しくない入力の順序を変えても出力の順序は変わらないこと、加算なら一方の入力に値を足すと出力もその値だけ増えること(オーバーフローを法として)を確認できる。

他の条件で見たのと同じく、メタモルフィックテストは正確性のために有効には実行されなかった。それなりの性質はいくつか確認されていた(フレーム境界にスキップ可能フレームを挿入しても出力が変わらない、正当なブロックの再分割で出力が変わらない、など)が、これらは agent が比較的よく間違える領域に触れておらず、確認しても役に立たなかった。全体として、agent はあの古いジョークのファンのようだ。

警察官が、酔っ払いが街灯の下で何かを探しているのを見て、何をなくしたのかと尋ねた。酔っ払いは鍵をなくしたと言い、二人で街灯の下を探した。数分後、警察官がここでなくしたのかと確認すると、酔っ払いは違う、公園でなくしたと答えた。なぜここを探すのかと聞かれて、酔っ払いは「ここだけ明るいから」と答えた。

面白いことに、メタモルフィックテストは medium より xhigh のほうが使われなかった。

ECC

ECC の Rust テスト skillはそれなりに機能したが、主な理由は skill の大部分が無視されたことだった。agent はたいていこの skill を開いて読んでおり(160 回中 153 回が読んだ)、それによってテストがより多く生成されたようだ。この条件では agent が生成するテストが多いだけでなく、agent がいつ skill を読んだか(早い、遅い、読まない)で見ると、テストの増加量に露出度に応じた勾配があった。

ECC のスコアはほぼ Default と同じくらい良いが、agent が skill により多く触れたときの挙動からすると、これはランダムだと思う。agent がこの skill を早く見るほど挙動が影響を受け、正確性の結果は悪くなる。

ECC が素点で良く見えるのは、skill を読まなかった 7 個の agent が異常に良く、100% 正解だったからであり、また ECC を遅く見てほとんど影響を受けなかった別の 9 個の agent も良く、正解率 100% だったからだ。これで ECC の珍しい結果も説明できる。medium と xhigh のスコアが同じなのは(agent がほぼ skill を見なかった実行がほとんど medium で起きた)、skill が呼び出されたかどうかに何らかのバイアスがあるかもしれないが、全体的なパターンは ECC が無効であることを示している。ただし、ECC がお守りのように働き、skill が実際には使われていないときに結果を改善すると思っているなら別で、それは低い effort のほうでより起こりやすい。

もちろん、agent は見ていない skill に影響されるべきではないし、skill が実際に使われた場合で採点すべきだ。skill を見たことが agent に実際に影響したケースでは、ECC のスコアは平均以下(Rust 組み込みフレームワークと Creusot の間)で、失敗モードは Rust 組み込みフレームワークと非常によく似ていた。小さくて無意味なテストが大量に出るのだ。この skill は agent に赤緑 TDD を使うよう指示する。agent の挙動は TDD の実践者が TDD と呼ぶものではないだろうが、機能を実装する前に小さなテストを書いてはおり、それがテスト数の多さにつながっている。前述のとおり、これは agent の有効な開発の仕方ではないので、結果は指示なしで skill も使わない場合より悪くなる。

ちなみに、Caveman モードを試したときに指摘したとおり、分散はかなり大きく、人は少数回の実行に惑わされて skill が役に立つと思いがちだ。このケースでは 1 つの skill に対して 160 回の実行を試しており、これはかなり多く、分別のある人がするより多い。それでも表面上、スコアだけを見れば ECC は悪くなさそうに見える。

LLM を使うときに避けられないノイズを平均化するには、はるかに多くの実行回数が要る。ここでやったように結果を検査し、少し人間の頭を使うこともできるが、公開 LLM ベンチマークの話になると、skill であれ何であれ、そうしている例をほとんど見ない(LLM に結果を分析させることも試したが、いつもどおり、現在公開されている SOTA モデルでも分析はひどく、基本的な推論ミスだらけだった)。むしろ目にするのは、つまらない統計上の理由で意味がないのにヘッドラインの数字を広めるか、もっと悪い場合はベンチマーク自体に致命的な欠陥がある、Senior SWE-Bench で見たとおりに

Default

Default は agent にテストや検証の指示を一切与えない。

ここまで見てきたことを踏まえると、Default が平均より上なのは意外ではない。特定のライブラリや特定のテスト技法を使うよう求められると、agent のやることはたいてい役に立たない。無駄な作業をさせる指示を与えないほうが、無駄な作業をさせる指示を与えるより良い、というのは筋が通っている。

Audit

Audit は実装後にコードを監査するよう agent に求める。160 回のうち 152 回は実際にそうし、151 の agent が問題を発見したと主張し、その監査を理由に変更を加えた。agent はたいてい妥当な領域を選んで監査するが、独立した監査をまったく新しいコンテキストで行うことは少なく(私はよく agent にそう要求する)、監査の中で以前すでに犯したのと同じミスを繰り返すことも多い。

42 回は独立した agent を使ったが、これらの実行は実際にはスコアが悪かった(因果関係ではなく、agent がより悪い、あるいは難しい状況に置かれたから独立した監査を選んだ、という可能性がある)。Audit は xhigh で最高の正しさを出したが、medium では平均を下回り、しかもこれらの監査はすべてコストを大幅に増やした。特に xhigh で顕著だった。平均すると Audit の成績は Default と同程度で、xhigh で本当に良いのか、medium で本当に悪いのかはわからない。本当かもしれないが、判断するには証拠が足りないと思う。

Em Chu のコメント:

ここでの結果は自分の経験と一致する。コードの監査は今のところ自分のトークン消費が最も多いところで、かなり有用だと感じているからだ。ただ、いつも二つの指示を出している:

  • サブ agent を派生させないこと。自分でコードや diff を読んで理解すること
  • コードを一切実行しないこと なぜなら、LLM にこのどちらかをやらせると明らかに馬鹿になるからだ(もちろん定量化はしていないが……)。コンテキストウィンドウを超えるような大きな変更をしたことは一度もないのに、デフォルトでは本当にコードを読まず、推論もしない。 あと、たいてい「敵対的にやれ」「考えうるすべての機能の組み合わせを考慮しろ」「入力空間全体を考えろ」といったおまじないも付け加えるが、これが役に立っているかはよくわからない。

それを試すのは面白そうだが、最近の記事で書いたとおり、記事はもっと簡潔に書くようにしているので、また別の記事の題材になるかもしれない。

Audit and fuzz risky areas

Zstd の場合、この指示が守られると、agent は FSEHuffman、ビットリーダー、状態に大量に注目した。全体として、これらはまさに agent が問題を見落としがちな領域なので、agent がこれらの領域をリスクありと判断したのは正しい。指定された領域をファズするほうが、単に Fuzzing 条件を選ぶより良い。

medium では agent はほとんどこの指示を無視して実行しなかったが、xhigh では実際に従った。この条件は悪い成績ではなかったが、指示を加えない場合より良いとも思えない。

agent が実際に何をしたかを見ると、問題の一つは、agent がしばしばランダムな入力を大量に生成するだけで、そうした入力はたいてい無効で、面白い状況を何もテストできないことだ。

人間のテスターがランダムテストを生成するときは、ふつう「面白い」入力を生む方向にランダム化を誘導しようとするが、agent にはそれができていない。agent の出力チェックも効果が薄く、多くの場合クラッシュを探すだけだ。ファジングはクラッシュを探すだけで性質を検査しないものとよく見なされるので、それほど意外ではないかもしれないが、Zstd 実装をテストしている人なら、おそらくそれは望んでいない。

Make no mistakes

この条件は技術的には Default よりスコアが高いが、挙動に実質的な違いは見えず、スコアもかなり近い。ランダムな変動だと思う。結果を見たあらゆるレベルで、Default のランダムサンプリングと区別がつかなかった。

Kani

Kani は Rust のモデル検査ライブラリだ。

「実際に実行されるコードに形式手法を使う」という点で、Kani のカバレッジが最も良い。Kani は実際に Zstd のコードに対して使われたからだ。ただしそれはたまにしか起きず、ほとんどの使用は表面的だった。

実際の Kani の使用が自明でないバグを一例で捕まえ、Rust コードの変更につながった。160 回に 1 回では大したことではないが、agent がときどき Kani をうまく使えることを示している(つまり、RL 環境に置けばモデルは Kani をもっと効果的に使えるよう学習できる、ということだと思う)。

Kani のコストは他の条件より明らかに高い。Kani の出力を繰り返し読むのが高くつき、入力トークンのコストが膨らむためらしい。

ACL2

ACL2 は定理証明器だ。ここで結果について少し注意が必要な点がある。多くのケースで ACL2 は OOM する(上限 192 GiB)。OOM になった結果は集計から外されており、これが何らか不透明な形で結果にバイアスをかけている。

ACL2 のスコアは Default より高いが、これが因果的で有意だとしたら意外に思う。他のほぼすべての形式手法で見たのと同じように、ACL2 は正確性に目立った影響を与えないものを証明するのに使われることがほとんどで、なぜ正確性が上がるのか分からない。

これほど多くの条件が異なる状況では、他の条件がひどく劣化していない限り、Default やそれと等価に見える Make no mistakes が最上位に来るとは思っていなかった。

Proptest

Proptest はプロパティベーステストのライブラリだ。

他のランダム化テストで見たのと同じく、ほとんどのテストは面白みがなく、ランダム性への過度な依存がカバレッジの悪さを招いている。

プロパティベーステスト全体がうまく使われていない一方で、proptest のシュリンク(shrink、テスト失敗を引き起こすより単純な入力を探す仕組み)が価値を提供した場面はあり、これは他のほとんどのケースで見た「ほぼ価値なし」よりましだ。

プロパティベーステスト

他の技術ベースのアプローチと同様、agent はすべての選択肢が入ったコンテナを受け取る。どの agent も proptest を選んだので、これは実質的に 2 つ目の proptest 条件になった。

proptest 条件と同じく、テストはたいてい良くないが、ときどきバグを実際に見つけており、シュリンクもいくらか効果があったようだ。

少し面白いと思うのは、この 2 つ目の「偶発的に生まれた」proptest の枝も、proptest と同じく平均を大きく上回るスコアを出したことだ。

Skill

ここでの Skill とは、私が書いたあの skill を指す。「単純な skill を持たせる」とどうなるかを試すためのものだ(私が agent に関連するテスト skill を探させたときに見つかる、大きくて複雑な skill 群とは対照的に)。

skill を使うべきなのかもしれないが、普段は使わず、プロンプトを出し、何が起きるか見て、またプロンプトを出す、というやり方に頼っている。だから良い skill とは何かについて直感がまったくない。練習していないからだ。ただ Max Bittker が、私の頭の中にあるテストに関する知識の一部をコードに落とし込もうとするテスト skill を使えば面白い結果が見られるだろうと提案した。結果を見て、彼は「だから言っただろう」という反応をした。

これについて事前登録した予測はしていないが、頭の中ではうまくいかないだろうと思っていた。2015 年にこれを人に伝えようとした経験から言うと——あれはほぼ失敗だったと言っていい——私は人がどうテストすべきかを言葉で明示的に書き出すのが得意ではない。座って実演すれば、たいていは一度で相手が変わり、平均をはるかに超えるバグハンターになる。だが実演で何かを伝えられることと、やり方を書き下して伝えられることは、別の(そしてより易しい)技能だ

このケースで skill はこうだった:

  • 実装前に、微妙なバグが潜んでいそうな領域を考え、それぞれについて起こりうる誤りと筋の通る別の解釈を書き出し、結果に差が出るチェックを考える(非対称な例、境界の両側の例を優先する)
  • 実装後に、リスクの高い領域について、本番コードのコンテキストなしで独立に結果を導出し比較する(まっさらなコンテキスト、補助関数を再利用しない)
  • 可能ならプロパティベーステストやランダム入力で空間を探索し、「panic しない」「クラッシュしない」といった類のランダム化への投資は最小限にする
  • ランダム化するときは、面白い状態やコードパスを探索できる入力に寄せる(すべて同じエラーパスに落ちるような入力を素朴にランダム化しない)。そのためには構造化されたランダム入力が必要かもしれない
  • 細部に自信がなければ、独立した推論で何が正しいか確認する(まっさらなコンテキスト、補助関数を再利用しない)

これは最高スコアを取ったが、期待通りには機能しなかった。「まっさらなコンテキスト」の部分はほとんど実際には行われず、書いておく意味がなかった。これが有効で、agent にもっと頻繁にやらせるよう改良すべきものなのか、それとも削るべきものなのかも分からない(技術的には最適な頻度でたまたま起きている可能性もあるが、非常に疑わしい)。

「Audit and fuzz risky」で指摘したように、agent はリスク領域の特定の仕方は分かっているようだ。ここでも同じだが、それは agent が正しいことをしたという意味には必ずしもならない。たとえば agent は Zstd でエンコードとデコードのビットストリーム順が反転している点をリスクとして特定したが、それをカバーするテストはうまく作れなかった。具体例を見ると、medium の 35 回目の実行で、ある agent はそれをリスク点として特定し、独立した導出と監査を行ったが、それでも失敗した。関連するテストはあったが、入力が回文だったため順序を反転しても同じ結果になり、順序を逆にした実装がテストを通過してしまった。

もう一つの問題——問題と呼べるなら——は、ファジング/プロパティベーステストがすべて「手作業」で行われたことだ。agent は proptest をそれなりに使えるようで、proptest には頼れる便利な仕組みがあるのだから、この skill は agent に proptest を使うよう指示するだけで簡単に改善できただろう。「大量の役に立たない、過度にランダムなテストを生成する」という定番の失敗パターンを減らす意図の指示は方向としては効いており、多少なりとも意味のあるテストを生成する agent の割合は増えたが、それでも私が人間(あるいは人間が積極的に指導した agent)なら書くだろうものよりは劣る。反復なしでは、どんな汎用的な指示が良いのか分からない(数分かけて Zstd の構造を見て Zstd 専用の指示を出す、という他の問題でうまくいっているやり方と比べて)。

skill の初稿として、ひどいとは思わないが、本当に使えるとも思わない。もっとずっと多くの例で試して、RFC 系の問題やビット操作の多い問題などに過学習していないことを確かめれば、改良版なら機能するだろうという想像はできる。ただ、普段 skill を作らないし、skill を反復改良したこともないので、この skill は、自分や誰かが実際に agent を駆動するときのやり方を反映できていない。

自分はまずプロンプトを出し、それから結果を見る(コードとは限らないが、少なくとも agent が何をしたと言っているか、起きたことについての agent 的な要約は見る。実験によっては実際の結果の一部も見る)という流れに慣れていて、それに基づいて次のプロンプトを出す。だから情報を先に置くことに慣れていない。そして情報を先に置くことと、情報に反応することは、かなり別の問題だ。以前のファジングの仕事で、agent がよく陥る失敗モードを見てきた。この skill はその失敗モードを防ぐためのものだ。ただ、たとえ時々でも振り返って見るなら、これをやるのは完全に先に置くより簡単になる。そしてこれらの前置きの指示は、標準的な失敗モードを止めるには足りない。いくらかは緩和するけれど。

全体についてのコメント

前述のとおり、データをきれいで読みやすい形に分解しようとはしなかった。そうしなかったのは、agent が実際に何をしたかを見ると、そのほとんどがかなり無効に見えたからだ。「Verus を下手に使う agent」が「QuickCheck を下手に使う agent」よりどれだけマシかを見るのは、特に面白いとは思わない。少し面白いと思ったのは、リスクのある箇所や微妙なバグが入りやすい箇所を特定するよう求めると、agent にはそれができることだ。

ただ全体として、どのライブラリや手法を勧めても、agent はその手法を使えなかった。前述のとおり、agent にただ「テスト」させる、あるいは繰り返しもっとテストさせるだけでは、ひどいテストが出てくる。結局、テスト手法を使わせても(その一部は個人的に非常に有効だと思っている)、同じくひどいテストが出てきた。自分が書いた雑な skill は、少しは改善するように見えたが、本当に役立つには、自分がかけた 2 分ではまったく足りない。Yossi Kreinin は、ソフトウェアテストの現状はひどいと評している。つまり、agent が訓練で教えられたものに戻るなら、ひどい結果を予想すべきだということになる。そして見ているのはまさにそれだ。

なぜか agent は proptest の使用は少しマシなようだった。とはいえ、proptest のマニュアルを読み、テストの方針をいくらか与えられた理性的な人ならできるだろうと自分が期待する水準には、遠く及ばない。もっと方針を与えられた agent は、他のライブラリより proptest のほうが有効に使えるのか、興味がある。ただそれは別の記事の話題だ。30 分で一本出したいと思っていて、この記事はすでに 9000 字近くあり、30 分で打ち終えるのに妥当な字数を超えている。

agent に良いテストを書かせるには?

自分の経験では、まともなテストとトリアージの構造を agent に作らせておけば、大量の監督なしに agent がそこへ有効に足していくのは、そこそこ機能する。自分の背景(バイアス)から、頼りにするテストのやり方は何らかのランダム化テスト/ファジング/プロパティベーステストになりがちだ。

この件は Jamie Brandon と話した。彼もスナップショットテストで同じことに気づいていた。あるプロジェクトで、agent(複数のモデルを使った)にスナップショットテストをさせたところ、やっていると言ってやらない(ユニットテストを書いて、スナップショットテストを書いたと言う)ことがあった。別のプロジェクトでは、モック IO を使ったまともなエンドツーエンドテストを書かせられたが、それはテストを別の crate に移し、AGENTS.md に「テストはこの crate に置く、公開インターフェースを変更しない」と書いた場合に限られた。

少なくとも今のところ、自分は Jamie より agent にテストコードを書かせるほうに頼っている(自分の習慣は CLI で agent にタイプすることだ。少なくとも今は、彼のほうが自分より手書きのコードを好む)。ただ、何らかのまともな構造さえ作れば、やり方はあまり関係ないようだ。

以前に見たあの問題と似ている。多少まともなことなら何でもやれば機能するように見える。agent に数言「話しかけ」、軽い指示をいくつか与えるだけなら——Zstd や IMAP の評価でやったように——agent はひどくやる。だが、やったことを見て数言打てば、たいていすぐに良い位置へ持っていける(少なくとも他の問題ではそういう経験がある)。モデルがなぜ機能するのかを理解しようとしたことはないので、自分のでっち上げで、おそらく完全に間違っている推測はこうだ。モデルの内部のどこかに、こういうことをうまくやる方法についての理解がある。それはデフォルトの挙動ではないし、デフォルトに十分近いわけでもない——どの手法を使うべきか名指しすれば効く、というほどには近くない——が、モデルが十分に導かれれば、こういうことをうまくやる知識は実際に発揮される。

これが skill として有効にパッケージできるのか、それとも AI ラボがモデルを訓練してテストや形式手法を強化し始めるのか、あるいは彼らがやっている、これらの能力を直接改善しない事柄が、間接的に十分改善して、agent がほとんど監督や構造なしにまともなテストを書けるようになるのか、興味がある。

予測の正確さ

  • TDD は振るわない(確信度 55%)

正しい

形式手法は過剰に振る舞わない(確信度 52%)

  • 正しいが、自分が予想した理由ではない。agent はそもそもそれらを有効に使えなかったので、過剰に振る舞うはずがない

「間違えるな」は指示なしより良い結果にならない(確信度 95%)

  • 正しい。これはほとんどの条件を上回る。何もしないほうが、agent に無効なことをさせるよりましだから

ECC skill は過剰な性能を発揮しない

  • 正しい。もっと skill を使っていれば、この点についての確信はもっと高かっただろう。この skill のテキストの大部分は何もできないように見えるし、効きそうに見える部分は逆効果に見える

Hegel skill は過剰な性能を発揮しない

  • 正しい。また「もっと skill を使っていれば確信が高まった」例だ。効かなかった理由はまさに自分が推測したとおりだった。自分の感覚にまったく自信が持てないだけだ

ToB skill は過剰な性能を発揮しない

  • 正しい

登録はしていないが、自分が暗黙に抱いていた推測は見て取れる(結果を見たときに意外だと感じたから):

  • Lean は形式手法の中で比較的よく機能する

誤り

自分の skill は凡庸か、ひどい出来になる

  • 誤り。とはいえ Max Bittker は何が起きるかを正しく予想し、事前に教えてくれ、実際と一致する理由まで示していた。何かが起きる理由を誰かが指摘したのに自分が信じず、その後ほんとうにその指摘どおりの理由で起きると、かなり間抜けな気分になる

Skill

いろいろな時期に、自分はひどい/時代遅れの/役に立たないワークフローを持っていると感じたことがある。人が何をしているかを聞いて、試すのが面倒だったからだ。skill についてもしばらくそう感じていた。ほとんど使っていなかったからだ。代わりにやっていたのは、大きなスクラッチパッドを維持して、ときどきそこからコピペしてプロンプトにすることだった。コードブロックを保存するためにコメントアウトしていて、バージョン管理を使わないようなものだ。

だがその後Thorsten Ball のこの講演を見て、彼は skill にそれほど依存していないと言っていた。LLM をそれなりに有効に使っていて skill もあまり使わない人たちとも何人か話し、自分はそれほど多くを見逃していないのかもしれないと思い始めた。

そこでこの実験をやった。自分の感覚では、見てきた skill は役に立たないし、おそらく実際には有害だ。確信度は低い。skill について何も知らないからだ。これらの skill の性能はほぼ予想どおりだったので、agent が物事にどう反応するかを観察し、小さな実験をたくさん回して得た直感は、skill についてもそれなりに通用するようだ。テストを改善すると謳いながらこの実験には含めなかった skill もたくさん見たが、それらは被験 skill と同じ失敗の仕方をしそうに見える。

「公式」skill でもう2つ実験を回した(詳細はここでは扱わない。いつか別の1万字の記事で書くかもしれない)。企業が自社製品を支えるために提供している skill だ。ひとつは大手 AI ラボのもので、もうひとつは「小さな」企業(数十億ドル規模)のものだったが、どちらの場合も skill は結果を悪くした。ここで見たのと同じように。面白いのは、これらの実験を終えたあと、個人利用における skill の見通しが以前よりむしろ良くなったことだ。失敗の仕方が予測可能に見えるので、高価な実験を大量にせずに直せる。公開リリースする skill を作り、それが本当に優れていて、さまざまなモデルや harness で通用することを期待するのは難しそうだ(claude と codex は「求めている」プロンプトのスタイルが違うように見えるので、skill についても当然そうなる)。だが、多くの skill を個人利用で「使わないほうがまし」にしている問題だけを解決するのは、かなり実行可能に見える?

skill の書き方についての素朴な考え

良い skill の書き方を説明できるほど skill を知らないが、この記事で見たすべての skill(自分が1、2分で書いたものを除く)と、もう2つの実験の skill については、どれも人間向けのチュートリアル説明のように書かれていた。つまり skill の目的は、何かのやり方を説明することのように見える。skill をひとつしか書いたことのない者の素朴な考えとしては、モデル自体がその主題についてある程度知っているはずの場合(ここでももうひとつの実験でもそうだ)、これはおそらく最適ではない。モデルにはもともとデフォルトの行動分布があるので、その行動を変更する記述を与えるほうが自然だと思う。人間や、その分野の知識がない agent がゼロからこれをやれるようにする説明を書くよりは。

明らかな問題は、harness、モデル、effort の段階が違えばデフォルトの行動も変わることだが、プロンプトや skill にテキストを大量に放り込んでもそれは変わらない。agent をデフォルトから遠ざける長ったらしいやり方になるだけで、そのテキストの多くは意図しない方向への押し込みを生みかねない。ここで見たように、大部分のテキストはただ無視される(何が、いつ無視されるかは、もちろん harness、モデル、effort 次第だ)。たとえば ECC の skill は、読まれてもほとんどの指示が無視された。TDD の指示は影響力があった(結果を悪くした)が、TDD をやれという指示自体は、はっきり書かれているにもかかわらず、実際には守られなかった。

どんなプロンプトが効くかはモデルのバージョンや effort の段階で十分に変わるので、「コードをちゃんとテストする」のような一般的な事柄について、こうした skill がこれほど多くのモデルと effort の段階で通用するようにするにはどうすればいいのか、自分にはわからない。GPT-5.5 から GPT-5.6 に変わるだけで自分の働き方は大きく変わった。GPT-5.5 でかなり信頼できたことの多くが、うまくいかなくなったか、信頼性がずっと下がったからだ(全体的な能力水準は上がっているように見えても)。GPT-5.5 をプロンプトするやり方で GPT-5.6 をプロンプトしないのと同じように、同じ skill 一式を使いたいとも思わないだろう。

AI ラボの中の人以外に、わざわざ skill の eval を回して、どのモデルと effort レベルで何が効くかを調べ、モデルと effort ごとに skill を組み合わせたセットを作る人がいるとは思わないし、AI ラボが競合の harness やモデルに最適化した skill を作ることも期待していない。だから汎用的な「テスト」skill のようなものについては懐疑的だ(前述のとおり、ここで試していないテスト skill をいくつか読んだけど、どれもここで試した既存の skill とまったく同じ失敗の仕方をしそうに見えた)。ただ、自分のユースケースに合い、よく使う特定の harness/モデル/effort に合わせた skill をいくつか持つことなら想像できる。

テストに限って言えば、特定のモデルが特定の effort レベルで特定の落とし穴にはまり、そこから引き離したいということはあっても、agent に渡したい汎用的なテストワークフローがあるわけではない。何をテストするか、どの品質水準を求めるか、どの次元で品質を担保するかによって変わるからだ。だから、agent が一般にやるべきテスト手順を並べた汎用テスト skill が欲しいとは思わない。これは多くの作業に当てはまることで、汎用的なやり方ではなくタスク固有のやり方でやってほしい。質問を投げかけてから agent に正しい指示を出す skill というのはあり得る。ただモデルの進歩が速いことを考えると、自分のために何かを作るなら、そんな skill を使い物になるまで調整する時間は割に合わない。agentic プロダクトを作っていて多くの人に使ってもらいたいなら話は別かもしれないが、僕が試した skill はここで試したものと同じ失敗の仕方をしていたので、大して効果のない skill を作るのはかなり簡単そうに見える。

skill が汎用的に役立つのは、agent にワークフローの実行方法や API/インターフェースとのやり取りの仕方を教えるような場合だろう。たとえば Sawyer Hood の、agent にウェブブラウザを操作させる skill がそうだ。試していないので推薦はしないが、読んだ限りでは、agent にウェブブラウザを操作させたいときに面倒をかなり減らしてくれそうな類のものに思える。ただ、あの skill 自体(とスクリプト)を読むと、そのスタイルはここで試したテスト skill とはかなり違う。

コメント/訂正/議論をくれた Max Bittker、Yossi Kreinin、Em Chu、Dennis Snell、@panoramic.blue、Jamie Brandon に感謝する。

追記:最近の記事何本かに、できるだけ速く半成品(ほとんど直しもレビューも整理もしていないもの)を書き出す実験をしているという注記を入れている。agent のおかげで実験をものすごい速さで回せるようになったからで、そうでもなければ何も書けなかった。この実験は実のところ、プログラミング言語のトークンコスト/正しさの実験の直後にやったものだが、先にインタプリタとネイティブコードコンパイラで正規表現エンジンを作った件ripgrep のフォーク版を走らせる実験(codex の ripgrep クエリにネイティブコードコンパイラを使う)、その他まだ書けていないいくつかのことを書きたいと思っていたので、書く時間が取れずにいる。1本30分で書き上げるのを目標にしてきたが、走らせた実験と書き出したものの量を比べると、まだかなり遅れている。

何年ものあいだ、こういう実験をして、変わった結果を何人かの友人に伝えて、そのまま次に進み、公の場で話すことはなかった。こうしたより速く(より低品質な)実験や記事について意見があれば聞かせてほしい(X Bsky Mastodon)。

付録:いくつかの反応

David R. MacIver はこう言っている

残念ながら @danluu は正しい。Hegel の skill は今のところかなりひどい。 多くの agent skill に共通する問題は、agent が agent skill を書くのが下手で、しかも全員(我々も含めて)が自分の skill を書くのに agent を使っていることだと思う。

共通の友人の話では、MacIver は(その後?)Hegel Skill の欠陥を確認するベンチマークを作り、おそらく skill を改善するか、Hegel のコメント/ドキュメントを調整して skill を不要にしようとしているらしい。この記事の成果が Hegel の改善された skill だけだったとしても、それは十分すごいことだと思う。前述のとおり、計測とベンチマークは過小評価されている。計測を一度公表するだけで、誰も知らなかった問題を指し示し、何らかの変化を後押しできることが多いからだ。格差がどこにあるかを示すだけで何かを動かした記事は、1本や2本ではない。これもその一つになれば嬉しい。

付録:agent のやらかし

ある分析のために agent に簡単な検索をさせたところ、perl プロセスを実行し、僕が kill するまで2時間20分走り続けた(こういうのは本当に自動で捕まえたい。かなりよくあることなので)。

サブ agent が比較的小さいファイル(44kB、1364行)に対して perl で正規表現検索をかけたのだが、その式は PCRE では退化していて、組み合わせ爆発レベルの計算量になっていた。ここで数分かけて組んだ FRE 正規表現エンジンで再実行してみたところ、マッチは0.7秒で完了した(Rust regex は0.6秒)。

agent が外で作業しているとき、何が起きるかは分からない。だが、これはそもそも起きるべきではなかった。理由は三つある。第一に、agent はこの種の組み合わせ爆発を引き起こす正規表現エンジンを呼び出すべきではない。より安全な正規表現エンジン(たとえばデフォルト設定の ripgrep)を使わない理由はない。第二に、あの式は間違っている。実際の正規表現は成功時に無意味な大きなキャプチャを返すだけで、やりたかったことをしていない。第三に、なぜサブ agent(あるいは harness)はサブ agent の終了時に自動でそれを殺さなかったのか。もちろん、サブ agent が走らせるものすべてをそう扱うべきではないが、agent はこうした暴走プロセスをよく放置する。

agent の残り物(メモリをリークする agent、容量を食う一時ビルド成果物など)を掃除する専用のプロセスは確かにある。だが、それは暴走した perl プロセスを探してはいない。これも追加すべきものの一つだが、間違いなく自分だけがこの問題に遭っているわけではない。この馬鹿げたツールをオープンソースにしてもいいとは思うが、主流の harness がこの問題を直せば obsolete になるのだから、公開したところで使う理由があるようには思えない。

付録:実験の詳細

素早く書き上げるため、この部分は省略する(すみません!)。各条件の分布は、言語が正確性と token コストにどう影響するかを調べたときに見たものと根本的に変わらない。どういうわけか、30 分でさっと書くつもりだったこの記事はすでに 1 万字近くになっており、これは明らかに 30 分で書ける量ではない(30 分で 1 万字なら毎分 300 字以上ということになる)。

一点指摘しておきたいのは、プログラミング言語についてのあの記事と同様、いくつかの事柄はかなり面白い/説得力のある結果に見えた(少なくとも、トップレベルの図だけを見て、際立ったものがあるかどうかを見るという観点では)が、よく見ると、agent に短いプロンプトを与えて実験を組ませたことによる実験ミスだったということだ。それらのミスを直すと、はるかに退屈なネガティブな結果が得られる——ただし、codex が役立つかもしれないと提案したいくつかの skill が逆効果に見えた、という部分を除いて。


実際の結果を見ると、以前と同様、すべての方法がほとんど効いていない。適していると思うだろう方法でさえそうだ。たとえば TLA+ は IMAP の状態と並行性に自然に合うので良い成績を出すと思うかもしれない。原理的には、プロトコルの多くの高レベルな部分をモデル化するのが得意なはずだ。しかし、Zstd の結果で見たように、agent は「Rust で実装の大部分を終わらせてから」、最初のプロンプトで使うように言われたツールを取り出す(80 の agent のうち、コードを書く前に実際に TLA+ を使ったのは 5 つだけで、75 は逆の順序だった)。どういうわけか、agent はほとんど常に、小さなメールボックスの変更をモデル化するために TLA+ を使うと決める。複数のオブザーバー、イベントキュー、UIDVALIDITY とメールボックスのエポック(epoch)といったものをモデル化するために TLA+ を使った agent はいない——そして agent はこれらを実装する際に多くのミスを犯した。代わりに agent がモデル化したのは CONDSTORE や QRESYNC といったもので、Default 条件はこれらの箇所でテスト通過率が 99.6% を超えており、TLA+ の agent はこれらをモデル化したにもかかわらず通過率はむしろ低かった。

このために試したすべての評価に共通点が一つある。それらはすべて RFC であり、RFC は極めて非現実的だ。だがその「非現実的さ」とは、仕様が、ほとんどすべてのプログラマーが agent に何かを実装させるときに与える仕様よりもはるかに明確で、詳細で、曖昧さが少ないという点にある。ここで見た失敗モードは、現実世界のほとんどの問題で同じか、それ以上に悪いと予想している。

RL 環境やランダム化テストに関連する研究がまったくないわけではない。たとえばこの論文もある。だが、具体的な指示が何もないときにモデルが{PBT, fuzzing, randomized testing, etc.}のどれについてもこれほど無力だという事実を見るかぎり、こうした研究が大手 AI ラボのモデル訓練に本気で取り入れられているようには思えない。

人類学が人間とどうポーカーを打つかという話なら、こうした概念を使うのは今でも妥当だろう。カジュアルな対局では、人間はソルバーの手順を研究して何が最適に近いかを理解できるほどにはならないのが普通だからだ。しかし、コーディングエージェントが何を得意とし何を苦手とするかという話になると、カクテルパーティー的な思いつきを振りまく理由は私には見当たらない。実験を回して何が効くかを確かめればいいのだし、これまでに回された実験は、X が良いとか悪いとかいった、あちこちで吹聴されている抽象的な根拠を支持していない。

Footnotes

  1. 全体として、IMAP の結果はそれほど面白くないと思う。もっと面白いかもしれないと思って試したのは、より「ビジネスロジック」的な問題に見えたからだが、言語を調べたときに見たあの pandoc 評価のように大規模で高コストではなかった——あの評価では、効果の薄いいくつかの言語では 1 回の実行に 1000 ドルを超える API コストがかかり、Rust でも 1 回の実行に 700 ドルかけてテスト通過率 30% にしかならなかった(あの評価で使った指標はずっと難しいことに注意:満点に達した実行の割合だ)。IMAP 評価では、満点を取った実行は 1 回だけだった(「Make no mistakes」、medium 档)。全体として、いくつかのロジックの断片は明らかに 5.6 Sol には難しすぎて一度で正しくできない。一つの見方としては、Make no mistakes が medium 档で 2.5% を出し、他のすべての条件の 0% を圧倒したということだ。ついに、「Make no mistakes」が効くという証拠が出た!

  2. 面白いことに、ChatGPT にこれを確認させたところ、RL 環境で agent を訓練してテストさせたという論文があるとして、この段落は間違っていると言われ、論文を三つリンクされた——そしてそれらの論文はまさに、ほとんどのプログラマーのように単体テストを書くよう agent を訓練することで、agent を悪いテストを書くように訓練していた。それはまさに、今日見ている LLM の悪いテストを生むと私が予想するやり方だ:より効果的なテスト技法を理解している人に agent を導かせる必要がある。正確性を重視する複数の独立したサブ領域で、人々はそれぞれ関連するいくつかの技法に収束しており、それらは概して小さな単体テストを書くこととは逆だ。もちろん、人々が正確性を真剣に扱うときにやることとは逆のことをするよう agent を訓練しても、良い正確性にはつながらないだろう。

  3. 自国語の優位性を主張する人々の根拠が、モデルがずっと高性能になってから正しくなる可能性はある。ただ、そうなるはずだと考えられる理由は何も見当たらない。私が推測するなら、ポーカーに近い形になる。何が最も効くかについてはさまざまな説があったが、シミュレーションが強力になり、コンピュータが多くの状況で人間を打ち負かすようになると、それらはすべて覆った。たとえば「レンジアドバンテージ」のような、ソルバーの最適戦略と一致するとよく言われる「現代的な」概念でさえ、よく見ればソルバーのデータ由来ですらない。あれはカクテルパーティーで話すのにうってつけの概念にすぎず、わかりやすく、もっともらしく聞こえるだけだ。

出典: danluu.com← ホームへ戻る