多 Agent 协作别过度设计:A2A(Agent2Agent)协议

摘要:很多开发者做多Agent协作时,分不清什么时候该上A2A协议。本文从工程落地角度通俗拆解A2A‑1.0,区分进程内协作与远程跨域Agent协作边界,结合Spring AI Alibaba实战案例,讲清楚A2A能干什么、什么必须业务自己实现,避免过度设计。

很多开发者在做AI Agent多智能体协作时,都会混淆一个核心问题:到底什么时候需要用A2A协议?本地多Agent协作能不能用A2A?A2A能解决哪些问题、哪些坑它管不了?

一、核心大前提:90%的本地多Agent,根本不需要A2A

首先纠正一个高频误区:A2A不是本地多Agent调度框架,它是跨机器、跨团队、跨安全域的AI智能体通信标准协议。

如果你的所有Agent角色都运行在同一个进程、同一个Spring服务 内,直接本地方法调用即可,例如 agent.run(input),简单高效、无网络开销、无协议冗余,完全没必要引入A2A协议。

二、严格界定:什么时候才需要启用A2A协议?

只有满足任意一条分布式、跨域协作场景,才考虑接入A2A协议,这是落地选型的核心标准:

  1. 独立部署:各个Agent拆分独立服务,部署在不同机器、不同集群,无法本地调用;
  2. 跨团队维护:不同Agent由不同业务团队独立开发、迭代、运维,需要标准化跨团队对接;
  3. 跨安全域调用:存在内外网隔离、甲乙双方服务对接、跨租户权限隔离等场景,需要标准化远程通信。

三、A2A的能力边界:能做什么、绝不做什么

搞懂边界,才能避免落地踩坑,这是A2A最核心的工程认知:

✅ A2A协议核心解决的问题

统一远程AI Agent的协作规范,标准化解决四大核心问题:远程Agent能力发现、标准化消息交互、长异步任务全链路跟踪、结构化结果/大文件产物交付。

❌ A2A不负责、不自动实现的能力

A2A只是通信层、数据格式层标准,不包含任何分布式一致性、任务容错能力,以下逻辑必须业务层自行实现:

  • 并发读写冲突处理、乐观锁版本控制;
  • 服务宕机后的任务接管、租约机制;
  • 接口重试导致的重复执行、幂等校验;
  • 分布式隔离令牌(fencingToken)、任务归属控制。

四、A2A核心名词拆解(通俗版+工程释义)

A2A整套协作体系由五大核心组件构成,全程对应外包工单协作通俗类比,秒懂核心逻辑:

1、Agent Card(Agent能力名片)

通俗类比 :外包公司公开的宣传手册+接口说明书

工程释义:机器可读的标准化能力描述文件,是远程Agent的唯一对接入口。调用方对接前,必须先读取对方的Agent Card,明确核心信息:Agent能力范围、接口地址、入参出参Schema、认证方式、权限要求。

等同于调用第三方API前查阅的官方文档,A2A将人工文档标准化、机器自动化解析,实现Agent无感能力发现。

2、Task(全局唯一任务)

通俗类比 :带唯一工单号的正式服务工单

工程释义 :A2A的核心调度单元,每个任务拥有全局唯一 taskId。区别于普通同步接口,A2A专为长异步Agent任务设计,任务可长时间执行、可中途暂停、可多轮恢复,不局限于单次请求响应。

3、Message(交互消息)

通俗类比 :供需双方来回沟通的邮件、补充说明

工程释义:Agent间的标准化交互载体,支持多轮对话、参数补充、需求变更、状态同步,打破单次请求响应的局限,适配Agent复杂交互式协作场景。

4、Context(会话上下文)

通俗类比 :工单专属归档文件夹

工程释义:全局会话容器,将同一个Task下的所有消息、操作日志、最终产物、状态变更全部关联绑定,实现全链路可追溯、可复盘。

5、Artifact(任务产物)

通俗类比 :外包最终交付的报告、成果文件

工程释义:任务最终交付结果,支持多种形态:结构化JSON数据、PDF报告、代码文件、数据资源URI。针对大文件场景,A2A默认传递资源引用URI,不阻塞消息体,保障传输效率。

五、A2A细粒度任务状态机(核心重点)

普通接口只有「成功/失败」两种状态,完全无法适配Agent长任务、可暂停、可恢复的场景。A2A定义了全套细粒度任务状态枚举,精准描述任务全生命周期:

任务状态枚举 状态含义 场景说明
TASK_STATE_UNSPECIFIED 未知/未初始化 任务默认初始状态,未提交未初始化
TASK_STATE_SUBMITTED 任务已提交 任务创建完成,等待Agent开始处理
TASK_STATE_WORKING 任务执行中 远程Agent正在正常执行业务逻辑
TASK_STATE_INPUT_REQUIRED 暂停‑需外部输入 任务中断,缺少业务参数/用户指令,等待补充后恢复
TASK_STATE_AUTH_REQUIRED 暂停‑需权限授权 任务中断,缺少资源访问权限、登录授权,授权后可继续执行
TASK_STATE_COMPLETED 任务成功完成 执行完毕,可读取Artifact产物
TASK_STATE_FAILED 任务执行失败 程序异常、业务校验失败,任务终止
TASK_STATE_CANCELED 任务主动取消 调用方主动终止未完成任务
TASK_STATE_REJECTED 任务被拒绝 参数非法、权限不足,Agent直接拒收任务

核心重点 :INPUT_REQUIRED、AUTH_REQUIRED是A2A的精髓,任务既非成功也非失败,属于挂起暂停状态,完美适配Agent长任务交互式协作场景。

六、关键避坑:A2A不解决分布式一致性问题

这是A2A落地最大的误区,再次重点强调:A2A只定义通信格式、任务字段、交互规范,不提供任何分布式容错能力。

以下核心分布式能力,必须由业务代码自行实现:

  • 多Worker并发修改同一Task:A2A无乐观锁、无版本控制,需业务自行加version字段防冲突;
  • 服务宕机任务接管:A2A无租约机制,需自行实现 leaseUntil、ownerAgentId 归属管控;
  • 重试重复执行:A2A无幂等机制,需业务自行做幂等校验、fencingToken隔离。

七、落地选型对比:进程内DAG VS 分布式A2A

一张表搞定所有场景选型,彻底告别纠结:

部署场景 调用方式 是否需要A2A 是否需要分布式防护逻辑
所有Agent同进程、同一个Spring服务 本地方法调用 agent.run() 不需要 不需要
Agent独立部署、跨团队/跨安全域 网络远程调用 需要完整A2A协议 必须实现Version、租约、幂等、令牌隔离

八、实战案例:需求Agent + 代码Agent,基于Spring AI Alibaba

以「需求整理Agent + 代码生成Agent」为例,对比进程内借鉴A2A思想 和远程微服务启用完整A2A‑1.0协议两种落地模式,基于Spring AI Alibaba框架。

场景1:同进程部署(Spring AI Alibaba,只借鉴A2A思想,不引入A2A协议)

两个Agent都在同一个Spring Boot应用中,使用Spring AI Alibaba构建Agent实例,没有网络调用,借鉴A2A结构化数据契约的设计思想,拒绝大段自然语言传递参数。

❌ 错误做法:Agent之间传递大段自然语言文本

需求Agent输出文本:"我整理好了登录模块需求,需要做token校验,接口响应不能太慢,麻烦你实现一下"

问题:Token开销高,LLM自由转述容易丢失约束条件,下游Agent需要做文本理解,输出不稳定,无法做结构化参数校验。

✅ 正确做法:Agent之间传递结构化POJO对象

借助Spring AI Alibaba的StructuredOutputConverter,强制需求Agent输出强类型RequirementOutput对象,而不是自由文本字符串。

  1. 先定义POJO实体

    import com.fasterxml.jackson.annotation.JsonClassDescription;
    import java.util.List;

    @JsonClassDescription("登录模块开发需求输出对象")
    public class RequirementOutput {
    private String taskId;
    private String taskType;
    private List dependencies;
    private List acceptanceCriteria;
    private String requirementDesc;
    private String artifactUri;

    复制代码
     // getter / setter 省略

    }

  2. Agent内部使用StructuredOutputConverter强制结构化输出

    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.converter.BeanOutputConverter;

    /**

    • 需求整理Agent,Spring AI Alibaba 进程内Agent
      */
      public class RequirementAgent {

      private final ChatClient chatClient;
      // 结构化输出转换器,指定目标POJO
      private final BeanOutputConverter outputConverter
      = new BeanOutputConverter<>(RequirementOutput.class);

      public RequirementAgent(ChatClient chatClient) {
      this.chatClient = chatClient;
      }

      public RequirementOutput run(String userInput) {
      return chatClient.prompt()
      .user(u -> u.text("整理下面业务需求,输出标准化开发需求:{input}\n{formatInstructions}")
      .param("input", userInput)
      .param("formatInstructions", outputConverter.getFormatInstructions()))
      .call()
      .entity(outputConverter); // 直接反序列化为Java对象
      }
      }

  3. 编排器直接本地调用串联两个Agent

    // 编排器代码,同一个Spring进程内
    RequirementAgent requirementAgent;
    CodeAgent codeAgent;

    // 用户原始输入
    String userInput = "实现登录接口,需要token校验,接口响应小于200ms,依赖user_db";

    // 需求Agent直接返回强类型对象,不是字符串
    RequirementOutput reqOut = requirementAgent.run(userInput);
    // 直接把POJO传给下游代码Agent,无自然语言中转
    CodeResult codeRes = codeAgent.run(reqOut);

