从 App 到 Agent:鸿蒙意图框架 Intents Kit 实战开发「小艺出行」
从 App 到 Agent:鸿蒙意图框架 Intents Kit 实战开发「小艺出行」
引言:Agent 时代,应用的角色正在被重新定义
在正式展开之前,先描述一个本项目开发过程中验证成功的真实场景。
用户表达一句"帮我加个明天九点去机场的行程",系统随即理解这一意图,从中抽取出目的地、时间等结构化参数,并将其分发给本项目开发的出行应用,由应用完成行程的创建——整个过程中,用户无需主动打开应用,任务即被准确完成
这一场景揭示了 Agent 时代一个根本性的变化:用户不再关心是哪一个应用完成了任务,只关心任务本身是否被正确完成。 应用能够被系统精准调用的前提,是它已向系统"声明"了自身具备处理相应意图的能力。
而这种向系统声明能力、并由系统完成意图分发的机制,正是本文要探讨的核心:鸿蒙的意图框架(Intents Kit)。
笔者从事 App 开发多年,鸿蒙从早期版本一路使用到 HarmonyOS NEXT。过去开发关注的核心始终是页面布局、交互设计与动效实现。而本文希望系统性地记录一次开发范式的转变——从"开发一个供用户主动打开的 App",到"构建一个供系统随时调用的 Agent 能力",并在此过程中探讨"意图即服务"这一理念的工程价值。
为使论述具备可验证性,本文将以一个真实、可编译的 Demo——「小艺出行」——为主线,完整呈现其从零构建到在手机与鸿蒙 PC 双端运行的全过程。
鸿蒙手机端运行截图:
鸿蒙电脑PC端运行截图:

一、开发范式的转变:从"应用中心"到"意图中心"
在进入代码实现之前,有必要先厘清本次开发中最重要的认知升级,因为它决定了后续全部技术选型的方向。
1.1 传统模式:以应用为中心
传统 App 的设计遵循以应用为中心的逻辑。以添加行程为例,用户的操作路径通常是:解锁设备 → 定位应用图标 → 打开应用 → 等待首页加载 → 找到"新增"入口 → 填写表单 → 保存提交。这一链路涉及多个步骤,开发者需要为每一步设计界面、优化性能,并通过埋点分析用户流失环节。
在这一模式下,应用开发的核心目标是获取并留住用户的注意力。应用本身是一个用户必须主动"抵达"的目的地。
1.2 Agent 模式:以意图为中心
Agent 时代的设计遵循以意图为中心的逻辑。同样是添加行程,用户只需表达一句"帮我加个明天九点去机场的行程"即可完成。
在这一过程中,用户全程无需打开任何应用。用户表达"意图",系统中的智能体(小艺)负责理解该意图,并将其路由给最合适的能力提供方执行。
其中的关键在于路由机制。应用不再是孤立的目的地,而是成为系统能力网络中的一个节点。开发者的核心工作,也从"设计吸引用户的界面",转变为"向系统清晰声明:可处理哪些意图、需要哪些参数、执行后返回何种结果"。
1.3 对开发者的实际影响
这一范式转变可归纳为三个层面:
- 从流量获取到意图接入:核心不再是将用户"引导进入"应用,而是让系统在恰当的时机调用应用能力。
- 从界面开发到契约定义:核心产出物从一系列 UI 页面,转变为一份清晰的"意图契约"(能力范围与参数定义)。
- 从单点体验到全场景触达:一次能力声明,即可在手机、平板、PC、车机等所有小艺可达的设备上生效。
理解这三个层面,再审视意图框架的相关 API,其设计逻辑便清晰可循。

