鋭意開発中のAIツールのpipeですが、今回は手順書とロール定義だけでリバーシを白黒それぞれにGeminiを割り当てて対戦させるデモを作ってみました。まだ、エラーハンドリングやUIなんかは後回しにしているので荒削りですがよかったらcloneしてみてもらえると嬉しいです。
最新の状態であれば以下をgemini-cliに食わせるだけです。WebUIも用意していますが、実行毎に止まってしまうのでオススメ出来ません。そのうち、CLIツールも作りたいと考えています。
@roles/conductor.md として @procedures/reversi_game.md を実行して。
1. 概念:なぜ2ファイルでマルチエージェントが動くのか
従来のチャットベースのLLM利用では、複雑なタスクを実行する際、全てを1つの長い会話履歴(ブラックボックス)に詰め込んでいました。pipeはこれを根本的に変え、「役割(Role)の定義」と「実行手順(Procedure)の委任」に特化した構造を採用します。
| 要素 | 対応ファイル(今回追加) | 役割(エージェント) |
|---|---|---|
| 役割の定義 | roles/games/reversi_player.md | リバーシプレイヤー(ワーカーエージェント):純粋に「最善の手を考える」という専門的なタスクに集中します。 |
| 実行手順の委任 | procedures/reversi_game.md | ゲームコンダクター(マネージャーエージェント):ゲームのルールを把握し、手順に従って「次の手番のエージェントにタスクを委任する」というマネジメントタスクを実行します。 |
この設計により、メインの実行ロジック(takt.py)は、「どのRoleに」「どのProcedureを実行させるか」を制御するだけのシンプルなパイプ役に徹することができます。
2. 実装:リバーシ・デモのファイル構造
リバーシ・デモを実現するために、新たに導入された2つのMarkdownファイルは、それぞれ以下のような明確な指示をLLMに与えます。
A. roles/games/reversi_player.md
このファイルは、エージェントに 「リバーシのプレイヤー」というペルソナ を与えます。
- 役割: 特定のプレイヤー(白または黒)として振る舞う。
- タスク: 現在の盤面と有効な手(可能な移動)を分析する。
-
出力ルール: 最善と判断した手を厳密なJSON形式(例:
{"action": "move", "position": "C4"})で出力することのみを求めます。これにより、エージェントの応答が一貫し、後続のプログラム(コンダクター)が容易に処理できるようになります。
B. procedures/reversi_game.md
このファイルは、エージェントに 「リバーシのゲーム進行役(コンダクター)」という手順 を与えます。
-
手順(フロー):
- 現在の盤面と手番を提示する。
- 次に手を指すエージェント(例:
reversi_player.md)に対して、デリゲーション(委任) コマンドを発行する。 - デリゲーションコマンドの実行結果(プレイヤーのJSON応答)を受け取る。
- ルールに基づいて盤面を更新する。
- ゲーム終了条件をチェックし、次の手番のエージェントに対して手順を繰り返す。
- 目的: 複雑なゲームのロジック(勝敗判定、石の反転など)を、LLM自体の推論でなく、明確な手順として指示することで、ブレのない安定した実行を実現します。
3. 効果:マルチエージェントの「協調」を最小限で実現
この2ファイルのアプローチがもたらす最大の利点は、エージェント間の責任と関心の分離です。
- コンダクター(マネージャー)エージェントは、「ゲーム全体を管理し、タスクを委任する」というマネジメントに集中します。
- プレイヤー(ワーカー)エージェントは、「最善の一手を見つける」という専門タスクの実行に集中します。
これにより、LLMは巨大で曖昧なプロンプトを処理するのではなく、それぞれのコンテキストで最小限かつ最も効率的な推論を行うだけで済みます。これがpipeが目指す「構造的なLLMコンテキスト制御」であり、最小限のファイルで高度なマルチエージェントワークフローを構築する鍵となるのです。
この設計は、ゲームに限らず、ソフトウェア開発、データ分析など、複雑なタスクの分解と専門家への委任が必要なあらゆる分野に応用可能です。
リクエストを無駄に消費してしまうので、デモ自体は最後まで回さない方がいいと思いますがこんな感じで展開していきます。
│ ✓ Shell python3 takt.py --purpose "Reversi White Player" --background "Playing a game of Reversi." --roles "roles/games/reversi_player.md" --instruction "You are the White player ('W'). Wait for… │
│ │
│ /Users/s-age/.pyenv/versions/3.12.11/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:648: ArbitraryTypeWarning: <built-in function any> is not a Python type (it │
│ may b │
│ e an instance of an object), Pydantic will allow any object with no validation since we cannot even enforce that the input is an instance of the given type. To get rid of this error │
│ wrap │
│ the type with `pydantic.SkipValidation`. │
│ │
│ warnings.warn( │
│ │
│ Conductor Agent: Creating new session... │
│ │
│ New session created: f0799d4436185032cf7ac4140dddf5bd7df7d4a3302b9a9875709d9f06b89a67 │
│ │
│ │
│ │
│ Executing with Gemini API... │
│ │
│ Token Count: 807 / 1000000 tokens │
│ │
│ --- Response Received --- │
│ │
│ I am the White player ('W') and am waiting for instructions. │
│ │
│ ------------------------- │
│ │
│ │
│ │
│ Successfully added response to session f0799d4436185032cf7ac4140dddf5bd7df7d4a3302b9a9875709d9f06b89a67. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ WriteFile Writing to games/reversi_001.json │
│ │
│ 1 { │
│ 2 "game_id": "reversi_001", │
│ 3 "player_b_session_id": "8e8f5aff3e52e4cf75fecb451044ff4e0c7b3ebd2c4e19fda9ba5c700620915b", │
│ 4 "player_w_session_id": "f0799d4436185032cf7ac4140dddf5bd7df7d4a3302b9a967", │
│ 5 "board": [ │
│ 6 [".", ".", ".", ".", ".", ".", ".", "."], │
│ 7 [".", ".", ".", ".", ".", ".", ".", "."], │
│ 8 [".", ".", ".", ".", ".", ".", ".", "."], │
│ 9 [".", ".", ".", "W", "B", ".", ".", "."], │
│ 10 [".", ".", ".", "B", "W", ".", ".", "."], │
│ 11 [".", ".", ".", ".", ".", ".", ".", "."], │
│ 12 [".", ".", ".", ".", ".", ".", ".", "."], │
│ 13 [".", ".", ".", ".", ".", ".", ".", "."] │
│ 14 ], │
│ 15 "current_player": "B", │
│ 16 "status": "ongoing", │
│ 17 "turns": [] │
│ 18 } │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ The game reversi_001 has been initialized, and both Black and White player agents are ready.
Now, I will start the main game loop.
The first step is "2. a. Read Current State".
I will use read_file to load the current game state from games/reversi_001.json.
╭─────────────────────────────────────╮
│ ✓ ReadFile games/reversi_001.json │
╰─────────────────────────────────────╯
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ Shell python3 takt.py --session 8e8f5aff3e52e4cf75fecb451044ff4e0c7b3ebd2c4e19fda9ba5c700620915b --instruction "You are player 'B'. The current board is:\n[['.', '.', '.', '.', '.', '.', '.', … │
│ │
│ /Users/s-age/.pyenv/versions/3.12.11/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:648: ArbitraryTypeWarning: <built-in function any> is not a Python type (it │
│ may b │
│ e an instance of an object), Pydantic will allow any object with no validation since we cannot even enforce that the input is an instance of the given type. To get rid of this error │
│ wrap │
│ the type with `pydantic.SkipValidation`. │
│ │
│ warnings.warn( │
│ │
│ Conductor Agent: Continuing session: 8e8f5aff3e52e4cf75fecb451044ff4e0c7b3ebd2c4e19fda9ba5c700620915b │
│ │
│ │
│ │
│ Executing with Gemini API... │
│ │
│ Token Count: 1095 / 1000000 tokens │
│ │
│ --- Response Received --- │
│ │
│ [2, 3] │
│ │
│ ------------------------- │
│ │
│ │
│ │
│ Successfully added response to session 8e8f5aff3e52e4cf75fecb451044ff4e0c7b3ebd2c4e19fda9ba5c700620915b. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ WriteFile Writing to games/reversi_001.json │
│ │
│ 5 "board": [ │
│ 6 [".", ".", ".", ".", ".", ".", ".", "."], │
│ 7 [".", ".", ".", ".", ".", ".", ".", "."], │
│ 8 - [".", ".", ".", ".", ".", ".", ".", "."], │
│ 9 - [".", ".", ".", "W", "B", ".", ".", "."], │
│ 8 + [".", ".", ".", "B", ".", ".", ".", "."], │
│ 9 + [".", ".", ".", "B", "B", ".", ".", "."], │
│ 10 [".", ".", ".", "B", "W", ".", ".", "."], │
│ 11 [".", ".", ".", ".", ".", ".", ".", "."], │
│ 12 [".", ".", ".", ".", ".", ".", ".", "."], │
│ 13 [".", ".", ".", ".", ".", ".", ".", "."] │
│ 14 ], │
│ 15 - "current_player": "B", │
│ 15 + "current_player": "W", │
│ 16 "status": "ongoing", │
│ 17 - "turns": [] │
│ 17 + "turns": [{"player": "B", "move": [2, 3]}] │
│ 18 } │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────────────╮
│ ✓ ReadFile games/reversi_001.json │
╰─────────────────────────────────────╯