Prime Intellectが公開したマルチエージェントRL訓練フレームワーク:「1体のAgentを鍛える」から「複数Agentが作用し合う集団を鍛える」へ
- Agentの訓練は長らく2つの問題に阻まれてきました。決め打ちのテストが正しい解答を0点にするケース、静的で数ラウンドでモデルに学習され尽くす問題集。
- 今回の解決策は、AgentにAgentを評価させること。評価者はサンドボックスに入りコードを実際に実行してから判定し、出題者は難易度を「ちょうど半分が解ける」水準に調整します。
- 既製の4環境はすべてオープンソースですが、「スコアを誰と比較するか」という問題だけで、2つの新しいアルゴリズムが生まれました。
1つのAgentから、複数のAgentへ
強化学習で1つのAgentを訓練する光景は、長らくこうでした。1つのモデルが固定の問題集に取り組み、答えは決め打ちのテストが採点します。すると3つの問題が発生します。テストのアサーションはしばしば実装の詳細に依存し、正しい解答でも期待された書き方でなければ0点になる。問題集は静的で、モデルは数ラウンドで内容を学習し尽くす。そして対話する相手が存在しないため、交渉や駆け引きを学ぶことができません。
Prime Intellectが本日公開したのは、まさにこれらの問題に取り組むものです。トレーニングスタックを「1つのAgentを鍛える」から「複数のAgentが相互に作用する集団を鍛える」へと拡張し、Agent間の任意の相互作用を調整したり、学習に参加させる役割を指定したり、相互作用全体に功績を分配したりできます。コードはverifiers v0.3.0 と prime-rl v0.8.0 にあり、8月7日に公開されました。
まず区別すべき点があります。マルチエージェントアプリケーションオーケストレーションフレームワークは、複数のAgentが協力して作業を完了させることに焦点を当てています。一方、今回のツールキットが焦点を当てるのは、作業が完了した後、その相互作用の中で誰が学習すべきか、各役割の功績をどう計算するかです。後述しますが、「スコアを誰と比較するか」という問題だけでも、2つの新しいアルゴリズムが必要になりました。
追加された2つのクラス:AgentとEnv
verifiersの以前のバージョンでは、シングルエージェントの実行に必要な部品はすでに分解されていました。Taskset(実行するタスク)、Harnessモデルがその中で実行されるプログラム。どのツールを呼び出せるか、結果をどう読み取るか、どうステップを進めるかを決定します。モデルをエンジンとするなら、ハーネスは車全体です。同じエンジンでも、レーシングカーに載せるかトラックに載せるかで、できる仕事はまったく異なります。(モデルを駆動するプログラム)、Runtime(このプログラムをどのマシンで実行するか)です。今回の変更は非常にシンプルです。これら3つを1つにまとめたものをAgentと名付けました。
タスクを与えると、Trace1つのAgentが1回の実行を完了するまでの完全な記録:発言、ツール呼び出し、取得スコア、消費トークン数など。各車両のドライブレコーダーのようなものです。相互作用内の全Agentの記録をまとめたものがEpisodeです。(トレース)が返ってきます。この実行中に何を言い、どのツールを呼び、どれだけのスコアを取り、どれだけのトークンを消費したかがすべて含まれています。サンドボックスの起動、ツールサービスのインストール、タイムアウト再試行などの面倒な処理はすべて、この1つのアクションに隠蔽されています。したがって、任意の骨格、任意のモデル、任意の実行環境を自由に組み合わせて、通常のコードで制御できます。
Envはマルチエージェントの本拠地であり、シグネチャも同様に一言で表せます。Env.run(task, agents)。初期タスクと、準備済みのAgentグループが与えられ、残りの制御フローは自分でコーディングします。各Agentの実行が完了すると、そのトレースは自動的にEpisode(つまり相互作用全体の記録)に統合されます。
Env.run は通常のasync Pythonです。順次実行したい場合はawaitを2回、並行実行したい場合はTaskGroupを開始し、対話をさせたい場合はwhileループを書きます。DSLも、グラフオーケストレーションも、ステートマシン設定ファイルもありません。以前のシングルエージェントは新しいフレームワークでは1行に縮小され、この抽象化レイヤーは何も追加しません。
class SingleAgentEnv(Env[SingleAgentEnvConfig]):
async def run(self, task: Task, agents: Agents) -> None:
await agents.agent.run(task)
もう1つの便利な設計として、Agentの名前は設定フィールド名から自動的に生成されます。設定クラスに solver と judge という2つのフィールドを書くと、コード内で agents.solver と agents.judge として利用でき、追加の登録は不要です。
Agentを評価者としてテストを覆す
まず、これがどれほど一般的な問題かを説明します。ソフトウェアエンジニアリングの訓練タスクでは、正誤はテストケースによって判定されますが、そのテストケースはしばしば特定の実装詳細を検証しています。変数名、関数がリストを返すのかジェネレーターを返すのかなどです。美しく機能する解答でも、テストが期待する書き方でなければ0点になります。このシグナルが訓練にフィードバックされると、モデルが学ぶスキルは歪みます。つまり、テストが何を求めているかを推測することを学び、タスクを正しく実行することを学ばなくなるのです。
大きなモデルに一回で採点させればいいのでしょうか?一度の呼び出しで正確な判定を下すのは非現実的です。コードベースを見ることも、テストを実行することも、何かを検証することもできず、テキストの断片を見て推測するしかありません。
評価者をAgentにすると状況は変わります。独自のサンドボックスを持ち、コードを調べたり、どのテストがどの行で失敗しているかを確認したり、自分で実行してみたり、そして決め打ちのテストの結論を覆すことができます。
class AgenticJudgeEnv(vf.Env[AgenticJudgeEnvConfig]):
async def run(self, task, agents) -> None:
solution = await agents.solver.run(task)
if not solution.ok:
raise
await agents.judge.run(JudgeTask.from_trace(solution))
評価者に与えられるプロンプトは明確に書かれています。
トレースからこのAgentが何をしたかを再構築し、自分のサンドボックスでの実際の実行を通じて検証してください。自分で検証できる結果については、トレース内の記述を決して鵜呑みにしないでください。
verifiersリポジトリ内の評価者用採点プロンプト
判定をスコアに変換する方法:評価者は事前に定義された複数の基準に従って項目ごとに採点し、結果をJSONとしてサンドボックス内の指定されたファイルに書き込みます。フレームワークは厳密に検証し、各判定を解答者のトレース上の指標として記録し、加重平均を取って judge と呼ばれる報酬にします。デフォルトでは評価者のスコアのみを採用し、タスクセット自身の報酬重みは0です。両方を使用したい場合は、重みを1に調整するだけです。
ブログにはない3つの設計
評価者が作業するサンドボックスは選択式です。共有版では、評価者は解答者が使用したサンドボックスに直接入り、ワークスペースはそのまま保持され、変更履歴も見えます。分離版では、評価者に新しいサンドボックスを与え、解答者が正式に提出した成果物のみを復元します。どの環境IDを選ぶかでセキュリティ境界が決まるため、実際の動作と矛盾する可能性のあるスイッチを残すことはありません。
評価者の骨格はコードを実行できなければなりません。この点は環境構築時にチェックされ、チャットのみのツールなし骨格を接続するとエラーになります。実行なしで下せる判定は、プラグイン式採点器であり、Agent評価者ではありません。
作業開始前に環境をクリーンアップします。解答者が先にサンドボックスを使用するため、評価者が作業を開始する前に、フレームワークは判定ファイルとトレースファイルを削除します。これは2つの事態を防ぐためです。解答者が事前に判定ファイルを埋め込んで評価者の結果を偽装すること、またはアップロードパスにシンボリックリンクを仕掛けて評価者の書き込み操作を任意のファイルにリダイレクトすることです。
もう1つ一貫した原則があります。評価者のトークンは訓練データに決して入りません。評価者は測定のための物差しであり、物差し自体が最適化の対象になるべきではありません。
出題するAgentと解答するAgent
強化学習が有用な学習シグナルを得るには、問題の難易度がモデルの現在のレベルに近い必要があります。簡単すぎる問題は情報がなく、難しすぎる問題も情報がありません。静的データセットはこれを提供できません。モデルが強くなっても、自動的に難しくなるわけではないからです。
その方法は、モデル自身に問題を生成させることです。1つのAgentが出題者となり、シードトピックから新しい問題を構築します。別のAgentグループが解答者となり、それぞれが問題を解きます。解答者が正解するとスコアを得ます。これは理解しやすいでしょう。出題者のスコアは問題自体の出来映えではなく、その問題が解答者グループに何をもたらしたかだけを見ます。
全員が正解すれば、簡単すぎることを意味します。全員が間違えれば、難しすぎるか、問題自体に欠陥があることを意味します(例えば、出題者自身が計算した答えが間違っている場合)。半分の人が解けたとき、その問題がトレーニングに与える情報が最も多くなります。この量はlearnability(学習価値)と呼ばれます。式で表すと 4p(1−p) であり、pは解答率で、p=0.5のとき正確に1になります。
この曲線が自動カリキュラムのすべての秘密です。モデルが強くなれば、生成する問題も自動的に難しくなります。人間が難易度を調整する必要はありません。このアイデアはAbsolute Zero(Zhaoら、2025)に由来します。
async def run(self, task: vf.Task, agents: vf.Agents) -> None:
proposed = await agents.proposer.run(task)
solve_task = SolveTask.from_trace(proposed)
async with asyncio.TaskGroup() as tg:
for _ in range(self.config.n):
tg.create_task(agents.solver.run(solve_task))
リポジトリ内の実装は、この例よりもかなり具体的です。シードトピックは6つあります:比率混合、数論、組合せ、幾何、確率、剰余算術。出題者はまずコードを書いて数字を構築し、答えをエンドツーエンドで検証してから、最後にJSONを1行出力する必要があります。答えは整数でなければならず、ごまかしの余地を与えません。デフォルトでは4人の解答者が同じ問題を解きます。
2つの座席はまったく異なる設定が可能で、これは非常に実用的です:
| 座席 | 骨格 | 実行場所 | この設定の理由 |
|---|---|---|---|
| 出題者 | codex | 本物のサンドボックス | 問題構築と答えの検証のためにコードを書く必要があるため |
| 解答者 ×4 | null(ツールなし) | 不要 | 解答のみで操作はしないため、モデル呼び出しのコストのみ |
どちらを学習させるかもスイッチで選択できます。両方学習させることも、出題者のみ、または解答者のみを学習させることも可能です。
2つのモデルがポーカーで対戦、相手は自分自身
クーン・ポーカーはゲーム理論の教科書に出てくる玩具モデルで、その小ささは一言で説明できます。合計3枚のカードJ、Q、K(Kが最大)、各プレイヤーは1チップのアンティを賭け、それぞれ1枚のカードを伏せて受け取ります。先手は「チェック」か「1チップのベット」を選び、1、2アクションでゲームは終了し、勝敗は±1または±2チップです。
これを選んだのは、小ささゆえです。数万ハンドを繰り返して戦略がどこに収束するかを見ることができ、ゲーム自体の複雑さに予算を費やす必要がありません。
環境は3つのことを管理します:誰がどの伏せカードを持っているか、現在の公開状況、この手番での有効なアクション。すべてホスト側で判定されます。モデルは応答に角括弧付きのアクションを含めるだけでよく、例えば [check] のようにします。2つ以上含めた場合や間違えた場合は、もう一度尋ねられ、それでもダメなら棄権と判定されます。タスクセットは無限で、各ハンドにランダムシードがあり、好きなだけハンドを実行できます。
2つの座席はデフォルトで同じモデルにバインドされており、これが自己対局です。戦略が強くなると、相手も一緒に強くなり、カリキュラムが自動的に難易度を上げていきます。別途対戦相手サービスを用意する必要はありません。
功績をどう異なる役割に分配するか
前述の2つの自己対局の例は、どちらも同じ問題を提起します:今回得たスコアは、何と比較すべきか。
パフォーマンスを評価するには、「通常のレベル」と比較して初めて良し悪しが決まります。この通常のレベルをベースラインと呼びます。期末試験で80点が良いか悪いかは、クラスの平均点によって決まります。ベースラインはその平均点です。GRPOのアプローチはまさにこれです:同じ問題に対して一連の答えを生成し、そのグループの平均点より高いものを強化します。
しかし、クラスの半分が別の問題用紙を受けていたら、平均点は意味を持ちません。マルチロールトレーニングではこの問題に直面します。しかも、2つの異なる形で発生します。
ある人の失敗を、別の人のせいにしない
出題-解答の相互作用では、解答者の成績は「同じ問題への他の試行」とのみ比較されるべきです。問題をまたいで比較すると、難しい問題での失敗を使って、正常に機能している解答者を罰することになります。出題者の成績は「同じシードトピックから生成された他の問題」とのみ比較されるべきです。クラシックなGRPOはこの階層を表現できません。一連のロールアウトをフラットにして平均を計算するため、難易度の異なる問題と異なる役割がすべて混ざってしまいます。
Hierarchical GRPOが行うのはグループ化です。これを(役割、相互作用)ごとにグループ化し、各グループが独自のベースラインを計算します。コードは驚くほど短く、グループ化、グループ平均の減算、それだけです。
ゼロサムゲームでは、平均スコアは常に0
ポーカーでは別の形で機能不全に陥ります。ゼロサムゲームでは、一連の対局の報酬平均は常にほぼ0です。一方が勝てば他方が負けるため、戦略の良し悪しに関係なく一定です。これをベースラインとして使用することは、ベースラインがないことと同じです。
さらに厄介なのは、2つの座席の報酬スケールが逆であることです。混ぜて計算すると、先手の構造的な優位性がこの役割の永続的な功績として読み取られてしまいます。先手は単に良いポジションに座っているだけなのに、常に報酬を受け取っていることになるのです。
RAEの解決策は、各役割に独自の移動平均線を維持することです。この役割が通常どれだけのスコアを得るかをローリングで記憶し、功績は今回のスコアから自分の通常レベルを引いたものに等しくなります。シングルエージェント環境でこれを使用すると、自動的に移動平均ベースラインを持つREINFORCEに退化します。
両アルゴリズムはprime-rl内にあり、それぞれ数十行です。RAEはSPIRALの論文(arXiv:2506.24119)に由来します。
ユーザーもAgentにする
アシスタントタイプのタスクには共通点があります。「1つのプロンプトと1つの応答」では表現できないことです。実際の人間は、あなたには見えない背景を持っており、情報は少しずつ明かされ、あなたの応答に反応し、いつ完了とみなすかを決めるのは人間側です。
そこで、ユーザーもAgentにします。1つの相互作用は、ユーザーとアシスタントの間の往復対話であり、両方のトレースがこの相互作用の記録に残ります。模擬ユーザーはデフォルトで凍結されトレーニングされません。評価者と同じで、環境の一部だからです。アシスタントのトレースは元のタスクに基づいて採点され、トレーニングに使用されます。
情報は次のように分配されます。タスクセット内の1行のデータは、ユーザー側の世界として解釈されます。そのプロンプトはユーザーのシステムプロンプト内の「あなたの状況」になり、アシスタントは同じタスクを受け取りますが、プロンプトは空にされます。したがって、アシスタントは最初は何をすべきか分からず、対話を通じて見つけ出すしかありません。
ユーザーのペルソナにはいくつかのルールが書かれています。自分の言葉で話す、短く話す、ペルソナを維持する、作業はアシスタントが行う(ツール呼び出し、回答、形式はすべてアシスタントが行い、代わりに行わない)、詳細は尋ねられたら話す。目標が達成されたか、アシスタントには達成不可能だと確信した場合は、終了マークを返して締めくくります。デフォルトでは最大8ラウンドで、対話が発散するのを防ぎます。
async with (
agents.user.interaction(user_task) as user,
agents.assistant.interaction(assistant_task) as assistant,
):
ask = await user.turn("Hello! How can I help you today?")
while True:
answer = await assistant.turn(ask.last_reply)
if answer.terminated:
break
ask = await user.turn(answer.last_reply)
if ask.terminated:
break
これにより、異なるユーザーグループ、ペルソナ、非表示の目標、相互作用戦略を同じインターフェースに挿入でき、アシスタント側も個別に設定できます。これは前述の3つの環境と同じ部品の異なる組み合わせであり、別の評価パイプラインではありません。
現在入手できるもの
リポジトリには既製の骨格が13個あり、Claude Code、Codex、Kimi Codeなどの実際のプログラミングAgentが含まれており、モデルに直接組み合わせて使用できます。
実行環境は4種類:ローカルプロセス、Docker、Modal、そして自社のクラウドサンドボックスです。前述の4つの環境に加えて、リポジトリには BestOfNEnv も含まれています。同じタスクをn回独立して試行し、最良のものと、しきい値を超えた試行があったかどうかを示します。拒否サンプリングやpass@k評価に役立ちます。
同じAgent抽象化は、自社でも合成データ生成とフィルタリングのパイプラインに使用されています。各トレースの形式は統一され、監査可能であり、それ自体がデータ成果物です。ブログの最後の例はわずか10数行で、PoolsideのLaguna-S2.1モデルが pool 骨格で実行され、PyPIでverifiersの最新リリースバージョンを確認するというものです。前述のトレーニングと同じAgentを使用しています。
Prime Intellect がオープンソース化したマルチエージェントRLトレーニングフレームワーク:1つのAgentから、相互に作用するAgent群へ
追加された中核はたった2つのクラス。4つの既製環境はすべてリポジトリですぐ動作し、図解で解説します。
↓ 一枚で完了 · 動く図あり
強化学習でのAgentトレーニングは、長らく2つの問題に阻まれてきました。テストの採点が厳格すぎて、正しい解法でも期待された書き方でなければ0点になる。問題集が静的なので、モデルは数ラウンドで内容を学習し尽くし、その後は無駄になる。
Prime Intellect は今回、トレーニングフレームワークを1つのAgentから、相互に作用するAgent群を鍛えるものへと拡張しました。追加された中核は2つのクラスのみ。Agentは骨格プログラム、モデル、実行環境を1つの再利用可能な部品にまとめ、タスクを与えるとトレースを返します。Envは自分で書く制御フローで、通常のasync Pythonです。シングルエージェントのシナリオは新しいフレームワークでは1行に縮小できます。
最初の例は採点の問題を扱います。決め打ちのテストはコードの書き方などの詳細に固執することが多く、美しく機能する解法がテストの期待する書き方を満たさないために0点になる。モデルが学ぶのは、テストが何を求めているかを推測することです。
コードベースに入れず、テストも実行できない
テキストを見て推測するだけ
どのテストがどの行で失敗しているかを確認し、自分で実行して検証
判定は決め打ちのテストを覆せる
評価者が作業を始める前に、判定ファイルは先にクリアされ、改ざんや妨害を防ぎます。また、評価者自身の対話記録はトレーニングに参加しません。評価者は物差しであり、一緒に調整されるべきではないからです。
2つ目の例は問題不足の問題を扱います。有用な学習シグナルはモデルの現在のレベルに密着している必要があります。簡単すぎる問題は栄養にならず、難しすぎる問題も栄養になりません。静的 問題集はこれを提供できません。
その方法は、1つのAgentが出題し、他の複数のAgentが解答することです。出題側のスコアは問題の見栄えではなく、解答したモデル群がどれだけ正解したかだけを見ます。全員正解なら簡単すぎることを意味し、全員不正解なら難しすぎるか問題自体に欠陥があることを意味します。半分が正解したとき、その問題がトレーニングに与える情報量が最大になります。
リポジトリでは他にも2つの遊び方が実行できます。2つのAgentがミニマムなポーカーで対戦し心理戦を学ぶ。戦略と対戦相手が一緒に強くなり、別途対戦相手を用意する必要がありません。ユーザーもAgentにして、意図的にタスク情報を相手側に隠し、アシスタントに聞き出させる。
これらの自己対局はすべて同じ問題にぶつかります。今回のスコアは誰と比較すべきか。2つの座席の報酬は比較できないことがあります。Prime Intellect はこのために2つの新しいアルゴリズムを書きました。1つは役割と相互作用ごとにグループ化して各グループのベースラインを計算するもの、もう1つは各役割に個別の履歴水準線を持たせるものです。
少し違うだけでも0点
実行してみる
全員不正解も無意味
価値は最大
すぐに使える
トレーニング効果の数値
今回はすべて別のAgentに。
