Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

エージェント型環境とベンチマーク

動機:なぜエージェントに環境が必要なのか

会話型言語モデルの評価は、原理的には単純です。プロンプトを提示し、応答を集め、参照回答または人間の判断でスコアを付けます。エージェントの評価は根本的に異なります。エージェントは世界で行動し、結果を観察し、複数ステップの系列にわたって振る舞いを適応させなければなりません。単一の応答ではこれを捉えられず、構造化された環境だけが可能にします。

範囲。 ここでの環境は強化学習の意味、すなわちエージェントが訓練や評価のために相互作用する世界を指します。エージェントをサービング時にホストする本番インフラストラクチャ(ハーネス、オーケストレータ)ではありません。実行サンドボックスはこのような環境を可能にするため、ここで扱いますが、エージェントハーネス自体は第18章で説明しています。

Important

チャットボットとエージェントの評価ギャップ

チャットボット評価 は、単一生成の品質、すなわち流暢さ、事実性、有用性を測定します。 エージェント評価方策の品質を測定します。多様で長期的なタスクにわたり、エージェントは確実に目標を達成できるでしょうか。これは単なる量的な差ではなく、異なるインフラストラクチャを必要とする差です。

専用のエージェント型環境が必要になる理由は3つあります。

安全な探索

現実世界のシステム、本番データベース、稼働中のウェブサイト、金融APIは、訓練中のエージェントによる探索的な振る舞いを受け止められません。サンドボックス化された環境は忠実な複製を提供し、エージェントが不可逆な被害を起こさずに失敗、復旧、学習できます。セキュリティ分離(Dockerコンテナやネットワーク制限付きVMなど)は任意ではなく、第一級の設計要件です。

再現可能な評価

ベンチマークでは、すべてのエージェントが同じ条件で同じタスクに直面する必要があります。ある研究室で報告された結果を別の研究室で再現できるよう、環境は要求に応じて決定論的に動作し、バージョン管理され、配布可能でなければなりません。この性質がなかったため、これまでエージェントのベンチマークは比較が困難でした。

カリキュラム学習

難しいタスクでエージェントをゼロから訓練するのは、サンプル効率が悪いものです。エージェントの改善に応じてタスクの複雑さを段階的に増やす難易度カリキュラムを提供する環境は、目標性能に達するまでに必要な環境との相互作用数を大幅に減らします。これは人間の学習方法にも似ています。全体の習得に先立って、サブスキルを習得します。

Tip

LLMのRL「ジム」としての環境

OpenAI Gym(Brockman et al. 2016)がRLアルゴリズムとシミュレーション制御タスクのインターフェースを標準化したように、エージェント型環境はLLMベースのエージェントと、エージェントが解くべき多様なタスクとのインターフェースを標準化します。対応関係は明確です。reset() は新しいエピソードを初期化し、step(action) は世界を進めて観察と報酬を返し、render() は現在の状態を人間が読める形で表示します。

環境設計の原則

適切に設計されたエージェント型環境は、直交する4つの設計軸、すなわち観測空間アクション空間報酬信号エピソード構造を公開します。それぞれを正しく設計することが必要であり、4つすべてを同時に正しく設計することが環境エンジニアリングの技術です。

観測空間の設計

観測とは、エージェントが各ステップで見るものです。LLMベースのエージェントでは、観測はほぼ常にテキストとして表示されますが、元の素材は大きく異なります。

  • 純粋なテキスト: ターミナル出力、ファイル内容、API応答、エラーメッセージ。あらゆるLLMと最も互換性がありますが、空間的・視覚的構造を失います。

  • 構造化(JSON/XML): 機械可読な状態表現。正確なグラウンディングを可能にしますが、エージェントは文章を読むのではなく構造を解析する必要があります。

  • マルチモーダル: スクリーンショット、アクセシビリティツリー、レンダリング済みHTML。GUIやウェブタスクに必要であり、視覚能力を持つモデルまたは別の知覚モジュールを要します。

  • ハイブリッド: スクリーンショットとアクセシビリティツリーの組み合わせ(OSWorldとVisualWebArenaで使用)は、視覚的コンテキストと構造化された要素識別子の両方を与え、2つのモダリティの長所を組み合わせます。

Warning

観測のリーク

よくある設計ミスは、エージェントがアクセスできてはいけない情報を観測に含めることです。例えば、正解、報酬値、将来のタスクステップなどです。観測のリークはベンチマークスコアを水増しし、その情報が存在しない現実環境に配置されたときに、壊滅的に失敗するエージェントを生みます。

アクション空間の設計

アクション空間は、エージェントができることを定義します。LLMエージェントでは、アクションは通常、環境が解析して実行するテキスト文字列です。一般的なアクション種別には次があります。

  • ツール呼び出し: 外部関数(検索、計算機、カレンダー)の構造化された呼び出し。JSONやXMLのFunction Calling構文で表すことが多いものです。

  • コード実行: エージェントがコードを書き、サンドボックスで実行します。stdout/stderrが次の観測として返ります。最も表現力の高いアクション種別です。

  • API相互作用: ウェブサービスへのHTTPリクエスト、データベースクエリ、シェルコマンド。

  • GUIアクション: click(x,y)type("text")scroll(direction)key("Enter")。コンピュータ利用環境で使います。

  • 自然言語: 別のエージェント、人間、またはサブタスク計画器に向けた自由形式のテキスト。

報酬信号の設計

報酬設計は環境エンジニアリングで最も難しい部分です。報酬は次を満たさなければなりません。

  1. 整合している: 高い報酬が、表面的な代理指標ではなく、真のタスク完了に対応すること。

  2. 学習可能である: エージェントが進捗を出せる程度に信号が密であること。長期的タスクの純粋な疎報酬は、追加のシェーピングなしでは学習できないことが多くあります。

  3. 改ざん耐性がある: エージェントがタスクを実際に完了せず、高い報酬だけを得られないこと(報酬ハッキングの防止)。