说明:该模式只是借鉴A2A的"结构化产物"思想,没有Agent Card、没有网络Task状态机、没有Artifact协议对象 ,属于进程内最优方案。
关键点:BeanOutputConverter会自动把JSON Schema传给大模型,强制LLM输出符合POJO结构,避免自由文本带来的信息丢失。

场景2:跨团队远程部署,Spring AI Alibaba对接A2A‑1.0协议(带代码样例支撑)

业务背景:

  • A团队:编排服务(Spring Boot + Spring AI Alibaba),内部运行需求Agent;
  • B团队:独立微服务,部署代码Agent,对外暴露A2A 1.0标准HTTP接口;
  • 两套服务属于不同服务、不同团队,跨安全域,必须走网络调用,使用完整A2A协议。

说明:Spring AI Alibaba目前没有封装完整A2A客户端SDK,业务侧基于RestTemplate/WebClient封装A2A HTTP调用,下面是简化可运行伪代码,还原完整流程。

复制代码
import org.springframework.web.reactive.function.client.WebClient;
import java.util.UUID;

/**
 * A团队编排服务:A2A远程调用B团队代码Agent
 */
public class A2ACodeAgentClient {

    private final WebClient webClient;
    // B团队代码Agent服务地址
    private static final String AGENT_BASE_URL = "http://code-agent-service";

    public A2ACodeAgentClient(WebClient.Builder builder) {
        this.webClient = builder.baseUrl(AGENT_BASE_URL).build();
    }

    // 步骤1:拉取远程Agent Card(能力名片)
    public String fetchAgentCard() {
        return webClient.get()
                .uri("/.well-known/agent-card")
                .retrieve()
                .bodyToMono(String.class)
                .block();
    }

    // 步骤2:创建A2A Task,生成全局taskId
    public String createA2ATask() {
        String taskId = "a2a-task-" + UUID.randomUUID();
        // 请求体省略A2A标准task对象
        webClient.post()
                .uri("/v1/tasks")
                .bodyValue("""
                        {
                            "taskId":"%s",
                            "state":"TASK_STATE_SUBMITTED"
                        }
                        """.formatted(taskId))
                .retrieve()
                .toBodilessEntity()
                .block();
        return taskId;
    }

    // 步骤3:发送Message,携带结构化需求payload
    public void sendMessage(String taskId, RequirementOutput reqOut) {
        webClient.post()
                .uri("/v1/tasks/%s/messages".formatted(taskId))
                .bodyValue("""
                        {
                            "taskId":"%s",
                            "payload":%s
                        }
                        """.formatted(taskId, Jackson.toJson(reqOut)))
                .retrieve()
                .toBodilessEntity()
                .block();
    }

    // 轮询任务状态,生产环境建议改为webhook回调
    public TaskStatus pollTaskStatus(String taskId) {
        String resp = webClient.get()
                .uri("/v1/tasks/%s".formatted(taskId))
                .retrieve()
                .bodyToMono(String.class)
                .block();
        return parseTaskStatus(resp);
    }

    // 发送授权信息,恢复挂起任务 AUTH_REQUIRED
    public void resumeTaskWithAuth(String taskId, String authToken) {
        webClient.post()
                .uri("/v1/tasks/%s/messages".formatted(taskId))
                .bodyValue("""
                        {
                            "taskId":"%s",
                            "payload":{"authToken":"%s"}
                        }
                        """.formatted(taskId, authToken))
                .retrieve()
                .toBodilessEntity()
                .block();
    }
}

编排主流程伪代码:

复制代码
// 编排入口
public void runRemoteAgentFlow(String userInput){
    // 1、本地需求Agent输出结构化对象(Spring AI Alibaba结构化输出)
    RequirementOutput reqOut = requirementAgent.run(userInput);

    A2ACodeAgentClient a2AClient = new A2ACodeAgentClient(webClientBuilder);

    // 2、拉取远程Agent Card做能力校验
    String agentCard = a2AClient.fetchAgentCard();

    //3、创建A2A Task
    String taskId = a2AClient.createA2ATask();

    //4、发送结构化需求到远程Agent
    a2AClient.sendMessage(taskId, reqOut);

    //5、轮询状态
    while(true){
        TaskStatus status = a2AClient.pollTaskStatus(taskId);
        if("TASK_STATE_AUTH_REQUIRED".equals(status.getState())){
            // 任务挂起,业务获取授权凭证,恢复任务
            String authToken = getBizAuthToken();
            a2AClient.resumeTaskWithAuth(taskId, authToken);
        }else if("TASK_STATE_COMPLETED".equals(status.getState())){
            // 读取Artifact产物URI,下载结果文件
            List<Artifact> artifacts = status.getArtifacts();
            for (Artifact artifact : artifacts) {
                downloadArtifact(artifact.getUri());
            }
            break;
        }else if(List.of("TASK_STATE_FAILED","TASK_STATE_REJECTED","TASK_STATE_CANCELED").contains(status.getState())){
            throw new RuntimeException("A2A任务异常:"+status.getState());
        }
    }
}

⚠️重要工程提醒
虽然使用A2A协议完成通信,但Spring业务侧仍然需要自己在数据库维护:
version乐观锁版本号、ownerAgentId任务持有者、leaseUntil租约过期时间、fencingToken隔离令牌,处理Worker宕机、并发更新、重试幂等问题。A2A协议不会帮你实现这部分分布式逻辑。
补充:生产环境不建议轮询,优先使用A2A规范webhook回调接收状态变更;示例仅演示协议交互逻辑,异常处理、超时、重试、序列化做了简化。

九、全文总结(核心干货复盘)

  1. A2A是跨域远程Agent通信协议,本地同进程Spring应用内多Agent直接方法调用,无需过度设计;
  2. 核心适用场景:Agent独立部署、跨团队维护、跨安全域调用;
  3. 五大核心组件:AgentCard(能力发现)、Task(长任务调度)、Message(多轮交互)、Context(链路追溯)、Artifact(产物交付);
  4. 核心价值:标准化长任务生命周期,支持任务暂停、外部输入、授权恢复,解决远程Agent互通;
  5. 核心边界:A2A只管通信与数据格式,分布式一致性、容错、幂等、租约全部业务自研;
  6. Spring AI Alibaba中:进程内优先用BeanOutputConverter做结构化POJO传参;只有远程跨服务时,才对接完整A2A协议。
相关推荐
FRing032 小时前
什么是AI、模型和token、vibecoding介绍、ClaudeCode环境搭建
人工智能·ai应用开发·智能体开发
Java成神之路-7 天前
大模型API输出莫名截断?
ai应用开发
Java成神之路-9 天前
大模型输出工程契约设计:从Prompt软约束到Schema硬约束
prompt·ai应用开发
Jing_jing_X11 天前
大模型只会输出token,是怎么“调用工具“的?
ai·agent·个人开发·ai应用开发
孙启超1 个月前
【AI开发之Rust】第 5 课:引用与生命周期 —— 借用能活多久?
人工智能·后端·rust·llm·ai应用开发
Java成神之路-1 个月前
RAG 工程最优解:意图路由分流架构
人工智能·ai应用开发·springaialibaba
星野云联AIoT技术洞察2 个月前
Dify 适合什么 AI 应用项目:Workflow、RAG、Agent 与自研系统的边界
agent·workflow·llmops·dify·rag·ai agent·ai应用开发
dogstarhuang2 个月前
OpenAI GPT-5.6 降价后如何重算 API 账单?多模型路由与成本治理实战
服务器·网络·人工智能·大模型·api·ai应用开发·接口管理
孙启超2 个月前
【AI应用开发】什么是混合检索(Hybrid Search)?向量检索 + BM25 关键词检索,适用场景与 RRF 融合原理
人工智能·缓存·llm·向量数据库·bm25·向量化·ai应用开发