A2A Integration 深入解析:构建跨平台Agent通信协议

A2A(Agent-to-Agent)协议为Agent之间的通信提供了标准化方案。本文将深入剖析A2A协议的设计原理、服务端实现、客户端集成,以及安全认证机制。

环境准备

本文示例代码基于以下技术栈:

组件 版本要求
JDK 17+
Spring Boot 3.2+
Spring AI 2.0.0-M3+
spring-ai-agent-utils 0.7.0

Maven依赖

xml 复制代码
<dependencies>
    <!-- Spring AI 核心 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-core</artifactId>
        <version>2.0.0-M3</version>
    </dependency>
    
    <!-- Spring AI Agent Utils -->
    <dependency>
        <groupId>org.springaicommunity</groupId>
        <artifactId>spring-ai-agent-utils</artifactId>
        <version>0.7.0</version>
    </dependency>
    
    <!-- Spring WebFlux (用于响应式HTTP客户端) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
</dependencies>

一、A2A协议概述

1.1 当前Agent生态的碎片化

1.2 A2A协议的设计目标

核心理念:像HTTP统一Web通信一样,统一Agent间通信


二、协议规范详解

2.1 核心概念

概念 说明 类比
Agent Card Agent元数据描述 OpenAPI Spec
Task 执行单元 HTTP Request
Artifact 执行产物 HTTP Response Body
Message 通信消息 WebSocket Message
Part 消息部分 MIME Part

2.2 Agent Card规范

Agent Card是Agent的"身份证",描述其能力、接口和元数据:

json 复制代码
{
  "schemaVersion": "1.0",
  "agentId": "payment-expert-agent",
  "name": "Payment Expert Agent",
  "description": "处理支付系统相关任务的专业Agent",
  "version": "2.1.0",
  "provider": {
    "name": "Deep Blue Team",
    "url": "https://deepblue.example.com",
    "contact": "support@deepblue.example.com"
  },
  "capabilities": {
    "streaming": true,
    "pushNotifications": true,
    "stateTransitionHistory": true,
    "artifacts": true
  },
  "authentication": {
    "schemes": ["bearer", "api_key"],
    "credentialsUrl": "https://auth.deepblue.example.com/credentials"
  },
  "endpoints": {
    "task": "https://payment-agent.example.com/a2a/tasks",
    "taskSubscribe": "wss://payment-agent.example.com/a2a/tasks/subscribe",
    "agentCard": "https://payment-agent.example.com/.well-known/agent.json"
  },
  "skills": [
    {
      "id": "payment-integration",
      "name": "支付网关集成",
      "description": "集成支付宝、微信支付、Stripe等支付网关",
      "tags": ["payment", "integration", "gateway"],
      "inputSchema": {
        "type": "object",
        "properties": {
          "gateway": {
            "type": "string",
            "enum": ["alipay", "wechat", "stripe"]
          },
          "action": {
            "type": "string",
            "enum": ["create", "query", "refund"]
          },
          "orderId": { "type": "string" },
          "amount": { "type": "number" }
        },
        "required": ["gateway", "action"]
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean" },
          "transactionId": { "type": "string" },
          "message": { "type": "string" }
        }
      }
    },
    {
      "id": "order-management",
      "name": "订单状态管理",
      "description": "管理订单生命周期状态转换"
    }
  ],
  "metadata": {
    "language": "zh-CN",
    "region": "CN",
    "responseTime": {
      "p50": "500ms",
      "p95": "2s",
      "p99": "5s"
    },
    "rateLimit": {
      "requests": 100,
      "window": "1m"
    }
  }
}

Agent Card字段详解

字段路径 必需 说明
schemaVersion 协议版本
agentId Agent唯一标识
name 显示名称
description 功能描述
version Agent版本
provider 提供者信息
capabilities 能力声明
authentication.schemes 认证方式
endpoints.task 任务提交端点
skills 技能列表
skills[].inputSchema 输入JSON Schema
skills[].outputSchema 输出JSON Schema

2.3 Task生命周期

2.4 Task Request/Response格式

Task Request

json 复制代码
{
  "id": "task-550e8400-e29b-41d4-a716-446655440000",
  "sessionId": "session-123",
  "skillId": "payment-integration",
  "input": {
    "gateway": "alipay",
    "action": "create",
    "orderId": "ORD-2024-001",
    "amount": 99.99,
    "currency": "CNY"
  },
  "metadata": {
    "priority": "high",
    "deadline": "2024-12-31T23:59:59Z",
    "callbackUrl": "https://client.example.com/callback"
  },
  "requestedOutputModes": ["text", "artifact"]
}

Task Response