報酬種別長所短所
疎(最後に0/1)整合し、ハックしにくい学習が難しい
密(ステップ単位)学習しやすいシェーピングのアーティファクトが生じやすい
内在的(好奇心)探索を促すタスクから乖離する可能性がある
LLM-as-judge柔軟でニュアンスを扱える高価で一貫性がない
実行ベース正解そのもの検証可能なタスクのみ

エージェント型環境の報酬信号の種別とトレードオフ。

エピソード構造

エピソードは複数の方法で構成できます。

  • 固定長: エージェントが正確に\(T\)ステップ実行します。実装は単純ですが、すでに解決したタスクにも計算資源を使います。

  • 早期終了: エージェントが完了を通知するか、終端状態に到達するとエピソードが終了します。より効率的ですが、信頼できる終了検出器が必要です。

  • オープンエンド: 固定のホライズンを持たず、資源予算(トークン、API呼び出し、実時間)を使い切るまでエージェントが動作します。実際の配置に最も近い一方、評価が最も難しくなります。

適応的なエピソード長と早期終了

近年の研究は、訓練開始前にエピソード長を固定しなければならないという前提に疑問を呈しています。

  • ホライズンのカリキュラム。 AELA(Yoo and Shin 2025)は短いエピソードから始め、方策エントロピーの収束で測ったエージェントの能力向上に応じてホライズンを徐々に延ばします。初期の短いエピソードにより、訓練サンプルごとにより多様な初期状態を経験できます。

  • RLペナルティとしての切り捨て。 DLER(Z. Liu et al. 2025)は、最も単純な長さ制御であるハードな切り捨てが、バッチ単位の報酬正規化と動的サンプリングを組み合わせれば推論モデルでうまく機能することを示します。これにより、途中で打ち切ったロールアウトから報酬信号を失うことを避けます。

  • 学習された停止。 固定予算ではなく、モデル自身がいつ推論を止めるかを学習できます。Q. Liu et al. 2025は3つの戦略を提案しています。連続する推論ステップが同じ回答に収束したら止める、思考終了トークンの確率を高める、隠れ状態の活性値で軽量分類器を訓練して最適な停止点を予測する、の3つです。

  • 部分ロールアウトの再利用。 APRIL(Mei et al. 2025)はロールアウト要求を多めに発行し、目標バッチ数に達した時点で終了します。不完全な応答は、後続ステップでウォームスタート用のプレフィックスとして再利用され、少数の遅いサンプルがバッチ全体を止めるロングテールの停滞をなくします(スループットが20〜35%向上)。TLT(Hu et al. 2025)は、遅延サンプルを投機的デコードする適応的なドラフトモデルをオンザフライで訓練し、同じボトルネックに対処します(エンドツーエンドで1.7\(\times\)高速化、損失なし)。

難易度カリキュラムと適応的環境

静的ベンチマークはエージェント能力の固定されたスナップショットを測定します。適応的環境はさらに進み、エージェントの性能をオンラインで監視し、エージェントを「発達の最近接領域」に保つようタスク難易度を調整します。学習できる程度に難しく、ときどき成功できる程度に易しい領域です。技法には次があります。

  • 手続き的生成: パラメータ化された分布からタスクをサンプリングし、最近の成功率に基づいて難易度パラメータを調整します。Prioritized Level Replay(Jiang et al. 2021)は、生成された各レベルを推定学習可能性(GAEの大きさなど)でスコアリングし、価値の高いレベルをより頻繁に再生します。

  • セルフプレイ/敵対的環境設計: PAIRED(Dennis et al. 2020)は、主人公エージェントと敵対エージェントの間の後悔を最大化する環境を提案するよう敵対者を訓練します。手作業で難易度スケジュールを設計せず、複雑さが増す自然なカリキュラムを生成します。

  • 後知恵による再ラベル付け: 失敗した軌跡を、エージェントが実際に達成した目標で再ラベル付けし、失敗からも学習信号を与えます(Hindsight Experience Replay、HER)(Andrychowicz et al. 2017)。

  • LLM向け難易度ターゲット型データ選択: RLVR訓練では、すべての問題が同じ信号を与えるわけではありません。近年の研究は、中程度の難易度、すなわちモデルが約30〜70%の割合で解く問題を優先します。これらは最大の勾配情報を与えます(Wang et al. 2025)。ADCL(X. Liu et al. 2025)はモデルの改善に応じて定期的に難易度を再推定し、古くなったカリキュラムを避けます。

エージェント型環境の種類

コード実行サンドボックス

LLMにとって最も基本的なエージェント型環境は、コード実行サンドボックスです。エージェントがコードを書き、サンドボックスが実行し、出力を返します。この単純なループが、現実世界のエージェント配置の驚くほど大きな割合を支えています。

Dockerベースの分離 が最も一般的なアプローチです。各エピソードで既知のイメージから新しいコンテナを起動し、その中でエージェントのコードを実行し、エピソード終了時にコンテナを破棄します。ネットワークアクセス、ファイルシステムへの書き込み、プロセス生成はすべてコンテナレベルで制御できます。1

E2B (Environments to Benchmarks)2はマネージドクラウドサンドボックスAPIを提供します。エージェントがHTTP経由でコードを送り、E2Bが200ms未満で起動する分離済みFirecracker microVMで実行し、stdout/stderrを返します。E2Bがコンテナのライフサイクル管理というインフラストラクチャの複雑さを処理するため、エージェント訓練ループへ容易に統合できます。

