谷歌新发布的智能体通信协议(A2A)解析

A2A 概述

A2A到底是什么?

A2A(Agent2Agent)是一种开放协议,旨在实现不透明Agent之间的通信和互操作。其主要作用就是为不同的Agent提供一种共同的“语言”标准,在它们需要“交流”的时候可以更加简单、高效与安全,实现信息共享与任务分配,从而更快的完成任务,即使它们的底层平台完全不同:

在这里插入图片描述

A2A架构是怎样的?

也就是“基于A2A规范的Agent集成架构”,大致描述如下:

在这里插入图片描述

A2A与MCP什么关系?

A2A在诞生动机、架构甚至协议方面都与MCP非常相似,但它们之间的关系与区别还是很清楚的:

MCP解决的是Agent与外部工具/数据之间的集成,是Agent的“内部事务”;A2A解决的是Agent与Agent之间的集成,属于更高层次的集成关系。

它们之间是可以共存与协作的,比如:

在这里插入图片描述

在这样的架构中Agent通过MCP使用工具,Agent与Agent之间则通过A2A产生互动与协作。

技术原理

基于HTTP+JSON的AI代理通信协议,主要包含以下模块:

Agent Card

  • 位置:/.well-known/agent.json
  • 作用:JSON格式的代理能力说明书(含API端点、技能、认证方式)

角色模型

  • 服务端:提供API接口(必须实现tasks/send等标准方法)
  • 客户端:通过HTTP调用其他代理

任务流程

  • 生命周期:submitted → working → (input-required) → completed/failed

  • 交互单元:

    • 消息:由多类型Part组成(文本/文件/结构化数据)
    • 工件:任务产出的结构化结果

通信机制

  • 基础模式:HTTP + JSON-RPC风格接口

  • 高级功能:

    • 实时推送:SSE协议(tasks/sendSubscribe)
    • 异步通知:Webhook回调
    • 多模态支持:通过Part类型实现

UX协商

  • 代理间预先协商交互形式(如文本/语音/表单)

示例demo

server端controller:

package com.example.spring_ai.a2a.server.controller;

import com.example.spring_ai.a2a.server.bean.TaskRequest;
import com.example.spring_ai.a2a.server.bean.TaskResponse;
import com.example.spring_ai.a2a.server.service.TaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
 * TaskController
 * 实现任务提交与异步处理逻辑
 *
 **/
@RestController
@Slf4j
public class AgentController {

    @Autowired
    private TaskService taskService;

    @GetMapping("/.well-known/agent.json")
    public Map<String, Object> getAgentCard() {
        return Map.of(
                "name", "CalculateAgent",
                "version", "1.0",
                "description", "提供计算服务",
                "endpoints", Map.of(
                        "task_send", "/api/tasks/submit",
                        "task_get", "/api/tasks/subscribe_sse"
                ),
                "input_schema", Map.of(
                        "type", "object",
                        "properties", Map.of(
                                "tasks", Map.of("type", "string", "enum", List.of("task1", "task2")),
                                "required", List.of("tasks")
                        ),
                        "authentication", Map.of("methods", List.of("API_Key"))
                ));
    }

    @PostMapping("/api/tasks/submit")
    public ResponseEntity<TaskResponse> submitTask(
            @RequestHeader("Authorization") String apiKey,
            @RequestBody TaskRequest request
    ) {
        // 认证校验(示例简化)
        if (!"SECRET_KEY".equals(apiKey)) {
            return ResponseEntity.status(401).build();
        }

        // 异步处理任务
        CompletableFuture.runAsync(() ->
                taskService.processTask(request.getTaskId(), request.getParams())
        );

        return ResponseEntity.accepted().body(
                new TaskResponse(request.getTaskId(), "submitted", "", "")
        );
    }

    @GetMapping(value = "/api/tasks/subscribe_sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<TaskResponse> streamTaskStatus(@RequestParam("taskId") String taskId) {
        log.info("[server] updates task {} with streamTaskStatus", taskId);
        return taskService.streamTaskStatus(taskId);
    }

}

service:

package com.example.spring_ai.a2a.server.service;

import com.example.spring_ai.a2a.server.bean.TaskResponse;
import com.example.spring_ai.a2a.server.bean.TaskStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
@Slf4j
public class TaskService {

    private final Map<String, TaskStatus> taskStore = new ConcurrentHashMap<>();