json 复制代码
{
  "id": "task-550e8400-e29b-41d4-a716-446655440000",
  "sessionId": "session-123",
  "status": "COMPLETED",
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-15T10:30:45Z",
  "output": {
    "text": "支付订单创建成功,交易号: ALI2024011510304512345678",
    "artifacts": [
      {
        "id": "artifact-001",
        "name": "payment-qr-code.png",
        "type": "image/png",
        "content": "base64-encoded-image-data...",
        "metadata": {
          "size": 10240,
          "expiresAt": "2024-01-15T11:00:00Z"
        }
      }
    ]
  },
  "history": [
    {
      "timestamp": "2024-01-15T10:30:00Z",
      "status": "SUBMITTED",
      "message": "任务已接收"
    },
    {
      "timestamp": "2024-01-15T10:30:05Z",
      "status": "WORKING",
      "message": "正在创建支付订单..."
    },
    {
      "timestamp": "2024-01-15T10:30:45Z",
      "status": "COMPLETED",
      "message": "订单创建完成"
    }
  ],
  "metrics": {
    "durationMs": 45000,
    "tokenUsage": {
      "input": 150,
      "output": 80
    }
  }
}

2.5 流式响应(SSE)

对于长时间运行的任务,支持Server-Sent Events流式推送:

vbnet 复制代码
GET /a2a/tasks/{taskId}/subscribe HTTP/1.1
Accept: text/event-stream
Authorization: Bearer <token>

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

event: status
data: {"status": "WORKING", "message": "正在处理支付请求..."}

event: message
data: {"type": "progress", "percent": 30, "detail": "验证订单信息"}

event: message
data: {"type": "progress", "percent": 60, "detail": "调用支付网关"}

event: artifact
data: {"id": "art-001", "name": "qr-code.png", "type": "image/png", 
       "content": "base64..."}

event: status
data: {"status": "COMPLETED", "message": "支付订单创建成功"}

event: done
data: {}

三、服务端实现

3.1 Spring Boot A2A Server

依赖配置

xml 复制代码
<dependency>
    <groupId>org.springaicommunity</groupId>
    <artifactId>spring-ai-a2a-server</artifactId>
    <version>0.2.0</version>
</dependency>

配置类

java 复制代码
@Configuration
@EnableA2AServer
public class A2AServerConfig {
    
    @Bean
    public AgentCard agentCard() {
        return AgentCard.builder()
            .schemaVersion("1.0")
            .agentId("payment-expert-agent")
            .name("Payment Expert Agent")
            .description("支付系统专业Agent")
            .version("2.1.0")
            .provider(ProviderInfo.builder()
                .name("Deep Blue Team")
                .url("https://deepblue.example.com")
                .build())
            .capabilities(Capabilities.builder()
                .streaming(true)
                .pushNotifications(true)
                .artifacts(true)
                .build())
            .authentication(Authentication.builder()
                .schemes(List.of("bearer", "api_key"))
                .build())
            .skills(List.of(
                Skill.builder()
                    .id("payment-integration")
                    .name("支付网关集成")
                    .description("集成主流支付网关")
                    .inputSchema(loadSchema("schemas/payment-input.json"))
                    .outputSchema(loadSchema("schemas/payment-output.json"))
                    .build(),
                Skill.builder()
                    .id("order-management")
                    .name("订单管理")
                    .description("管理订单状态")
                    .build()
            ))
            .build();
    }
    
    @Bean
    public A2ATaskHandler taskHandler() {
        return A2ATaskHandler.builder()
            .skill("payment-integration", new PaymentIntegrationHandler())
            .skill("order-management", new OrderManagementHandler())
            .build();
    }
}

3.2 Skill Handler实现

java 复制代码
@Component
public class PaymentIntegrationHandler implements SkillHandler {
    
    private final PaymentService paymentService;
    private final ArtifactStore artifactStore;
    
    @Override
    public String getSkillId() {
        return "payment-integration";
    }
    
    @Override
    public SkillResult execute(TaskInput input, TaskContext context) {
        String gateway = input.getString("gateway");
        String action = input.getString("action");
        
        // 状态更新回调
        context.updateStatus(TaskStatus.WORKING, "正在处理" + gateway + "支付...");
        
        try {
            switch (action) {
                case "create":
                    return handleCreatePayment(input, context);
                case "query":
                    return handleQueryPayment(input, context);
                case "refund":
                    return handleRefund(input, context);
                default:
                    throw new IllegalArgumentException("未知操作: " + action);
            }
        } catch (PaymentException e) {
            context.updateStatus(TaskStatus.FAILED, e.getMessage());
            return SkillResult.failure(e.getMessage());
        }
    }
    
