前言

上篇我们把项目的模块架构搭好了,这篇来啃一个硬骨头——HMAF

说实话,我第一次看 HMAF 的官方文档时有点懵。概念多,层次深,跟以前写 UIAbility 的思路完全不一样。花了几天时间把 Demo 跑通之后才算真正理解。今天试着把这些概念掰开了讲清楚。

HMAF 是什么

HMAF 全称 HarmonyOS Multi-Agent Framework,翻译过来就是鸿蒙多智能体框架。

一句话概括:它让你可以在鸿蒙应用里创建和管理多个 Agent(智能体),每个 Agent 有自己的 Skill(技能),Agent 之间还能通过 A2A 协议互相通信。

你可以把它想象成一个"智能体管理平台"。传统 App 是用户点按钮触发逻辑,而基于 HMAF 的 App 可以主动感知用户意图,调度合适的 Agent 去执行任务。

核心概念:Agent 和 Skill

HMAF 里最核心的两个概念就是 Agent 和 Skill,搞清楚它俩的关系,后面的东西就好理解了。

Agent(智能体) 是一个能自主决策的实体。它接收请求,判断该用哪个 Skill 处理,然后执行并返回结果。Agent 不一定要有界面,它可以纯后台运行。

Skill(技能) 是 Agent 的能力单元。一个 Agent 可以有多个 Skill,每个 Skill 负责一件具体的事。

打个比方:Agent 是你雇的一个助理,Skill 是这个助理会的具体技能——订机票、查天气、安排日程。你跟助理说"帮我看看明天北京的天气",助理匹配到"天气查询"这个 Skill,执行完把结果告诉你。

一个 Agent 和多个 Skill 的关系就像这样:

SmartLifeAgent(生活助手智能体)
├── WeatherSkill(天气查询)
├── CalendarSkill(日程管理)
├── DeviceControlSkill(设备控制)
└── NewsSkill(资讯播报)

Agent 的生命周期

Agent 从生到死经历四个阶段,每个阶段都有对应的回调方法:

创建 (onCreate) → 注册 (onRegister) → 运行 (onInvoke) → 销毁 (onDestroy)

创建阶段,Agent 实例被创建出来。这时候做一些初始化工作,比如加载配置、注册 Skill 列表。但 Agent 还不能接收外部请求。

注册阶段,Agent 向 HMAF 框架注册自己。注册成功后,系统就知道有这个 Agent 了,可以把对应的请求路由过来。

运行阶段,Agent 开始接收请求。每次请求进来都会触发 onInvoke 回调,Agent 在里面做 Skill 匹配和执行。

销毁阶段,Agent 被回收。释放资源,注销回调,清理干净。

用代码来看更直观:

import { Agent, AgentConfig, SkillContext, AgentRequest, AgentResponse } from '@kit.AgentKit'

export class SmartLifeAgent extends Agent {
  // 创建阶段:初始化
  onCreate(config: AgentConfig): void {
    console.info('SmartLifeAgent 正在创建...')
    this.registerSkills([
      new WeatherSkill(),
      new CalendarSkill()
    ])
  }

  // 注册阶段:向系统报到
  onRegister(): void {
    console.info('SmartLifeAgent 注册成功,等待调用')
  }

  // 运行阶段:核心处理逻辑
  async onInvoke(request: AgentRequest): Promise<AgentResponse> {
    console.info(`收到请求: ${request.intent}`)

    // 根据意图匹配合适的 Skill
    const skill = this.matchSkill(request.intent)
    if (!skill) {
      return AgentResponse.error('抱歉,我不太理解你的意思')
    }

    // 执行 Skill 并返回结果
    const context: SkillContext = {
      agentId: this.getId(),
      requestId: request.id,
      params: request.params
    }
    const result = await skill.execute(context)
    return AgentResponse.success(result)
  }

  // 销毁阶段:清理
  onDestroy(): void {
    console.info('SmartLifeAgent 已销毁')
    this.unregisterAllSkills()
  }
}

跟 UIAbility 有什么不同

很多人会问:Agent 跟 UIAbility 到底啥区别?我用 UIAbility 不也能后台处理任务吗?

这个问题我当初也纠结过,后来发现两者的定位完全不同:

维度 UIAbility Agent (HMAF)
核心定位 管理 UI 界面 管理智能体能力
生命周期 onCreate → onStart → onForeground → onBackground → onDestroy onCreate → onRegister → onInvoke → onDestroy
触发方式 用户操作触发(点击、跳转) 系统/其他 Agent 调用触发
界面 必须有 WindowStage 不需要界面
运行环境 前台为主 后台为主
通信方式 Want 跳转 A2A 协议
多实例管理 系统按需创建 Agent 池统一管理

一个关键区别:UIAbility 是被动的,用户点了才有反应。Agent 可以是被动的也可以是主动的,它可以被系统调度,也可以被其他 Agent 通过 A2A 协议唤醒。

比如用户对着手机说"明天天气怎么样然后帮我加个日程",系统可以把这个请求拆成两部分:天气 Agent 处理天气查询,然后把结果传给日程 Agent 让它创建日程。两个 Agent 通过 A2A 协作完成,全程不需要打开任何界面。

在 module.json5 里声明 Agent

Agent 的配置跟 UIAbility 的 abilities 是平级的,都写在 module.json5 里:

// agent/src/main/module.json5
{
  "module": {
    "name": "agent",
    "type": "har",
    "agents": [
      {
        "name": "SmartLifeAgent",
        "srcEntry": "./ets/SmartLifeAgent.ets",
        "description": "$string:agent_desc",
        "skills": [
          {
            "name": "weather_query",
            "description": "查询指定城市的天气信息",
            "parameters": {
              "location": {
                "type": "string",
                "description": "城市名称",
                "required": true
              },
              "date": {
                "type": "string",
                "description": "日期,默认今天",
                "required": false
              }
            }
          },
          {
            "name": "calendar_manage",
            "description": "创建或查询日程",
            "parameters": {
              "action": {
                "type": "string",
                "description": "操作类型:create 或 query",
                "required": true
              },
              "title": {
                "type": "string",
                "description": "日程标题",
                "required": false
              }
            }
          }
        ],
        "systemEntry": {
          "voiceCommands": [
            "查天气",
            "今天天气怎么样",
            "帮我安排日程"
          ],
          "textPatterns": [
            ".*天气.*",
            ".*日程.*"
          ]
        }
      }
    ]
  }
}

几个关键字段说一下:

  • srcEntry 指向 Agent 的入口文件
  • skills 数组定义了这个 Agent 拥有的所有 Skill
  • systemEntry 告诉系统什么时候该路由到这个 Agent,包括语音命令和文本匹配模式

A2A 协议:Agent 之间的通信

HMAF 不光管单个 Agent,还管 Agent 之间的协作。A2A(Agent-to-Agent)协议就是干这个的。

假设我们的天气 Agent 查完天气后,想自动通知日程 Agent “明天有雨,建议把户外活动改到室内”,可以这样写:

import { A2AClient, AgentMessage } from '@kit.AgentKit'

// 在 WeatherSkill 执行完之后
async function notifyCalendarAgent(weatherResult: WeatherResult): Promise<void> {
  const a2aClient = new A2AClient()

  const message: AgentMessage = {
    fromAgentId: 'smart_life_agent',
    toAgentId: 'calendar_agent',
    action: 'suggest_reschedule',
    payload: {
      reason: `明天${weatherResult.condition}`,
      suggestion: weatherResult.condition.includes('雨')
        ? '建议将户外活动改为室内'
        : '天气不错,可以安排户外活动',
      date: weatherResult.date
    }
  }

  await a2aClient.send(message)
}

A2A 的通信是异步的,发送方不等接收方处理完就继续执行。这跟传统的函数调用不一样,更像是发消息。

写在后面

HMAF 的门槛确实比 UIAbility 高一些。概念多了,Agent、Skill、A2A 这些东西要一起理解才行。

我自己的经验是:不要一上来就在代码层面理解,先在脑子里建一个"助理 + 技能"的模型。Agent 就是你的助理,Skill 就是助理会的本事,A2A 就是助理之间传话。这个模型建立起来之后,再看代码就容易多了。

另外有个建议:不是所有功能都得做成 Agent。纯 UI 交互的东西,老老实实用 UIAbility 就好。Agent 更适合那些"需要理解意图、做决策、可以后台执行"的场景。别为了用新特性而用新特性,把事情搞复杂了反而不美。

下一篇我们动手写一个完整的天气查询 Agent,从 Skill 定义到系统级调用,把整个链路跑通。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