A2A (Agent2Agent) は AI エージェント同士がやり取りするためのオープンなプロトコル。 2025年4月に Google から発表されたのち Linux Foundation に寄贈され、2026年3月に v1 がリリースされた。
サンプルを pull してきて動かしてみる。
$ git clone https://github.com/a2aproject/a2a-samples.git
$ cd a2a-samples/samples/python/agents/helloworld
$ uv run .
INFO: Started server process [310015]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:9999 (Press CTRL+C to quit)
コードを見ると次のようにエージェントやスキルのメタデータが定義されている。
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.routes import (
create_agent_card_routes,
create_jsonrpc_routes,
)
from a2a.server.tasks import InMemoryTaskStore
from a2a.types import (
AgentCapabilities,
AgentCard,
AgentInterface,
AgentSkill,
)
skill = AgentSkill(
id='hello_world',
name='Returns hello world',
description='just returns hello world',
tags=['hello world'],
examples=['hi', 'hello world'],
)
public_agent_card = AgentCard(
name='Hello World Agent',
description='Just a hello world agent',
version='0.0.1',
default_input_modes=['text/plain'],
default_output_modes=['text/plain'],
capabilities=AgentCapabilities(
streaming=True, extended_agent_card=True
),
supported_interfaces=[
AgentInterface(
protocol_binding='JSONRPC',
url='http://127.0.0.1:9999',
)
],
skills=[skill],
)
request_handler = DefaultRequestHandler(
agent_executor=HelloWorldAgentExecutor(),
task_store=InMemoryTaskStore(),
agent_card=public_agent_card,
extended_agent_card=extended_agent_card,
)
routes = []
routes.extend(create_agent_card_routes(public_agent_card))
routes.extend(create_jsonrpc_routes(request_handler, '/'))
app = Starlette(routes=routes)
uvicorn.run(app, host='127.0.0.1', port=9999)
これらの情報は create_agent_card_routes() によって作られた /.well-known/agent-card.json エンドポイントで返される。 このエンドポイントには extendedAgentCard の情報は含まれず、認証後 GetExtendedAgentCard method を呼ぶことで取得できる。
$ curl http://127.0.0.1:9999/.well-known/agent-card.json | jq
{
"name": "Hello World Agent",
"description": "Just a hello world agent",
"supportedInterfaces": [
{
"url": "http://127.0.0.1:9999",
"protocolBinding": "JSONRPC"
}
],
"version": "0.0.1",
"capabilities": {
"streaming": true,
"extendedAgentCard": true
},
"defaultInputModes": [
"text/plain"
],
"defaultOutputModes": [
"text/plain"
],
"skills": [
{
"id": "hello_world",
"name": "Returns hello world",
"description": "just returns hello world",
"tags": [
"hello world"
],
"examples": [
"hi",
"hello world"
]
}
],
"preferredTransport": "JSONRPC",
"protocolVersion": "0.3",
"supportsAuthenticatedExtendedCard": true,
"url": "http://127.0.0.1:9999"
}
処理は AgentExecutor.execute() で行う。引数で EventQueue が渡されるので state や artifact を入れる。
from a2a.helpers import (
new_task_from_user_message,
new_text_artifact,
new_text_message,
)
from a2a.server.agent_execution import AgentExecutor, RequestContext
class HelloWorldAgentExecutor(AgentExecutor):
"""Test AgentProxy Implementation."""
def __init__(self) -> None:
self.agent = HelloWorldAgent()
async def execute(
self,
context: RequestContext,
event_queue: EventQueue,
) -> None:
"""Execute the agent process and enqueue the final response."""
task = context.current_task or new_task_from_user_message(
context.message
)
await event_queue.enqueue_event(task)
await event_queue.enqueue_event(
TaskStatusUpdateEvent(
task_id=context.task_id,
context_id=context.context_id,
status=TaskStatus(
state=TaskState.TASK_STATE_WORKING,
message=new_text_message('Processing request...'),
),
)
)
result = await self.agent.invoke()
await event_queue.enqueue_event(
TaskArtifactUpdateEvent(
task_id=context.task_id,
context_id=context.context_id,
artifact=new_text_artifact(name='result', text=result),
)
)
await event_queue.enqueue_event(
TaskStatusUpdateEvent(
task_id=context.task_id,
context_id=context.context_id,
status=TaskStatus(state=TaskState.TASK_STATE_COMPLETED),
)
)
async def cancel(
self, context: RequestContext, event_queue: EventQueue
) -> None:
"""Raise exception as cancel is not supported."""
raise Exception('cancel not supported')
これに対して JSON-RPC で SendMessage を呼ぶと次のような結果が返る。v1.0 で method 名が message/send から変わった。
$ curl -X POST http://127.0.0.1:9999/ \
-H "Content-Type: application/json" \
-H "A2A-Version: 1.0" \
--data '{
"jsonrpc": "2.0",
"id": "1",
"method": "SendMessage",
"params": {
"message": {
"messageId": "m1",
"role": "ROLE_USER",
"parts": [
{"text": "Hi"}
]
}
}
}' | jq
{
"result": {
"task": {
"id": "4b8a26fc-6d19-47cd-969f-299b133a3db0",
"contextId": "eeaf4afc-c905-42d6-a094-a1640eb291ca",
"status": {
"state": "TASK_STATE_COMPLETED"
},
"artifacts": [
{
"artifactId": "276f0b78-df06-44dd-982b-e495f94b22df",
"name": "result",
"parts": [
{
"text": "Hello, World!"
}
]
}
],
"history": [
{
"messageId": "m1",
"contextId": "eeaf4afc-c905-42d6-a094-a1640eb291ca",
"taskId": "4b8a26fc-6d19-47cd-969f-299b133a3db0",
"role": "ROLE_USER",
"parts": [
{
"text": "Hi"
}
]
},
{
"messageId": "992ffcdc-6460-4151-b95e-8fd02b9f9ca6",
"role": "ROLE_AGENT",
"parts": [
{
"text": "Processing request..."
}
]
}
]
}
},
"id": "1",
"jsonrpc": "2.0"
}
Accept: text/event-stream でリクエストを送ると Server Side Events によるストリーミング通信が行える。
$ curl -N -X POST http://127.0.0.1:9999/ \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-H "A2A-Version: 1.0" \
--data '{
"jsonrpc": "2.0",
"id": "1",
"method": "SendStreamingMessage",
"params": {
"message": {
"messageId": "m1",
"role": "ROLE_USER",
"parts": [
{"text": "Hi"}
]
}
}
}'
data: {"result": {"task": {"id": "d9abadef-d57e-4bf5-b66a-eeebeaf0c21e", "contextId": "e9f88d43-9465-4875-abc7-c4dac56f8b89", "status": {"state": "TASK_STATE_SUBMITTED"}, "history": [{"messageId": "m1", "contextId": "e9f88d43-9465-4875-abc7-c4dac56f8b89", "taskId": "d9abadef-d57e-4bf5-b66a-eeebeaf0c21e", "role": "ROLE_USER", "parts": [{"text": "Hi"}]}]}}, "id": "1", "jsonrpc": "2.0"}
data: {"result": {"statusUpdate": {"taskId": "d9abadef-d57e-4bf5-b66a-eeebeaf0c21e", "contextId": "e9f88d43-9465-4875-abc7-c4dac56f8b89", "status": {"state": "TASK_STATE_WORKING", "message": {"messageId": "3270cd18-7bd9-483a-bd62-cf47aa93f791", "role": "ROLE_AGENT", "parts": [{"text": "Processing request..."}]}}}}, "id": "1", "jsonrpc": "2.0"}
data: {"result": {"artifactUpdate": {"taskId": "d9abadef-d57e-4bf5-b66a-eeebeaf0c21e", "contextId": "e9f88d43-9465-4875-abc7-c4dac56f8b89", "artifact": {"artifactId": "a3f1f687-a6dd-4bfe-b5a4-012083753627", "name": "result", "parts": [{"text": "Hello, World!"}]}}}, "id": "1", "jsonrpc": "2.0"}
data: {"result": {"statusUpdate": {"taskId": "d9abadef-d57e-4bf5-b66a-eeebeaf0c21e", "contextId": "e9f88d43-9465-4875-abc7-c4dac56f8b89", "status": {"state": "TASK_STATE_COMPLETED"}}}, "id": "1", "jsonrpc": "2.0"}