    private SkillResult handleCreatePayment(TaskInput input, TaskContext context) {
        String orderId = input.getString("orderId");
        BigDecimal amount = input.getBigDecimal("amount");
        String currency = input.getString("currency", "CNY");
        
        context.updateProgress(20, "验证订单信息");
        
        // 创建支付订单
        PaymentRequest request = PaymentRequest.builder()
            .orderId(orderId)
            .amount(amount)
            .currency(currency)
            .build();
        
        context.updateProgress(40, "调用支付网关");
        PaymentResult result = paymentService.createPayment(
            input.getString("gateway"), request);
        
        context.updateProgress(80, "生成支付二维码");
        
        // 生成二维码产物
        Artifact qrCode = artifactStore.store(
            Artifact.builder()
                .name("payment-qr-code.png")
                .type("image/png")
                .content(generateQRCode(result.getPayUrl()))
                .build()
        );
        
        return SkillResult.success()
            .text("支付订单创建成功,交易号: " + result.getTransactionId())
            .artifact(qrCode)
            .metadata(Map.of(
                "transactionId", result.getTransactionId(),
                "payUrl", result.getPayUrl()
            ));
    }
}

3.3 端点配置

A2A Server自动暴露以下端点:

端点 方法 说明
/.well-known/agent.json GET Agent Card
/a2a/tasks POST 创建任务
/a2a/tasks/{id} GET 查询任务状态
/a2a/tasks/{id} DELETE 取消任务
/a2a/tasks/{id}/subscribe GET SSE订阅任务更新
/a2a/tasks/{id}/artifacts/{artifactId} GET 下载产物

Controller实现(框架自动生成,此处展示原理)

java 复制代码
@RestController
@RequestMapping("/a2a")
public class A2ATaskController {
    
    private final A2ATaskService taskService;
    private final SseEmitterService sseService;
    
    @PostMapping("/tasks")
    public ResponseEntity<TaskResponse> createTask(
            @RequestBody TaskRequest request,
            @RequestHeader("Authorization") String auth) {
        
        // 验证认证
        if (!authenticate(auth)) {
            return ResponseEntity.status(401).build();
        }
        
        // 创建任务
        TaskResponse response = taskService.createTask(request);
        return ResponseEntity.accepted().body(response);
    }
    
    @GetMapping("/tasks/{id}")
    public ResponseEntity<TaskResponse> getTask(
            @PathVariable String id,
            @RequestHeader("Authorization") String auth) {
        
        return taskService.getTask(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }
    
    @GetMapping("/tasks/{id}/subscribe")
    public SseEmitter subscribeTask(
            @PathVariable String id,
            @RequestHeader("Authorization") String auth) {
        
        return sseService.createEmitter(id);
    }
}

3.4 流式推送实现

java 复制代码
@Service
public class SseEmitterService {
    
    private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
    
    public SseEmitter createEmitter(String taskId) {
        SseEmitter emitter = new SseEmitter(300_000L);  // 5分钟超时
        
        emitter.onCompletion(() -> emitters.remove(taskId));
        emitter.onTimeout(() -> emitters.remove(taskId));
        
        emitters.put(taskId, emitter);
        return emitter;
    }
    
    public void sendStatus(String taskId, TaskStatus status, String message) {
        SseEmitter emitter = emitters.get(taskId);
        if (emitter != null) {
            try {
                emitter.send(SseEmitter.event()
                    .name("status")
                    .data(Map.of(
                        "status", status.name(),
                        "message", message
                    )));
            } catch (IOException e) {
                emitters.remove(taskId);
            }
        }
    }
    
    public void sendProgress(String taskId, int percent, String detail) {
        SseEmitter emitter = emitters.get(taskId);
        if (emitter != null) {
            try {
                emitter.send(SseEmitter.event()
                    .name("message")
                    .data(Map.of(
                        "type", "progress",
                        "percent", percent,
                        "detail", detail
                    )));
            } catch (IOException e) {
                emitters.remove(taskId);
            }
        }
    }
    
    public void complete(String taskId) {
        SseEmitter emitter = emitters.remove(taskId);
        if (emitter != null) {
            emitter.send(SseEmitter.event().name("done").data("{}"));
            emitter.complete();
        }
    }
}

四、客户端集成

4.1 A2A Client配置

java 复制代码
@Configuration
public class A2AClientConfig {
    
    @Bean
    public A2AClient a2aClient(WebClient.Builder webClientBuilder) {
        return A2AClient.builder()
            .webClientBuilder(webClientBuilder)
            .defaultTimeout(Duration.ofMinutes(5))
            .retryConfig(RetryConfig.builder()
                .maxAttempts(3)
                .backoff(Duration.ofSeconds(1))
                .build())
            .build();
    }
}

4.2 发现远程Agent

java 复制代码
@Service
public class AgentDiscoveryService {
    
