基于 A2A 协议的生产应用实践指南(Java)
一、A2A 协议概述
1.1 什么是 A2A
Agent2Agent (A2A) 是由 Google 发起并已捐赠给 Linux Foundation 的开放协议标准,旨在让不同框架、不同厂商构建的 AI Agent 能够互相发现、通信和协作。
核心设计原则:
- 互操作性:连接用不同平台(LangGraph、CrewAI、自定义方案等)构建的 Agent
- 不透明执行:Agent 协作时无需暴露内部状态、记忆或工具实现,保护知识产权
- 异步优先:原生支持长时间运行的任务和人机协作场景
- 模态无关:支持文本、音视频、结构化 JSON 数据等多种内容类型
1.2 A2A vs MCP 的关系
| 维度 | A2A | MCP |
|---|---|---|
| 定位 | Agent-to-Agent 通信 | Agent-to-Tool 通信 |
| 核心功能 | Agent 之间发现、协商、任务委派 | Agent 连接工具、API、数据源 |
| 类比 | Agent 之间的"公共互联网" | Agent 的"工具箱" |
| 互补关系 | A2A Client 通过 A2A 请求远程 Agent 工作 | 远程 Agent 内部通过 MCP 调用工具完成工作 |
1.3 协议技术栈
┌─────────────────────────────────────────────┐
│ Layer 3: Protocol Bindings │
│ JSON-RPC 2.0 / gRPC / HTTP+JSON (REST) │
├─────────────────────────────────────────────┤
│ Layer 2: Abstract Operations │
│ SendMessage / GetTask / CancelTask / ... │
├─────────────────────────────────────────────┤
│ Layer 1: Canonical Data Model │
│ Task / Message / AgentCard / Part / ... │
└─────────────────────────────────────────────┘
通信机制:JSON-RPC 2.0 over HTTP(S),使用 SSE 做流式推送
Agent 发现 :通过 /.well-known/agent-card.json 获取 Agent Card(类似"名片")
二、核心数据模型
2.1 AgentCard(Agent 名片)
Agent Card 是 A2A 协议中 Agent 自描述的元数据文档,描述 Agent 的身份、能力、技能和通信要求。
json
{
"name": "Order Query Agent",
"description": "电商订单查询与处理智能体,支持订单查询、退款申请、物流追踪",
"version": "1.0.0",
"url": "https://agent.example.com",
"provider": {
"organization": "Example Corp",
"url": "https://www.example.com"
},
"capabilities": {
"streaming": true,
"pushNotifications": false
},
"defaultInputModes": ["text/plain", "application/json"],
"defaultOutputModes": ["text/plain", "application/json"],
"skills": [
{
"id": "order-query",
"name": "订单查询",
"description": "根据订单号查询订单详情",
"tags": ["order", "query", "search"],
"examples": ["查询订单 ORD-20250417-001 的详情"]
},
{
"id": "refund-apply",
"name": "退款申请",
"description": "发起退款申请流程",
"tags": ["refund", "return"],
"examples": ["对订单 ORD-20250417-001 申请退款"]
}
]
}
2.2 Task(任务)
Task 是 A2A 的核心工作单元,有唯一 ID 和状态生命周期。
json
{
"id": "task-uuid-001",
"contextId": "context-uuid-001",
"status": {
"state": "TASK_STATE_WORKING",
"message": {
"role": "ROLE_AGENT",
"parts": [{"text": "正在查询订单信息..."}],
"messageId": "msg-001"
},
"timestamp": "2025-04-17T10:30:00.000Z"
},
"artifacts": [],
"history": []
}
TaskState 生命周期:
SUBMITTED → WORKING → COMPLETED
→ FAILED
→ CANCELED
→ INPUT_REQUIRED(需要用户额外输入)
→ AUTH_REQUIRED(需要认证授权)
→ REJECTED
2.3 Message(消息)
Message 是客户端与服务端之间的一次通信单元:
json
{
"messageId": "msg-uuid-001",
"contextId": "context-uuid-001",
"taskId": "task-uuid-001",
"role": "ROLE_USER",
"parts": [
{"text": "帮我查询订单 ORD-20250417-001 的物流状态"}
]
}
Part 类型(消息内容的最小单元):
| 类型 | 字段 | 说明 |
|---|---|---|
| TextPart | text |
纯文本内容 |
| FilePart | raw(base64)或 url |
文件(图片、文档等) |
| DataPart | data |
结构化 JSON 数据 |
三、协议绑定与 API
A2A 支持三种协议绑定,以下是 HTTP+JSON (REST) 绑定的端点映射:
3.1 核心 REST 端点
| 操作 | 方法 | 端点 |
|---|---|---|
| 发送消息 | POST | /message:send |
| 流式发送消息 | POST | /message:stream |
| 获取任务状态 | GET | /tasks/{id} |
| 列出任务 | GET | /tasks |
| 取消任务 | POST | /tasks/{id}:cancel |
| 订阅任务更新 | POST | /tasks/{id}:subscribe |
| 获取 Agent Card | GET | /.well-known/agent-card.json |
3.2 请求/响应示例
发送消息:
http
POST /message:send HTTP/1.1
Content-Type: application/json
A2A-Version: 1.0
{
"message": {
"role": "ROLE_USER",
"parts": [{"text": "查询订单 ORD-001"}],
"messageId": "msg-001"
}
}
响应:
json
{
"task": {
"id": "task-001",
"contextId": "ctx-001",
"status": {
"state": "TASK_STATE_COMPLETED",
"timestamp": "2025-04-17T10:30:00Z"
},
"artifacts": [
{
"artifactId": "art-001",
"name": "订单查询结果",
"parts": [{"text": "订单 ORD-001 已发货,预计明天送达"}]
}
]
}
}
四、Java 生产实践
4.1 项目结构
使用 Spring Boot 3 + Java 21 构建:
a2a-demo/
├── pom.xml
├── src/main/java/com/example/a2a/
│ ├── A2aDemoApplication.java
│ ├── model/
│ │ ├── AgentCard.java
│ │ ├── AgentSkill.java
│ │ ├── AgentCapabilities.java
│ │ ├── Task.java
│ │ ├── TaskStatus.java
│ │ ├── TaskState.java
│ │ ├── Message.java
│ │ ├── Part.java
│ │ ├── Artifact.java
│ │ ├── SendMessageRequest.java
│ │ ├── SendMessageResponse.java
│ │ └── JsonRpcRequest.java
│ ├── client/
│ │ └── A2AClient.java
│ ├── server/
│ │ ├── A2AAgentController.java
│ │ ├── AgentCardController.java
│ │ └── TaskManager.java
│ └── config/
│ └── A2aProperties.java
4.2 Maven 依赖
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>a2a-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
4.3 核心数据模型定义
java
package com.example.a2a.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* A2A Agent Card - Agent 的自描述元数据
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AgentCard {
private String name;
private String description;
private String version;
private String url;
private Provider provider;
private AgentCapabilities capabilities;
private List<String> defaultInputModes;
private List<String> defaultOutputModes;
private List<AgentSkill> skills;
private Map<String, SecurityScheme> securitySchemes;
@Data
public static class Provider {
private String organization;
private String url;
}
@Data
public static class SecurityScheme {
private String type; // "http", "apiKey", "oauth2"
private String scheme; // "Bearer"
private String description;
}
}
java
package com.example.a2a.model;
import lombok.Data;
import java.util.List;
/**
* Agent 能力声明
*/
@Data
public class AgentCapabilities {
private boolean streaming = false;
private boolean pushNotifications = false;
}
java
package com.example.a2a.model;
import lombok.Data;
import java.util.List;
/**
* Agent 技能描述
*/
@Data
public class AgentSkill {
private String id;
private String name;
private String description;
private List<String> tags;
private List<String> examples;
}
java
package com.example.a2a.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.time.Instant;
import java.util.List;
/**
* A2A Task - 核心工作单元
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Task {
private String id;
private String contextId;
private TaskStatus status;
private List<Artifact> artifacts;
private List<Message> history;
}
java
package com.example.a2a.model;
import lombok.Data;
import java.time.Instant;
/**
* Task 状态
*/
@Data
public class TaskStatus {
private TaskState state;
private Message message;
private Instant timestamp;
public static TaskStatus of(TaskState state) {
TaskStatus status = new TaskStatus();
status.setState(state);
status.setTimestamp(Instant.now());
return status;
}
public static TaskStatus of(TaskState state, Message message) {
TaskStatus status = of(state);
status.setMessage(message);
return status;
}
}
java
package com.example.a2a.model;
public enum TaskState {
TASK_STATE_SUBMITTED,
TASK_STATE_WORKING,
TASK_STATE_COMPLETED,
TASK_STATE_FAILED,
TASK_STATE_CANCELED,
TASK_STATE_INPUT_REQUIRED,
TASK_STATE_AUTH_REQUIRED,
TASK_STATE_REJECTED
}
java
package com.example.a2a.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.util.List;
/**
* A2A Message - 通信单元
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Message {
private String messageId;
private String contextId;
private String taskId;
private String role; // "ROLE_USER" or "ROLE_AGENT"
private List<Part> parts;
}
java
package com.example.a2a.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.util.Map;
/**
* A2A Part - 消息内容的最小单元
* 只能包含 text / raw / url / data 其中之一
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Part {
/** 纯文本内容 */
private String text;
/** 文件 base64 字节 */
private String raw;
/** 文件 URL */
private String url;
/** 结构化 JSON 数据 */
private Object data;
/** 文件名 */
private String filename;
/** MIME 类型 */
private String mediaType;
/** 元数据 */
private Map<String, Object> metadata;
/** 便捷:创建文本 Part */
public static Part text(String text) {
Part part = new Part();
part.setText(text);
return part;
}
}
java
package com.example.a2a.model;
import lombok.Data;
import java.util.List;
/**
* Artifact - 任务输出产物
*/
@Data
public class Artifact {
private String artifactId;
private String name;
private String description;
private List<Part> parts;
}
4.4 A2A Server 端实现
Agent Card 端点
java
package com.example.a2a.server;
import com.example.a2a.model.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Agent Card 控制器
* 对应 A2A 协议的 Agent 发现端点:GET /.well-known/agent-card.json
*/
@RestController
public class AgentCardController {
private final AgentCard agentCard;
public AgentCardController() {
// 构建当前 Agent 的 Card
AgentCard.Provider provider = new AgentCard.Provider();
provider.setOrganization("Example Corp");
provider.setUrl("https://www.example.com");
AgentCapabilities caps = new AgentCapabilities();
caps.setStreaming(true);
AgentSkill orderQuery = new AgentSkill();
orderQuery.setId("order-query");
orderQuery.setName("订单查询");
orderQuery.setDescription("根据订单号查询订单详情、物流状态");
orderQuery.setTags(List.of("order", "query", "logistics"));
AgentSkill refund = new AgentSkill();
refund.setId("refund-apply");
refund.setName("退款申请");
refund.setDescription("发起退款申请并查询退款进度");
refund.setTags(List.of("refund", "return"));
this.agentCard = new AgentCard();
agentCard.setName("Order Service Agent");
agentCard.setDescription("电商订单查询与处理智能体");
agentCard.setVersion("1.0.0");
agentCard.setUrl("http://localhost:8081");
agentCard.setProvider(provider);
agentCard.setCapabilities(caps);
agentCard.setDefaultInputModes(List.of("text/plain"));
agentCard.setDefaultOutputModes(List.of("text/plain", "application/json"));
agentCard.setSkills(List.of(orderQuery, refund));
}
@GetMapping("/.well-known/agent-card.json")
public AgentCard getAgentCard() {
return agentCard;
}
}
任务管理器
java
package com.example.a2a.server;
import com.example.a2a.model.*;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* 内存任务管理器(生产环境应替换为数据库持久化)
*/
@Component
public class TaskManager {
private final Map<String, Task> taskStore = new ConcurrentHashMap<>();
/**
* 创建新任务
*/
public Task createTask(String contextId, Message userMessage) {
Task task = new Task();
task.setId(UUID.randomUUID().toString());
task.setContextId(contextId != null ? contextId : UUID.randomUUID().toString());
task.setStatus(TaskStatus.of(TaskState.TASK_STATE_SUBMITTED));
task.setHistory(List.of(userMessage));
taskStore.put(task.getId(), task);
return task;
}
/**
* 获取任务
*/
public Task getTask(String taskId) {
return taskStore.get(taskId);
}
/**
* 更新任务状态
*/
public void updateStatus(String taskId, TaskState state) {
Task task = taskStore.get(taskId);
if (task != null) {
task.setStatus(TaskStatus.of(state));
}
}
/**
* 向任务追加 Agent 回复并标记完成
*/
public void completeWithArtifact(String taskId, Artifact artifact, Message agentReply) {
Task task = taskStore.get(taskId);
if (task == null) return;
task.setStatus(TaskStatus.of(TaskState.TASK_STATE_COMPLETED, agentReply));
task.setArtifacts(List.of(artifact));
// 追加历史
if (task.getHistory() != null) {
task.getHistory().add(agentReply);
} else {
task.setHistory(List.of(agentReply));
}
}
/**
* 取消任务
*/
public Task cancelTask(String taskId) {
Task task = taskStore.get(taskId);
if (task == null) return null;
task.setStatus(TaskStatus.of(TaskState.TASK_STATE_CANCELED));
return task;
}
}
A2A 消息处理控制器
java
package com.example.a2a.server;
import com.example.a2a.model.*;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
/**
* A2A Server 控制器
* 实现 A2A HTTP+JSON 协议绑定
*/
@RestController
@RequestMapping
public class A2AAgentController {
private final TaskManager taskManager;
public A2AAgentController(TaskManager taskManager) {
this.taskManager = taskManager;
}
/**
* A2A 核心操作:发送消息
* POST /message:send
*/
@PostMapping(value = "/message:send",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public SendMessageResponse sendMessage(@RequestBody SendMessageRequest request) {
Message userMessage = request.getMessage();
// 1. 创建任务
Task task = taskManager.createTask(
userMessage.getContextId(),
userMessage
);
// 2. 异步处理任务(生产环境应提交到线程池)
String taskText = extractText(userMessage);
processTaskAsync(task.getId(), taskText);
// 3. 立即返回任务信息
SendMessageResponse response = new SendMessageResponse();
response.setTask(task);
return response;
}
/**
* 获取任务状态
* GET /tasks/{id}
*/
@GetMapping(value = "/tasks/{id}",
produces = MediaType.APPLICATION_JSON_VALUE)
public Task getTask(@PathVariable String id) {
Task task = taskManager.getTask(id);
if (task == null) {
throw new A2ATaskNotFoundException(id);
}
return task;
}
/**
* 取消任务
* POST /tasks/{id}:cancel
*/
@PostMapping(value = "/tasks/{id}:cancel",
produces = MediaType.APPLICATION_JSON_VALUE)
public Task cancelTask(@PathVariable String id) {
Task task = taskManager.cancelTask(id);
if (task == null) {
throw new A2ATaskNotFoundException(id);
}
return task;
}
// ===================== 业务处理逻辑 =====================
/**
* 模拟异步处理任务
* 生产环境中应调用 LLM / 业务服务 / MCP 工具
*/
private void processTaskAsync(String taskId, String userText) {
// 更新为处理中
taskManager.updateStatus(taskId, TaskState.TASK_STATE_WORKING);
try {
// 模拟业务处理(这里替换为实际的 LLM 调用或 MCP 工具调用)
Thread.sleep(100);
// 构建结果
String resultText = handleBusinessLogic(userText);
Artifact artifact = new Artifact();
artifact.setArtifactId(UUID.randomUUID().toString());
artifact.setName("处理结果");
artifact.setParts(List.of(Part.text(resultText)));
Message agentReply = new Message();
agentReply.setMessageId(UUID.randomUUID().toString());
agentReply.setTaskId(taskId);
agentReply.setRole("ROLE_AGENT");
agentReply.setParts(List.of(Part.text("已为您完成处理,结果如下:")));
taskManager.completeWithArtifact(taskId, artifact, agentReply);
} catch (Exception e) {
taskManager.updateStatus(taskId, TaskState.TASK_STATE_FAILED);
}
}
/**
* 实际业务逻辑处理
* 生产环境此处对接 LLM 或业务 API
*/
private String handleBusinessLogic(String userText) {
// 简单示例:关键词匹配
if (userText.contains("订单") && userText.contains("查询")) {
return "已查询到订单信息:\n" +
"订单号:ORD-20250417-001\n" +
"状态:已发货\n" +
"快递:顺丰速运 SF1234567890\n" +
"预计送达:2025-04-18";
} else if (userText.contains("退款")) {
return "退款申请已提交:\n" +
"退款单号:REF-" + UUID.randomUUID().toString().substring(0, 8) + "\n" +
"预计 1-3 个工作日处理完成";
}
return "已收到您的请求:" + userText + ",正在处理中...";
}
private String extractText(Message message) {
if (message.getParts() == null || message.getParts().isEmpty()) {
return "";
}
return message.getParts().stream()
.filter(p -> p.getText() != null)
.map(Part::getText)
.findFirst()
.orElse("");
}
/**
* A2A 自定义异常 - 任务未找到
*/
@ResponseStatus(org.springframework.http.HttpStatus.NOT_FOUND)
public static class A2ATaskNotFoundException extends RuntimeException {
public A2ATaskNotFoundException(String taskId) {
super("Task not found: " + taskId);
}
}
}
SendMessageRequest / Response
java
package com.example.a2a.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
/**
* SendMessage 请求
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SendMessageRequest {
private Message message;
private SendMessageConfiguration configuration;
}
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SendMessageConfiguration {
private boolean returnImmediately = false;
private int historyLength = 10;
}
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SendMessageResponse {
private Task task;
private Message message;
}
4.5 A2A Client 端实现
java
package com.example.a2a.client;
import com.example.a2a.model.*;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.UUID;
/**
* A2A 协议客户端
* 支持发现 Agent Card、发送消息、轮询任务状态
*/
@Slf4j
public class A2AClient {
private final HttpClient httpClient;
private final ObjectMapper objectMapper;
private final String baseUrl;
private AgentCard cachedAgentCard;
public A2AClient(String baseUrl) {
this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
this.objectMapper = new ObjectMapper();
this.objectMapper.registerModule(new JavaTimeModule());
}
// ==================== Agent 发现 ====================
/**
* 获取远程 Agent 的 Agent Card
* GET /.well-known/agent-card.json
*/
public AgentCard fetchAgentCard() {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/.well-known/agent-card.json"))
.header("A2A-Version", "1.0")
.timeout(Duration.ofSeconds(10))
.GET()
.build();
HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException(
"Failed to fetch agent card, status: " + response.statusCode());
}
this.cachedAgentCard = objectMapper.readValue(response.body(), AgentCard.class);
log.info("Discovered agent: {} - {}", cachedAgentCard.getName(),
cachedAgentCard.getDescription());
return cachedAgentCard;
} catch (Exception e) {
throw new RuntimeException("Failed to discover agent at " + baseUrl, e);
}
}
/**
* 获取缓存的 Agent Card,如不存在则重新获取
*/
public AgentCard getAgentCard() {
if (cachedAgentCard == null) {
return fetchAgentCard();
}
return cachedAgentCard;
}
// ==================== 消息操作 ====================
/**
* 发送消息给远程 Agent
* POST /message:send
*/
public Task sendMessage(String text) {
return sendMessage(text, null, null);
}
/**
* 发送消息并等待任务完成
* POST /message:send,然后轮询直到任务到达终态
*/
public Task sendMessageAndWait(String text) {
Task task = sendMessage(text, null, false);
return waitForCompletion(task.getId(), Duration.ofSeconds(30));
}
/**
* 发送消息
* @param text 消息文本
* @param contextId 上下文 ID(用于多轮对话)
* @param returnImmediately 是否立即返回(不等待完成)
*/
public Task sendMessage(String text, String contextId, Boolean returnImmediately) {
try {
Message message = new Message();
message.setMessageId(UUID.randomUUID().toString());
message.setRole("ROLE_USER");
message.setParts(java.util.List.of(Part.text(text)));
if (contextId != null) {
message.setContextId(contextId);
}
SendMessageRequest request = new SendMessageRequest();
request.setMessage(message);
if (returnImmediately != null) {
SendMessageConfiguration config = new SendMessageConfiguration();
config.setReturnImmediately(returnImmediately);
request.setConfiguration(config);
}
String body = objectMapper.writeValueAsString(request);
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/message:send"))
.header("Content-Type", "application/json")
.header("A2A-Version", "1.0")
.timeout(Duration.ofSeconds(30))
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = httpClient.send(httpRequest,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException(
"Send message failed, status: " + response.statusCode()
+ ", body: " + response.body());
}
JsonNode root = objectMapper.readTree(response.body());
JsonNode taskNode = root.get("task");
if (taskNode != null) {
return objectMapper.treeToValue(taskNode, Task.class);
}
// 直接返回 Message 的情况
JsonNode msgNode = root.get("message");
if (msgNode != null) {
Message agentMsg = objectMapper.treeToValue(msgNode, Message.class);
// 包装成已完成的 Task
Task task = new Task();
task.setId(UUID.randomUUID().toString());
task.setStatus(TaskStatus.of(TaskState.TASK_STATE_COMPLETED, agentMsg));
return task;
}
throw new RuntimeException("Invalid response: no task or message found");
} catch (Exception e) {
throw new RuntimeException("Failed to send message", e);
}
}
// ==================== 任务管理 ====================
/**
* 获取任务状态
* GET /tasks/{id}
*/
public Task getTask(String taskId) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/tasks/" + taskId))
.header("A2A-Version", "1.0")
.timeout(Duration.ofSeconds(10))
.GET()
.build();
HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 404) {
throw new RuntimeException("Task not found: " + taskId);
}
if (response.statusCode() != 200) {
throw new RuntimeException(
"Get task failed, status: " + response.statusCode());
}
return objectMapper.readValue(response.body(), Task.class);
} catch (Exception e) {
throw new RuntimeException("Failed to get task: " + taskId, e);
}
}
/**
* 取消任务
* POST /tasks/{id}:cancel
*/
public Task cancelTask(String taskId) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/tasks/" + taskId + ":cancel"))
.header("A2A-Version", "1.0")
.timeout(Duration.ofSeconds(10))
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
return objectMapper.readValue(response.body(), Task.class);
} catch (Exception e) {
throw new RuntimeException("Failed to cancel task: " + taskId, e);
}
}
/**
* 轮询等待任务完成
*/
public Task waitForCompletion(String taskId, Duration timeout) {
long deadline = System.currentTimeMillis() + timeout.toMillis();
long interval = 500; // 500ms 轮询间隔
while (System.currentTimeMillis() < deadline) {
Task task = getTask(taskId);
TaskState state = task.getStatus().getState();
switch (state) {
case TASK_STATE_COMPLETED:
case TASK_STATE_FAILED:
case TASK_STATE_CANCELED:
case TASK_STATE_REJECTED:
return task;
case TASK_STATE_INPUT_REQUIRED:
case TASK_STATE_AUTH_REQUIRED:
log.info("Task {} requires input: {}",
taskId, task.getStatus().getMessage());
return task;
default:
// 继续轮询
break;
}
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Task polling interrupted", e);
}
}
throw new RuntimeException("Task " + taskId + " timed out after " + timeout);
}
}
4.6 多 Agent 编排实践(Host Agent 模式)
在实际生产中,推荐使用 Host Agent 模式:一个路由 Agent 负责发现和调度其他专业 Agent。
java
package com.example.a2a.client;
import com.example.a2a.model.*;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Agent 注册中心
* 管理多个远程 Agent 的连接和发现
*/
@Slf4j
public class AgentRegistry {
private final Map<String, AgentEntry> agents = new ConcurrentHashMap<>();
/**
* Agent 注册条目
*/
public static class AgentEntry {
public AgentCard card;
public A2AClient client;
public String url;
}
/**
* 发现并注册一个远程 Agent
* 自动获取 Agent Card 并建立连接
*/
public void discoverAgent(String url) {
A2AClient client = new A2AClient(url);
AgentCard card = client.fetchAgentCard();
agents.put(card.getName(), new AgentEntry() {{
this.card = card;
this.client = client;
this.url = url;
}});
log.info("Registered agent: {} at {}", card.getName(), url);
}
/**
* 根据技能标签匹配最合适的 Agent
*/
public Optional<AgentEntry> findAgentByTag(String tag) {
return agents.values().stream()
.filter(entry -> entry.card.getSkills() != null)
.filter(entry -> entry.card.getSkills().stream()
.anyMatch(skill -> skill.getTags() != null
&& skill.getTags().contains(tag)))
.findFirst();
}
/**
* 向指定 Agent 发送消息并等待结果
*/
public Task delegateTo(String agentName, String text) {
AgentEntry entry = agents.get(agentName);
if (entry == null) {
throw new RuntimeException("Agent not found: " + agentName);
}
return entry.client.sendMessageAndWait(text);
}
/**
* 获取所有已注册 Agent 摘要
*/
public String getAgentsSummary() {
StringBuilder sb = new StringBuilder("可用 Agent 列表:\n");
agents.forEach((name, entry) -> {
sb.append(" - ").append(name)
.append(": ").append(entry.card.getDescription()).append("\n");
if (entry.card.getSkills() != null) {
for (AgentSkill skill : entry.card.getSkills()) {
sb.append(" 技能: ").append(skill.getName())
.append(" (").append(skill.getTags()).append(")\n");
}
}
});
return sb.toString();
}
}
使用示例:
java
package com.example.a2a;
import com.example.a2a.client.AgentRegistry;
import com.example.a2a.model.Task;
import com.example.a2a.model.AgentRegistry.AgentEntry;
import java.util.Optional;
/**
* Host Agent 编排示例
* 路由 Agent 根据用户意图将任务委派给专业 Agent
*/
public class HostAgentExample {
public static void main(String[] args) {
AgentRegistry registry = new AgentRegistry();
// 1. 发现并注册专业 Agent
registry.discoverAgent("http://localhost:8081"); // 订单服务 Agent
registry.discoverAgent("http://localhost:8082"); // 客服 Agent
registry.discoverAgent("http://localhost:8083"); // 数据分析 Agent
// 2. 根据用户意图路由
String userInput = "帮我查询订单 ORD-001 的物流状态";
if (userInput.contains("订单") || userInput.contains("退款")) {
Task result = registry.delegateTo("Order Service Agent", userInput);
System.out.println("结果: " + result.getArtifacts().get(0).getParts().get(0).getText());
} else if (userInput.contains("投诉") || userInput.contains("人工")) {
Task result = registry.delegateTo("Customer Service Agent", userInput);
System.out.println("结果: " + result.getArtifacts().get(0).getParts().get(0).getText());
}
}
}
4.7 生产环境注意事项
线程池与异步处理
java
@Configuration
public class A2aAsyncConfig {
@Bean
public ThreadPoolTaskExecutor a2aTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("a2a-task-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
任务持久化
生产环境应将 TaskManager 替换为数据库实现:
java
@Repository
public class JpaTaskRepository implements TaskStore {
@PersistenceContext
private EntityManager em;
@Override
public Task createTask(Task task) {
em.persist(task);
return task;
}
@Override
@Transactional
public void completeWithArtifact(String taskId, Artifact artifact, Message reply) {
Task task = em.find(Task.class, taskId);
task.setStatus(TaskStatus.of(TaskState.TASK_STATE_COMPLETED, reply));
task.setArtifacts(List.of(artifact));
em.persist(artifact);
em.merge(task);
}
}
安全认证
java
@Component
public class A2aAuthFilter extends OncePerRequestFilter {
@Value("${a2a.auth.api-key}")
private String expectedApiKey;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
// Agent Card 端点允许匿名访问
if (request.getRequestURI().equals("/.well-known/agent-card.json")) {
filterChain.doFilter(request, response);
return;
}
String auth = request.getHeader("Authorization");
if (auth == null || !auth.equals("Bearer " + expectedApiKey)) {
response.setStatus(401);
response.getWriter().write("{\"error\":{\"code\":401,\"message\":\"Unauthorized\"}}");
return;
}
filterChain.doFilter(request, response);
}
}
错误处理
java
@RestControllerAdvice
public class A2aErrorHandler {
@ExceptionHandler(A2ATaskNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Map<String, Object> handleTaskNotFound(A2ATaskNotFoundException ex) {
return Map.of(
"error", Map.of(
"code", 404,
"status", "NOT_FOUND",
"message", ex.getMessage(),
"details", List.of(Map.of(
"@type", "type.googleapis.com/google.rpc.ErrorInfo",
"reason", "TASK_NOT_FOUND",
"domain", "a2a-protocol.org"
))
)
);
}
}
五、架构模式对比
5.1 P2P 模式 vs Host Agent 模式
| 维度 | P2P(对等网络) | Host Agent(中心路由) |
|---|---|---|
| 复杂度 | Agent 间 N*(N-1)/2 连接 | Agent 只需连接 Host |
| 扩展性 | 新 Agent 需通知所有其他 Agent | 新 Agent 只需注册到 Host |
| 调试 | 困难(分布式决策) | 简单(Host 集中决策) |
| 推荐场景 | 少量固定 Agent | 生产环境首选 |
5.2 推荐的生产架构
┌──────────────┐
│ 用户/客户端 │
└──────┬───────┘
│
┌──────▼───────┐
│ Host Agent │ ← 路由 + 编排
│ (Gateway) │
└──┬───┬───┬───┘
│ │ │
┌────────────┘ │ └────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ 订单 Agent │ │ 客服 Agent │ │ 数据分析 Agent │
│ :8081 │ │ :8082 │ │ :8083 │
└─────┬─────┘ └─────┬─────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ 订单 DB │ │ 工单系统 │ │ 数据仓库 │
└──────────┘ └──────────┘ └──────────────┘
六、总结
A2A 协议为构建多 Agent 协作系统提供了标准化的通信方案。本文的核心要点:
- Agent Card 是 A2A 的发现机制,每个 Agent 必须在
/.well-known/agent-card.json提供自描述信息 - Task 是 A2A 的核心工作单元,有完整的生命周期状态管理
- Host Agent 模式 是生产环境的推荐架构,避免 P2P 的连接爆炸问题
- A2A + MCP 互补:A2A 负责 Agent 间通信,MCP 负责 Agent 与工具的交互
- Java 实现可直接使用
HttpClient+ Jackson,无需额外 SDK 依赖