引子:一句"赶飞机",两个 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 Card | JSON 元数据文档,描述智能体的身份、能力、端点地址、技能和认证要求 | 生意场上的名片:先递名片,再谈事 |
| 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 就是智能体在协作网络里的简历。区别在于,智能体的简历多了两项硬信息——端点地址和认证要求。功能被调可以匿名,智能体协作必须验明正身。
三、一单是怎么谈成的:状态机走完才算数
把引子里那个场景的协作链路画出来:
链路里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 是你在智能体协作网络里的席位,席位放出来的时候,坐上去的人最少。
参考与出处
本文的协议概念、接口名与能力描述来自以下官方文档与发布说明:
- 通过 AgentAbilityExtension 实现智能体间 A2A 协议通信(AI / Agent Framework Kit)
- @ohos.app.agent.AgentExtensionAbility 接口参考(Ability Kit)
- A2A Server 模块 API 文档
- 小艺 AgentChips 交互流程
- HarmonyOS 7(API 26)面向 App 与 Agent 开放 Skill、Agent 及 AI 开放能力(官方新闻)
- HarmonyOS 新能力一览(7 / API 26)
最后一句:图标时代比的是谁的门面亮,A2A 时代比的是谁的名片硬——把 Agent Card 当名片写,把任务状态机当谈判记录记,下一个被系统拉去谈事的,就是你的智能体。
面向已做 Skill 化的开发者,本文把 Agent Card、任务状态机讲得落地,适合判断 HarmonyOS 7 智能体协作的接入时机与能力边界。