🧐
【論文解説】大規模で依存関係も複雑なコードもLLMでリファクタリングできるのか
Title: RefactorBench: Evaluating Stateful Reasoning in Language Agents Through Code
Link: http://arxiv.org/abs/2503.07832
Code: https://github.com/microsoft/RefactorBench
Intro / Conclusion
- LLMは多ファイル間の依存関係管理や複雑な指示への対応が難しい。一方,これを検証するための体系的なベンチマークは不足。
- 100の大規模な複数ファイルにまたがるリファクタリングタスクを手動で作成。各タスクは複雑な依存関係を考慮する必要がある。
- ASE (Automated Software Engineering) システムがより現実的で複雑なタスク (複数のファイルにまたがるマルチリファクタリング) を公平に評価するためのRefactorBenchを提案。
RefactorBench
RefactorBenchはマルチファイルリファクタリングタスクのベンチマークであり,各タスクの目標は指定されたリファクタ規則に従うようにリポジトリを変更するパッチを生成することにある。
以下は本ベンチマークの構成プロセス。
- GPT-4oを活用して対象リポジトリ内のファイルを1つずつモデルに提示する。このとき4oには「対象となるファイルの全内容」「”リファクタリング”という本で提示されているリファクタリング事例」を与える。与えられたファイルとリファクタリング事例を元に具体的な行番号と改善内容の提案を生成。生成されたリファクタリング提案を人間が手動で精査する。これによりリファクタリング候補と対象ファイルおよび修正箇所をissueとして設定する。
- 経験豊富なプログラマが,前のstepでGPT-4oによって生成されたリファクタリング提案を元にそれを具体化して質の高い手作業による修正案を作成する(修正案は「リファクタリング」という本の原則に忠実に従う)。 各種修正案が言語モデルによって実現できるかを確認して修正ができた場合は参照解として採用。
- 以上で確認された主要な修正箇所毎に1つの新しいユニットテストが作成される。ASTベースのテストセットを作成しているため,コードの構造や意味の正しさが正確。テストを反復的に繰り返すことで,各タスクが要求する最小限のテストセットが完成。※各タスクを平均6個程度のサブタスクに分解
- 各タスクについてエージェントが具体的に何を行うべきかを伝えるための指示文を作成する (2と3が完成した後に作成)。まず簡潔でありながら網羅的なタスクの要約文を基本指示文として専門家が作成。そして基本指示文をfew-shot例として”曖昧/抽象的な指示”と”詳細で具体的な指示”を作成する。
Experiments
- 評価はSWE-agentを用いる。本手法はSWE-benchによる評価でOSSとして最高性能を記録している。
また,本手法は単一エージェント方式のPOMDP構造を忠実に実装した構成であるため,原因の切り分けがしやすい。
- 公開されているエージェントは,テスト結果を赤→緑にすることを目的としたプロンプトで学習/評価されている。つまり,「GitHub Issue に添付されたバグ再現手順 → テスト失敗 → パッチを当てる」という典型パターンのバグ修正に最適化されすぎている。
- 著者らはプロンプトを全面カスタムし,バグ修正ではなく,コード整理タスクと明示。
Baseline
- SWE-agentという自動リファクタリングエージェントを用いる。ユーザーが本当にローカルでコードを書き替えている状態を再現するために,コンテナ化された環境でターゲットのリポジトリをリファクタリングする。また,全てのタスクに対して1インスタンスあたり$10.002以内というコスト上限を設定。
- それぞれのタスクに対し仕様通り(テストケース通り)にリファクタリングできたケースの割合を計測。以下はプロンプト毎の成功率 (使用モデルはGPT-4)。
- lazy (最小限の指示): 12%
- base (基本的な指示): 18%
- descriptive (詳細な指示): 27% ※具体的で丁寧な指示を与えるほど正しくリファクタリングできる。
- claude-3.5-sonnetで「descriptive」なプロンプトを使うと成功率35%でGPT-4よりもやや良い。
- プロの開発者が「base」指示且つ1タスク5分以内でチャレンジすると87%のタスクで成功。このことからLLMベースのリファクタリングは現時点で人間よりも劣ることが分かる。
- GPT-4による成功したケースの平均アクション(コード変更やテスト実行など1つ1つの操作)数は45.8回で,一発で正解に辿り着けず,何度も試行錯誤していることが分かる。
- さらに,ランダムに3つのタスクを選び,そのタスクの説明を1つの長い指示としてまとめて「1つの複合タスク」としてSWE-agentに解かせる実験を実施。(実際の開発現場では1つ1つの単純なリファクタリングではなく,複数の修正を同時に計画/実行する必要があることが多いためより現実的なシナリオを想定した実験と言える)
- 結果,単独では解決できていたタスクが複合されると1つも解決できなくなる。
- 長期計画(複数のリファクタリングを組み合わせて全体をうまく完了させる能力 例: Aの修正→Bの修正→Cの修正を正しい順序でミスなくやりきる力)は苦手。
Analysis
- RefactorBenchでLLMエージェントを動かした結果,典型的な3つの失敗モードがあることが分かった。失敗モードの自動判定にはGPT-4を使用した。
エージェントは関連する場所を見つけられず,該当箇所を変更できない。
- どのファイルを直せばよいかという情報を明示的にエージェントに渡しているにも関わらず約44%のテストで修正が必要なファイルを一切編集しない。(過去の研究だと修正箇所は見つけるが,修正内容が間違っているというパターンが主流であった)
- 6ファイル以上を同時に修正しないといけないタスクはどの手法でも1つも成功しなかった。同時編集するファイル数が多いと全く歯が立たない。ファイル数や依存関係が増えるとLLMの性能が落ちることは先行研究でも報告されている。
- LLMエージェントはうまく探索したり,過去のアクションを活用して次のアクションに活かすのが苦手と考えられる。
エージェントは一時的にエラーのある中間状態を経由しないと正解に辿り着けない。
- タスクの多くは1つの箇所の編集だけでは正解にならず,複数の場所を順序立てて修正しないといけないため,途中の編集で一時的にコード全体がエラー状態になることがよくある。
- 78.4%の失敗がコードの編集ステップで起こっており,「編集の順番ややり方」に失敗原因が集中。
- Lint(自動構文チェックや自動エラー検出)を使って、「エラーが出る編集」を拒否してしまうと、必要な中間ステップを踏めず複雑なタスクが解けなくなる。つまり,「常にエラーなししか許さない」ようなガードレール(制約)をかけると、人間らしい柔軟なタスク解決能力が阻害されると考えられる。
エージェントはコンテキスト肥大化やそれに伴う目的の見失いにより失敗する。
- エージェントが繰り返しエラー対応やコマンド送信を繰り返すとコンテキスト (やり取り履歴) が肥大化する。履歴が長くなるほど重要情報が抜ける。
- フィードバックループやエラーリカバリーにハマると本来のゴールを見失い途中の課題が新たな目的になってしまう。→In-Context Reward Hacking (ICRH)
Discussion