Modal 3は、より強力なGPUサポートを備えた同様のマネージド実行モデルを提供し、タスクの一部としてMLワークロードを実行する必要があるエージェントに適しています。

Warning

サンドボックス脱出とセキュリティ

コード実行サンドボックスは主要な攻撃対象です。十分な能力を持つエージェント(またはプロンプトインジェクションされたペイロード)は、カーネルエクスプロイト、ネットワーク経由の持ち出し、リソース枯渇によってサンドボックスから脱出しようとする可能性があります。多層防御が不可欠です。コンテナ分離をseccompプロファイル、読み取り専用ルートファイルシステム、ネットワーク外向き通信のフィルタリング、CPU/メモリcgroupと組み合わせます。エージェント生成コードをホストレベルの権限で実行してはいけません。

ウェブ環境

ウェブ環境はエージェントにブラウザを与え、実在またはシミュレーションされたウェブサイト上でタスクを完了させます。

WebArenaS. Zhou et al. 2024)は、4つの機能的なウェブアプリケーション(ECストア、ソーシャルフォーラム、GitLab、CMS)と地図サービスからなるセルフホスト型テストベッドで、合計812の長期的タスクを収録します。エージェントはブラウザ自動化APIで相互作用し、タスクには複数ステップのナビゲーション、フォーム入力、情報検索が必要です。人間の性能は約78%で、最先端のLLMエージェントは約35〜45%を達成します。

VisualWebArenaKoh et al. 2024)は、ウェブページ上の画像を解釈する必要がある視覚グラウンディングタスクでWebArenaを拡張します。観測はスクリーンショットとアクセシビリティツリーの組み合わせであり、エージェントは両方のモダリティに基づいてアクションをグラウンディングしなければなりません。

Mind2WebDeng et al. 2023)は、人間の実演によって収集された、137の実在ウェブサイトにまたがる2,000タスクの大規模データセットです。WebArenaと異なり、Mind2Webは未知のウェブサイトへの汎化に焦点を当てるため、より難しい分布外テストになります。

Note

WebArenaタスクの例

タスク: 「ECサイトで50ドル未満の最も安い赤いドレスを探し、カートに追加してください。」

エージェントの軌跡:

  1. 衣料品カテゴリへ移動します。

  2. 色フィルタで赤を選択します。

  3. 価格の昇順で並べ替えます。

  4. 50ドル未満の最初の商品を特定します。

  5. 「カートに追加」をクリックします。

  6. カートの内容を確認します。

環境は最終的なカート状態を正解商品と照合します。正しければ報酬は1、そうでなければ0です。

コンピュータ利用環境

コンピュータ利用環境では、エージェントが完全なデスクトップOSを制御し、スクリーンショットやアクセシビリティAPIを通じて観測します。

OSWorldXie et al. 2024)は、3つのOS(Ubuntu、Windows、macOS)にまたがるデスクトップ自動化を、業務アプリ(LibreOffice、VS Code、Chrome、GIMPなど)を使う369タスクで評価します。エージェントはスクリーンショットを観測し、pyautogui風のマウス・キーボードコマンドで操作します。人間とエージェントの差は顕著で、アノテーターは約72%のタスクに成功する一方、最強のLLMエージェントは\(\sim\)18%にとどまり、ピクセルレベルのGUI制御の難しさを示しています。

WindowsAgentArenaBonatti et al. 2024)はWindows 11に特化し、19アプリケーションにまたがる154タスクを収録します。Excelの数式、PowerPoint編集、Outlookのメール管理など、エンタープライズワークフローを重視します。

Important

スクリーンショットのボトルネック

コンピュータ利用エージェントは根本的な課題に直面します。スクリーンショットは高次元(通常は\(1920 \times 1080 \times 3\)ピクセル)ですが、情報の大部分は現在のアクションに関係ありません。効率的なエージェントは画面の小さな領域に注意を向け、ピクセル座標ではなく名前でインタラクティブ要素を特定するためアクセシビリティツリーを使い、以前に訪れたUI状態のコンパクトなワーキングメモリを維持します。

ソフトウェアエンジニアリング環境

ソフトウェアエンジニアリング(SWE)環境では、バグ修正、機能実装、テスト作成など、現実世界のプログラミングタスクをエージェントに解かせます。

SWE-benchJimenez et al. 2024)は、広く使われている12のPythonプロジェクト(Django、Flask、scikit-learnなど)から2,294件の実際のプルリクエストを収録します。各インスタンスは、正しいパッチを適用した後だけ通過する保留テストスイートとIssue説明を組み合わせます。エージェントはリポジトリ構造を理解し、関係するコードを見つけ、修正を実装し、テストスイートで検証しなければなりません。 SWE-bench Verified サブセット(500件)は正しさを人間が検証しており、標準的な評価対象です。

SWE-agentJ. Yang et al. 2024)は、ベンチマーク環境であると同時にエージェントフレームワークでもあります。Agent-Computer Interface(ACI)を導入します。これはLLMエージェント向けに最適化されたシェルコマンド(search_fileopeneditなど)の集合で、生のbashと比べてアクション空間の複雑さを減らします。

Note

SWE-benchワークフロー

入力: GitHub Issueの説明と、Issueが登録されたコミット時点の完全なリポジトリ。

エージェントのアクション: find_filevieweditpython -m pytest tests/

報酬: エージェントのパッチ後に対象テストがすべて通れば1、それ以外は0です。部分点はありません。

科学研究環境

科学研究環境は、論文を読み、仮説を立て、実験を設計し、結果を解釈するという自律的な知識生成へエージェントを促します。

