我们将用约 100 行 Python 写出第一个完整的 A2A 系统:一个不需要 LLM 的掷骰子 Agent(Server)+ 一个会做能力发现和流式调用的 Client。
所有代码和输出均已在 Python 3.11 + a2a-sdk 1.3.0 下实际跑通。
mkdir a2a-demo && cd a2a-demo
python3 -m venv venv
./venv/bin/pip install "a2a-sdk[http-server]" uvicorn httpx
# 本教程验证版本:a2a-sdk 1.3.0
┌──────────────────┐ ① GET /.well-known/agent-card.json ┌──────────────────┐
│ dice_client.py │ ─────────────────────────────────────────>│ dice_server.py │
│ (A2A Client) │ <───────────── AgentCard ────────────────│ (A2A Server) │
│ │ │ │
│ │ ② SendStreamingMessage("roll 3d6") │ DiceAgent │
│ │ ─────────────────────────────────────────>│ Executor │
│ │ <══ Task / 状态更新 / 产物 (SSE 流式) ════│ (纯 Python │
└──────────────────┘ │ 逻辑,无 LLM) │
└──────────────────┘
Server 端只需要实现一个 AgentExecutor(业务逻辑),SDK 负责其余一切:
HTTP 路由、JSON-RPC 解析、任务状态管理、SSE 流式。
创建一个dice_server.py:
"""实战一:最小 A2A Server —— 掷骰子 Agent(不需要 LLM)。
运行: ./venv/bin/python dice_server.py (监听 http://localhost:9999)
"""
import random
import re
import uvicorn
from starlette.applications import Starlette
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
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, TaskUpdater
from a2a.types import (
AgentCapabilities,
AgentCard,
AgentInterface,
AgentSkill,
Part,
Task,
TaskState,
TaskStatus,
)
class DiceAgentExecutor(AgentExecutor):
"""核心业务逻辑:解析 "NdM" 格式并掷骰子。"""
async def execute(
self, context: RequestContext, event_queue: EventQueue
) -> None:
# 第一个事件必须是 Task 对象本身(状态 submitted)
if context.current_task is None:
task = Task(
id=context.task_id,
context_id=context.context_id,
status=TaskStatus(state=TaskState.TASK_STATE_SUBMITTED),
history=[context.message] if context.message else [],
)
await event_queue.enqueue_event(task)
# TaskUpdater 是向事件队列发布任务状态/产物的辅助类
updater = TaskUpdater(event_queue, context.task_id, context.context_id)
await updater.start_work() # 状态 -> working
user_text = context.get_user_input() # 取出用户消息里的文本
m = re.search(r"(\d+)\s*[dD]\s*(\d+)", user_text)
n, faces = (int(m.group(1)), int(m.group(2))) if m else (1, 6)
n = min(n, 100)
rolls = [random.randint(1, faces) for _ in range(n)]
result = f"掷 {n} 个 {faces} 面骰子: {rolls},总和 = {sum(rolls)}"
# 以 Artifact(任务产物)形式返回结果
await updater.add_artifact(
[Part(text=result)], name="dice-result"
)
await updater.complete() # 状态 -> completed(终态)
async def cancel(
self, context: RequestContext, event_queue: EventQueue
) -> None:
updater = TaskUpdater(event_queue, context.task_id, context.context_id)
await updater.cancel()
def build_agent_card() -> AgentCard:
"""Agent Card:对外公开的“名片”,发布在 /.well-known/agent-card.json"""
return AgentCard(
name="Dice Agent",
description="一个会掷骰子的极简 A2A Agent,输入如 '2d6' 即掷 2 个 6 面骰。",
version="1.0.0",
supported_interfaces=[
AgentInterface(
url="http://localhost:9999/",
protocol_binding="JSONRPC",
protocol_version="1.0",
)
],
capabilities=AgentCapabilities(streaming=True),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[
AgentSkill(
id="roll_dice",
name="掷骰子",
description="按 NdM 格式掷 N 个 M 面骰子并返回结果",
tags=["dice", "game"],
examples=["roll 2d6", "掷 3d20"],
)
],
)
def main() -> None:
card = build_agent_card()
handler = DefaultRequestHandler(
agent_executor=DiceAgentExecutor(),
task_store=InMemoryTaskStore(),
agent_card=card,
)
app = Starlette(
routes=[
*create_agent_card_routes(card), # GET /.well-known/agent-card.json
*create_jsonrpc_routes(handler, rpc_url="/"), # POST / (JSON-RPC 2.0)
]
)
uvicorn.run(app, host="0.0.0.0", port=9999, log_level="warning")
if __name__ == "__main__":
main()
关键点:
supported_interfaces)、技能、流式能力;start_work/add_artifact/
complete/failed/requires_input…);DefaultRequestHandler + create_jsonrpc_routes:把 Executor 接到
JSON-RPC 端点上;create_agent_card_routes 负责发布名片。运行这个脚本:
./venv/bin/python dice_server.py # 终端 1
然后访问浏览器的 http://localhost:9999/.well-known/agent-card.json 路径:

创建一个dice_client.py:
"""实战一:A2A Client —— 发现 Agent、发送消息、接收流式结果。
运行: ./venv/bin/python dice_client.py
"""
import asyncio
import uuid
from a2a.client import A2ACardResolver, create_client
import httpx
from a2a.types import Message, Part, Role, SendMessageRequest, TaskState
BASE_URL = "http://localhost:9999"
async def main() -> None:
# 1) 能力发现:拉取 /.well-known/agent-card.json
async with httpx.AsyncClient() as http:
resolver = A2ACardResolver(http, BASE_URL)
card = await resolver.get_agent_card()
print(f"[发现] Agent: {card.name} v{card.version}")
print(f"[发现] 描述: {card.description}")
print(f"[发现] 技能: {[s.name for s in card.skills]}")
print(f"[发现] 支持流式: {card.capabilities.streaming}")
# 2) 创建客户端并发送消息(SDK 自动选择 JSON-RPC binding)
client = await create_client(card)
request = SendMessageRequest(
message=Message(
message_id=str(uuid.uuid4()),
role=Role.ROLE_USER,
parts=[Part(text="帮我 roll 3d6")],
)
)
# 3) 迭代流式响应(Task / status_update / artifact_update 事件)
print("\n[发送] 帮我 roll 3d6")
async for event in client.send_message(request):
if event.HasField("task"):
print(f"[事件] Task 创建: id={event.task.id[:8]}... "
f"状态={TaskState.Name(event.task.status.state)}")
elif event.HasField("status_update"):
su = event.status_update
print(f"[事件] 状态更新: {TaskState.Name(su.status.state)}")
elif event.HasField("artifact_update"):
au = event.artifact_update
text = "".join(p.text for p in au.artifact.parts if p.HasField("text"))
print(f"[事件] 产物 '{au.artifact.name}': {text}")
await client.close()
if __name__ == "__main__":
asyncio.run(main())
关键点:
A2ACardResolver:拉取并解析 Agent Card(能力发现);create_client(card):根据卡片自动选择传输绑定(这里是 JSON-RPC);client.send_message() 返回异步迭代器:Server 支持流式时走 SSE,
不支持时 SDK 自动降级为单次响应——Client 代码不用改。运行 Client:
./venv/bin/python dice_client.py
Client 输出:

也可以不用 SDK,直接 curl 裸调 JSON-RPC:
curl -s http://localhost:9999/ -X POST \
-H 'Content-Type: application/json' -H 'A2A-Version: 1.0' \
-d '{"jsonrpc":"2.0","id":1,"method":"SendMessage",
"params":{"message":{"messageId":"msg-001","role":"ROLE_USER",
"parts":[{"text":"roll 2d6"}]}}}'
返回:
