HarmonyOS 7 Agent A2A 实战:让日程智能体和打车智能体自己谈成一单

文章来源声明: 原文作者:威哥爱编程; 来源站点:掘金; 原文链接:https://juejin.cn/post/7686753873660969006; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

面向已做 Skill 化的开发者,本文把 Agent Card、任务状态机讲得落地,适合判断 HarmonyOS 7 智能体协作的接入时机与能力边界。

> 本文基于 HarmonyOS 7(API 26)官方《通过 AgentAbilityExtension 实现智能体间 A2A 协议通信》开发指导与 Agent Framework Kit 相关接口参考整理。文中代码示例为说明问题自写,接口名均出自官方文档并标注出处,方法签名与配置细节以官方 SDK 为准;未核实到底层调用细节的部分以示意性伪代码呈现并标注;文中不含实测数据,场景为按官方能力描述做的推演,运行表现以真机实测为准;7.0 相关能力需升级至 HarmonyOS 7 并以实际支持机型为准。

HarmonyOS 7 Agent A2A 实战

引子:一句"赶飞机",两个 App 自己谈成了

兄弟们好,我是V哥。

先描述一个按官方能力推演的场景:用户对小艺说"明早七点半赶飞机,帮我安排"。系统拆解意图,找到行程应用登记的日程智能体,日程智能体算出"七点十分出门",然后把"需要一辆车"这个子意图交回系统;系统撮合到打车应用登记的打车智能体,两边按 A2A 协议把时间、地点、车型谈拢,用户确认后订单落地,行程卡片自动更新(运行表现以真机实测为准)。

全程用户没打开任何一个 App。上一期V哥写过 Skill 化——应用把功能递进系统的意图分发池;这一期往上走一层:智能体和智能体之间,自己谈。这就是 HarmonyOS 7(API 26)新增的 Agent A2A 能力(官方发布说明)。

官方在发布说明里举的例子是健身 Agent:训练前中后的记录、指导、回顾、问答,全链路健身管理。V哥把"日程 + 打车"这个组合拆成三件事讲清楚:A2A 到底协作了什么、智能体怎么把自己挂进系统、一单是怎么按状态机谈成的。


一、A2A 不是接口互调,是意图协商

先纠正一个最常见的误解。很多同学一听"智能体之间通信",脑子里浮现的是 A 应用 import B 应用的 SDK,然后调 B 的方法。那叫接口互调,不叫 A2A。

接口互调的前提是调用方认识对方:知道对方是谁、方法签名是什么、出错返回什么。而 A2A 的前提恰恰相反——双方互不相识。官方对这套机制的表述是(通过 AgentAbilityExtension 实现智能体间 A2A 协议通信):

A2A(Agent to Agent)协议用于智能体之间的通信。A2A 服务端负责接收客户端请求、触发智能体执行任务、更新任务状态和返回执行结果。

V哥从官方的基本概念里读出的关键,是几个名词撑起的协商结构:

概念官方定义在"一单生意"里的角色
Agent CardJSON 元数据文档,描述智能体的身份、能力、端点地址、技能和认证要求生意场上的名片:先递名片,再谈事
Task有唯一标识和明确定义生命周期的有状态工作单元这单生意本身,全程可跟踪
Message客户端与智能体之间一次通信的单元,带 user / agent 角色谈判桌上说的每一句话
Artifact任务过程中生成的有形交付物,有唯一的产物 ID 和名称成单后的交付:订单、票据、结果
TaskState已提交、工作中、需要用户输入、已完成、已取消、已失败、已拒绝、需要认证谈判进度条

注意"认证要求"这三个字。接口互调的世界里,权限是编译期定死的;A2A 的世界里,智能体在名片上写清楚"找它办事需要什么资质",客户端解析 Agent Card 才知道如何安全有效地交互。发现、认证、协商、交付,四个环节都长在协议里,而不是长在某一次硬编码里——这是V哥判断"A2A 不是接口互调"的依据。

还有一条官方边界划在前头:实现 A2A 协议通信从 API 26.0.0 开始支持,AgentExtensionAbility 首批接口从 API 24 起就有,仅支持 Stage 模型,且不支持在 har 包中使用(接口参考)。7.0 相关能力需升级至 HarmonyOS 7 并以实际支持机型为准。


二、把智能体"挂"进系统:Agent Card 与 A2A 服务端

上一期 Skill 化的活儿是"写契约",这一期的活儿是"写名片 + 写服务端"。官方给的开发路径很清晰:服务端继承 AgentExtensionAbility 获得跨 App 进程通信能力,再通过 Agent Framework Kit 创建支持 A2A 协议的 Server 实例。V哥的打车智能体骨架这么写(接口名出自官方开发指导,方法签名以官方 SDK 为准):

<span>// RideAgentExtAbility.ets —— 打车智能体的 A2A 服务端骨架</span>
<span>import</span> { hilog } <span>from</span> <span>'@kit.PerformanceAnalysisKit'</span>;
<span>import</span> { common, <span>Want</span>, <span>AgentExtensionAbility</span> } <span>from</span> <span>'@kit.AbilityKit'</span>;
<span>import</span> { <span>RequestContext</span>, createA2AServer, <span>Server</span>, <span>TaskState</span>, <span>Role</span> } <span>from</span> <span>'@kit.AgentFrameworkKit'</span>;

<span>const</span> <span>TAG</span> = <span>'=====RideA2AServer===='</span>

<span>export</span> <span>default</span> <span>class</span> <span>RideAgentExtAbility</span> <span>extends</span> <span>AgentExtensionAbility</span> {
  <span>// 服务端实例:负责处理智能体通信和任务生命周期管理</span>
  <span>private</span> <span>server</span>: <span>Server</span> | <span>null</span> = <span>null</span>;

  <span>// 核心业务逻辑:A2A Server 收到客户端请求后触发</span>
  <span>private</span> agentOnData = <span>(<span>method: <span>string</span>, context: RequestContext</span>) =></span> {
    <span>// V哥先做防御:从结构化上下文里取 AgentID,取不到直接拒绝</span>
    <span>const</span> <span>agentId</span>: <span>string</span> = context.<span>getAgentId</span>() ?? <span>''</span>;
    <span>if</span> (!agentId) {
      hilog.<span>error</span>(<span>0x0000</span>, <span>TAG</span>, <span>'Agent ID not found in request context'</span>);
      <span>return</span>;
    }
    <span>const</span> <span>taskId</span>: <span>string</span> = context.<span>getTaskId</span>() ?? <span>''</span>;

    <span>switch</span> (method) {
      <span>case</span> <span>'Execute'</span>:
        <span>// ① 先报"开工":把任务推进到工作中</span>
        <span>this</span>.<span>server</span>?.<span>updateStatus</span>(taskId, {
          <span>state</span>: <span>TaskState</span>.<span>WORKING</span>,
          <span>message</span>: { <span>messageId</span>: <span>'msg1'</span>, <span>role</span>: <span>Role</span>.<span>AGENT</span>,
            <span>parts</span>: [{ <span>mediaType</span>: <span>'text/plain'</span>, <span>text</span>: <span>'正在为你锁定车辆...'</span> }] }
        });
        <span>// ② 业务跑完,把交付物(订单结果)作为产物挂到任务上</span>
        <span>this</span>.<span>server</span>?.<span>addArtifact</span>(taskId, {
          <span>artifactId</span>: <span>'ride-order'</span>,
          <span>parts</span>: [{ <span>mediaType</span>: <span>'application/json'</span>, <span>text</span>: <span>'{"orderNo":"...","eta":"6min"}'</span> }]
        });
        <span>// ③ 最后通知任务完成,整单闭环</span>
        <span>this</span>.<span>server</span>?.<span>updateStatus</span>(taskId, {
          <span>state</span>: <span>TaskState</span>.<span>COMPLETED</span>,
          <span>message</span>: { <span>messageId</span>: <span>'msg3'</span>, <span>role</span>: <span>Role</span>.<span>AGENT</span>,
            <span>parts</span>: [{ <span>mediaType</span>: <span>'text/plain'</span>, <span>text</span>: <span>'车辆已预订成功!'</span> }] }
        });
        <span>break</span>;
      <span>case</span> <span>'Cancel'</span>:
        <span>// 用户取消:收尾逻辑写在这里,别让任务悬着</span>
        hilog.<span>info</span>(<span>0x0000</span>, <span>TAG</span>, <span>'Cancel called'</span>);
        <span>break</span>;
      <span>default</span>:
        <span>break</span>;
    }
  }

  <span>// 实例创建完成:首次收到请求时创建 A2A Server</span>
  <span>async</span> <span>onCreate</span>(<span>want: Want</span>) {
    <span>try</span> {
      <span>const</span> card = <span>this</span>.<span>context</span>.<span>agentCard</span>;   <span>// 智能体名片来自组件上下文</span>
      <span>this</span>.<span>server</span> = <span>createA2AServer</span>(card, <span>this</span>.<span>agentOnData</span>);
    } <span>catch</span> (error) {
      hilog.<span>error</span>(<span>0x0000</span>, <span>TAG</span>, <span>`Failed to create server: <span>${error}</span>`</span>);
    }
  }

  <span>// 客户端建连:开门营业,启动服务端</span>
  <span>onConnect</span>(<span>want: Want, proxy: common.AgentHostProxy</span>) {
    <span>this</span>.<span>server</span>?.<span>start</span>();
  }

  <span>// 收到请求体:转交 server.onMessage,回包走 proxy.sendData</span>
  <span>async</span> <span>onData</span>(<span>proxy: common.AgentHostProxy, data: <span>string</span></span>) {
    <span>this</span>.<span>server</span>?.<span>onMessage</span>(data, <span>(<span>response: <span>string</span></span>) =></span> {
      proxy.<span>sendData</span>(response);
    });
  }

  <span>// 断连与销毁:打烊,回收资源</span>
  <span>onDisconnect</span>(<span>want: Want, proxy: common.AgentHostProxy</span>) {
    <span>this</span>.<span>server</span>?.<span>stop</span>();
  }
  <span>onDestroy</span>(<span></span>) {
    <span>this</span>.<span>server</span>?.<span>stop</span>();
  }
}

V哥把官方要求的生命周期方法全部列出来了,因为这套骨架的时序本身就是协议:create(建名片)→ start(开门营业)→ onMessage(接单)→ stop(打烊)onCreate 里那句 this.context.agentCard 值得多看一眼——智能体的名片不是在代码里现场拼的,它来自组件上下文,是随应用声明与配置登记好的(Agent Card 的具体配置方式以官方 SDK 文档为准)。

对照上一期会发现一个同构关系:SKILL.md 是 Skill 在意图分发池里的简历,Agent Card 就是智能体在协作网络里的简历。区别在于,智能体的简历多了两项硬信息——端点地址和认证要求。功能被调可以匿名,智能体协作必须验明正身。


三、一单是怎么谈成的:状态机走完才算数

把引子里那个场景的协作链路画出来:

A2A 协作链路

链路里V哥最想展开的是"意图协商"这一段,因为它和接口互调的差别全在这里。日程智能体发起打车需求后,双方不是一次调用定生死,而是在 Task 的状态机里推进:

(示意性伪代码,描述协商语义;TaskState 各枚举字段以官方 SDK 为准)

Task 已提交
  └─> 打车 Agent:工作中(WORKING,正在匹配车辆)
        └─> 打车 Agent:需要用户输入(跨 App 动作用户拍板:确认车型与价格)
              └─> 用户确认
                    └─> 打车 Agent:工作中(锁定车辆)
                          └─> 已完成(COMPLETED)+ Artifact(订单交付)

两个值得停下来想的点。

第一,"需要用户输入"是写进协议的状态,不是你自定义的弹窗。 官方列出的任务状态里有这么一项,说明鸿蒙的 A2A 在协议层面就承认:智能体协作走到有副作用的动作时,必须停下来让人拍板。V哥的判断是——查天气、算出发时间这类只读动作可以自动流转,而下单、扣款这种动作,就该老老实实停在"需要用户输入",把确认权还给用户。协议给了你踩刹车的位置,别把油门焊死。

第二,Artifact 和 Message 是两种东西。 谈判过程中说的话是 Message——指令、上下文、状态更新;最终交付的是 Artifact——有唯一 ID 和名称的交付物。把订单结果当聊天消息发出去,下游就没法可靠地引用它,这在多轮、多智能体的协作里是致命的。

还有一类特殊请求单独提一下:官方定义了 PerceptionSuggest 类型的请求,用于小艺感知用户当前所在 App、向智能体请求推荐内容(AgentChips 场景),交互流程有单独文档。也就是说,你的智能体不只是"被别的智能体调",还能在小艺的推荐位上主动露脸。


四、V哥的三条判断

① 撮合靠系统,被撮合的资格靠自己。 日程智能体不需要认识打车智能体——它把子意图交给系统,系统按 Agent Card 撮合。官方把这一范式概括为"意图即服务",小艺作为系统级智能体承担意图理解与服务分发(官方发布说明)。撮合质量取决于系统,而轮不轮得到你进场,取决于名片上的能力描述写得清不清——写得含糊,协商还没开场你就出局了。

② 端 A2A 与云 A2A 是双轨,不是二选一。 官方口径里,端侧保障低时延本地响应、隐私数据不出端,云侧支撑复杂任务的深度推理。日程、行程这类敏感数据走端侧,跨城比价、长程规划这类重推理走云侧,按任务属性分轨,别一刀切。

③ 开发者的活儿从"写页面"变成了"写能力"。 这期和上一期合起来看,脉络很清楚:Skill 化让功能被系统调用,A2A 让智能体彼此协商,界面退居"最后一步",能力登记成了第一等公民。V哥的原话:以前你的应用是一座楼,用户找门牌号;现在你的应用是一个谈判代表,系统按名片找它谈事。 代表的水平体现在名片和谈判记录(Agent Card 与任务状态机)上,不体现在装修上。


五、收尾:协作网络的价值,随节点超线性增长

A2A 的红利有个特点:单看你自己的智能体,价值有限;网络里接入的智能体越多,每一次协商的可能组合就越多。日程智能体今天跟打车谈,明天可能跟机票、酒店、会议助手谈——而你的接入成本,就是一张名片加一个服务端骨架。

7.0 刚发布,协作网络里的节点还不多。上一期V哥说 Skill 化是新门票,这一期补一句:A2A 是你在智能体协作网络里的席位,席位放出来的时候,坐上去的人最少。


参考与出处

本文的协议概念、接口名与能力描述来自以下官方文档与发布说明:


最后一句:图标时代比的是谁的门面亮,A2A 时代比的是谁的名片硬——把 Agent Card 当名片写,把任务状态机当谈判记录记,下一个被系统拉去谈事的,就是你的智能体。