PaperQA2Lála et al. 2023)は、PDFコーパスを検索し、関連する箇所を抽出し、引用付きの回答を統合して科学的質問に答える検索拡張エージェントです。文献に基づく推論のツールとベンチマークの両方として機能します。

AI ScientistLu et al. 2024)はエンドツーエンドの研究自動化システムです。研究の方向性を与えると、エージェントが仮説を生成し、実験を書いて実行し、結果を解釈し、論文の草稿を作成します。環境にはPython実行サンドボックス、文献検索API、LaTeXコンパイラが含まれます。

MLAgentBenchHuang et al. 2024)は、計算予算内で与えられたデータセットのモデル精度を改善するなど、機械学習エンジニアリングタスクでエージェントを評価します。エージェントはデータを読み、訓練スクリプトを書き、実験を実行し、反復できます。

ゲームとシミュレーション環境

ゲームは、明確に定義された報酬信号を持ち、現実世界への影響がない、豊かで長期的な環境を提供します。

NetHackKüttler et al. 2020)は、非常に大きな状態空間を持つ手続き生成ローグライクであり、長期計画、インベントリ管理、予期しないイベントへの適応を必要とします。NetHack Learning Environment(NLE)はGym互換インターフェースを提供します。

Voyager / MinecraftG. Wang et al. 2023)はMinecraftゲームエンジンをオープンエンドな環境として使います。Voyagerは、徐々に難しくなるタスク(木を集める \(\to\) 道具を作る \(\to\) シェルターを建てる \(\to\) ネザーを探索する)のカリキュラムと、エピソードをまたいで再利用可能なコード断片を蓄積するスキルライブラリを導入します。

GAIAMialon et al. 2024)は、ウェブ検索、コード実行、ファイル解析などの連鎖したツール利用を必要とする466問を出題し、推論ステップ数に基づいて3つの難易度に分類します。ベンチマークは、人間の能力(\(\sim\)正解率92%)と現在のLLMエージェント(GPT-4+プラグインは公開時に\(\sim\)15%、後続システムは\(\sim\)30%に到達)の差をはっきり示します。

マルチエージェント環境

マルチエージェント環境では、2つ以上のLLMエージェントが互いに、または共有された世界と相互作用します。

  • 交渉: 私的な効用関数を持つエージェントが、対話を通じて合意に達しなければなりません。代表的な環境にはDealOrNoDeal(Lewis et al. 2017)とCaSiNo(Chawla et al. 2021)があります。

  • 討論: 2つのエージェントが対立する立場を論じ、審判エージェント(または人間)が議論の品質を評価します。敵対的な圧力によって真実に沿った推論を引き出すために使います。

  • 協調的タスク完了: 相補的な能力(プランナー、実行者、批評者)を持つエージェントが、単独では解けないタスクを完了するために協調します。フレームワークにはAutoGen(Wu et al. 2023)、CrewAI(Moura 2023)、MetaGPT(S. Hong et al. 2024)があります。

  • 競争ゲーム: エージェントがゼロサムゲーム(チェス、囲碁、ポーカー)をプレイし、相手もLLMエージェント自身です。これらの環境でのセルフプレイは、狭い領域で人間を超える性能を生みました。

OpenEnv:標準化されたエージェント型環境インターフェース

エージェント型環境の増加により、環境ごとに異なるAPI、異なる観測形式、異なる足場が必要になるという分断問題が生じています。 OpenEnvFace 2025)は、これに直接対処するHugging Faceの新しいオープンソースフレームワークです。エージェント型実行環境向けに、Gymnasiumスタイル(Towers et al. 2024)のインターフェース(step()reset()state())を提供し、分離されたDockerベースの配置がWebSocketで通信します。OpenEnvは、さまざまな環境にまたがるLLMエージェント向けの統一形式プラットフォームを提供するAgentGym(Xi et al. 2024)や、ウェブエージェントベンチマークの観測空間とアクション空間を標準化するBrowserGym(Le Sellier De Chezelles et al. 2024)など、より広い標準化の取り組みを補完します。以下の設計原則は、これらのプロジェクトから収束しつつあるベストプラクティスをまとめたものです。

標準化されたエージェント・環境インターフェース

OpenEnvはエージェント型実行環境向けの型付きインターフェースを定義します。設計はGymnasiumの単純さを踏襲しますが、HTTP/WebSocket経由でツールと相互作用するLLMエージェントを対象とします。

  • env.reset() \(\to\) StepResult: 新しいエピソードを開始し、初期観測を返します。

  • env.step(action) \(\to\) StepResult(observation, reward, done): 1つのアクションを実行し、結果の観測、スカラー報酬、終了フラグを返します。

  • env.state() \(\to\):現在の環境状態(エピソードID、ステップ数、環境固有フィールド)。

  • env.close(): リソースを解放します(コンテナ停止、接続切断)。

アクションと観測は、環境ごとに固有の、強く型付けされたPythonデータクラスです。例えば、コーディング環境はCodeAction(code=...)を定義し、stdoutstderrexit_codeを含む観測を返します。ゲーム環境は独自のアクション/観測型を定義します。環境ごとの型付けにより、3つの中核メソッド(resetstepstate)を共通に保ちながら、構造化され予測可能なインターフェースをエージェントに与えます。

アーキテクチャ

各環境はEnvironmentを継承するPythonクラス(reset()step()を実装)です。FastAPI/WebSocketエンドポイントを公開するHTTPEnvServerを介してDockerコンテナ内で提供されます。クライアントはEnvClientの環境固有サブクラスを使い、シリアライズと接続ライフサイクルを処理します。コンテナはfrom_docker_image()でローカル起動するか、ベースURLでリモート接続できます。

from coding_env import CodeAction, CodingEnv