    private final A2AClient a2aClient;
    private final Map<String, AgentCard> cachedCards = new ConcurrentHashMap<>();
    
    /**
     * 从well-known端点获取Agent Card
     */
    public AgentCard discoverAgent(String baseUrl) {
        String cardUrl = baseUrl + "/.well-known/agent.json";
        
        return cachedCards.computeIfAbsent(baseUrl, url -> {
            return a2aClient.fetchAgentCard(url)
                .block(Duration.ofSeconds(10));
        });
    }
    
    /**
     * 根据能力搜索Agent
     */
    public List<AgentCard> searchByCapability(String capability) {
        // 从注册中心查询
        String registryUrl = "https://agent-registry.example.com/search";
        
        return a2aClient.searchRegistry(registryUrl, 
            Map.of("capability", capability))
            .collectList()
            .block();
    }
    
    /**
     * 验证Agent Card
     */
    public ValidationResult validateCard(AgentCard card) {
        List<String> errors = new ArrayList<>();
        
        // 检查必需字段
        if (card.getAgentId() == null) {
            errors.add("缺少agentId");
        }
        if (card.getSkills() == null || card.getSkills().isEmpty()) {
            errors.add("缺少skills定义");
        }
        
        // 检查端点可达性
        try {
            a2aClient.ping(card.getEndpoints().getTask())
                .block(Duration.ofSeconds(5));
        } catch (Exception e) {
            errors.add("端点不可达: " + e.getMessage());
        }
        
        return new ValidationResult(errors.isEmpty(), errors);
    }
}

4.3 提交任务

java 复制代码
@Service
public class A2ATaskClient {
    
    private final A2AClient a2aClient;
    
    /**
     * 同步提交任务(阻塞等待结果)
     */
    public TaskResponse submitAndWait(AgentCard agent, TaskRequest request) {
        return a2aClient.submitTask(agent.getEndpoints().getTask(), request)
            .flatMap(response -> {
                if (response.getStatus() == TaskStatus.COMPLETED) {
                    return Mono.just(response);
                }
                // 等待任务完成
                return pollUntilComplete(agent, response.getId());
            })
            .block(Duration.ofMinutes(10));
    }
    
    /**
     * 异步提交任务(返回Mono)
     */
    public Mono<TaskResponse> submitAsync(AgentCard agent, TaskRequest request) {
        return a2aClient.submitTask(agent.getEndpoints().getTask(), request);
    }
    
    /**
     * 流式订阅任务
     */
    public Flux<TaskEvent> subscribe(AgentCard agent, String taskId) {
        String subscribeUrl = agent.getEndpoints().getTaskSubscribe();
        
        return a2aClient.subscribeTask(subscribeUrl, taskId);
    }
    
    /**
     * 轮询任务状态
     */
    private Mono<TaskResponse> pollUntilComplete(AgentCard agent, String taskId) {
        return Mono.defer(() -> 
            a2aClient.getTask(agent.getEndpoints().getTask(), taskId)
        )
        .flatMap(response -> {
            if (response.getStatus().isTerminal()) {
                return Mono.just(response);
            }
            // 等待1秒后重试
            return Mono.delay(Duration.ofSeconds(1))
                .then(pollUntilComplete(agent, taskId));
        });
    }
}

4.4 与Spring AI Agent集成

java 复制代码
@Configuration
public class A2ASubagentConfig {
    
    @Bean
    public SubagentConfig a2aPaymentSubagent(AgentDiscoveryService discovery) {
        // 发现远程Agent
        AgentCard paymentAgent = discovery.discoverAgent(
            "https://payment-agent.example.com");
        
        return SubagentConfig.builder()
            .name("payment-a2a")
            .description("远程支付Agent (A2A)")
            .model("a2a:" + paymentAgent.getAgentId())  // 特殊标记
            .metadata(Map.of(
                "a2a_agent_card", JsonUtils.toJson(paymentAgent),
                "a2a_endpoint", paymentAgent.getEndpoints().getTask()
            ))
            .build();
    }
    
    @Bean
    public A2ASubagentExecutor a2aExecutor(A2ATaskClient taskClient) {
        return new A2ASubagentExecutor(taskClient);
    }
}

A2A Subagent执行器

java 复制代码
public class A2ASubagentExecutor implements SubagentExecutor {
    
    private final A2ATaskClient taskClient;
    
    @Override
    public SubagentResult execute(SubagentTask task) {
        // 从metadata获取Agent Card
        AgentCard agentCard = JsonUtils.fromJson(
            task.getMetadata().get("a2a_agent_card"),
            AgentCard.class
        );
        
        // 构建A2A Task Request
        TaskRequest request = TaskRequest.builder()
            .id(UUID.randomUUID().toString())
            .sessionId(task.getSessionId())
            .input(extractInput(task))
            .build();
        
        // 提交任务
        TaskResponse response = taskClient.submitAndWait(agentCard, request);
        
        // 转换结果
        return SubagentResult.builder()
            .success(response.getStatus() == TaskStatus.COMPLETED)
            .summary(response.getOutput().getText())
            .artifacts(convertArtifacts(response.getOutput().getArtifacts()))
            .build();
    }
    
