随着 AI Agent 系统的复杂度不断提升,单个 Agent 已经难以应对所有业务场景。一个现代化的 Agent 系统往往由多个专精 Agent 组成——有的擅长数据分析,有的精通自然语言处理,有的专门处理外部 API 调用。这些 Agent 之间需要高效、可靠、安全的通信机制,A2A(Agent to Agent)协议正是为此而生。
A2A 不是又一个 RPC 框架,它是专门为 Agent 场景设计的通信协议,内置了 Agent 发现、能力协商、任务分发、结果聚合等语义。理解 A2A 的设计哲学,是构建可扩展多 Agent 系统的关键。
为什么需要 A2A:从单体到分布式 Agent 系统
单体 Agent 的局限性
在系统早期,一个"万能 Agent"似乎是最简单的方案:
用户请求 → [万能 Agent] → 结果
但随着业务增长,这种架构很快遇到瓶颈:
- 职责膨胀:一个 Agent 要处理太多类型的任务,Prompt 越来越长,上下文窗口被浪费
- 迭代冲突:不同功能的开发团队修改同一个 Agent,频繁产生冲突
- 性能瓶颈:所有请求都走同一个执行路径,无法针对特定任务优化
- 故障隔离差:一个功能的 bug 可能导致整个 Agent 不可用
- 技术锁定:所有功能必须用同一种语言实现,无法利用各语言生态优势
分布式 Agent 架构
A2A 支持的分布式架构将系统拆分为多个专精 Agent:
┌─────────────────┐
│ Orchestrator │
│ Agent (调度) │
└────────┬────────┘
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Data Agent │ │ NLP Agent │ │ API Agent │
│ (数据处理) │ │ (语言理解) │ │ (外部调用) │
└──────────────┘ └──────────────┘ └──────────────┘
▲ ▲ ▲
└──────────────────┼──────────────────┘
│
┌────────┴────────┐
│ A2A Protocol │
│ (Agent 通信层) │
└─────────────────┘
核心优势:
| 维度 | 单体 Agent | 分布式 Agent (A2A) |
|---|---|---|
| 职责 | 臃肿 | 单一职责,高内聚 |
| 迭代速度 | 慢(互相影响) | 快(独立部署) |
| 性能 | 无法针对性优化 | 各 Agent 独立扩缩容 |
| 容错 | 单点故障 | 部分故障不影响全局 |
| 技术栈 | 锁定单一语言 | 多语言自由组合 |
| 复用性 | 低 | 高(Agent 即服务) |
A2A vs MCP:互补而非竞争
A2A 和 MCP(Model Context Protocol)经常被混淆,但它们是解决不同层面问题的协议:
定位差异
┌─────────────────────────────────────────────────────────────┐
│ Agent 系统架构 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ A2A 层 │ │
│ │ Agent ↔ Agent 通信 │ │
│ │ (任务分发、结果聚合、能力协商) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Agent A │ │ Agent B │ │ Agent C │ │
│ │ (Go) │ │ (Python) │ │ (Java) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ MCP 层 │ │
│ │ Agent ↔ 外部工具 通信 │ │
│ │ (文件系统、数据库、API、浏览器) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 文件系统 │ │ 数据库 │ │ 外部 API │ │ 浏览器 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
详细对比
| 对比维度 | A2A | MCP |
|---|---|---|
| 通信方向 | 双向对等(Agent ↔ Agent) | 单向调用(Agent → 工具) |
| 通信对象 | 其他 Agent | 外部工具/系统 |
| 核心语义 | 任务协商、能力发现、结果流式返回 | 工具调用、上下文传递 |
| 生命周期 | 长期会话,多轮交互 | 单次调用,即时返回 |
| 状态管理 | 有状态(Session 保持) | 通常无状态 |
| 错误处理 | 复杂(重试、降级、超时协商) | 简单(成功/失败) |
| 典型场景 | 多 Agent 协作、任务编排 | 文件读写、数据库查询、API 调用 |
| 协议层次 | 应用层协议 | 工具接口协议 |
实际协作示例
用户:"分析这份销售数据,生成报告并发邮件给团队"
Orchestrator Agent
│
├── A2A ──→ Data Agent (Go)
│ │
│ ├── MCP ──→ 读取 Excel 文件
│ ├── MCP ──→ 执行数据分析
│ └── A2A ──→ 返回分析结果
│
├── A2A ──→ Report Agent (Python)
│ │
│ ├── MCP ──→ 生成 PDF 报告
│ └── A2A ──→ 返回报告 URL
│
└── A2A ──→ Email Agent (Java)
│
├── MCP ──→ 发送邮件
└── A2A ──→ 返回发送状态
Orchestrator Agent ──→ 汇总结果返回用户
在这个流程中:
- A2A 负责 Agent 之间的任务分发和结果收集
- MCP 负责每个 Agent 与外部工具的具体交互
两者缺一不可,共同构成完整的 Agent 系统通信栈。
A2A 协议核心概念
Agent Card(Agent 名片)
每个暴露 A2A 服务的 Agent 都需要提供 Agent Card,描述自己的能力、接口和认证方式:
{
"name": "data-processor",
"version": "1.2.0",
"description": "数据处理专家,支持 CSV/Excel/JSON 的分析和转换",
"url": "https://agent.example.com/a2a",
"capabilities": {
"streaming": true,
"pushNotifications": false
},
"skills": [
{
"id": "csv-analysis",
"name": "CSV 数据分析",
"description": "分析 CSV 文件,返回统计摘要和可视化建议",
"inputModes": ["text", "file"],
"outputModes": ["text", "file"]
},
{
"id": "excel-conversion",
"name": "Excel 格式转换",
"description": "将 Excel 转换为其他格式(CSV/JSON/PDF)",
"inputModes": ["file"],
"outputModes": ["file"]
}
],
"authentication": {
"type": "apiKey",
"header": "X-API-Key"
}
}
Task(任务)
A2A 中的基本工作单元是 Task,它有自己的生命周期:
Task 生命周期:
submitted → working → input-required → working → completed
↓ ↑
failed ←───────┘
cancelled
// A2A Task 结构(简化)
type Task struct {
ID string `json:"id"`
SessionID string `json:"sessionId"`
Status TaskStatus `json:"status"`
Input map[string]interface{} `json:"input"`
Output map[string]interface{} `json:"output,omitempty"`
Artifacts []Artifact `json:"artifacts,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
CompletedAt *time.Time `json:"completedAt,omitempty"`
}
type TaskStatus string
const (
TaskStatusSubmitted TaskStatus = "submitted"
TaskStatusWorking TaskStatus = "working"
TaskStatusInputRequired TaskStatus = "input-required"
TaskStatusCompleted TaskStatus = "completed"
TaskStatusFailed TaskStatus = "failed"
TaskStatusCancelled TaskStatus = "cancelled"
)
Message(消息)
Task 执行过程中的通信单元:
type Message struct {
Role MessageRole `json:"role"` // user / agent
Parts []Part `json:"parts"` // 消息内容片段
Timestamp time.Time `json:"timestamp"`
}
type MessageRole string
const (
RoleUser MessageRole = "user"
RoleAgent MessageRole = "agent"
)
type Part struct {
Type string `json:"type"` // text / file / data
Data interface{} `json:"data"`
}
快速开始:启用 A2A 支持
服务端:暴露 Agent
package main
import (
"context"
"fmt"
"log"
"net/http"
"time"
"google.golang.org/adk/a2a"
"google.golang.org/adk/agent"
)
func main() {
ctx := context.Background()
// 创建 Agent
myAgent, err := agent.New(agent.Config{
Name: "data-processor",
Model: model,
Instruction: "你是一个数据处理专家...",
})
if err != nil {
log.Fatal(err)
}
// 创建 A2A 服务器
server, err := a2a.NewServer(ctx,
a2a.WithAgent(myAgent),
a2a.WithPort(8080),
a2a.WithAgentCard(a2a.AgentCard{
Name: "data-processor",
Version: "1.2.0",
Description: "数据处理专家,支持 CSV/Excel/JSON",
URL: "http://localhost:8080/a2a",
Capabilities: a2a.Capabilities{
Streaming: true,
PushNotifications: false,
},
Skills: []a2a.Skill{
{
ID: "csv-analysis",
Name: "CSV 数据分析",
Description: "分析 CSV 文件,返回统计摘要",
InputModes: []string{"text", "file"},
OutputModes: []string{"text", "file"},
},
},
}),
a2a.WithAuth(a2a.APIKeyAuth{
Header: "X-API-Key",
Validator: func(key string) bool {
// 生产环境:查询数据库或调用认证服务
return key == "production-api-key" || key == "staging-api-key"
},
}),
a2a.WithRateLimit(100, time.Minute), // 每分钟 100 请求
a2a.WithCORS(a2a.CORSConfig{
AllowedOrigins: []string{"https://orchestrator.example.com"},
AllowedMethods: []string{"POST", "GET", "OPTIONS"},
}),
)
if err != nil {
log.Fatal(err)
}
// 启动服务器(支持优雅关闭)
log.Println("A2A server starting on :8080")
if err := server.Serve(); err != nil {
log.Fatal(err)
}
}
客户端:调用外部 Agent
package main
import (
"context"
"fmt"
"log"
"time"
"google.golang.org/adk/a2a/client"
)
func main() {
ctx := context.Background()
// 创建 A2A 客户端
c, err := a2aclient.New(ctx,
a2aclient.WithURL("http://data-agent:8080/a2a"),
a2aclient.WithAPIKey("production-api-key"),
a2aclient.WithTimeout(30*time.Second),
a2aclient.WithRetry(3, time.Second), // 失败重试 3 次
)
if err != nil {
log.Fatal(err)
}
defer c.Close()
// 1. 获取 Agent Card(能力发现)
card, err := c.GetAgentCard(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Agent: %s v%s\n", card.Name, card.Version)
fmt.Printf("Skills: %d\n", len(card.Skills))
// 2. 发送任务
task, err := c.SendTask(ctx, &a2a.Task{
Input: map[string]interface{}{
"skill": "csv-analysis",
"data": "file://uploads/sales.csv",
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Task submitted: %s (status: %s)\n", task.ID, task.Status)
// 3. 轮询任务状态(或使用流式更新)
for task.Status != a2a.TaskStatusCompleted && task.Status != a2a.TaskStatusFailed {
time.Sleep(time.Second)
task, err = c.GetTask(ctx, task.ID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Task status: %s\n", task.Status)
}
// 4. 获取结果
if task.Status == a2a.TaskStatusCompleted {
fmt.Printf("Result: %v\n", task.Output)
} else {
fmt.Printf("Task failed: %v\n", task.Output)
}
}
A2A 与 HTTP API 的本质区别
| 特性 | A2A | 通用 HTTP API |
|---|---|---|
| 设计目标 | Agent 间协作 | 通用数据交换 |
| 语义层 | 任务生命周期、能力协商 | CRUD 操作 |
| 状态管理 | 内置 Task 状态机 | 通常无状态 |
| 流式支持 | 原生支持(SSE/WebSocket) | 需额外实现 |
| 错误处理 | 结构化错误码 + 重试语义 | 依赖 HTTP 状态码 |
| 认证 | Agent 级别(API Key / OAuth) | 用户级别 |
| 发现机制 | Agent Card 自动发现 | 手动配置或 Swagger |
| 多模态 | 原生支持文本/文件/数据 | 需自定义格式 |
关键洞察:A2A 不是替代 HTTP API,而是在 HTTP 之上构建了 Agent 协作的语义层。你可以把 A2A 看作"Agent 领域的 gRPC"——它定义了标准的消息格式和交互模式,让不同团队开发的 Agent 能够无缝协作。
生产环境考量
服务发现
在多 Agent 系统中,Agent 需要动态发现彼此:
// 使用 Consul 进行服务发现
type AgentRegistry struct {
client *api.Client
}
func (r *AgentRegistry) RegisterAgent(card *a2a.AgentCard) error {
service := &api.AgentServiceRegistration{
ID: card.Name,
Name: "a2a-agent",
Tags: []string{"a2a", "v1"},
Port: 8080,
Address: "agent.internal",
Check: &api.AgentServiceCheck{
HTTP: fmt.Sprintf("%s/health", card.URL),
Interval: "10s",
Timeout: "5s",
},
Meta: map[string]string{
"agent_card_url": fmt.Sprintf("%s/agent.json", card.URL),
},
}
return r.client.Agent().ServiceRegister(service)
}
func (r *AgentRegistry) DiscoverAgents(skill string) ([]*a2a.AgentCard, error) {
services, _, err := r.client.Health().Service("a2a-agent", "", true, nil)
if err != nil {
return nil, err
}
var agents []*a2a.AgentCard
for _, svc := range services {
// 获取 Agent Card
cardURL := svc.Service.Meta["agent_card_url"]
card, err := fetchAgentCard(cardURL)
if err != nil {
continue
}
// 过滤具备指定 skill 的 Agent
for _, s := range card.Skills {
if s.ID == skill {
agents = append(agents, card)
break
}
}
}
return agents, nil
}
熔断与降级
// 使用 gobreaker 实现熔断
type CircuitBreakerClient struct {
client *a2aclient.Client
breaker *gobreaker.CircuitBreaker
}
func NewCircuitBreakerClient(client *a2aclient.Client) *CircuitBreakerClient {
settings := gobreaker.Settings{
Name: "a2a-client",
MaxRequests: 100,
Interval: time.Minute,
Timeout: 5 * time.Second,
ReadyToTrip: func(counts gobreaker.Counts) bool {
failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
return counts.Requests >= 10 && failureRatio >= 0.5
},
OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) {
log.Printf("circuit breaker %s: %s -> %s", name, from, to)
},
}
return &CircuitBreakerClient{
client: client,
breaker: gobreaker.NewCircuitBreaker(settings),
}
}
func (c *CircuitBreakerClient) Call(ctx context.Context, req *a2a.Task) (*a2a.Task, error) {
result, err := c.breaker.Execute(func() (interface{}, error) {
return c.client.SendTask(ctx, req)
})
if err != nil {
return nil, err
}
return result.(*a2a.Task), nil
}
常见问题深度解析
Q:A2A 和 HTTP API 有什么区别?
A2A 在 HTTP 之上构建了 Agent 协作的语义层:
- 任务语义:A2A 的 Task 有完整的状态机(submitted → working → completed/failed),HTTP API 通常只是单次请求-响应
- 能力发现:A2A 通过 Agent Card 自动发现其他 Agent 的能力,HTTP API 需要手动维护接口文档
- 流式交互:A2A 原生支持流式结果返回(SSE),适合 Agent 的渐进式输出
- 多模态:A2A 的消息格式原生支持文本、文件、结构化数据
Q:可以同时用 A2A 和 MCP 吗?
强烈建议同时使用,它们解决不同层面的问题:
Agent A (Orchestrator)
│
├── A2A ──→ Agent B (数据处理) ──→ MCP ──→ 数据库
│
├── A2A ──→ Agent C (NLP) ──→ MCP ──→ 文件系统
│
└── A2A ──→ Agent D (API) ──→ MCP ──→ 外部 REST API
- A2A:Agent 之间的 orchestration 层
- MCP:Agent 与外部世界的工具层
两者互补,共同构成完整的 Agent 系统通信架构。
下一步
A2A 理念理解了,接下来看如何暴露 Go Agent 给外部调用。
← Cloud Run / GKE 部署 | Exposing →
想跟着学更多 Go ADK 实战?关注「全栈之巅-梦兽编程」公众号,每周更新 Go / AI 编程实战干货。
