エージェント評価 — ベンチとハーネスの作法
エージェントのスコアは「モデルの点」ではなく「モデル+ハーネス+環境+採点規則の点」です。SWE-benchの1件がどんな部品でできているか、手数が伸びると一手の差がなぜ桁で効くか、リポジトリやネットワークから答えが漏れる四つの経路、そして部分点を安全に設計する条件までを1から扱います。
要点
- エージェントのスコアは、モデル・ハーネス・環境・採点規則の四点セットで決まる数字である。
- SWE-benchは「直っている」をFAIL_TO_PASS、「壊していない」をPASS_TO_PASSで測り、両方満たしたときだけ1点とする。
- 一手あたり99%で50手なら完走率は0.61、95%なら0.077で、4ポイントの差が完走率では8倍近い差になる。
- 汚染はgit履歴・ネットワーク・採点用テスト・事前学習の四経路から入り、前の三つは自分で塞げる。
- 部分点は、回帰ゲートを二値で残し、中間目標に置き、二値のresolvedと併記する条件でのみ安全である。
SWE-bench: Can Language Models Resolve Real-World GitHub Issues?
一次資料 — この記事の根拠
この解説の公開 2026-08-27
SWE-bench: Can Language Models Resolve Real-World GitHub Issues?arXiv:2310.06770論文ページ·PDFSWE-agent: Agent-Computer Interfaces Enable Automated Software EngineeringarXiv:2405.15793論文ページ·PDF
「SWE-bench で50%」は何を言っているのか
発表資料に「SWE-bench Verified 50%」と書いてあったとします。素直に読めば「実際のGitHubのバグの半分を直せる」です。ところがこの数字は、モデル単体の性能ではありません。モデル・足回り(ハーネス)・作業環境・採点規則の四点セットで初めて決まる数字で、そのうち三つは棒グラフには出てきません。
比喩は採用試験です。四択の筆記で満点を取る人と、リポジトリを渡されて「このバグ直しておいて」で結果を出す人は、別の能力を測られています。後者で効くのは、テストの回し方を知っているか、直したついでに別の機能を壊さないか、詰まったときに引き返せるか。エージェント評価が測ろうとしているのはこの後者で、だから測り方そのものが設計対象になります。
ベンチマークを読むときの三つの問い — 何を測ったか、どう採点したか、問題が漏れていないか — は、エージェントでもそのまま必要です。その上に、これから見る三つが積み増しになります。
一問一答と違う三つのこと
1. 環境がある。 四択問題の入力は文字列ですが、エージェントの入力はリポジトリであり、シェルであり、ファイルシステムです。しかも入力が大きいだけでなく、エージェントは入力を書き換えられます。極端な話、落ちているテストを削除して「全部通りました」と報告できてしまう。採点する側は、環境の初期状態と最終状態の両方を管理しなければなりません。
2. 手数がある。 一発の生成なら成否は1回の勝負ですが、エージェントは何十手も打ちます。一手あたりの成功率を 、必要な手数を とすると、完走できる確率はこうなります。
は 手目で破綻しない確率、 は最後まで行くのに必要な手数、 は完走する確率です。要するに、一手ごとの生存率を全部掛け算したものが完走率という、当たり前の式です。
当たり前でなくなるのは数を入れたときです。一手99%で50手なら 、95%なら 。一手あたり4ポイントの差が、完走率では8倍近い差になります。逆側から見ると、完走率という一つの数字だけを眺めていても、どの一手が足りていないのかは永遠に分かりません。
3. 副作用がある。 四択の採点は答え合わせで終わりますが、エージェントは外に書き込みます。ファイルを消し、コマンドを叩き、APIを呼ぶ。評価環境が本番の認証情報を持っていた、という事故は採点以前の問題として起きます。エージェントの評価環境は、壊れてよい使い捨ての箱として作るのが前提です。
SWE-benchの1件を開ける
SWE-bench(Jimenez et al., 2023)は、実在のGitHub issueと、それを閉じた実際の修正コミットから機械的に作られたベンチマークです。12個のPythonリポジトリから集めた2,294件のタスクからなり、1件はこんな部品でできています。
{
"repo": "django/django",
"base_commit": "…", // 修正が入る直前の状態。ここから作業を始める
"problem_statement": "…", // issue本文。モデルに渡してよいのは原則ここだけ
"patch": "…", // 人間が実際に書いた修正(正解・モデルには見せない)
"test_patch": "…", // その修正と一緒に入ったテスト
"FAIL_TO_PASS": ["…"], // 修正前は落ち、修正後に通るべきテスト
"PASS_TO_PASS": ["…"] // 修正の前後どちらでも通り続けるべきテスト
}
設計の勘所は、正解を「人間のパッチとの一致」で測っていないことです。同じバグの直し方は何通りもあるので、文字列で比べれば正しい修正も不正解になります。代わりに置かれたのが実行による検証で、FAIL_TO_PASS が「直っていること」を、PASS_TO_PASS が「壊していないこと」を担当します。この二本立てが、動くけれど別の機能を巻き添えにするパッチを弾きます。
派生セットの名前も押さえておきます。Lite(300件)は評価を回しやすいものを選び直したサブセット、Verified(500件)は人手で1件ずつ確認し、issue本文だけでは仕様が定まらない問題や、テストが厳しすぎて正しい修正でも落ちる問題を除いたものです。数字を比べるときは、まずどのセットの数字かを確認します。母集団が違えば、同じ「50%」は別の量です。
resolved の定義は、論理積である
SWE-benchの主指標 resolved(解決率)は、1件ごとに0か1です。
と はさきほどの FAIL_TO_PASS と PASS_TO_PASS、 は「そのすべてについて成り立つ」、 はテスト が通ったかどうかです。要するに、直すべきテストが全部通り、かつ壊してはいけないテストが1つも落ちなかったときだけ1点、それ以外は0点。
この二値性は評価としては誠実です。八割方直った修正は、利用者から見れば直っていません。ただし開発の計器としては粗すぎて、「惜しかった」と「そもそも環境構築で終わった」が同じ0点になります。ここが部分点の設計動機で、後で扱います。
ハーネスがスコアの半分を決める
同じモデルでも、周辺の作りで数字は動きます。少なくとも次の四つが効きます。
- 情報の与え方: issue本文だけを渡すのか、検索で関連ファイルを添えるのか、「正解パッチが触るファイル」を最初から教えるのか。最後のoracle設定は上限値の測定であって、実運用で得られる数字ではありません。
- 道具の形: SWE-agent(Yang et al., 2024)は、ファイルを固定行数の窓で見せる、編集のたびに構文チェックの結果を返す、といったエージェント向けインターフェースの作り込みだけで解決率が変わることを示しました。モデルを替えなくても、道具を替えれば点は動きます。
- 予算: 最大ステップ数、トークン上限、コマンドのタイムアウト、試行回数。
- 採点の細部: パッチが適用できなかったときの扱い、テスト実行の打ち切り時間、不安定なテストの扱い。
だから正確には「モデルAは50%」ではなく「モデルA+ハーネスB+設定Cで50%」です。比較するときは一度に一つだけ動かす、という実験の基本がそのまま効きます。
そしてここに、公開ベンチ特有の力学が働きます。300件や500件を見ながらプロンプトとツールを何十回も調整すれば、その集合には効くが自社のリポジトリでは効かない作り込みが積もります。一回ごとの調整は正当な改善で、悪意はどこにもありません。それでも結果は過学習です。
過学習と評価設計の検証データへの過学習と同じ構造なので、防ぎ方も同じです。調整に使う集合と、最終判断に使う集合を分けるしかありません。
環境そのものが答えを持っている
エージェント評価の汚染は、学習データだけの話ではありません。作業環境の中に答えが置いてあることがあります。経路は主に四つです。
- git履歴:
base_commitにチェックアウトしても、cloneのオブジェクトに後続のコミットが残っていればgit log --allで正解パッチが読めます。履歴を切る、リモートとタグを外す、が最低条件です。 - ネットワーク: issue番号で検索すれば、それを閉じたPRが出てきます。ネット越しの調査を許すなら、そのスコアは「検索がうまいか」の測定に化けます。既定は遮断(コンテナならネットワークを無効にする)で、必要なら許可した事実を数字と一緒に書きます。
- 採点用テスト:
test_patchを最初から置いておくと、期待される関数名も戻り値もそのまま読めます。だから採点用テストは作業が終わったあとに入れます。さらに採点の直前で、テストファイルを正本の内容に戻してからテストパッチを当てる。エージェントがテストを書き換えて通した場合を、機械的に無効化するためです。 - 事前学習: 公開リポジトリの修正コミットはウェブ上に存在し、モデルの学習データに入りえます。対策はカットオフより後のissueで作り直すこと。同じ設計で問題を作り直して測り直す、という評価一般の常套手段と同じ発想です。
疑う手がかりは暗記の症状です。issue本文を言い換えたら急に解けなくなる、変数名を変えたら落ちる、パッチは合っているのに説明が食い違う。1と2は自分で塞げるので、まずそこから確認します。
部分点をどう設計するか
二値採点は正しいのですが、開発中は分散が大きく、進捗も見えません。そこで部分点を入れるなら、形はこうなります。
は中身が成り立てば1、そうでなければ0。 は FAIL_TO_PASS 側の各テスト、 はその重みです。要するに、「既存を壊していない」を通行条件にした上で、直せたテストの割合を点にするという形です。
設計の原則は四つあります。
- ゲートは二値のままにする。回帰に部分点を与えると、「機能Aを直して機能Bを壊した」が加点になります。
- 部分点は中間目標に置く。環境構築が通った、再現テストが書けた、該当ファイルに到達した、といった観測できる通過点です。逆に、「正解パッチとの文字列一致度」のような結果と無関係な量に点をつけてはいけません。似た見た目のパッチを書く方向に最適化が働きます。
- 二値の resolved を必ず併記する。部分点は開発用の計器であって、対外的な成績ではありません。両方出せば、部分点だけが伸びて resolved が動かない状態にも気づけます。
- 公開すると最適化される。どのテストに何点付くかを公開した瞬間、そのテストだけを通す実装が現れます。テストの一部を隠す、定期的に入れ替える、といった運用が要ります。
1回の実行では足りない
エージェントの実行は、温度0に設定しても再現しません。推論側のバッチ構成、外部コマンドの応答時間、環境の時刻依存。同じ入力から違う軌跡が出るのが常態です。したがって1回の実行で出た差は、実力差ではなく揺らぎかもしれません。
- pass@k(k回のうち1回でも成功)は、k個の中から正解を選ぶ仕組みがある場合にだけ製品の数字になります。選別できないなら上限の測定です。
- pass^k(k回すべて成功)は信頼性の指標です。τ-benchはこの形を採っています。完走率が0.5のエージェントに同じ仕事を5回頼んで全部うまくいく確率は、素朴に考えれば 。利用者が体感するのはこちらです。
- 標本誤差を出す。500件のベンチで3ポイント差は、多くの場合ノイズの範囲です。何件あれば信じてよいかの計算はRAG評価の実務の式がそのまま使えます。
- 不安定なテストを切り分ける。実行順序や時刻に依存するテストは、正しいパッチを落とします。ここで効くのが正解パッチをハーネスに通す対照実験で、これが100%にならないなら、落ちているのはモデルではなくハーネスです。
- コストを併記する。1件あたりのトークン、実時間、ステップ上限。予算を10倍にして+3ポイントは、勝ちとは呼べません。
コードで書く: 最小の採点ハーネス
これまでの原則を、そのまま採点関数にするとこうなります。
def score(inst, model_patch):
repo = checkout(inst.repo, inst.base_commit) # 履歴もネットも切った使い捨ての複製
if not apply(repo, model_patch):
return 0.0 # 当たらないパッチは0点。例外にしない
restore(repo, inst.test_files) # エージェントによるテスト改変を捨てる
apply(repo, inst.test_patch) # 採点用テストは最後に入れる
r = run_tests(repo, timeout=900) # 打ち切り時間も採点規則の一部
if any(r[t] != "pass" for t in inst.PASS_TO_PASS):
return 0.0 # 回帰ゲート: 壊したら無条件で0
return mean(r[t] == "pass" for t in inst.FAIL_TO_PASS)
resolved = score(inst, patch) == 1.0 # 対外的な数字はこちら
順番に意味があります。restore を apply(test_patch) より前に置くことで漏洩と改竄の両方を塞ぎ、回帰ゲートを平均より前に置くことで部分点が回帰を隠すのを防いでいます。逆にすると、どちらも静かに壊れます。
現場ではこう使う
エージェント評価を書くのは、社内にコーディングエージェントを入れるか判断する人、エージェント基盤を作っている開発者、そしてベンダーやモデルの乗り換えを決める人です。使う場面は、モデル差し替えの意思決定、リリース前の回帰確認、そして「うちのリポジトリで本当に効くのか」の検証です。
実際に触る対象は、ベンチ側では FAIL_TO_PASS / PASS_TO_PASS、Lite と Verified の選択、1件ごとのDockerイメージのタグ固定。ハーネス側では最大ステップ数、トークン上限、テストのタイムアウト、コンテナのネットワーク無効化、温度と乱数種。運用側では、全軌跡(コマンドと出力)のログ保存と、正解パッチによる対照実行です。ログを捨てると、後から「なぜ落ちたか」を一件も説明できなくなります。
事故になる落とし穴は、だいたい決まった顔ぶれです。ハーネスを直した直後に、過去の数字と並べて比較する(比較不能になっています)。ネットワークを開けたまま測ってスコアが跳ねる(検索能力を測っています)。テスト改変を検知していない。1回の実行で3ポイント差を報告する。部分点を最適化目標にしてテスト特化の実装を育てる。不安定なテストの失敗をモデルの失敗として計上する。コストを書かない。
面接でも実務でも問われるのは、たいてい同じ一問です。「このエージェントはSWE-benchで50%だが、うちのリポジトリでは何%か」。答えの筋道は三つに分けます。分布のずれ(SWE-benchはPythonの著名OSSで、テストが揃っていることが前提。社内コードのテスト密度は違う)、汚染(そのOSSの修正は学習済みかもしれない)、ハーネス差(公開の数字は作り込まれた足回りの数字)。だから答えは「測ってみないと分からない、まず社内のissueを30〜50件でベンチ化する」になります。この自前セットの作り方は、LLMエージェントを1から解説で見たループの失敗モードを、そのまま観測項目に変換していくのが早道です。
まとめ
- エージェントのスコアは、モデル・ハーネス・環境・採点規則の四点セットで決まる。三つを固定せずに比較すると何も言えない
- SWE-benchは「直っている」を
FAIL_TO_PASS、「壊していない」をPASS_TO_PASSで測り、両方満たしたときだけ1点にする - 汚染はgit履歴・ネットワーク・採点用テスト・事前学習の四経路から入る。前の三つは自分で塞げる
- 部分点は、回帰ゲートを二値で残し、中間目標に置き、二値スコアと併記する条件でのみ安全
- 1回の実行では足りない。pass^k・標本誤差・正解パッチによる対照実行・コストを、スコアと同じ場所に書く
コメント
コメントにはログインが必要です