一、背景说明

HarmonyOS 7 把系统交互从"用户打开 App → 手动操作"推进到"意图即服务":用户只需说出想要的结果,系统级小艺理解意图后自动匹配应用的 Skill 并把服务递上来,全程无跳转。对开发者来说,这意味着应用多了一种被系统直接"召唤"的入口。本文以一个"断食倒计时查询"Skill 为例,完整走通 Skill 的声明、注册、入口脚本与调试全流程,并汇总五个真实联调踩坑点。

二、技术原理

小艺 Skill 的本质是"声明 + 薄封装",不是重写业务逻辑。整体结构只有三样:

  1. skillProfiles 注册:在 module.json5 中声明本应用提供哪些 Skill,包括触发语句(用户可能怎么问)、入口脚本路径与返回方式。小艺基于这些描述做意图匹配。
  2. SKILL.md 能力描述:用结构化文档向系统描述 Skill 的功能边界、参数与输出格式,小艺的大模型依据它判断"用户这句话归不归你管"。描述要具体,太笼统会匹配不到。
  3. 入口脚本:Skill 被命中后端侧执行的薄封装层,只做两件事——解析小艺传入的参数、调用应用已有业务层并把结果格式化为自然语言返回。端侧运行,数据不出设备。

调用链路:用户对小艺说话 → 大模型意图匹配(依据 skillProfiles + SKILL.md)→ 端侧执行入口脚本 → 脚本 import 应用业务层取数 → 结果自然语言回传 → 小艺播报。

三、实现步骤

环境要求:DevEco Studio 26.0.0 Release(API 26.0.0),HarmonyOS 7 真机或模拟器。

  1. 在 module.json5 中注册 skillProfiles。注意:skillProfiles 放在 module 顶级,不要嵌进 extensionAbilities,否则 schema 校验失败:
// entry/src/main/module.json5
{
  "module": {
    "name": "entry",
    "type": "entry",
    "ability": [ /* ...原有配置... */ ],
    "skillProfiles": [
      {
        "name": "fasting_timer_query",
        "description": "查询用户的断食倒计时:剩余时长、当前所处断食阶段、今日目标完成度",
        "entry": "scripts/fasting_query.ets",
        "triggerExamples": [
          "断食还剩多久",
          "我现在断食到哪个阶段了",
          "今天的断食目标完成了吗"
        ]
      }
    ]
  }
}
  1. 编写 SKILL.md 能力描述文件,放在 Skill 目录下,向小艺声明能力边界与 I/O:
# Skill: fasting_timer_query(断食倒计时查询)

## 功能
查询轻食刻应用内的断食计时状态,返回剩余时长与当前阶段。

## 输入参数
- 无必填参数。可选 phaseFilter: string,取值 "all" | "current"。

## 输出格式
自然语言单句,格式:`已断食 X 小时 Y 分钟,距离 16:8 目标还剩 Z 小时 W 分钟。`

## 边界
- 不执行修改操作(开始/结束断食请引导用户打开应用)。
- 数据仅来自本机应用沙箱,无网络请求。
  1. 实现入口脚本。要点:只做参数解析与结果格式化,直接 import 应用已有的服务层,不要在脚本里重写业务逻辑:
// entry/src/main/ets/scripts/fasting_query.ets
import { FastingService } from '../service/FastingService'; // 复用已有业务层
import { hilog } from '@kit.PerformanceAnalysisKit';

const DOMAIN: number = 0x0000;
const TAG: string = 'SkillFastingQuery';

interface SkillResult {
  status: string;      // "success" | "error"
  speechText: string;  // 返回给小艺播报的自然语言
}

