A2A 协议介绍:跨 Agent 通信的通用语言
Table of Contents
A2A 协议介绍:跨 Agent 通信的通用语言
随着 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 编程实战干货。