# Option 1: Launch a local Docker container
client = CodingEnv.from_docker_image("coding-env:latest")

# Option 2: Connect to a remote deployment
# client = CodingEnv(base_url="http://localhost:8000")

# Interact with the environment
result = client.reset()
print(result.observation.stdout)
print(result.observation.stderr)
print(result.observation.exit_code)

result = client.step(CodeAction(code="print(2 + 2)"))
print(result.observation.stdout)       # "4\n"
print(result.observation.exit_code)    # 0
print(result.reward, result.done)

# Check state
state = client.state()
print(state.episode_id, state.step_count)

client.close()

サーバーとしての環境

新しい環境を作成するには、Environment基底クラスを実装するだけで済みます。

from openenv.core.env_server import Environment, create_app
from dataclasses import dataclass

@dataclass
class MyAction:
    text: str

@dataclass
class MyObservation:
    response: str
    reward: float = 0.0
    done: bool = False

class MyEnvironment(Environment):
    def reset(self) -> MyObservation:
        return MyObservation(response="Ready")

    def step(self, action: MyAction) -> MyObservation:
        return MyObservation(response=f"Echo: {action.text}",
                             reward=1.0, done=False)

app = create_app(MyEnvironment(), MyAction, MyObservation)
# Run: uvicorn module:app --host 0.0.0.0 --port 8000

ハーネス統合(実験的)

RFC 0054は、RL訓練フレームワークがMCPスタイルのツール呼び出しを通じて環境と相互作用する、ハーネス向けの層を導入します。build_harness_rollout_func()ヘルパーはTRL互換のロールアウト関数を生成し、OpenEnvをTorchForge(M. P. Team 2025)のような既存の訓練パイプラインへ直接橋渡しします。

ガバナンス

OpenEnvは、Meta-PyTorch、NVIDIA、Unsloth、Modal、Prime Intellect、Reflection、Hugging Faceなどを含む技術委員会によってオープンに運営されています。単一ベンダーの思惑ではなく、業界全体の意見を取り入れて標準が発展することを保証します。

環境レジストリと発見

OpenEnv環境はHugging Face SpacesまたはローカルDockerイメージとして配置でき、手動インストールなしで発見・利用できます。配置先にかかわらず、同じクライアントインターフェースが動作します。

from echo_env import EchoAction, EchoEnv

# Connect to a remote HF Space deployment
client = EchoEnv(base_url="https://openenv-echo-env.hf.space")
result = client.reset()
print(result.observation.echoed_message)  # "Echo environment ready!"

result = client.step(EchoAction(message="Hello!"))
print(result.observation.echoed_message)  # "Hello!"
print(result.reward)
client.close()

OpenEnvエコシステムはすでに70以上の環境(OpenSpielゲーム、Atari、BrowserGym、コーディングサンドボックス、金融RL、交通シミュレーションなど)にまたがっています。RFC 0025は、エージェントが実行時に未知の環境が受け付けるアクションを問い合わせられる、正式なツール発見プロトコルを提案します。

構成的環境

現実のエージェント配置で単一のツールしか使わないことはまれです。OpenEnvは、型付きアクションを通じて複数の能力を公開する豊かな環境をサポートします。例えば、コーディング環境は1つのサンドボックスセッション内でコード実行、ファイルI/O、シェルコマンドをサポートします。

from coding_env import CodeAction, CodingEnv

client = CodingEnv.from_docker_image("coding-env:latest")
result = client.reset()

# Execute code
result = client.step(CodeAction(code="x = 42\nprint(x)"))
print(result.observation.stdout)   # "42"
print(result.observation.exit_code)  # 0

# State persists across steps within an episode
result = client.step(CodeAction(code="print(x + 1)"))
print(result.observation.stdout)   # "43"

state = client.state()
print(state.step_count)  # 2

client.close()

多様なツールアクセス(コード+ウェブ+ファイル)を必要とするエージェント向けに、OpenEnvのRFC 0036はMCP統合を提案し、MCP互換の任意のツールサーバーをOpenEnv環境としてラップできるようにします。さらに、openenv CLIは1つのコマンドで新しい環境をHugging Face Spacesへ足場作成、ビルド、配置できます。

環境のバージョン管理と再現性

ベンチマークの完全性を保つには、評価時点で環境の挙動を凍結する必要があります。ベストプラクティスは次のとおりです。

  • セマンティックバージョニング: WebArena-v1.2.0 は、マイナーバージョンの範囲内で後方互換性を保証します。

  • Dockerイメージの固定: 環境のランタイムを、コンテンツアドレス付きハッシュを持つDockerイメージとしてパッケージ化します。

  • シードベースの決定性: 確率的要素(手続き生成やネットワーク応答)すべてにシードを設定してログに記録し、任意の軌跡を正確に再生できるようにします。

  • リーダーボードのスナップショット: 公開リーダーボードにスコアとともに環境バージョンを記録し、ベンチマークが気づかないうちに変化することを防ぎます。

カスタム環境の構築

LLMエージェント向けGymnasiumスタイルAPI

Gymnasium API(Towers et al. 2024)7(OpenAI Gymの後継)は、RL環境の事実上の標準です。LLMエージェント向けに適応するには、2つの変更が必要です。(1) 観測とアクションを数値配列ではなく文字列(または文字列を含む辞書)にすること、(2) step メソッドで非同期のツール実行を処理することです。

報酬関数のエンジニアリング

