随着 AI Agent 系统的复杂度不断提升,单个 Agent 已经难以应对所有业务场景。一个现代化的 Agent 系统往往由多个专精 Agent 组成——有的擅长数据分析,有的精通自然语言处理,有的专门处理外部 API 调用。这些 Agent 之间需要高效、可靠、安全的通信机制,A2A(Agent to Agent)协议正是为此而生。

A2A 不是又一个 RPC 框架,它是专门为 Agent 场景设计的通信协议,内置了 Agent 发现、能力协商、任务分发、结果聚合等语义。理解 A2A 的设计哲学,是构建可扩展多 Agent 系统的关键。


为什么需要 A2A:从单体到分布式 Agent 系统

单体 Agent 的局限性

在系统早期,一个"万能 Agent"似乎是最简单的方案:

用户请求 → [万能 Agent] → 结果

但随着业务增长,这种架构很快遇到瓶颈:

  1. 职责膨胀:一个 Agent 要处理太多类型的任务,Prompt 越来越长,上下文窗口被浪费
  2. 迭代冲突:不同功能的开发团队修改同一个 Agent,频繁产生冲突
  3. 性能瓶颈:所有请求都走同一个执行路径,无法针对特定任务优化
  4. 故障隔离差:一个功能的 bug 可能导致整个 Agent 不可用
  5. 技术锁定:所有功能必须用同一种语言实现,无法利用各语言生态优势

分布式 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 │  │ 浏览器    │  │
│   └──────────┘  └──────────┘  └──────────┘  └──────────┘  │
└─────────────────────────────────────────────────────────────┘

详细对比

对比维度A2AMCP
通信方向双向对等(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 编程实战干货。