    private Map<String, Object> extractInput(SubagentTask task) {
        Map<String, Object> input = new HashMap<>();
        input.put("goal", task.getGoal());
        input.put("context", task.getContext());
        return input;
    }
}

五、安全与认证

5.1 认证方案

A2A支持多种认证方式:

5.2 服务端认证实现

java 复制代码
@Configuration
@EnableWebSecurity
public class A2ASecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/.well-known/**").permitAll()
                .requestMatchers("/a2a/**").authenticated()
            )
            // API Key认证
            .addFilterBefore(new ApiKeyFilter(), UsernamePasswordAuthenticationFilter.class)
            // Bearer Token认证
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
        
        return http.build();
    }
}

@Component
public class ApiKeyFilter extends OncePerRequestFilter {
    
    @Value("${a2a.api-keys}")
    private Set<String> validApiKeys;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response,
                                    FilterChain filterChain) {
        
        String apiKey = request.getHeader("X-API-Key");
        
        if (apiKey != null && validApiKeys.contains(apiKey)) {
            Authentication auth = new ApiKeyAuthentication(apiKey);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        
        filterChain.doFilter(request, response);
    }
}

5.3 客户端认证配置

java 复制代码
@Bean
public A2AClient a2aClientWithAuth(WebClient.Builder builder) {
    return A2AClient.builder()
        .webClientBuilder(builder
            .defaultHeader("X-API-Key", "${A2A_API_KEY}")
            // 或使用OAuth2
            .filter(oauth2Filter())
        )
        .build();
}

private ExchangeFilterFunction oauth2Filter() {
    return (request, next) -> {
        // 从OAuth2服务器获取token
        return getToken()
            .flatMap(token -> {
                ClientRequest authorizedRequest = ClientRequest.from(request)
                    .headers(headers -> headers.setBearerAuth(token))
                    .build();
                return next.exchange(authorizedRequest);
            });
    };
}

5.4 权限控制

java 复制代码
@Component
public class A2APermissionEvaluator {
    
    /**
     * 检查客户端是否有权限调用特定skill
     */
    public boolean hasPermission(Authentication auth, String skillId) {
        // 从token中提取权限
        Set<String> permissions = extractPermissions(auth);
        
        // 权限规则
        Map<String, Set<String>> skillPermissions = Map.of(
            "payment-integration", Set.of("payment:write", "payment:read"),
            "order-management", Set.of("order:write", "order:read"),
            "refund", Set.of("refund:write")  // 敏感操作需要特殊权限
        );
        
        Set<String> required = skillPermissions.getOrDefault(skillId, Set.of());
        return permissions.containsAll(required);
    }
    
    private Set<String> extractPermissions(Authentication auth) {
        if (auth instanceof ApiKeyAuthentication) {
            // API Key关联的权限
            return getPermissionsForApiKey(auth.getCredentials().toString());
        } else if (auth.getPrincipal() instanceof Jwt jwt) {
            // JWT中的权限声明
            return jwt.getClaimAsStringSet("permissions")
                .stream()
                .collect(Collectors.toSet());
        }
        return Set.of();
    }
}

六、监控与可观测性

6.1 指标收集

java 复制代码
@Configuration
public class A2AMetricsConfig {
    
    @Bean
    public MeterRegistryCustomizer<MeterRegistry> a2aMetrics() {
        return registry -> {
            // 任务计数器
            Counter.builder("a2a.tasks.total")
                .description("Total A2A tasks")
                .tag("agent", "payment-expert")
                .register(registry);
            
            // 任务延迟直方图
            DistributionSummary.builder("a2a.task.duration")
                .description("A2A task duration")
                .baseUnit("milliseconds")
                .register(registry);
        };
    }
}

@Aspect
@Component
public class A2AMetricsAspect {
    
    private final MeterRegistry meterRegistry;
    
    @Around("@annotation(A2ATask)")
    public Object measureTask(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        String skillId = extractSkillId(pjp);
        
        try {
            Object result = pjp.proceed();
            
            // 成功计数
            meterRegistry.counter("a2a.tasks.total",
                "skill", skillId,
                "status", "success"
            ).increment();
            
            return result;
        } catch (Exception e) {
            // 失败计数
            meterRegistry.counter("a2a.tasks.total",
                "skill", skillId,
                "status", "failure",
                "error", e.getClass().getSimpleName()
            ).increment();
            
            throw e;
        } finally {
            // 记录延迟
            long duration = System.currentTimeMillis() - start;
            meterRegistry.summary("a2a.task.duration",
                "skill", skillId
            ).record(duration);
        }
    }
}