    public void processTask(String taskId, Map<String, String> params) {
        log.info("[server] Processing task {} with params {}", taskId, params);
        List<String> tasks = Arrays.asList(params.get("tasks").split(","));
        String progress =  "0/" + tasks.size();
        String finalRes = "";
        taskStore.put(taskId, new TaskStatus("working", progress, ""));
        try {
            // 模拟任务执行
            for (int i = 0; i < tasks.size(); i++) {
                progress = (i + 1) + "/" + tasks.size();
                //随机返回success或者failed
                String res = Math.random() < 0.5 ? "success" : "failed";
                finalRes = finalRes + "{" + tasks.get(i) + ":" + res + "}, ";
                taskStore.put(taskId, new TaskStatus("working", progress, finalRes));
                Thread.sleep(10000);
            }
            taskStore.put(taskId, new TaskStatus("completed", progress, finalRes));
        } catch (InterruptedException e) {
            taskStore.put(taskId, new TaskStatus("failed", progress,
                    Map.of("error", "任务被中断"))
            );
            Thread.currentThread().interrupt(); // 恢复中断状态
        } catch (Exception e) {
            // 处理其他异常
            taskStore.put(taskId, new TaskStatus("failed", progress,
                    Map.of("error", e.getMessage()))
            );
        }
        log.info("[server] Processing task {} end", taskId);
    }

    public Flux<TaskResponse> streamTaskStatus(String taskId) {
        return Flux.interval(Duration.ofSeconds(1))
                .map(tick -> {
                    TaskStatus taskStatus = taskStore.get(taskId);
                    if (taskStatus == null) {
                        return new TaskResponse(taskId, "not_found", null, null);
                    } else {
                        return new TaskResponse(
                                taskId,
                                taskStatus.getStatus(),
                                taskStatus.getProgress(),
                                taskStatus.getResult()
                        );
                    }
                })
                .takeUntil(response ->
                        "not_found".equals(response.getStatus()) ||
                                "completed".equals(response.getStatus()) ||
                                "failed".equals(response.getStatus())
                )
                .doOnNext(response ->
                        log.info("Sending SSE: {}", response))
                .doOnComplete(() ->
                        log.info("SSE stream completed for task {}", taskId))
                .onErrorResume(e -> {
                    log.error("Error in SSE stream for task {}", taskId, e);
                    return Flux.empty();
                });
    }
}

测试client:

package com.example.spring_ai.a2a.client.service;

import com.example.spring_ai.a2a.server.bean.TaskRequest;
import com.example.spring_ai.a2a.server.bean.TaskResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Flux;
import reactor.util.retry.Retry;

import java.io.IOException;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;

@Service
@Slf4j
public class AgentClient {
    private final RestTemplate restTemplate;
    private final WebClient webClient;

    String baseUrl = "http://localhost:8181";

    public AgentClient(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
        this.webClient = WebClient.builder()
                .baseUrl(baseUrl)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();
    }

    public SseEmitter tasksPlan(String tasks) throws InterruptedException {

        // 获取WeatherAgent的AgentCard
        log.info("[client] Start to get AgentCard");
        Map<String, Object> agentCard = restTemplate.getForObject(
                "http://localhost:8181/.well-known/agent.json",
                Map.class
        );
        if (agentCard == null) {
            throw new RuntimeException("Failed to fetch agent card");
        }
        log.info("[client] Get AgentCard: %s".formatted(agentCard));

        // 构造A2A标准任务请求
        Map<String, Object> endpoints = (Map<String, Object>) agentCard.get("endpoints");
        String taskSubmitUrl = baseUrl + endpoints.get("task_send");
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "SECRET_KEY");
        String taskId = UUID.randomUUID().toString();
        Map<String, String> params = Map.of("tasks", tasks);
        ResponseEntity<TaskResponse> response = restTemplate.exchange(
                taskSubmitUrl,
                HttpMethod.POST,
                new HttpEntity<>(new TaskRequest(taskId, params), headers),
                TaskResponse.class
        );
        log.info("[client] Step task_send end, {}", response);

        // SSE订阅
        SseEmitter sseEmitter = new SseEmitter(60_000L);
        Flux<TaskResponse> eventStream = webClient.get()
                .uri(uriBuilder -> uriBuilder
                        .path(endpoints.get("task_get").toString())
                        .queryParam("taskId", taskId)
                        .build())
                .header("Authorization", "SECRET_KEY")
                .accept(MediaType.TEXT_EVENT_STREAM)
                .retrieve()
                .bodyToFlux(TaskResponse.class)
                .filter(resp -> taskId.equals(resp.getTaskId()))
                .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)));

        eventStream.subscribe(
                resp -> {
                    try {
                        sseEmitter.send(resp);
                        if ("completed".equals(resp.getStatus())) {
                            sseEmitter.complete(); // 服务端标记完成时关闭
                        }
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                },
                error -> System.err.println("SSE连接异常: " + error.getMessage())
        );
        log.info("[client] Step sse_subscribe end ");

        return sseEmitter;
    }
}

完整示例:https://gitee.com/jackyzhang888/spring-ai-demo/tree/master/src

测试:

在这里插入图片描述

参考:https://zhuanlan.zhihu.com/p/1894801654202748934

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

造夢先森

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值