チュートリアル: GoogleのA2Aプロトコルを使った2つのPythonエージェントの構築ステップバイステップ
Updated on

Googleのエージェント間(A2A)プロトコルは、どのフレームワークやベンダーから来たものであるかに関係なく、異なるAIエージェントがコミュニケーションをとり共同作業を行うためのオープンスタンダードです。より簡単に言うと、A2Aはエージェントが相互にやりとりするための共通言語を提供します。このチュートリアルでは、A2Aプロトコルを使用してお互いに通信する2つの簡単なPythonエージェントを作成する方法を説明します。1つはA2Aサーバーとして機能し、もう1つはA2Aクライアントとして機能します。セットアップをカバーし、A2Aの基本的な動作を説明し、両エージェントの注釈付きコード例を提供します。A2Aの事前知識は前提とせず、複雑なツールチェーンやマルチエージェントのオーケストレーションといった高度な概念を避け、初心者向けに親しみやすくしています。
A2Aプロトコルとは?
A2Aの基本は、エージェントがメッセージ、リクエスト、およびデータを交換する方法を標準化するプロトコルです。以下はA2Aの主要な概念のいくつかです:
- エージェントカード: エージェントの機能、スキル、エンドポイントURL、認証要件を記述した公開メタデータファイル(通常
/.well-known/agent.json
にホスト)。他のエージェントやクライアントはこのカードを取得して、エージェントが何をできるかを発見します。これは、A2Aネットワーク内でのエージェントの「名刺」に相当します。 - A2Aサーバー: HTTP APIエンドポイントを公開し、A2Aメソッドを実装するエージェント(例えば、タスクを送信するためのエンドポイント)。リクエストを受け取って、他のエージェントに代わってタスクを実行します。
- A2Aクライアント: A2AサーバーのURLにリクエストを送信してタスクや会話を開始するアプリケーションまたはエージェント。エージェント間のシナリオでは、1つのエージェントのクライアントコンポーネントがA2Aを通じて別のエージェントのサーバーコンポーネントを呼び出します。
- タスク: A2Aの基本的な作業単位。クライアントはメッセージを送信することによりタスクを開始します(
tasks/send
リクエストを使用)。各タスクにはユニークなIDがあり、状態のライフサイクルを持ちます(例:submitted
,working
,input-required
,completed
, など)。 - メッセージ: クライアント(役割
"user"
)とエージェント(役割"agent"
)の間のコミュニケーションの単一ターン。クライアントのリクエストはメッセージ(“user”からの)、エージェントの答えはメッセージ(“agent”からの)として構成されます。 - パート: メッセージ内のコンテンツの一部。メッセージは1つまたは複数のパートで構成されます。パートはテキスト、ファイル、または構造化データであり、例えば、
TextPart
はプレーンテキストを持ち、ファイルパートは画像やその他のバイナリデータを持つかもしれません。このチュートリアルでは、わかりやすくするために単純なテキストパーツを使用します。
A2A通信の仕組み(基本フロー): 1つのエージェントがA2Aを介して他のエージェントと通信したい場合、そのインタラクションは通常、標準化されたフローに従います:
- 発見: クライアントエージェントは最初に、サーバーの
/.well-known/agent.json
URLからエージェントカードを取得して他のエージェントを発見します。このカードはクライアントにエージェントの名前、できること、リクエストを送信する場所を教えます。 - 開始: その後、クライアントはサーバーエージェントのA2Aエンドポイントにリクエストを送信してタスクを開始します。通常、
tasks/send
エンドポイントにPOST
として送信します。この最初のリクエストにはユニークなタスクIDと初回のユーザーメッセージ(例:質問やコマンド)が含まれます。 - 処理: サーバーエージェントはタスクリクエストを受け取り、処理します。エージェントがストリーミング応答をサポートしている場合、(Server-Sent Eventsを使用して)作業しながら中間更新をストリームバックするかもしれませんが、我々の簡単な例ではストリーミングは使用しません。それ以外の場合、エージェントはタスクを処理し、完了時に最終的な応答を送信します。応答にはタスクの結果が含まれ、通常はエージェントからの1つまたは複数のメッセージ(およびタスクの一部である場合はファイルのようなアーティファクト)が含まれます。基本的なテキストインタラクションの場合、最終応答にはエージェントの回答メッセージが含まれます。
要約すると、A2Aはエージェントのための明確なリクエスト-レスポンスサイクルを定義しています:クライアントがエージェントの機能(エージェントカード)を見つけ、タスクを送信し(初回のユーザーメッセージと共に)、エージェントの応答を受け取る(メッセージとアーティファクトとして)、すべてが一貫したJSONフォーマットで行われます。では、環境をセットアップして、これを実際に確認するための最小限の例を構築しましょう。
インストールとセットアップ
コードを書く前に、必要なツールがインストールされているか確認してください:
- Python 3.12以降 – A2Aサンプルでは互換性のためにPython 3.12+(さらにはPython 3.13)を推奨しています。
- UV(Pythonパッケージマネージャ) – これは依存関係の管理とサンプルの実行用にA2Aプロジェクトが推奨するオプションのツールです。UVはモダンなパッケージマネージャですが、pip/venvを使用して手動で依存関係をインストールすることもできます。このチュートリアルでは、シンプルに通常のPythonツールを使用します。
- Google A2Aリポジトリ – GitHubリポジトリをクローンして、公式のA2Aコードを取得します。
これによりプロジェクトがマシンにダウンロードされます。我々のチュートリアルコードはこのリポジトリの一部(特にサンプルA2Aクライアントユーティリティ)を参照します。
git clone https://github.com/google/A2A.git
- 依存関係 – UVを使用する場合、依存関係を自動で扱わせることができます。例えば、リポジトリの
samples/python
ディレクトリからuv sync
(依存関係のインストール)を実行したり、公式READMEに示されたようにuv run ...
を実行できます。pipを使用する場合は、例のコードに必要なライブラリを手動でインストールしてください。FlaskライブラリはシンプルなHTTPサーバーをエージェントに作成するために使用し、requestsはクライアントがエージェントを呼び出すために使用します。(公式のA2AサンプルコードはFastAPI/UVICornと内部クライアントライブラリを使用していますが、初心者向けのアプローチとしては、わかりやすくするためにFlaskとrequestsを使用します。)pip install flask requests
シンプルなA2Aサーバーエージェントの作成
最初にサーバーサイドを構築します:タスクの受信をリッスンし応答するエージェントです。我々のエージェントは非常にシンプルで、テキストプロンプトを受け取り、フレンドリーメッセージと共にエコーするだけです。そのシンプルさにもかかわらず、A2Aのプロトコル構造に従うため、どのA2Aクライアントでも通信可能です。
エージェントの機能とエージェントカード: エージェントが発見可能になるためには、エージェントカードを提供する必要があります。実際のデプロイメントでは、サーバーの/.well-known/agent.json
にJSONファイルをホストします。Flaskアプリの場合、このデータをエンドポイントを介して提供できます。エージェントカードには、通常、エージェントの名前、説明、A2AエンドポイントのURL、サポートされる機能(能力)などのフィールドが含まれます。我々はキーとなるいくつかのフィールドのみを持った最小限のエージェントカードを構築します。
tasks/sendエンドポイント: A2Aはエージェントが特定のAPIエンドポイントを実装することを期待します。最も重要なのはtasks/send
で、クライアントが新しいタスク(ユーザーメッセージを伴う)をエージェントに送るために呼び出すものです。我々のFlaskアプリは、これらのリクエストを処理するための/tasks/send
ルートを定義します。入力されるJSON(タスクIDとユーザーのテキストメッセージを含む)を解析し、処理して(我々の場合はエコー応答を生成し)、A2Aフォーマットに従ったJSON応答を返します。
Flaskを使用して、シンプルなA2Aサーバーエージェントのコードを書いてみましょう:
from flask import Flask, request, jsonify
app = Flask(__name__)
# Define the Agent Card data (metadata about this agent)
AGENT_CARD = {
"name": "EchoAgent",
"description": "A simple agent that echoes back user messages.",
"url": "http://localhost:5000", # The base URL where this agent is hosted
"version": "1.0",
"capabilities": {
"streaming": False, # This agent doesn't support streaming responses
"pushNotifications": False # No push notifications in this simple example
}
# (In a full Agent Card, there could be more fields like authentication info, etc.)
}
# Serve the Agent Card at the well-known URL.
@app.get("/.well-known/agent.json")
def get_agent_card():
"""Endpoint to provide this agent's metadata (Agent Card)."""
return jsonify(AGENT_CARD)
# Handle incoming task requests at the A2A endpoint.
@app.post("/tasks/send")
def handle_task():
"""Endpoint for A2A clients to send a new task (with an initial user message)."""
task_request = request.get_json() # parse incoming JSON request
# Extract the task ID and the user's message text from the request.
task_id = task_request.get("id")
user_message = ""
try:
# According to A2A spec, the user message is in task_request["message"]["parts"][0]["text"]
user_message = task_request["message"]["parts"][0]["text"]
except Exception as e:
return jsonify({"error": "Invalid request format"}), 400
# For this simple agent, the "processing" is just echoing the message back.
agent_reply_text = f"Hello! You said: '{user_message}'"
# Formulate the response in A2A Task format.
# We'll return a Task object with the final state = 'completed' and the agent's message.
response_task = {
"id": task_id,
"status": {"state": "completed"},
"messages": [
task_request.get("message", {}), # include the original user message in history
{
"role": "agent", # the agent's reply
"parts": [{"text": agent_reply_text}] # agent's message content as a TextPart
}
]
# We could also include an "artifacts" field if the agent returned files or other data.
}
return jsonify(response_task)
# Run the Flask app (A2A server) if this script is executed directly.
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
サーバーコードの理解: 上記のコードで何が起こっているのか見てみましょう:
- Flaskアプリを作成し、グローバルな
AGENT_CARD
ディクショナリを定義しました。これには我々のエージェントの基本情報が含まれています。特に、名前、説明、自身を指すURL(ローカルホストとポート5000を指す)、および能力のセクションがあります。我々のエージェントはシンプルな同期応答だけを行うためstreaming: False
を設定しました(エージェントがストリーミングをサポートする場合、tasks/sendSubscribe
を処理し、部分結果をストリームしますが、ここではカバーしません)。 /.well-known/agent.json
ルートを設定し、エージェントカードJSONを返します。これは、発見のためにエージェントのカードが見つかると期待される標準的な場所を模しています。/tasks/send
のPOSTルートを定義します。A2Aクライアントがタスクを送信すると、Flaskはhandle_task()
を呼び出します。この関数内で:- リクエストのJSONボディを
task_request
に解析します。A2Aスキーマによれば、これは"id"
(タスクID)とユーザーの最初のメッセージを表す"message"
オブジェクトを含みます。 - ユーザーのメッセージのテキストを抽出します。A2Aではメッセージには複数のパーツ(テキスト、ファイル)が含まれることがあります。ここでは最初のパーツがテキストであると仮定し、
task_request["message"]["parts"][0]["text"]
を取得します。より堅牢な実装では、パーツタイプを検証しエラーを処理します(我々のコードではシンプルなtry/exceptを行い、フォーマットが期待通りでない場合は400エラーを返します)。 - 次にエージェントの応答を生成します。我々のロジックは微妙で、ユーザーテキストを用いて
"Hello! You said: ..."
を前置します。実際のエージェントでは、ここが主要なAIロジックが行われる場所です(LLMを呼び出したり、計算を実行したりします)。 - 次に、応答Taskオブジェクトを構築します。クライアントがこれはどのタスクかを知るために
id
を返し(つまりおよび終了したタスクの状態を"completed"
として設定し、メッセージのリストを提供します。我々はオリジナルのユーザーメッセージを含め(文脈/履歴のため)、それに続いてエージェントの返事としてもう一つのメッセージを追加します。各メッセージにはrole
("user"
または"agent"
)とparts
のリストがあります。我々のエージェントのメッセージにはテキスト応答を含む単一のTextPart
を使用します。 - 最後に、これをJSONとして返します。FlaskはPythonの辞書をJSONにシリアライズしてくれます。
- リクエストのJSONボディを
このサーバーが稼働している状態で、我々のエージェントは事実上http://localhost:5000
で“ライブ”になります。エージェントカードを提供し、タスクリクエストを処理することができます。
シンプルなA2Aクライアントエージェントの作成
次に、サーバーエージェントと通信するクライアントが必要です。このクライアントは別のエージェントやユーザー向けアプリケーションでも良いです。A2A用語でそれはA2Aクライアントです。なぜなら、サーバーエージェントのA2A APIを使用するためです。チュートリアルとして、私たちは簡単なPythonスクリプトを書き、EchoAgentに質問を送信し、答えを受け取る役を果たします。
クライアントのステップ:
- エージェントの発見: クライアントはエージェントカードを取得してサーバーエージェントのエンドポイントや機能を知る必要があります。エージェントカードは
http://localhost:5000/.well-known/agent.json
にあると分かっているので、Pythonのrequests
ライブラリを使用してこのURLをGETできます。 - タスクを送信: 次に、クライアントはエージェントの
tasks/send
エンドポイントにタスクを表現するJSONペイロードをPOSTリクエストで送信します。このペイロードには以下が含まれます:- ユニークな
"id"
(タスクID、任意のユニークな文字列または番号で。例:"task1") "message"
オブジェクト(role: "user"
とユーザーのクエリを含むTextPart
から成るparts
リスト)
- ユニークな
- 応答を受け取る: エージェントはタスクを表現するJSONで応答し、エージェントの返信メッセージを含めます。クライアントはこれを読み取り、エージェントの返信を抽出する必要があります。