6.2 分布式追踪

java 复制代码
@Configuration
public class TracingConfig {
    
    @Bean
    public Tracing tracing() {
        return Tracing.newBuilder()
            .localServiceName("payment-a2a-agent")
            .spanReporter(AsyncReporter.create(URLConnectionSender.create(
                "http://zipkin:9411/api/v2/spans"
            )))
            .propagationFactory(B3Propagation.FACTORY)
            .build();
    }
    
    @Bean
    public HttpTracing httpTracing(Tracing tracing) {
        return HttpTracing.create(tracing);
    }
}

// 在任务处理中添加追踪
@Override
public SkillResult execute(TaskInput input, TaskContext context) {
    Span span = tracer.nextSpan()
        .name("a2a.task." + getSkillId())
        .tag("task.id", context.getTaskId())
        .tag("session.id", context.getSessionId())
        .start();
    
    try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
        // 执行任务
        return doExecute(input, context);
    } finally {
        span.finish();
    }
}

6.3 日志聚合

java 复制代码
@Slf4j
@Component
public class A2ATaskLogger {
    
    private final StructuredArguments TASK_ID = keyValue("taskId");
    private final StructuredArguments SESSION_ID = keyValue("sessionId");
    private final StructuredArguments SKILL_ID = keyValue("skillId");
    
    public void logTaskStart(TaskContext context) {
        log.info("A2A task started", 
            TASK_ID.value(context.getTaskId()),
            SESSION_ID.value(context.getSessionId()),
            SKILL_ID.value(context.getSkillId()),
            keyValue("input", context.getInput())
        );
    }
    
    public void logTaskComplete(TaskContext context, SkillResult result) {
        log.info("A2A task completed",
            TASK_ID.value(context.getTaskId()),
            keyValue("success", result.isSuccess()),
            keyValue("durationMs", context.getDuration().toMillis()),
            keyValue("artifacts", result.getArtifacts().size())
        );
    }
    
    public void logTaskError(TaskContext context, Exception e) {
        log.error("A2A task failed",
            TASK_ID.value(context.getTaskId()),
            keyValue("error", e.getMessage()),
            keyValue("stackTrace", Arrays.toString(e.getStackTrace()))
        );
    }
}

七、Agent注册中心

7.1 设计目标

当Agent数量增多时,需要注册中心进行管理:

7.2 注册中心实现

java 复制代码
@Service
public class AgentRegistryService {
    
    private final RedisTemplate<String, String> redis;
    private static final String REGISTRY_KEY = "a2a:agents";
    private static final String CAPABILITY_INDEX = "a2a:capabilities";
    
    /**
     * 注册Agent
     */
    public void register(AgentCard card) {
        String agentJson = JsonUtils.toJson(card);
        
        // 存储Agent Card
        redis.opsForHash().put(REGISTRY_KEY, card.getAgentId(), agentJson);
        
        // 索引能力
        for (Skill skill : card.getSkills()) {
            redis.opsForSet().add(
                CAPABILITY_INDEX + ":" + skill.getId(),
                card.getAgentId()
            );
        }
        
        // 设置过期时间(用于健康检查)
        redis.expire(
            REGISTRY_KEY + ":heartbeat:" + card.getAgentId(),
            Duration.ofSeconds(60)
        );
    }
    
    /**
     * 按能力发现Agent
     */
    public List<AgentCard> findByCapability(String capability) {
        Set<String> agentIds = redis.opsForSet().members(
            CAPABILITY_INDEX + ":" + capability
        );
        
        if (agentIds == null || agentIds.isEmpty()) {
            return List.of();
        }
        
        return agentIds.stream()
            .map(id -> redis.opsForHash().get(REGISTRY_KEY, id))
            .filter(Objects::nonNull)
            .map(json -> JsonUtils.fromJson(json.toString(), AgentCard.class))
            .filter(this::isHealthy)
            .collect(Collectors.toList());
    }
    
    /**
     * 健康检查
     */
    private boolean isHealthy(AgentCard card) {
        String heartbeatKey = REGISTRY_KEY + ":heartbeat:" + card.getAgentId();
        return Boolean.TRUE.equals(redis.hasKey(heartbeatKey));
    }
}

7.3 心跳与健康检查

java 复制代码
@Scheduled(fixedRate = 30000)  // 每30秒
public void sendHeartbeat() {
    redis.opsForValue().set(
        REGISTRY_KEY + ":heartbeat:" + agentCard.getAgentId(),
        Instant.now().toString(),
        Duration.ofSeconds(60)
    );
}