二、场景选择:为何采用出行助手
在候选场景(记账、点餐、快递查询等)中,最终选定出行助手作为演示场景,主要基于以下几点考量,它们都高度契合意图分发的特性:
- 意图表达天然口语化。“加个明天去机场的行程”“我明天有什么安排”"导航去公司"等表达,均为用户的自然语言输入,是意图框架的理想应用对象。
- 参数可清晰结构化。目的地、出行方式、时间、备注等要素,能够准确映射到意图框架的参数 schema,便于演示系统从自然语言中抽取结构化参数的过程。
- 可演示多意图协同。出行并非单一动作,至少涵盖"添加、查询、导航"三类操作,可完整展示多个意图如何共存与分派,而非孤立的单一功能。
- 天然具备跨设备特性。出行场景涉及手机查询、PC 规划、车机导航等多终端协作,契合鸿蒙"一次开发、多端部署"的设计理念。
基于以上考量,本文构建了「小艺出行」:一个纯端侧、无后端依赖、向小艺开放三种出行能力的应用。

三、Demo 整体架构:三个页面与三个意图
首先给出整体结构概览,后续章节将逐层展开。
三个页面(底部 Tab 切换,深色科技风):
- 概览:品牌头、"下一程"渐变大卡、今日行程列表,以及一块"小艺能帮你做什么"的引导区。
- 行程:全部行程的时间线,支持手动新增(底部弹层)、左滑删除、点标签切换完成状态。
- 我的:把三个意图能力做成了可开关的清单,配上统计卡片和关于信息。
三个意图(开放给小艺的能力):
| 用户对小艺说 | 命中意图 | App 行为 |
|---|---|---|
| 「加个明天9点去首都机场的行程」 | AddTrip |
创建行程并跳到行程页 |
| 「我明天有什么安排」 | QueryTrips |
汇总行程、语音式播报 |
| 「导航去公司」 | NavigateTo |
真实拉起地图规划路线 |
工程目录长这样(只列关键部分):
entry/src/main/
├── ets/
│ ├── entryability/EntryAbility.ets 入口,解析意图 Want、沉浸式全屏
│ ├── common/
│ │ ├── Theme.ets 设计主题(色板/尺寸)
│ │ └── TripCard.ets 行程卡组件(多页复用)
│ ├── intents/
│ │ └── TripIntentExecutor.ets 意图执行器(三意图共用,按名分派)
│ ├── model/
│ │ ├── Trip.ets 行程模型 + Preferences 持久化
│ │ ├── IntentHandler.ets Want 参数解析
│ │ └── NavHelper.ets 拉起地图导航
│ └── pages/
│ ├── Index.ets 三 Tab 主壳 + 意图结果回流
│ ├── OverviewTab.ets / TripsTab.ets / ProfileTab.ets
├── resources/base/profile/insight_intents.json 意图声明
└── module.json5 注册 intent metadata

四、核心实现:向系统声明应用能力
这是从 App 到 Agent 转变中最关键的一步。与传统开发首先编写页面不同,本项目首先编写的是一份 JSON 配置文件——意图声明。
4.1 意图声明 insight_intents.json
意图声明需要向系统明确三项信息:应用具备哪些意图、每个意图的定义、执行该意图所需的参数。以"添加行程"为例:
{
"intentName": "AddTrip",
"domain": "TravelManagement",
"displayName": "添加行程",
"llmDescription": "当用户想安排出行、添加行程、记录一次出发时使用。可从表达中提取目的地 destination、出行方式 type、时间 when、备注 note。例如「帮我加个明天9点去首都机场的行程」。",
"keywords": ["加行程", "安排出行", "添加行程", "去", "出发"],
"parameters": {
"type": "object",
"properties": {
"destination": { "type": "string", "description": "目的地,必填" },
"type": { "type": "string", "enum": ["飞机", "高铁", "驾车", "打车", "地铁", "步行"] },
"when": { "type": "string", "description": "出发时间的自然语言描述" },
"note": { "type": "string", "description": "备注,可选" }
},
"required": ["destination"]
},
"executor": "ets/intents/TripIntentExecutor.ets"
}
这段配置中最值得关注的是 llmDescription 字段。它并非面向机器的枚举定义,而是以自然语言撰写、供大模型理解的能力说明。开发者需要清晰地向小艺表达:“在何种场景下应调用本应用,以及需要从用户表达中抽取哪些关键信息”。这代表了一种新的编程范式:开发者编写的是面向 AI 的提示(prompt),而非面向编译器的逻辑。
keywords 用于辅助召回,parameters 则定义了结构化契约——小艺依据该 schema,将用户的口语表达解析为 destination=首都机场、type=飞机、when=明天9点 等结构化字段。这一抽取过程由系统的大模型完成,开发者无需自行实现任何自然语言处理逻辑。