LLMエージェント環境の報酬関数は、通常、実行ベースです。環境が各エピソードの後に検証器を実行し、タスクが解決されていれば1、そうでなければ0を返します。明確な検証器がないタスクでは、次の選択肢があります。

  • LLMによる判定: 別のLLMがタスク記述に照らしてエージェントの最終状態を採点します。

  • ルーブリックベースの採点: 構造化されたルーブリックでタスクを下位基準に分解し、それぞれを独立に採点します。

  • 人間によるアノテーション: 人間の評価者が軌跡のランダムサンプルを採点し、そのスコアを自動代理指標の較正に使います。

状態管理とチェックポイント

長期タスクでは、実時間で何時間もかかることがあります。環境は次をサポートすべきです。

  • 状態のシリアライズ: 環境の完全な状態(ファイルシステム、ブラウザのCookie、データベースの内容)をディスクにシリアライズして復元できること。

  • エピソード途中のチェックポイント: エージェントが任意のステップでチェックポイントを保存し、そこから再開できること。これにより、木探索型の探索が可能になります。

  • 軌跡ログ: すべての観測、アクション、報酬を構造化ファイルに記録し、オフライン分析や報酬モデルの訓練に利用できること。

訓練データ収集のための並列化

RLでLLMエージェントを訓練するには、何百万回もの環境インタラクションが必要です。並列化の戦略には次があります。

  • プロセスレベルの並列化: 独立した環境プロセスを \(N\) 個起動し、軌跡を並列に収集します。

  • 非同期ロールアウトワーカー: 非同期イベントループ(例: asyncio)を使い、LLM推論のレイテンシと環境実行を重ね合わせます。

  • ベクトル化環境: 複数の環境を1回の step 呼び出しにまとめ、Pythonのオーバーヘッドを償却します。

  • クラウドネイティブなスケーリング: ジョブスケジューラ(Ray、SLURM)で環境ワーカーをクラスタ全体に分散し、中央のリプレイバッファで軌跡を集約します。

エージェント・環境インターフェースのパターン

6.2は、実際に使われている4つの主要なインターフェースパターンを示しています。

テキストベースの観測/アクション

エージェントは文字列の観測を受け取り、文字列のアクションを生成します。環境はアクションをパースし(例: <tool>...</tool> ブロックからツール呼び出しを抽出し)、結果を文字列として返します。これは最も互換性の高いパターンであり、特別なアーキテクチャなしに任意のLLMが参加できます。

構造化JSONの観測/アクション

観測とアクションは、定義済みスキーマを持つJSONオブジェクトです。これにより、厳密な検証(不正な形式のアクションを実行前に拒否)、構造化ログ、軌跡のプログラムによる分析が容易になります。一方で、エージェントは有効なJSONを確実に生成しなければならず、そのためにはファインチューニングか制約付きデコーディングが必要です。

マルチモーダル(スクリーンショット+アクセシビリティツリー)

コンピュータ操作環境やウェブ環境で使われます。観測はタプル(screenshot: PIL.Image, a11y_tree: dict)です。スクリーンショットは視覚的な文脈を提供し、アクセシビリティツリーは、ピクセルレベルの座標を指定せずにアクションで使える要素識別子を提供します。このハイブリッド方式は、スクリーンショットだけに基づく制御より堅牢です。

ストリーミング対ターンベースのインタラクション

現在の環境の多くはターンベースのモデルを使います。エージェントが完全なアクションを生成し、環境がそれを実行して、次の観測を返します。ストリーミング環境では、エージェントは到着した部分的な観測(例: 長時間実行されるコマンドの出力)を受け取り、ストリームの途中で実行を中断したり方向転換したりできます。これは人間のコンピュータとの関わり方に近い一方、より複雑なエージェントアーキテクチャが必要です。

評価ハーネスの設計

評価ハーネスとは、ベンチマークスイート全体でエージェントを実行し、結果を収集して要約統計を生成するインフラです。優れたハーネス設計は、優れた環境設計と同じくらい重要です。

決定論的環境対確率的環境

  • 決定論的環境 は、同じアクション列に対して同じ観測列を生成します。デバッグと再現が容易ですが、現実世界の変動性を反映しない場合があります。

  • 確率的環境 は、手続き生成、ネットワークレイテンシ、ユーザーシミュレーションなどのランダム性を導入します。平均性能と信頼区間を推定するには、タスクごとに複数回の実行が必要です。

Note

何回実行すれば十分か?

\(N\) 個のタスクと二値報酬を持つベンチマークでは、平均成功率の標準誤差は \(\sqrt{p(1-p)/N}\) です。\(N=500\) 個のタスクで \(p=0.4\) の場合、95%信頼区間はおよそ \(\pm 4.3%\) です。確率的環境では、\(\sqrt{k}\)(\(k\) はタスクあたりの独立実行回数)を掛けます。確率的ベンチマークでは、タスクごとに3〜5回実行するのが一般的です。

ホールドアウトされたテスト環境

ベンチマークの完全性を保つには、タスク単位だけでなく、環境単位で厳密に訓練/テストを分割する必要があります。WebArenaのタスクで訓練したエージェントは、訓練に使っていないホールドアウトされたタスク集合で評価すべきです。理想的には、ホールドアウト集合には訓練集合とは異なるウェブサイト、タスク種別、難易度が含まれます。

環境間汎化

エージェントにとっての究極のテストは、ある環境で学習したスキルを別の環境へ移せるかどうかです。環境間評価プロトコルでは、次を測定します。

  • ゼロショット転移: 環境Aで訓練し、ファインチューニングなしで環境Bをテストします。

  • 少数ショット適応: 評価前に環境Bから \(k\) 個のデモンストレーションを与えます。

  • 継続学習: 環境A、B、Cで順に訓練し、Cでの訓練後に3つすべてで性能を測定します。

人間ベースラインの収集

