返回 AI大模型从0到1——理论与实操

C3-11 A2A协议


🎯 本节目标:说清楚 A2A 协议解决了什么问题,理解 Agent Card / Message / Task 三大核心概念,对比 MCP 与 A2A 的分工关系。

引子:Agent 与 Agent 之间怎么"说话"?

引入:Agent 与外部工具会说"同一种语言"了(MCP),那 Agent 与 Agent 之间呢?你现在有两个 Agent:一个负责写代码,一个负责做测试。它们怎么"对话"?怎么传递任务?怎么知道对方"能做什么"?

这就是 A2A(Agent-to-Agent Protocol) 要解决的问题。

A2A 是什么?

A2A = Agent 之间的"通用语言"

  • Google 在 2025 年 4 月发布
  • 现已捐赠给 Linux 基金会,是完全开放的标准
  • GitHub 开源: github.com/a2aproject/A2A

A2A 与 MCP 的分工

协议 定位 通信双方
MCP Agent ↔ 工具 / 数据源 Agent 调用外部能力
A2A Agent ↔ Agent Agent 之间委托任务、交换结果

💡 类比:MCP 是"员工用什么工具",A2A 是"员工之间怎么交接工作"。一个管"人机交互",一个管"人人交互"。

A2A 架构三大核心概念

① Agent Card(智能体名片)

每个支持 A2A 的 Agent,必须暴露一个 agent-card(JSON),告诉别人"我是谁、我能做什么"。

JSON
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  "name": "代码智能体",
  "description": "编写代码的智能体",
  "url": "http://localhost:9999/",
  "capabilities": {
    "streaming": true,
    "pushNotifications": false
  },
  "skills": [
    {
      "id": "88",
      "name": "coding agent",
      "description": "编代码的智能体",
      "tags": ["编码", "代码", "coding"],
      "examples": ["编写一个 hello world 程序", "编写一个快速排序的 python 程序"]
    }
  ],
  "version": "1.0.0"
}

作用:让其他 Agent 通过 HTTP GET /.well-known/agent.json 自动发现"对方能干什么"。

② Message(消息格式)

A2A 规定了 Agent 之间传递消息的标准格式:

Plain Text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from a2a.types import Message, TextPart, MessageSendParams

# 构建一条发送给其他 Agent 的消息
message = Message(
    role="user",          # user = 发送方,agent = 接收方
    parts=[TextPart(text="帮我写一个 Python 的两数求和函数")],
    message_id="unique-id-123",
)

# 包装成请求
request = SendMessageRequest(
    id="req-001",
    params=MessageSendParams(message=message)
)

③ Task(任务生命周期)

A2A 用 Task 来管理任务状态,类似"工单系统":

Python
1
2
3
4
5
6
Task 状态流转:
  submitted(已提交)
      
  working(工作中)
      
  completed(完成)    failed(失败)    canceled(取消)
Plain Text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from a2a.types import TaskState
from a2a.server.tasks import TaskUpdater

# 在 Server 端更新任务状态
updater = TaskUpdater(event_queue, task.id, task.context_id)

# 工作中
await updater.update_status(TaskState.working, ...)

# 完成
await updater.add_artifact(parts=[...])
await updater.complete()

完整实战:Entry Agent 调用 Coding Agent

架构

Python
1
2
3
4
5
6
7
用户
   提问
Entry Agent(主控/规划)
   通过 A2A 协议发送消息
Coding Agent(专家执行者,运行在 A2A Server )
   流式返回结果
Entry Agent 收到结果,展示给用户

Step 1:实现 Coding Agent Server(被调用方)

Plain Text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# coding_agent.py
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.types import AgentCard, AgentSkill, AgentCapabilities
from a2a.server.tasks import InMemoryTaskStore
from openai import AsyncOpenAI
import os, uvicorn

# 1. 定义 Agent 技能
skill = AgentSkill(
    id="88",
    name="coding agent",
    description="编代码的智能体",
    tags=["编码", "代码", "coding"],

    examples=["编写一个 hello world 程序", "编写一个快速排序的 python 程序"],
)

# 2. 构建 Agent Card(名片)
coding_agent_card = AgentCard(
    name="代码智能体",
    description="编写代码的智能体",
    url="http://localhost:9999/",
    capabilities=AgentCapabilities(streaming=True),
    skills=[skill],
    defaultInputModes=["text"],
    defaultOutputModes=["text"],
    version="1.0.0",
)