4.2 在模块中注册声明
仅有声明文件尚不足够,还需在 module.json5 中通过 metadata 向系统注册该意图声明:
"metadata": [
{ "name": "ohos.ability.intent.metadata", "resource": "$profile:insight_intents" }
]
这条配置即完成了应用向系统声明能力的注册动作。
4.3 意图执行器:能力的具体实现
意图声明解决了"应用能做什么"的问题,执行器则负责"如何执行"。本项目的三个意图共用一个执行器,按意图名称进行分派:
export default class TripIntentExecutor extends InsightIntentExecutor {
async onExecuteInUIAbilityForegroundMode(
name: string,
param: Record<string, Object>,
pageLoader: object
): Promise<insightIntent.ExecuteResult> {
let message = '';
if (name === 'QueryTrips') {
message = await this.doQuery(str(param['when']));
} else if (name === 'NavigateTo') {
const dest = str(param['destination']);
await openNavigation(this.context as common.UIAbilityContext, dest);
message = `正在为你拉起地图,规划前往「${dest}」的路线…`;
} else {
message = await this.doAdd(param); // AddTrip
}
// 结果写入全局状态,供 UI 回流展示
AppStorage.setOrCreate('lastIntentMsg', message);
AppStorage.setOrCreate('intentTick', Date.now());
return { code: 0, result: { message } } as insightIntent.ExecuteResult;
}
}
需要注意的是最后返回的 result.message——它不仅用于 UI 展示,还可被小艺进行语音播报。因此文案的撰写需要兼顾语音朗读的流畅度。这是与传统 App 开发的又一处差异:返回值需要同时考虑"可视"与"可听"两种呈现方式。

五、意图结果的 UI 回流机制
意图执行完毕后数据发生变更,而用户当前可能正处于应用的某个页面。如何实现界面的实时、无感更新,是本项目遇到的第一个技术难点。
最初的实现是在页面的 onPageShow 中执行刷新。但实践发现:当小艺(或深链)触发操作时,应用已处于前台显示状态,不会再次触发 onPageShow,导致列表无法更新,必须手动切换页面或重启应用,用户体验较差。
改进方案采用全局状态 + @Watch 监听的响应式机制。执行器将结果写入 AppStorage,页面订阅并监听其变化:
@StorageLink('intentTick') @Watch('onIntentResult') intentTick: number = 0;
@StorageLink('lastIntentMsg') lastIntentMsg: string = '';
@StorageLink('intentJumpTab') intentJumpTab: number = -1;
onIntentResult(): void {
// 弹出语音式反馈
promptAction.showToast({ message: '🎙 小艺:' + this.lastIntentMsg, duration: 2800 });
// 自动跳到相关 Tab(添加→行程页,导航→概览页)
if (this.intentJumpTab >= 0 && this.intentJumpTab <= 2) {
this.current = this.intentJumpTab;
}
}
该方案的核心在于:使用一个递增的 intentTick 时间戳作为触发信号。每次意图处理完成后更新该值,@Watch 随即响应——无论应用是否处于前台,UI 均可即时刷新、弹出反馈并自动跳转至相关页面。
这种"以时间戳作为信号驱动响应式刷新"的方法,是本项目在调试过程中总结出的一项实用技巧。
六、导航能力的实现:真实拉起地图应用
在初始版本中,"导航"意图仅弹出"正在规划路线"的示例提示。为使其具备实际产品价值,需要实现真实拉起地图应用的能力。
鸿蒙中的应用间跳转通过 startAbility + Want 实现。由于不同设备预装的地图应用存在差异,本项目设计了一条降级调用链:依次尝试 Petal 地图、高德、百度,任一成功即调用,全部失败时则使用系统隐式 Want 作为兜底方案。
export async function openNavigation(context: common.UIAbilityContext, dest: string): Promise<boolean> {
const schemes = [
{ name: 'PetalMaps', uri: `maps://routes?daddr=${encodeURIComponent(dest)}&type=drive` },
{ name: 'Amap', uri: `amapuri://route/plan/?dname=${encodeURIComponent(dest)}&t=0` },
{ name: 'BaiduMap', uri: `baidumap://map/direction?destination=${encodeURIComponent(dest)}&mode=driving` }
];
for (const s of schemes) {
try {
await context.startAbility({ uri: s.uri });
return true; // 谁先成功就用谁
} catch (e) { /* 该地图未安装,继续尝试下一个 */ }
}
// 兜底:系统隐式 Want,可能弹出可选地图列表
try {
await context.startAbility({ action: 'ohos.want.action.viewData', uri: `geo:0,0?q=${encodeURIComponent(dest)}` });
return true;
} catch (e) { return false; }
}
这一设计同样体现了 Agent 化的思路:应用无需自行实现地图功能,而是将"导航"这一子任务委托给系统中最擅长处理它的能力提供方。 应用之间由此从独立运行转变为能力协作。