すべてのベンチマークには、基準点として人間の性能を含めるべきです。人間ベースラインには3つの役割があります。

  1. タスクの難易度に対する上限を定めます。

  2. タスクがそもそも解けるかを明らかにします(ベンチマークのタスクには、曖昧または不可能だと判明するものもあります)。

  3. エージェントのスコアを解釈するための較正点を提供します(「エージェントは人間性能の40%を達成した」など)。

人間ベースラインは、分野の専門知識を持つ作業者(例: SWE-benchならクラウドワーカーではなくソフトウェアエンジニア)から収集し、効率を比較できるようタスク所要時間の測定も含めるべきです。

コード例:最小限のカスタムLLMエージェント環境

Note

LLMエージェント訓練用の最小限のカスタム環境

次のPythonクラスは、エージェントがPythonファイルを変更して失敗しているテストを通す、ファイル編集環境を実装しています。LLMエージェント向けに適応したGymnasium APIに従います。

"""
minimal_env.py  --  A minimal file-editing environment for LLM agents.

The agent receives a Python file with a bug and a failing test.
It must edit the file until the test passes.
Reward: 1.0 if all tests pass, 0.0 otherwise.
"""

from __future__ import annotations
import subprocess, shutil, tempfile, textwrap
from pathlib import Path
from dataclasses import dataclass, field
from typing import Any

# ---------------------------------------------------------------------------
# Data structures
# ---------------------------------------------------------------------------

@dataclass
class StepResult:
    observation: str          # Text fed to the LLM
    reward: float             # 0.0 or 1.0
    terminated: bool          # Episode over (task solved or max steps)
    truncated: bool           # Episode cut short (budget exceeded)
    info: dict[str, Any] = field(default_factory=dict)

# ---------------------------------------------------------------------------
# Environment
# ---------------------------------------------------------------------------

class FileEditEnv:
    """
    A Gymnasium-style environment for LLM-based code repair.

    Observation space : str  (file contents + test output)
    Action space      : str  (one of: view, edit, run_tests, submit)
    Reward            : 1.0 on passing all tests, 0.0 otherwise
    """

    MAX_STEPS = 20          # Hard episode limit
    TIMEOUT   = 30          # Seconds per test run

    def __init__(self, buggy_code: str, test_code: str,
                 task_description: str):
        self.buggy_code       = buggy_code
        self.test_code        = test_code
        self.task_description = task_description
        self._workdir: Path | None = None
        self._step_count = 0

    # ------------------------------------------------------------------
    # Core API
    # ------------------------------------------------------------------

    def reset(self, seed: int | None = None) -> tuple[str, dict]:
        """Initialise a fresh episode; return (observation, info)."""
        if self._workdir and self._workdir.exists():
            shutil.rmtree(self._workdir)

        self._workdir    = Path(tempfile.mkdtemp(prefix="fileenv_"))
        self._step_count = 0

        # Write initial files
        (self._workdir / "solution.py").write_text(self.buggy_code)
        (self._workdir / "test_solution.py").write_text(self.test_code)

        obs = self._build_observation(
            action_taken="[Episode start]",
            test_output=self._run_tests()
        )
        return obs, {"step": 0}

    def step(self, action: str) -> StepResult:
        """Execute one agent action; return StepResult."""
        self._step_count += 1
        action = action.strip()

        # --- Parse and dispatch action ---
        if action.startswith("view"):
            result_text = self._action_view()
        elif action.startswith("edit"):
            result_text = self._action_edit(action)
        elif action.startswith("run_tests"):
            result_text = self._run_tests()
        elif action.startswith("submit"):
            result_text = self._run_tests()
        else:
            result_text = (
                f"Unknown action: {action!r}\n"
                "Valid actions: view | edit <new_content> | "
                "run_tests | submit"
            )

        test_output = self._run_tests()
        passed      = "passed" in test_output and "failed" not in test_output
        reward      = 1.0 if passed else 0.0
        terminated  = passed or action.startswith("submit")
        truncated   = self._step_count >= self.MAX_STEPS

        obs = self._build_observation(action, test_output)
        return StepResult(obs, reward, terminated, truncated,
                          {"step": self._step_count,
                           "passed": passed})

    def render(self) -> str:
        """Return a human-readable summary of the current state."""
        if self._workdir is None:
            return "[Environment not initialised]"
        code = (self._workdir / "solution.py").read_text()
        return f"=== solution.py ===\n{code}\n"

    def close(self) -> None:
        """Release resources."""
        if self._workdir and self._workdir.exists():
            shutil.rmtree(self._workdir)
            self._workdir = None

    # ------------------------------------------------------------------
    # Private helpers
    # ------------------------------------------------------------------

    def _action_view(self) -> str:
        code = (self._workdir / "solution.py").read_text()
        return f"Current solution.py:\n```python\n{code}\n```"

    def _action_edit(self, action: str) -> str:
        # Expect: edit\n```python\n<code>\n```
        try:
            new_code = action.split("```python")[1].split("```")[0]
            (self._workdir / "solution.py").write_text(new_code)
            return "File updated successfully."
        except IndexError:
            return "Edit failed: wrap new code in ```python ... ```"

    def _run_tests(self) -> str:
        result = subprocess.run(
            ["python", "-m", "pytest", "test_solution.py",
             "-v", "--tb=short", "--no-header"],
            cwd=self._workdir,
            capture_output=True, text=True,
            timeout=self.TIMEOUT
        )
        return result.stdout + result.stderr

    def _build_observation(self, action_taken: str,
                           test_output: str) -> str:
        code = (self._workdir / "solution.py").read_text()
        return textwrap.dedent(f"""
            TASK: {self.task_description}
            STEP: {self._step_count}/{self.MAX_STEPS}

            --- Last action ---
            {action_taken}

            --- Current solution.py ---
            {code}

            --- Test output ---
            {test_output}

            --- Available actions ---
            view                          # show current file
            edit\n```python\n<code>\n```  # replace file contents
            run_tests                     # run pytest
            submit                        # finalise and end episode
        """).strip()

