ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

A2A 协议深度实战:Java 后端如何构建企业级 Agent-to-Agent 协作基础设施

A2A 协议深度实战:Java 后端如何构建企业级 Agent-to-Agent 协作基础设施 当 MCP 解决Agent 怎么调用工具之后A2A 正在回答Agent 怎么协作这个更大的问题。本文结合 Google A2A 1.0 规范与 A2A Java SDK 1.2.0从源码到生产拆解如何用 Java 后端承载跨团队、跨框架、跨组织的智能体协作网络。一、从 MCP 到 A2A为什么需要 Agent 之间的协作协议2024 年底 Anthropic 推出 MCPModel Context Protocol后AI 应用架构很快迎来一个共识把工具、数据、提示词统一成标准接口Agent 就能像插 USB-C 一样接入任意能力。但 MCP 解决的是垂直问题——单个 Agent 如何调用外部工具。当企业里出现几十个甚至上百个 Agent分别由不同团队用不同框架开发、部署在不同机房、对接不同大模型时新的问题浮现一个规划 Agent 如何把一个长任务安全地委派给库存 Agent、物流 Agent、客服 Agent被委派的 Agent 需要反问、需要人工介入、需要 10 分钟才能跑完调用方怎么等待结果跨组织的 Agent 如何互相发现能力、互相认证、交换结构化产物这正是 Google 在 2025 年 4 月提出、随后捐给 Linux 基金会的A2AAgent2Agent协议要回答的问题。如果说 MCP 是Agent 的 USB-CA2A 就是Agent 之间的 HTTP/REST——它让 Agent 像微服务一样互相协作但保留了大模型特有的异步、流式、可中断、需人工确认等语义。2026 年 1 月 A2A 发布1.0 稳定版核心数据模型冻结2026 年 8 月A2A Java SDK 1.2.0发布新增非 CDI 集成复用现有授权流、TaskStreamLifecycleHook 等关键特性。对 Java 后端工程师来说A2A 不再是 Python 专属的演示协议而是可以落地到 Spring Boot / Quarkus 生产环境的真实基础设施。二、A2A 核心概念五个名词、一条主线A2A 的协议设计刻意保持不炫技传输用 HTTP/gRPC消息用 JSON-RPC 2.0流式用 Server-Sent Events认证用 OAuth 2.0 / JWT。理解下面五个名词基本就掌握了协议主线。2.1 Agent Card智能体的身份证能力清单Agent Card 是一份发布在/.well-known/agent-card.json的 JSON 文档描述一个 Agent 的入口地址、支持协议、能力、技能列表、认证方式等。客户端在调用前必须先拉取并解析 Agent Card。{ name: Inventory Agent, description: 查询库存、锁定库存、释放库存, url: https://inventory.example.com/a2a, version: 1.0.0, capabilities: { streaming: true, pushNotifications: false, stateTransitionStreaming: true }, defaultInputModes: [text], defaultOutputModes: [text], skills: [ { id: stock_query, name: 库存查询, description: 根据 SKU 与仓库查询可用库存, tags: [inventory, stock], examples: [查询 SKU-10086 在上海仓的库存] } ], authentication: { schemes: [Bearer] } }2.2 Task有状态、有生命周期的协作单元Task 是 A2A 的核心。一次用户请求会对应一个 TaskTask 有明确的状态机submitted → working → input-required → completed / canceled / failed。A2A 支持多轮交互Agent 执行到一半发现信息不足可以把 Task 置为input-required等待调用方补充后再继续。2.3 Message / Part一次对话回合与内容的载体Message 是一次角色为user或agent的对话回合。Message 内部由多个 Part 组成Part 可以是TextPart文本、FilePart文件、DataPart结构化数据。这种设计与多模态大模型的消息格式天然对齐。2.4 Artifact任务的 durable outputAgent 完成任务后产出的结果叫 Artifact。一个 Task 可以有多个 Artifact每个 Artifact 同样由 Part 组成。客户端通过监听 Artifact 更新事件来收集最终结果。2.5 A2A vs MCP不是竞争是互补维度MCPA2A解决问题Agent ↔ 工具/数据Agent ↔ Agent关系垂直Agent 向下调用能力水平Agent 横向协作核心概念Tools、Resources、PromptsTask、Agent Card、Skill、Artifact交互模式同步请求/响应异步优先支持长任务与人机协同传输stdio / HTTP SSEHTTP / gRPC / JSON-RPC治理Linux Foundation / AAIFLinux Foundation / AAIF生产中的典型分层顶层编排 Agent 通过 A2A 委派给多个专业 Agent每个专业 Agent 内部通过 MCP 调用数据库、API、搜索引擎等工具。三、A2A Java SDK 1.2.0 源码解析A2A Java SDK 采用模块化设计核心包为io.a2a.sdk主要分为spec协议模型、server服务端、client客户端三大模块。下面结合 1.2.0 版本的关键接口分析其运行机理。3.1 服务端核心AgentExecutor 与 TaskUpdater服务端开发者通常只需要实现AgentExecutor接口并在execute方法里处理业务逻辑。SDK 通过TaskUpdater1.2.0 之前的旧版本叫AgentEmitter封装了状态推进、事件发送等样板代码。package com.example.a2a.inventory; import io.a2a.server.agentexecution.AgentExecutor; import io.a2a.server.agentexecution.RequestContext; import io.a2a.server.tasks.TaskUpdater; import io.a2a.spec.*; import jakarta.enterprise.context.ApplicationScoped; import java.util.List; ApplicationScoped public class InventoryAgentExecutor implements AgentExecutor { private final InventoryService inventoryService; public InventoryAgentExecutor(InventoryService inventoryService) { this.inventoryService inventoryService; } Override public void execute(RequestContext context, TaskUpdater updater) throws JSONRPCError { // 1. 新任务先提交已有任务则直接继续 if (context.getTask() null) { updater.submit(); } updater.startWork(); try { // 2. 解析用户消息中的结构化请求 StockRequest request parseStockRequest(context.getMessage()); // 3. 参数校验失败时将任务置为 input-required等待调用方补充 if (request.sku() null || request.warehouse() null) { updater.requestInput(List.of(new TextPart( 请提供 SKU 与仓库编码例如{\sku\:\SKU-10086\,\warehouse\:\SH-01\}))); return; } // 4. 调用业务服务带幂等令牌防止重复锁定 StockResult result inventoryService.queryAndLock( request.sku(), request.warehouse(), context.getTask() ! null ? context.getTask().id() : null ); // 5. 将结果封装为 DataPart作为 Artifact 产出 DataPart artifact new DataPart(result.toMap()); updater.addArtifact(List.of(artifact), null, null, null); updater.complete(); } catch (InventoryException e) { // 6. 业务异常转换为 A2A 标准错误 throw new JSONRPCError(JSONRPCErrorCode.INTERNAL_ERROR, e.getMessage(), e); } } Override public void cancel(RequestContext context, TaskUpdater updater) throws JSONRPCError { Task task context.getTask(); if (task null || task.status().state() TaskState.COMPLETED || task.status().state() TaskState.CANCELED) { throw new TaskNotCancelableError(); } // 回滚已锁定库存 inventoryService.releaseLock(task.id()); updater.cancel(); } private StockRequest parseStockRequest(Message message) { // 优先从 DataPart 解析 JSON否则从 TextPart 中用简单规则提取 // 生产环境建议接入 Spring AI 的 Structured Output for (Part part : message.parts()) { if (part instanceof DataPart dp) { return StockRequest.fromMap(dp.data()); } if (part instanceof TextPart tp) { return StockRequest.fromText(tp.text()); } } return new StockRequest(null, null, 0); } }注意TaskUpdater在 1.2.0 中的演进旧版本示例里直接操作EventQueue和AgentEmitter新版本把状态变更、Artifact 追加、完成、取消等操作收敛到TaskUpdater减少了开发者因状态机调用顺序错误导致的协议不一致问题。3.2 任务持久化TaskStore SPI默认实现是内存InMemoryTaskStore显然不适合生产。SDK 定义了 SPI 接口TaskStore企业需要对接 Redis、PostgreSQL 或 MongoDB。public interface TaskStore { Task create(Task task); Task get(String taskId); Task update(Task task); void delete(String taskId); }生产实现建议用数据库行锁或 Redis 分布式锁保证 Task 状态变更的原子性Task 历史消息history可以单独存对象存储元数据存关系库设置 TTL自动清理已完成且超过保留期限的任务。3.3 客户端核心A2ACardResolver ClientBuilder客户端调用远程 Agent 的流程非常清晰import io.a2a.client.A2ACardResolver; import io.a2a.client.Client; import io.a2a.client.ClientConfig; import io.a2a.client.transports.jsonrpc.JSONRPCTransport; import io.a2a.client.transports.jsonrpc.JSONRPCTransportConfig; import io.a2a.spec.*; import java.util.List; public class OrderOrchestrator { private final Client inventoryClient; public OrderOrchestrator(String inventoryAgentUrl) throws Exception { // 1. 发现 Agent Card AgentCard card new A2ACardResolver(inventoryAgentUrl).getAgentCard(); // 2. 声明我能接受的输出类型 ClientConfig config new ClientConfig.Builder() .setAcceptedOutputModes(List.of(text, data)) .build(); // 3. 构建客户端指定 JSON-RPC 传输 this.inventoryClient Client.builder(card) .clientConfig(config) .withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig()) .build(); } public StockResult lockStock(String sku, String warehouse) throws Exception { // 4. 构造消息 Message message Message.builder() .role(user) .parts(List.of(new DataPart(Map.of( sku, sku, warehouse, warehouse, operation, lock )))) .build(); // 5. 发送任务并同步等待结果也可使用流式订阅 Task task inventoryClient.sendMessage(message, null); // 6. 校验最终状态与产物 if (task.status().state() ! TaskState.COMPLETED) { throw new IllegalStateException(库存 Agent 未完成任务: task.status().state()); } return extractStockResult(task.artifacts()); } }3.4 传输层与线程池配置1.2.0 版本的参考实现基于 Quarkus但也支持 Spring Boot 集成。线程池默认配置# 默认核心线程 5最大 50 a2a.executor.core-pool-size16 a2a.executor.max-pool-size128 a2a.executor.keep-alive-seconds60 # 阻塞调用超时 a2a.blocking.agent.timeout.seconds90 a2a.blocking.consumption.timeout.seconds10对于 LLM 类 Agent必须把a2a.blocking.agent.timeout.seconds调到 60~120 秒对于高并发流式场景线程池要按 QPS × 平均流式时长来估算避免ForkJoinPool被大量 SSE 连接占满。四、企业级实战订单履约多 Agent 协作平台下面以一个真实的电商订单履约场景为例演示如何用 A2A Java 构建多 Agent 协作系统。4.1 业务背景用户下单后订单履约需要多个专业 Agent 协作库存 Agent查询并锁定库存物流 Agent匹配最优承运商、生成运单风控 Agent识别异常订单、触发人工审核客服 Agent在需要人工介入时生成待办。这些 Agent 由不同团队维护库存 Agent 用 Java Spring Boot物流 Agent 用 Python FastAPI风控 Agent 用 Go。A2A 的跨语言特性正好解决异构协作问题。4.2 架构设计┌─────────────────────────────────────────────────────────────┐ │ Order Orchestrator │ │ (Spring Boot A2A Java SDK) │ └──────────────────┬──────────────────────────────────────────┘ │ A2A / JSON-RPC ┌───────────┼───────────┐ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Inventory│ │ Logistics│ │ Risk │ │ Agent │ │ Agent │ │ Agent │ │ (Java) │ │ (Python) │ │ (Go) │ └─────┬────┘ └─────┬────┘ └─────┬────┘ │ MCP │ MCP │ MCP ┌──┴──┐ ┌──┴──┐ ┌──┴──┐ │Redis│ │ TMS │ │Rules│ │ MySQL │ API │ │Engine └─────┘ └─────┘ └─────┘4.3 编排器核心代码package com.example.a2a.order; import io.a2a.client.Client; import io.a2a.spec.*; import org.springframework.stereotype.Service; import java.util.List; import java.util.concurrent.*; Service public class OrderFulfillmentService { private final Client inventoryClient; private final Client logisticsClient; private final Client riskClient; private final ExecutorService executor; public OrderFulfillmentService( Client inventoryClient, Client logisticsClient, Client riskClient) { this.inventoryClient inventoryClient; this.logisticsClient logisticsClient; this.riskClient riskClient; // 独立线程池避免阻塞业务线程 this.executor new ThreadPoolExecutor( 8, 64, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(256), new ThreadFactoryBuilder().setNameFormat(a2a-orch-%d).build(), new ThreadPoolExecutor.CallerRunsPolicy() ); } public FulfillmentResult fulfill(Order order) throws Exception { // 1. 风控先走因为它是前置关卡 FutureRiskResult riskFuture executor.submit(() - runRiskAgent(order)); RiskResult risk riskFuture.get(30, TimeUnit.SECONDS); if (risk.requiresManualReview()) { return FulfillmentResult.manualReview(risk.reason()); } // 2. 库存与物流并行执行 FutureStockResult stockFuture executor.submit(() - runInventoryAgent(order)); FutureLogisticsResult logisticsFuture executor.submit(() - runLogisticsAgent(order)); try { StockResult stock stockFuture.get(60, TimeUnit.SECONDS); LogisticsResult logistics logisticsFuture.get(60, TimeUnit.SECONDS); // 3. 库存锁定成功但物流失败时必须回滚库存 if (!logistics.success()) { compensateInventory(order); return FulfillmentResult.failed(物流匹配失败已回滚库存); } return FulfillmentResult.success(stock, logistics); } catch (TimeoutException e) { stockFuture.cancel(true); logisticsFuture.cancel(true); compensateInventory(order); throw new FulfillmentException(Agent 协作超时, e); } } private StockResult runInventoryAgent(Order order) throws Exception { Message msg Message.builder() .role(user) .parts(List.of(new DataPart(Map.of( sku, order.sku(), warehouse, order.warehouse(), qty, order.qty(), orderNo, order.orderNo(), operation, lock )))) .build(); Task task inventoryClient.sendMessage(msg, null); if (task.status().state() TaskState.INPUT_REQUIRED) { // 实际生产中会回调前端让用户补充这里简化为抛出业务异常 throw new FulfillmentException(库存 Agent 需要补充参数: task.status().message()); } if (task.status().state() ! TaskState.COMPLETED) { throw new FulfillmentException(库存 Agent 执行失败: task.status().state()); } return StockResult.fromArtifact(task.artifacts().get(0)); } private void compensateInventory(Order order) { try { Message msg Message.builder() .role(user) .parts(List.of(new DataPart(Map.of( sku, order.sku(), warehouse, order.warehouse(), qty, order.qty(), orderNo, order.orderNo(), operation, release )))) .build(); inventoryClient.sendMessage(msg, null); } catch (Exception e) { // 补偿失败必须进死信队列人工兜底 deadLetterQueue.send(inventory-compensate, order, e); } } // runRiskAgent / runLogisticsAgent 与上面类似省略... }4.4 关键设计点幂等性每个 Task ID 作为库存锁定的幂等键防止网络重试导致重复锁定补偿事务A2A 不支持分布式事务必须自己实现 SAGA 补偿超时控制不同 Agent 设置不同超时LLM Agent 更长规则 Agent 更短任务取消当父流程失败时主动向子 Agent 发送tasks/cancel释放资源线程隔离编排器使用独立线程池避免一个 Agent 慢调用拖垮整个服务。五、生产踩坑清单5.1 Agent Card 缓存与失效生产环境不要每次调用都重新拉取/.well-known/agent-card.json。建议本地缓存 5~15 分钟调用失败时主动刷新缓存应对 Agent 端点迁移解析失败时降级为走配置中心的路由表。5.2 方法名陷阱message/send 还是 tasks/send官方早期 Java 示例和 TypeScript 指南中使用了tasks/send但 A2A 1.0 规范实际定义的方法是message/send。如果服务端按规范实现客户端用tasks/send会收到-32601 Method not found错误。务必核对 SDK 版本与规范版本并在集成测试里覆盖协议方法名断言。5.3 流式 SSE 连接泄漏A2A 1.2.0 新增TaskStreamLifecycleHook就是为了帮助观测和关闭ChildQueue。生产上要使用 try-with-resources 或响应式取消令牌管理 SSE 订阅设置 TCP keep-alive 与连接池上限监控a2a_active_streams指标出现单调上升立即排查泄漏。5.4 Task 状态并发冲突多个 Worker 同时更新同一个 Task 时如果没有行锁或 CAS会出现状态覆盖。建议自定义TaskStore时使用UPDATE tasks SET state ? WHERE state ? AND id ?进行状态机条件更新对input-required → working的转换必须校验只有当前会话能继续记录状态变更日志方便审计与排障。5.5 认证与授权不要只依赖 TLSA2A 默认支持 Bearer Token但企业场景建议使用 JWT mTLS 双向认证每个 Skill 声明最小权限服务端按 Skill 鉴权跨组织调用时使用短期访问令牌 审计日志避免长期 API Key 泄露。5.6 LLM Agent 的响应不可控如果远端 Agent 内部调用了 LLM它的输出时间、Token 消耗、幻觉风险都不在你的掌控之中。建议对远端 Agent 的响应做 JSON Schema 校验设置硬性 Token 上限与调用预算在 Agent Card 中要求对方暴露cost/tokens等 Artifact用于成本核算。5.7 跨版本 SDK 兼容性A2A Java SDK 的 groupId 目前是临时的io.github.a2asdk未来会变更同时 1.2.0 将AgentEmitter演进为TaskUpdater。建议将 A2A 客户端/服务端封装到独立模块对外暴露业务接口在 CI 中跑协议兼容性测试尤其关注message/send、状态机、Artifact 字段升级 SDK 时先在小流量环境验证。六、总结A2A 不是 MCP 的替代品而是企业多 Agent 架构中必不可少的横向协作层。对 Java 后端工程师而言A2A Java SDK 1.2.0 已经把协议细节封装得比较干净实现AgentExecutor、配置TaskStore、用ClientBuilder发现远端 Agent就能搭建起一个跨语言、跨团队、支持异步流式的 Agent 协作网络。真正的难点不在协议本身而在于生产化任务状态机的并发安全、SAGA 补偿、流式连接治理、跨团队 Agent 的契约与版本管理、成本与 Token 控制。把这些踩过坑之后A2A 才能成为企业级 AI 系统的神经系统而不是又一套好看的演示 Demo。如果你正在用 Java 做 AI Agent建议现在就把 A2A 纳入架构评估它比 MCP 更晚出现但解决的问题更大——不是Agent 能用什么而是Agent 们怎么一起把事情做成。
返回列表