七、双端适配:手机与鸿蒙 PC 的一致运行
鸿蒙"一次开发、多端部署"的能力在本项目中得到了充分验证。该 Demo 未为 PC 单独编写任何布局代码,即可在手机与鸿蒙 PC 上直接运行。
实现这一效果,依赖于开发之初即遵循的几项原则:
- 采用弹性布局而非固定尺寸:宽度使用
layoutWeight、百分比及Flex自动换行,避免硬编码 px,从而在 PC 大屏下自然展开。 - 安全区适配:通过
getWindowAvoidArea获取状态栏与导航条高度并存入全局状态,页面统一避让,可正确处理手机刘海与 PC 窗口边框。 - 组件化复用:将行程卡
TripCard抽象为独立组件,供概览页与列表页复用,在不同屏幕尺寸下表现一致。
尤为重要的是,意图能力天然具备跨端特性。同一份 insight_intents.json,在手机与鸿蒙 PC 上均可通过小艺触发——因为意图是注册于"系统"层面,而非某一具体屏幕。这正是第一章所述"一次声明、全场景触达"的实际体现。
本项目从模拟器到真机手机,再到鸿蒙 PC,全程无需修改代码即可运行,充分验证了鸿蒙跨端能力的工程价值。

八、开发过程中的典型问题与解决方案
在 Demo 的开发过程中遇到了若干典型问题,现选取几个具有代表性的进行记录,供开发者参考。
8.1 语音指令被分发至系统日历
这是最具代表性的一个现象:唤醒小艺添加行程时,行程被添加进系统日历,本应用无任何响应。
经排查,其原因在于:真实小艺是基于华为云端已上架的意图库进行匹配的。 系统日历早已上架了"日程"类意图,而本应用的 AddTrip 仅为本地声明,尚未通过 AppGallery Connect 完成意图框架能力申请与上架,因此小艺云端无法感知本应用具备处理该类意图的能力。
由此可明确区分两个层面:
- 本地声明(
insight_intents.json)仅解决"应用内部知晓自身能力"的问题; - 平台上架(在 AGC 申请意图框架能力并提审)才能使"小艺云端知晓应用能力",从而在真实语音场景中完成意图分发。
明确这一机制后,即可采用下述方式在本地验证代码的正确性。
8.2 未上架情况下的意图链路验证
在无法通过语音触发的情况下,可使用 aa start 将意图直接投递至应用——该方式与真实小艺分发所使用的 want.parameters 通道完全一致:
# 添加行程(结构化参数,与真实小艺同通道)
hdc shell "aa start -a EntryAbility -b com.example.smarttrip \
--ps op add --ps destination '首都机场' --ps type 'flight' --ps when 'tomorrow 9'"
初期为简便起见采用 URI 深链 ?a=x&b=y 方式传参,但发现 & 在多层 shell 环境中被解析为命令分隔符,导致仅首个参数能够传入。改用 --ps(string 参数)直接写入 want.parameters 后,问题得以解决。
8.3 命令行中文参数乱码
命令行传递中文参数(如 type=餐饮)经 hdc shell 处理后会出现编码错乱,导致解析失败。需要说明的是,这是命令行终端的编码限制,真实小艺采用结构化参数传递,不受此影响。 本项目的应对方案是:在参数解析中增加英文 key 到中文的映射(如 flight→飞机、drive→驾车),本地测试使用英文参数,既规避了乱码,又不影响真实场景。
8.4 环境变量污染导致构建失败
开发过程中曾遇到构建报错 ERR_WORKER_INVALID_EXEC_ARGV: --report-on-fatalerror is not allowed in NODE_OPTIONS,且 DevEco Studio 内部 Sync 亦无法执行。经定位,原因是 NODE_OPTIONS 环境变量被外部工具污染,而 DevEco Studio 作为 GUI 进程继承了启动时的异常环境。解决方案为彻底退出应用、清除该变量后重新启动。此类环境问题虽与代码无关,但排查耗时较长,故一并记录。
九、意图框架的工程价值分析
完成本 Demo 后,笔者对"意图即服务"这一理念有了更为具体的工程层面的认识。
其一,降低了应用的流量获取压力。 开发者无需再刻意引导用户进入应用,只要应用能力可靠、意图声明清晰,系统即可在恰当时机调用应用能力。竞争维度从"应用入口的显著程度"转向"应用能力的可靠程度"。
其二,重新赋予了垂直应用价值。 专注于单一功能并将其做到极致的应用,在传统模式下获客较为困难,而在意图分发机制下,可作为特定垂直意图的最优提供方被系统精准调用,这对独立开发者具有积极意义。
其三,促使开发者重新审视应用的边界。 导航功能的实现表明,应用无需追求功能大而全,完全可以将子任务委托给更专业的能力方。应用之间由此从相互独立转变为协作网络,而小艺则承担了调度中枢的角色。
与此同时,该模式也提出了新的能力要求:开发者需要掌握以自然语言撰写面向 AI 的能力说明(llmDescription)、将返回值优化为适合语音播报的文案、并为跨端场景进行弹性设计。 这些均是传统 App 开发中较少涉及的新课题。
十、结语
回到本文开头所述的场景——用户一句自然语言表达,系统即完成意图理解、参数抽取与应用调用,行程被准确创建。这一过程印证了一个核心观点:在 Agent 时代,一个应用真正的起点,不是它的首个页面,而是它向系统声明的能力契约。
从"开发一个供用户主动打开的 App",到"构建一个供系统随时调用的 Agent 能力",二者之间的差异并非几个 API,而是一整套开发思维的迁移。「小艺出行」Demo 虽仅包含三个页面与三个意图、代码量有限,却完整呈现了这一迁移的全过程,包括其中的技术难点与解决方案。
HarmonyOS 正在将"意图即服务"构建为每一位开发者均可使用的基础设施。对于长期从事鸿蒙开发的工程师而言,能够在这一范式转变的起点参与实践,具有重要的意义。
对于正在开发鸿蒙应用的开发者,笔者建议:选取一个熟悉的场景,为其编写意图声明并接入小艺。通过这一实践,可以真切地理解到——Agent 时代已经到来,并已具备完整的工程落地条件。
更多推荐



所有评论(0)