# ---------------------------------------------------------------------------
# Example usage
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    BUGGY = "def add(a, b):\n    return a - b\n"   # bug: minus not plus
    TESTS = (
        "from solution import add\n"
        "def test_add(): assert add(2, 3) == 5\n"
    )

    env = FileEditEnv(BUGGY, TESTS, "Fix the add() function.")
    obs, _ = env.reset(seed=0)
    print(obs)

    # Simulate one correct edit
    fix = "edit\n```python\ndef add(a, b):\n    return a + b\n```"
    result = env.step(fix)
    print(f"\nReward: {result.reward}  |  Terminated: {result.terminated}")
    env.close()

Important

例の環境における設計上の決定

  • テキストのみのインターフェース: 観測とアクションはプレーンな文字列であり、任意のLLMと互換性があります。

  • 実行ベースの報酬: 報酬はLLMによる判定ではなく、実際のテストスイートを実行して得られます。これにより、改ざんできず、目的と完全に整合します。

  • 分離されたサブプロセス: テストはタイムアウト付きで別プロセス内で実行され、無限ループによって訓練ループがクラッシュするのを防ぎます。

  • Gymnasium互換: reset/step/ render/close は標準APIに従うため、RL訓練フレームワークにそのまま組み込めます。

主要なエージェント型環境の比較

6.1は、この節で扱った主要なエージェント型環境の主要な特性をまとめたものです。

環境観測の種類アクション空間分野# タスク人間SoTA LLM
WebArenaテキスト+DOMブラウザAPIウェブナビゲーション81278%\(\sim\)45%
VisualWebArenaスクリーンショット+DOMブラウザAPI視覚ウェブ91088%\(\sim\)35%
Mind2Webスクリーンショット+DOMブラウザAPI実在ウェブサイト2,000\(\sim\)30%
OSWorldスクリーンショットマウス+キーボードデスクトップOS36972%\(\sim\)18%
WindowsAgentArenaスクリーンショットマウス+キーボードWindowsアプリ15475%\(\sim\)20%
SWE-bench Verifiedテキスト(リポジトリ)シェル+エディタコード修正500100%\(\sim\)50%
GAIA (Level 1)テキスト+ファイルツール呼び出し一般QA16592%\(\sim\)55%
GAIA (Level 3)テキスト+ファイルツール呼び出し難しいQA4292%\(\sim\)10%
NetHack (NLE)テキスト+グリフ離散アクションローグライクゲーム\(>\)1万スコア\(\sim\)5千スコア
Voyager (Minecraft)テキスト+コードコード実行オープンワールドゲームカリキュラム技術ツリー15以上
MLAgentBenchテキスト+コードシェル+エディタMLエンジニアリング13\(\sim\)40%

LLMエージェント向け主要エージェント型環境の比較。「SoTA」は執筆時点で公表されている最良のLLMエージェント結果を指します。人間の性能は、利用可能な場合に示しています。{#tab:env-comparison}

Tip

比較表の読み方

人間の性能とSoTA LLMの性能の差は、コンピュータ操作タスク(OSWorld: 72%対18%)で最大で、コード修正(SWE-bench: 100%対50%)で最小です。このパターンは、アクション空間の成熟度を反映しています。LLMは膨大な量のコードで訓練されてきましたが、スクリーンショットベースのインタラクションデータは比較的少ないためです。コンピュータ操作の訓練データが蓄積されるにつれ、この差は縮まると予想されます。

まとめ

エージェント型環境は、LLMエージェントが訓練・評価される基盤です。この節の要点は次のとおりです。

  1. 環境は不可欠です。 安全な探索、再現可能な評価、カリキュラム学習には、すべて構造化された環境が必要です。環境なしに、チャットボットとエージェントの評価の差を埋めることはできません。

  2. 4つの軸をすべて慎重に設計します。 観測空間、アクション空間、報酬信号、エピソード構造には、それぞれベンチマーク全体を無効にしかねない失敗モードがあります。

  3. 領域は豊富ですが分断されています。 コードサンドボックス、ウェブ環境、コンピュータ操作環境、SWE環境、科学環境、ゲーム、マルチエージェントアリーナは、それぞれ異なる能力をテストします。単一の環境では不十分です。

  4. 標準化が重要です。 OpenEnv(Face 2025)は、Docker分離を備えたGymnasiumスタイルAPIと、レジストリとしてのHugging Face Spacesを提供し、新しい環境の構築や環境間でのエージェント比較にかかるコストを削減します。

  5. 人間との差は現実に存在しますが、縮まりつつあります。 現在のLLMエージェントは、ほとんどのベンチマークで人間性能の20〜50%を達成しています。進歩が最も速いのは訓練データが豊富な分野(コード)で、最も遅いのは細粒度の知覚を必要とする分野(GUI制御)です。

Note

エージェント型環境における未解決の研究課題

  • 正しさが主観的または文脈依存であるタスクについて、報酬関数をどのように設計すればよいでしょうか?

  • タスク固有のファインチューニングなしに、単一のエージェントアーキテクチャをテキストベース環境とマルチモーダル環境にまたがって汎化させられるでしょうか?

  • 訓練に適した環境忠実度の水準はどの程度でしょうか。単純化されたシミュレータでの訓練は、実運用へ転移するでしょうか?

  • ベンチマークの解答を含む可能性がある、ますます大規模なウェブコーパスでLLMが訓練される中、ベンチマークの汚染をどのように防げばよいでしょうか?