export async function main(params: Record<string, string>): Promise<SkillResult> {
  try {
    // 1. 解析参数(本 Skill 无必填参数,容错处理可选过滤项)
    const phaseFilter: string = params?.phaseFilter ?? 'all';

    // 2. 调用应用已有服务层取数(端侧运行,数据不出设备)
    const state = await FastingService.getTimerState();

    // 3. 格式化为自然语言返回
    const hours: number = Math.floor(state.elapsedMinutes / 60);
    const minutes: number = state.elapsedMinutes % 60;
    const remainMinutes: number = state.targetMinutes - state.elapsedMinutes;
    const speechText = remainMinutes > 0
      ? `已断食 ${hours} 小时 ${minutes} 分钟,距离 16:8 目标还剩 ` +
        `${Math.floor(remainMinutes / 60)} 小时 ${remainMinutes % 60} 分钟。`
      : '恭喜,本轮 16:8 断食目标已达成。';

    hilog.info(DOMAIN, TAG, 'query ok, filter=%{public}s', phaseFilter);
    return { status: 'success', speechText: speechText };
  } catch (err) {
    hilog.error(DOMAIN, TAG, 'query failed: %{public}s', (err as Error).message);
    return { status: 'error', speechText: '断食数据读取失败,请打开轻食刻查看详情。' };
  }
}
  1. 被调用的业务层保持原样(示例):
// entry/src/main/ets/service/FastingService.ets
export class TimerState {
  elapsedMinutes: number = 0;
  targetMinutes: number = 16 * 60;
}

export class FastingService {
  static async getTimerState(): Promise<TimerState> {
    // 实际实现:从偏好库读取断食开始时间并计算
    // const store = preferences.getPreferencesSync(context, { name: 'fasting' });
    // ...
    const state = new TimerState();
    state.elapsedMinutes = 11 * 60 + 25;
    return state;
  }
}
四、运行效果说明

真机上长按电源/侧键或对小艺说出触发语句:

  1. 用户说"断食还剩多久"→ 小艺识别意图并命中 fasting_timer_query;
  2. 端侧执行 fasting_query.ets,返回"已断食 11 小时 25 分钟,距离 16:8 目标还剩 4 小时 35 分钟。";
  3. 小艺直接播报结果,全程无跳转、无界面拉起;
  4. 若 Skill 未注册或描述不匹配,小艺会走通用回答,说明配置未生效。
五、技术要点
  1. skillProfiles 层级:必须放在 module 顶级,嵌进 extensionAbilities 会 schema 校验失败,这是最常见的注册报错。
  2. description 决定命中率:大模型按 description 匹配意图,写"查询断食相关信息"这种笼统描述经常匹配不到;应写成"查询剩余时长、当前阶段、目标完成度"这类动词+宾语明确的句子,并配合 triggerExamples 给出真实问法。
  3. 脚本不写业务逻辑:入口脚本重写业务逻辑会导致双份数据源不一致;正确姿势是 import 已有服务层,脚本只做参数解析和结果格式化。
  4. 忘记注册 module.json5:只写 SKILL.md 不注册 skillProfiles,小艺完全不认识你的 Skill,调试时先确认注册是否生效。
  5. SKILL.md 长度限制:超长超限会被驳回,保持"功能/输入/输出/边界"四段式精简描述即可。
六、扩展建议
  1. 多 Skill 组合:一个应用可声明多个 skillProfiles(查询、开始断食、补记录各一个),每个 Skill 独立入口脚本,共享同一个服务层。
  2. 参数化触发:在 description 与入口脚本中支持槽位参数(如"断食还剩多久"扩展为"距 18:6 目标还剩多久"),通过 params 传入,提升自然语言覆盖率。
  3. 与实况窗联动:断食进行中可用 Live View Kit 挂一个计时实况窗(参见笔者上一篇实况窗开发文章),语音查询与卡片展示互补。
结语

本文用约 80 行代码完成了一个小艺 Skill 的完整接入:module.json5 注册、SKILL.md 描述、入口脚本三件套,并汇总了层级、描述、业务复用等五个真实踩坑点。HarmonyOS 7 时代,"被小艺一句话召唤"正在成为应用的第二入口,建议有明确查询/操作型服务的应用尽早接入,抢占意图匹配的心智。

Logo

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

更多推荐