@Scheduled(fixedRate = 60000)  // 每分钟清理
public void cleanupStaleAgents() {
    Set<Object> agentIds = redis.opsForHash().keys(REGISTRY_KEY);
    
    for (Object id : agentIds) {
        String heartbeatKey = REGISTRY_KEY + ":heartbeat:" + id;
        if (!Boolean.TRUE.equals(redis.hasKey(heartbeatKey))) {
            // 移除不健康的Agent
            AgentCard card = JsonUtils.fromJson(
                redis.opsForHash().get(REGISTRY_KEY, id).toString(),
                AgentCard.class
            );
            
            unregister(card);
            log.warn("Removed stale agent: {}", id);
        }
    }
}

八、最佳实践与踩坑指南

8.1 常见问题

问题 原因 解决方案
Agent Card获取失败 网络问题或端点错误 添加重试机制,缓存Agent Card
任务超时 任务执行时间过长 设置合理timeout,使用SSE流式返回
认证失败 Token过期或权限不足 实现Token刷新,检查权限配置
流式连接断开 网络不稳定 自动重连,保存任务状态
跨域问题 CORS配置缺失 配置allowedOrigins

8.2 版本兼容性

A2A协议需要考虑多个版本的兼容性问题:

版本兼容性矩阵

客户端版本 服务端版本 兼容性 说明
1.0 1.0 ✅ 完全兼容 基础功能
1.0 1.1 ✅ 向后兼容 新增可选字段
1.1 1.0 ⚠️ 部分兼容 新功能不可用
2.0 1.x ❌ 不兼容 协议重大变更

版本兼容性策略

java 复制代码
@Component
public class VersionCompatibilityChecker {
    
    private static final String CURRENT_VERSION = "1.2.0";
    private static final String MIN_SUPPORTED_VERSION = "1.0.0";
    
    /**
     * 检查兼容性
     */
    public CompatibilityResult checkCompatibility(AgentCard card) {
        String serverVersion = card.getSchemaVersion();
        
        if (serverVersion == null) {
            return CompatibilityResult.incompatible("缺少版本信息");
        }
        
        // 主版本号必须相同
        int clientMajor = getMajorVersion(CURRENT_VERSION);
        int serverMajor = getMajorVersion(serverVersion);
        
        if (clientMajor != serverMajor) {
            return CompatibilityResult.incompatible(
                String.format("主版本不匹配: 客户端=%d, 服务端=%d", 
                    clientMajor, serverMajor));
        }
        
        // 服务端版本不能低于最低支持版本
        if (compareVersions(serverVersion, MIN_SUPPORTED_VERSION) < 0) {
            return CompatibilityResult.incompatible(
                String.format("服务端版本过低: %s < %s", 
                    serverVersion, MIN_SUPPORTED_VERSION));
        }
        
        // 确定可用功能集
        FeatureSet features = determineAvailableFeatures(
            CURRENT_VERSION, serverVersion);
        
        return CompatibilityResult.compatible(features);
    }
    
    /**
     * 确定两端都支持的功能集
     */
    private FeatureSet determineAvailableFeatures(
            String clientVersion, String serverVersion) {
        
        FeatureSet features = new FeatureSet();
        
        // 基础功能(1.0+)
        features.enable("basic_tasks");
        features.enable("agent_card");
        
        // 1.1+ 功能
        if (compareVersions(clientVersion, "1.1.0") >= 0 &&
            compareVersions(serverVersion, "1.1.0") >= 0) {
            features.enable("streaming");
            features.enable("progress_callback");
        }
        
        // 1.2+ 功能
        if (compareVersions(clientVersion, "1.2.0") >= 0 &&
            compareVersions(serverVersion, "1.2.0") >= 0) {
            features.enable("artifacts");
            features.enable("multipart_response");
        }
        
        return features;
    }
    
    private int compareVersions(String v1, String v2) {
        String[] parts1 = v1.split("\\.");
        String[] parts2 = v2.split("\\.");
        
        for (int i = 0; i < Math.max(parts1.length, parts2.length); i++) {
            int num1 = i < parts1.length ? Integer.parseInt(parts1[i]) : 0;
            int num2 = i < parts2.length ? Integer.parseInt(parts2[i]) : 0;
            
            if (num1 != num2) {
                return Integer.compare(num1, num2);
            }
        }
        return 0;
    }
    
    private int getMajorVersion(String version) {
        return Integer.parseInt(version.split("\\.")[0]);
    }
}

降级策略

java 复制代码
@Component
public class A2AClientWithFallback {
    
    private final A2AClient client;
    private final VersionCompatibilityChecker compatibilityChecker;
    