# 3. 实现 Agent 执行逻辑
class CodingAgent:
    def __init__(self, api_key: str, base_url: str):
        self.client = AsyncOpenAI(api_key=api_key, base_url=base_url)

    async def stream(self, messages: list):
        response = await self.client.chat.completions.create(
            model="qwen-plus",
            messages=messages,
            stream=True,
        )
        async for chunk in response:
            if chunk.choices[0].delta.content:
                yield {
                    "is_final_answer": False,
                    "content": chunk.choices[0].delta.content,
                }
        yield {"is_final_answer": True, "content": "..."}

class CodingAgentExecutor:
    """A2A 要求的执行器接口"""
    async def execute(self, context, event_queue):
        task = context.current_task or new_task(context.message)
        updater = TaskUpdater(event_queue, task.id, task.context_id)

        # 调用 LLM 生成代码(流式)
        async for chunk in self.agent.stream(context.get_user_input()):
            if not chunk["is_final_answer"]:
                await updater.update_status(
                    TaskState.working,
                    new_agent_text_message(chunk["content"], ...)
                )
            else:
                await updater.add_artifact(...)
                await updater.complete()

# 4. 启动 Server
if __name__ == "__main__":
    request_handler = DefaultRequestHandler(
        agent_executor=CodingAgentExecutor(
            agent=CodingAgent(
                api_key=os.getenv("OPENAI_API_KEY"),
                base_url=os.getenv("OPENAI_BASE_URL")
            )
        ),
        task_store=InMemoryTaskStore(),
    )
    server = A2AStarletteApplication(
        agent_card=coding_agent_card,
        http_handler=request_handler,
    )
    uvicorn.run(server.build(), host="0.0.0.0", port=9999)

Step 2:实现 Entry Agent Client(调用方)

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# entry_agent.py
import httpx
from a2a.client import A2AClient, A2ACardResolver
from a2a.types import Message, TextPart, SendStreamingMessageRequest
from uuid import uuid4

class EntryAgent:
    async def invoke(self, base_url: str, prompt: str):
        """通过 A2A 协议调用远程 Agent"""
        async with httpx.AsyncClient() as httpx_client:
            # 1. 获取对方的 Agent Card(自动发现能力)
            resolver = A2ACardResolver(
                httpx_client=httpx_client,
                base_url=base_url
            )
            agent_card = await resolver.get_agent_card()

            # 2. 初始化 A2A Client
            client = A2AClient(
                httpx_client=httpx_client,
                agent_card=agent_card
            )

            # 3. 构建消息并发送(流式)
            message = Message(
                role="user",
                parts=[TextPart(text=prompt)],
                message_id=uuid4().hex,
            )
            streaming_request = SendStreamingMessageRequest(
                id=str(uuid4()),
                params=MessageSendParams(message=message)
            )

            # 4. 处理流式响应
            async for chunk in client.send_message_streaming(streaming_request):
                yield chunk

# 运行
# asyncio.run(EntryAgent().invoke("http://localhost:9999", "帮我写一个快速排序"))

A2A 消息流转完整示意图

Plain Text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[用户]  "写一个 Python 快速排序"
    
[Entry Agent]   通过 A2A Client 发现 Coding Agent  Agent Card
               构建 Message,调用 send_message_streaming()
[Coding Agent Server]
       收到请求,启动 LLM 生成代码(流式)
       通过 A2A 流式接口逐步返回生成内容
[Entry Agent]   收到流式 chunk,实时展示给用户
    
[用户]  看到代码逐步生成...

sequenceDiagram participant U as 用户 participant E as Entry Agent participant C as Coding Agent U->>E: "写一个 Python 快速排序" E->>C: A2A 协议:发现 Agent Card E->>C: 发送 Message(send_message_streaming) C->>C: LLM 流式生成代码 C-->>E: 逐步返回 chunk E-->>U: 实时展示给用户

A2A 核心要点总结

要点 说明
定位 Agent 之间的通信标准(类比 HTTP 对于网页)
核心概念 Agent Card(发现)、Message(通信)、Task(状态管理)
与 MCP 关系 互补:MCP = Agent↔工具,A2A = Agent↔Agent
当前状态 v1.0 已发布,Linux 基金会托管,主流框架正在接入
适用场景 多厂商 Agent 协作、跨框架 Agent 通信、Agent 能力市场

🎨 同一个系统内部多个Agent通信不需要用A2A协议