Pydantic AIで作る！AIエージェントによるじゃんけんバトル 🤖✂️📄🪨

巷ではAIエージェントのファクトチェック機能などが急速に進化していますが、「じゃんけんくらいはできるのでは？」と思い、試してみました。LLMを使わなくてもプログラムで実装できますが、今回はシンプルなケースとしてLLMを用いて試してみました。

![janken-ai-diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/cd99c891-a74c-45a8-ab9e-f5d332c7359e.png)

## はじめに

「AIに推論させる」といえば、情報検索や文章生成が一般的ですが、今回はちょっと変わったアプローチで、複数のAIエージェントを作成して「じゃんけん」をさせてみました。さらに、別のAIエージェントを審判として配置し、勝敗を判定させるシステムを構築しました。

この記事では、Pydantic AIライブラリを使用して、以下のようなシステムを実装します：
- マルチエージェントシステム（複数のエージェントが協調動作）
- 構造化出力による型安全な実装

## なぜPydantic AI？

Pydantic AIは、LLMとのやり取りをより構造化し、型安全にできるライブラリです。以下のような特徴があります：

- 型による安全な出力の定義
- 複数のエージェントの管理が容易
- エージェント間での情報の受け渡しがシンプル

## 環境構築

まずは必要なライブラリをインストールします。Google Colaboratoryでの実行を想定しています。

```python
!pip -q install pydantic-ai
!pip -q install nest_asyncio

import nest_asyncio
nest_asyncio.apply()

print("Setup complete!")
```

次に、APIキーを設定します。この例ではGemini APIを使用していますが、他のLLMプロバイダーも使用可能です。

```python
from google.colab import userdata
import os

os.environ["GEMINI_API_KEY"] = userdata.get('GEMINI_API_KEY')
from pydantic_ai import Agent
from pydantic_ai.models.gemini import GeminiModel

model = GeminiModel('gemini-2.0-flash', provider='google-gla')
```

## 基本：構造化出力

まずはPydantic AIの基本的な使い方として、構造化された出力を得る例を見てみましょう。

```python
from pydantic import BaseModel
from pydantic_ai import Agent

class CityLocation(BaseModel):
    city: str
    country: str

# CityLocation で構造化
agent = Agent(
    model,
    result_type=CityLocation
)

# Q1: 2012年のオリンピック開催都市はどこ？
question1 = "Where were the olympics held in 2012?"
result1 = agent.run_sync(question1)

print("[Q1]", question1)
print("[A1]", result1.data)   # -> CityLocation型
print("Usage1:", result1.usage())
```

このように、LLMの出力をPydanticモデルとして定義することで、型安全な結果を得ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/6f808166-6cbc-49d3-a676-249fb1ae2442.png)

## 実装: マルチエージェントによるじゃんけん

### プレイヤーエージェントの定義

まず、じゃんけんをするプレイヤーエージェントを作成します。

```python
system_prompt_player = """\
あなたはじゃんけんプレイヤーです。「rock」（グー）、「paper」（パー）、または「scissors」（チョキ）のいずれかを
選んでください。選択したら、単にその選択だけを返してください。
例: 「rock」または「paper」または「scissors」
"""

# プレイヤーエージェントを作成
player_a = Agent(
    model,
    deps_type=str,
    system_prompt=system_prompt_player,
)

player_b = Agent(
    model,
    deps_type=str,
    system_prompt=system_prompt_player,
)
```

### 審判エージェントの定義

次に、勝敗を判定する審判エージェントを作成します。

```python
system_prompt_referee = """\
あなたはじゃんけん（rock-paper-scissors）の審判です。
2人のプレイヤーの選択を比較し、ルールに基づいて勝者を判定します。

ルール:
- Rock（グー）はScissors（チョキ）に勝つ
- Scissors（チョキ）はPaper（パー）に勝つ
- Paper（パー）はRock（グー）に勝つ
- 同じ選択の場合は引き分け

プレイヤー1の名前と選択、プレイヤー2の名前と選択が与えられるので、
それに基づいて勝者を判定し、結果を説明してください。
"""

# 審判エージェントを作成
referee = Agent(
    model,
    deps_type=dict,
    system_prompt=system_prompt_referee,
)
```

### じゃんけんの実行

実際にじゃんけんを実行してみましょう。

```python
# プレイヤーAの選択を取得
player_a_result = player_a.run_sync('じゃんけんをしましょう。何を出しますか？', deps='Yashar')
player_a_choice = player_a_result.output.strip().lower()

# プレイヤーBの選択を取得
player_b_result = player_b.run_sync('じゃんけんをしましょう。何を出しますか？', deps='Anne')
player_b_choice = player_b_result.output.strip().lower()

print(f"プレイヤーA (Yashar)の選択: {player_a_choice}")
print(f"プレイヤーB (Anne)の選択: {player_b_choice}")

# 審判に判定してもらう - 両プレイヤーの情報を明示的に渡す
referee_input = f"""
プレイヤー1の名前: Yashar
プレイヤー1の選択: {player_a_choice}
プレイヤー2の名前: Anne
プレイヤー2の選択: {player_b_choice}

上記の情報に基づいて、勝者を判定してください。
"""

referee_result = referee.run_sync(referee_input, deps={})

print("\n審判の判定:")
print(referee_result.output)
```

### 実行結果

```
プレイヤーA (Yashar)の選択: paper
プレイヤーB (Anne)の選択: rock

審判の判定:
Yashar (paper) は Anne (rock) に勝ちます。なぜなら、Paper（パー）はRock（グー）に勝つからです。

**結果：Yashar の勝ち！**
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/f18cee4a-a1e0-4dba-ae1a-9cb34617cd20.png)

## 実装のポイント

### 1. エージェントの独立性

各エージェントは独自のシステムプロンプトと役割を持ち、それぞれが独立して動作します。

### 2. 責任の分離

- プレイヤーエージェント：じゃんけんの手を選択する責任
- 審判エージェント：勝敗を判定する責任

このように責任を明確に分離することで、シンプルで理解しやすいシステムになります。

### 3. deps（依存関係）の活用

`deps`パラメータを使用して、エージェントに追加のコンテキスト（プレイヤー名など）を渡すことができます。

### 4. 型安全性

Pydanticの型システムを活用することで、エージェント間のデータやり取りが型安全になります。

## マルチエージェントアプローチの利点

マルチエージェントアプローチでは、各エージェントが独立した役割を持つことで責任が明確に分離されます。プレイヤーは手の選択に、審判は勝敗判定に専念できるため、システムの拡張や変更が容易になります。また、実際のじゃんけんゲームと同じ構造を再現しているため、コードの理解が直感的になります。

## 拡張のアイデア

このシステムは様々な方向に拡張できます。複数のプレイヤーによるトーナメント戦の実装、対戦履歴を分析して相手の傾向を学習する戦略的なエージェントの開発、StreamlitやGradioを使用した対話型UIの追加などが考えられます。さらに、あいこが続いた場合の特殊ルールの導入や、勝率・手の使用頻度の統計分析を行うことで、より高度なゲームシステムに発展させることができます。

## まとめ

![summary-diagram-svg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/71773c27-c367-426d-ba24-a63efb6b0700.png)

AIエージェントが急速に進化する中、「じゃんけん」という誰もが知るシンプルなゲームでその可能性を探ってみました。プログラムで簡単に実装できる内容ですが、あえてLLMとPydantic AIを使うことで、マルチエージェントシステムの本質が見えてきます。

各エージェントに明確な役割を与え、責任を分離することで、単純なじゃんけんも拡張性の高いシステムに変貌します。この考え方は、カスタマーサポートやデータ処理パイプラインなど、実務での複雑な問題解決にも応用できる強力なアプローチです。

ぜひこのシンプルな例を出発点に、独自のAIエージェントシステムを作ってみてください！

## 参考資料

- [Pydantic AI公式ドキュメント](https://ai.pydantic.dev/)
- [Pydantic公式ドキュメント](https://docs.pydantic.dev/)
- [GitHub - Pydantic AI](https://github.com/pydantic/pydantic-ai)

## 感想

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/578beb2a-4c0a-4223-915a-a9834632eeb0.png)

Pydantic AIでじゃんけんAIを作ってみて感じたのは、「プログラムなのか自然言語なのか」「非構造なのか構造化なのか」の境界線のコントロールが想像以上に難しいということ。
こんなシンプルなゲームでも、どこまでLLMに任せてどこからコードで制御するか、その判断が設計の要になる。
AIシステム設計の本質的な難しさと面白さがここにある気がする。