    public Mono<TaskResponse> submitTask(AgentCard agent, TaskRequest request) {
        return Mono.fromSupplier(() -> compatibilityChecker.checkCompatibility(agent))
            .flatMap(result -> {
                if (!result.isCompatible()) {
                    return Mono.error(new IncompatibleVersionException(result.reason()));
                }
                
                // 根据可用功能集调整请求
                TaskRequest adjustedRequest = adjustRequest(request, result.features());
                
                return client.submitTask(agent.getEndpoints().getTask(), adjustedRequest);
            });
    }
    
    /**
     * 根据功能集降级请求
     */
    private TaskRequest adjustRequest(TaskRequest request, FeatureSet features) {
        TaskRequest.Builder builder = TaskRequest.from(request);
        
        // 如果不支持streaming,移除回调配置
        if (!features.has("streaming")) {
            builder.callbackUrl(null);
        }
        
        // 如果不支持artifacts,移除产物请求
        if (!features.has("artifacts")) {
            builder.requestArtifacts(false);
        }
        
        return builder.build();
    }
}

版本协商流程

java 复制代码
@Component
public class VersionCompatibilityChecker {
    
    public boolean isCompatible(AgentCard card) {
        String serverVersion = card.getSchemaVersion();
        String clientVersion = "1.0";
        
        // 语义化版本比较
        return compareVersions(clientVersion, serverVersion) >= 0;
    }
    
    private int compareVersions(String v1, String v2) {
        String[] parts1 = v1.split("\\.");
        String[] parts2 = v2.split("\\.");
        
        for (int i = 0; i < Math.max(parts1.length, parts2.length); i++) {
            int num1 = i < parts1.length ? Integer.parseInt(parts1[i]) : 0;
            int num2 = i < parts2.length ? Integer.parseInt(parts2[i]) : 0;
            
            if (num1 != num2) {
                return Integer.compare(num1, num2);
            }
        }
        return 0;
    }
}

8.3 错误处理规范

java 复制代码
public class A2AErrorHandler {
    
    /**
     * 标准化错误响应
     */
    public TaskResponse handleError(String taskId, Exception e) {
        A2AError error = classifyError(e);
        
        return TaskResponse.builder()
            .id(taskId)
            .status(TaskStatus.FAILED)
            .error(ErrorInfo.builder()
                .code(error.getCode())
                .message(error.getMessage())
                .details(error.getDetails())
                .retryable(error.isRetryable())
                .build())
            .build();
    }
    
    private A2AError classifyError(Exception e) {
        if (e instanceof AuthenticationException) {
            return A2AError.builder()
                .code("AUTH_FAILED")
                .message("认证失败")
                .retryable(false)
                .build();
        }
        
        if (e instanceof RateLimitExceededException) {
            return A2AError.builder()
                .code("RATE_LIMIT")
                .message("请求频率超限")
                .retryable(true)
                .details(Map.of("retryAfter", "60s"))
                .build();
        }
        
        if (e instanceof TimeoutException) {
            return A2AError.builder()
                .code("TIMEOUT")
                .message("任务执行超时")
                .retryable(true)
                .build();
        }
        
        // 默认内部错误
        return A2AError.builder()
            .code("INTERNAL_ERROR")
            .message("内部服务错误")
            .retryable(true)
            .build();
    }
}

九、总结

A2A协议的核心价值:

  1. 标准化通信 → 统一Agent间交互协议
  2. 能力发现 → Agent Card描述能力边界
  3. 互操作性 → 跨平台、跨厂商协作
  4. 可扩展性 → 支持流式、产物、回调

与其他模式的协作

模式 与A2A的关系
Subagent 远程Subagent通过A2A协议通信
Agent Skills Skill可以包含A2A端点配置
TodoWriteTool A2A任务可以更新Todo状态
AutoMemoryTools 保存A2A Agent偏好

协议选择指南


参考资料

相关推荐
三秋树1 小时前
豆包 Agent Harness 工程师入门 | 第 5 章 Skills 技能
人工智能·agent·ai编程
袋鱼不重2 小时前
Hermes Agent 直连飞书机器人
前端·后端·ai编程
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【26】Skills 生命周期深度解析
java·人工智能·spring
一个处女座的程序猿2 小时前
LLMs之Memory之MIA:《Memory Intelligence Agent》翻译与解读
llm·agent·memory
甲维斯2 小时前
智谱CodingPlan老套餐绝版了,全网token收拢!
人工智能·ai编程
OpenTiny社区3 小时前
电商系统集成GenUI SDK实操指南
前端·开源·ai编程
九章智算云4 小时前
一份CLAUDE.md,为何能让GitHub榜首项目狂揽6万星?
人工智能·ai·大模型·agent·ai工具·claude code·vibe-coding