news 2026/6/23 6:19:26

HarmonyOS 7.0 Skill开发实战:让你的App能力被AI智能体“一句话调用“

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS 7.0 Skill开发实战:让你的App能力被AI智能体“一句话调用“

从API 26开始,HarmonyOS给开发者带来了一个特别香的能力——Skill。简单说,它让你App里的业务功能可以被系统AI智能体直接调度,用户说一句话,AI就能帮你调起对应App的功能,而你几乎不用改原来的代码。

一、Skill是个啥?为啥要搞它?

想象一下这个场景:用户对小艺说"帮我查一下明天北京天气",然后你的天气App就被自动调起来返回了结果——用户甚至不需要手动打开你的App。

这就是Skill干的事。它本质是一个声明式的能力外化机制

  • 你写一份SKILL.md,告诉系统"我这个Skill能干啥、怎么调、返回啥"
  • 你写一个ArkTS入口脚本,当个"薄适配层",把AI传进来的参数转交给App内部已有的业务代码
  • module.json5里注册一下,绑定到某个Ability上

就这样,你的App能力就对外开放了,而且原来业务代码一行都不用改

注意:只支持Stage模型,FA模型用不了。

二、整体架构长啥样?

先看目录结构,以一个"天气查询Skill"为例:

Application/ ├── AppScope/ │ ├── app.json5 │ └── resources/ └── entry/ ├── skills/ ← 【固定值】Skill根目录 │ └── weather-query/ ← Skill名,跟SKILL.md的name一致 │ ├── scripts/ ← 【固定值】脚本目录 │ │ └── WeatherSkill.ets ← 入口脚本 │ └── SKILL.md ← 【固定值】描述文件 └── src/ └── main/ ├── ets/ │ ├── entryability/ │ │ └── EntryAbility.ets │ └── service/ │ └── WeatherService.ets ← App内已有的业务服务 ├── module.json5 └── resources/

几个关键点:

  • skills/目录名是固定的,必须放模块根目录下
  • scripts/也是固定
  • Skill目录名、SKILL.md里的name、module.json5里的name,三者必须完全一致

三、一步步来,手把手搞定

Step 1:配置module.json5

entry/src/main/module.json5module标签下加上skillProfiles

{ "module": { // ... 其他配置 "skillProfiles": [ { "name": "weather-query", // 跟SKILL.md的name、目录名保持一致 "abilityName": "EntryAbility", // 关联的Ability "srcEntries": [ // 脚本路径列表 "../../skills/weather-query/scripts/WeatherSkill.ets" ] } ], "requestPermissions": [ // Skill需要的权限 { "name": "ohos.permission.INTERNET" }, { "name": "ohos.permission.LOCATION" } ] } }

这里的srcEntries路径是相对于src/main/的,所以要用../../回到模块根目录再进skills/

Step 2:实现ArkTS入口脚本

入口脚本就是那个"薄适配层",它只干三件事:接参数 → 调业务 → 报结果

2.1 导入依赖
import{scriptManager}from'@kit.AbilityKit';import{BusinessError}from'@kit.BasicServicesKit';import{WeatherService,WeatherInfo,ForecastResult}from'../../../src/main/ets/service/WeatherService';
2.2 定义入口类

export default导出一个类,类里每个public async方法对应SKILL.md里声明的一项能力:

  • 方法名必须和SKILL.md的functionName严格一致
  • 第一个参数类型固定scriptManager.ArkTSScriptInfo
exportdefaultclassWeatherSkill{publicasyncqueryWeather(info:scriptManager.ArkTSScriptInfo,...argv:string[]):Promise<void>{// 具体实现见下文}publicasyncqueryForecast(info:scriptManager.ArkTSScriptInfo,...argv:string[]):Promise<void>{// 同理}}
2.3 解析和校验参数

AI智能体传进来的参数都在argv里,按位置排列,咱们得自己做校验:

// queryWeather:需要城市名,日期可选constcity:string=argv.length>0?argv[0].trim():'';constdate:string=argv.length>1?argv[1].trim():'';if(city.length===0){// 城市都没传,直接走错误分支constpayload:Record<string,Object>={'type':'result','status':'failed','errCode':'ERR_INVALID_PARAMS','errMsg':'city is required','suggestion':'你想查哪个城市的天气呢?'};awaitthis.report(info,{code:-1,result:payload});return;}
// queryForecast:城市必传,天数可选(默认7天)constcity:string=argv.length>0?argv[0].trim():'';if(city.length===0){constpayload:Record<string,Object>={'type':'result','status':'failed','errCode':'ERR_INVALID_PARAMS','errMsg':'city is required','suggestion':'请告诉我你想查哪个城市的预报'};awaitthis.report(info,{code:-1,result:payload});return;}constdays:number=argv.length>1?parseInt(argv[1],10):7;if(days<1||days>15){constpayload:Record<string,Object>={'type':'result','status':'failed','errCode':'ERR_INVALID_PARAMS','errMsg':'days must be between 1 and 15','suggestion':'目前只支持查询1到15天的预报哦'};awaitthis.report(info,{code:-1,result:payload});return;}
2.4 调用业务实现 + 构造结果回传

校验通过后,调App内部已有的业务接口,然后把结果按SKILL.md声明的契约封装成ExecuteResult,通过completeArkTSScriptInApp回传:

publicasyncqueryWeather(info:scriptManager.ArkTSScriptInfo,...argv:string[]):Promise<void>{constcity:string=argv.length>0?argv[0].trim():'';constdate:string=argv.length>1?argv[1].trim():'';if(city.length===0){constpayload:Record<string,Object>={'type':'result','status':'failed','errCode':'ERR_INVALID_PARAMS','errMsg':'city is required','suggestion':'你想查哪个城市的天气呢?'};awaitthis.report(info,{code:-1,result:payload});return;}try{constweather:WeatherInfo=WeatherService.getCurrentWeather(city,date);constdata:Record<string,Object>={'city':weather.city,'temperature':weather.temperature,'condition':weather.condition,'humidity':weather.humidity,'wind':weather.wind};constpayload:Record<string,Object>={'type':'result','status':'success','data':data};awaitthis.report(info,{code:0,result:payload});}catch(e){consterr=easBusinessError;if(err.code===404){constpayload:Record<string,Object>={'type':'result','status':'failed','errCode':'ERR_NOT_FOUND','data':{'searchedCity':city},'suggestion':`暂时没有找到${city}的天气数据`};awaitthis.report(info,{code:-1,result:payload});}else{constpayload:Record<string,Object>={'type':'result','status':'failed','errCode':'ERR_INTERNAL','errMsg':err.message,'suggestion':'查询天气出了点问题,稍后再试试吧'};awaitthis.report(info,{code:-1,result:payload});}}}
2.5 report方法——唯一的回包出口

建议把completeArkTSScriptInApp的调用统一封装到一个私有方法里,别在每个业务分支里重复写:

privateasyncreport(info:scriptManager.ArkTSScriptInfo,result:scriptManager.ExecuteResult):Promise<void>{try{awaitscriptManager.completeArkTSScriptInApp(info.context,info.requestCode,result);}catch(e){consterr=easBusinessError;console.error(`completeArkTSScriptInApp failed, code:${err.code}, message:${err.message}`);}}

这里用到两个关键接口成员:

  • info.context:绑定的Ability上下文,系统传进来的
  • info.requestCode:当前请求的标识码,回包时必须原样传回

Step 3:编写SKILL.md——整个机制的灵魂

SKILL.md是系统智能体做"意图→能力"匹配的唯一依据。写得好不好,直接决定你的Skill会不会被正确触发。

3.1 元数据(YAML Front Matter)
---name:weather-querydescription:提供城市天气查询与未来天气预报能力,响应"北京天气"、"明天上海热不热"、"未来一周深圳天气预报"等天气类指令---

name必须三处一致(目录名、SKILL.md的name、module.json5的name),description要简洁,这是AI做初次筛选的关键依据。

3.2 触发场景

用自然语言写,帮AI搞清楚"什么时候该调我、什么时候不该调我":

## 触发场景 当用户询问**某个城市的天气或预报**时调用。典型话术: - "北京今天天气怎么样" - "明天上海热不热" - "深圳未来一周天气预报" - "广州下雨了吗" 不调用的情况: - 用户说"帮我设个明天7点的闹钟"——这是闹钟功能,跟天气无关 - 用户说"今天适合跑步吗"——这是运动建议,除非明确提到天气查询 - 用户说"这张天空照片真好看"——这是社交评价,不是查天气 - 用户说"帮我关空调"——这是智能家居控制,不是天气查询

划重点:边界说明特别重要!不写清楚的话,AI很容易误触发。比如"今天适合出门吗"这种话,如果你的Skill只查天气不做出行建议,就要明确排除。

3.3 能力1:queryWeather的参数契约
### 场景1:查询天气(queryWeather) #### 执行参数 exec-cli(command: ohos-arkTSScript --skillName 'weather-query' --scriptPath 'scripts/WeatherSkill.ets' --functionName 'queryWeather' --args '{ "arg1": "北京", "arg2": "明天" }' ) 参数Schema: ```json { "args": { "type": "object", "properties": { "arg1": { "type": "string", "description": "城市名,如北京、上海、深圳" }, "arg2": { "type": "string", "description": "日期,如今天、明天、后天,可选" } }, "required": ["arg1"] } }
几个要点: - `command` 固定写 `ohos-arkTSScript` - `skillName` 跟SKILL.md的name一致 - `scriptPath` 是相对于Skill目录的脚本路径 - `functionName` 必须跟入口脚本的public方法名**严格对应** - `args` 的Schema决定了AI能传什么参数进来,`required` 标记必填项 #### 3.4 能力1:queryWeather的返回值契约 先把所有可能的返回结果列出来: ```markdown #### 执行返回值 结果示例: // 1. 查询成功 { "type": "result", "status": "success", "data": { "city": "北京", "temperature": "28℃", "condition": "晴", "humidity": "35%", "wind": "北风3级" } } // 2. 参数缺失 { "type": "result", "status": "failed", "errCode": "ERR_INVALID_PARAMS", "errMsg": "city is required", "suggestion": "你想查哪个城市的天气呢?" } // 3. 城市未找到 { "type": "result", "status": "failed", "errCode": "ERR_NOT_FOUND", "data": { "searchedCity": "阿凡达" }, "suggestion": "暂时没有找到阿凡达的天气数据" } // 4. 内部错误 { "type": "result", "status": "failed", "errCode": "ERR_INTERNAL", "errMsg": "network timeout", "suggestion": "查询天气出了点问题,稍后再试试吧" }

然后给出整体的JSON Schema约束:

{"type":"object","required":["type","status"],"properties":{"type":{"type":"string","const":"result"},"status":{"type":"string","enum":["success","failed"]},"data":{"type":"object"},"errCode":{"type":"string","enum":["ERR_INVALID_PARAMS","ERR_NOT_FOUND","ERR_INTERNAL"]},"errMsg":{"type":"string","minLength":1},"suggestion":{"type":"string","minLength":1}},"oneOf":[{"required":["data"],"properties":{"status":{"const":"success"}}},{"required":["errMsg","suggestion"],"properties":{"errCode":{"const":"ERR_INVALID_PARAMS"}}},{"required":["data","suggestion"],"properties":{"errCode":{"const":"ERR_NOT_FOUND"}}},{"required":["errMsg","suggestion"],"properties":{"errCode":{"const":"ERR_INTERNAL"}}}]}
3.5 能力2:queryForecast的参数契约
### 场景2:查询天气预报(queryForecast) #### 执行参数 exec-cli(command: ohos-arkTSScript --skillName 'weather-query' --scriptPath 'scripts/WeatherSkill.ets' --functionName 'queryForecast' --args '{ "arg1": "深圳", "arg2": "7" }' ) 参数Schema: ```json { "args": { "type": "object", "properties": { "arg1": { "type": "string", "description": "城市名,如深圳、杭州" }, "arg2": { "type": "string", "description": "预报天数,1-15,默认7" } }, "required": ["arg1"] } }
执行返回值

// 1. 查询成功
{
“type”: “result”,
“status”: “success”,
“data”: {
“city”: “深圳”,
“forecastDays”: 7,
“forecast”: [
{ “date”: “6月18日”, “high”: “33℃”, “low”: “26℃”, “condition”: “多云” },
{ “date”: “6月19日”, “high”: “32℃”, “low”: “25℃”, “condition”: “阵雨” }
]
}
}

// 2. 参数非法(天数超范围)
{
“type”: “result”,
“status”: “failed”,
“errCode”: “ERR_INVALID_PARAMS”,
“errMsg”: “days must be between 1 and 15”,
“suggestion”: “目前只支持查询1到15天的预报哦”
}

## 四、核心接口速查 整个Skill机制涉及的核心接口其实很少,就仨: | 接口 | 说明 | |------|------| | `ExecuteResult` | 脚本执行结果,包含 `code`(结果码)、`result`(结果内容)、`uris`(授权URI列表)、`flags`(URI读写权限) | | `ArkTSScriptInfo` | 入口函数的首参,包含 `requestCode`(请求标识)和 `context`(Ability上下文) | | `completeArkTSScriptInApp(context, requestCode, result)` | 上报执行结果,Promise异步回调 | `ExecuteResult` 的完整结构: ```typescript interface ExecuteResult { code: number; // 结果码,0为成功 result?: Record<string, Object>; // 脚本执行结果 uris?: Array<string>; // 需授权给调用方的URI列表 flags?: number; // URI读写权限 }

五、开发避坑指南

总结几个容易踩的坑:

  1. 名字一致性:Skill目录名、SKILL.md的name字段、module.json5的skillProfiles里的name,三个地方必须完全一样,少一个下划线都不行,否则注册不上。

  2. 方法名严格匹配:入口脚本里的public方法名必须跟SKILL.md里的functionName一模一样,大小写都别搞错。

  3. 第一个参数类型固定:入口方法的第一个参数必须是scriptManager.ArkTSScriptInfo,这是系统注入的上下文,不能换、不能省。

  4. argv是string数组:AI传进来的参数全是string,需要自己做类型转换(比如parseInt),同时做好校验和容错。

  5. 必须调用completeArkTSScriptInApp:不管成功还是失败,都必须调用这个接口回传结果,否则系统侧会一直等着,超时后认为执行失败。

  6. suggestion字段很重要:失败时一定要填suggestion,这是AI转述给用户的提示语,写得好用户体验就好,写得烂用户就一脸懵。

  7. 触发场景要写清楚边界:SKILL.md里"不调用的情况"一定要认真写,否则你的Skill会被AI在各种奇怪的场景下误触发。

  8. srcEntries路径:是相对于src/main/的相对路径,所以要从../../skills/开始写,别直接写skills/

六、整体调用流程

用一张流程图串一下整个调用链路:

用户语音/文字输入 ↓ 系统智能体解析意图 ↓ 匹配SKILL.md的触发场景 ↓ 按exec-cli构造调用参数(遵循args Schema) ↓ 调用入口脚本对应方法(argv传入) ↓ 入口脚本解析参数 → 校验 → 调用App业务接口 ↓ 按返回值契约构造ExecuteResult ↓ 调用completeArkTSScriptInApp回传结果 ↓ 系统智能体按result内容生成自然语言回复用户

七、写在最后

Skill这个机制的设计思路其实挺优雅的——声明式契约 + 薄适配层,把"能力描述"和"能力实现"彻底解耦。对AI来说,它只需要读懂SKILL.md就能调度你的能力;对你来说,只需要写个入口脚本做参数转换,原有业务代码完全不用动。

HarmonyOS 7.0这波是在认真做AI生态基础设施,Skill本质上就是App和AI之间的"USB接口"——标准化、即插即用。如果你的App有对外暴露能力的诉求(而且谁没有呢?),建议尽早熟悉这套机制,先人一步把Skill接入做好,等AI生态起来的时候你就是最早吃到红利的那批。


本文基于HarmonyOS API 26(7.0)编写,Skill相关接口起始版本为26.0.0,仅支持Stage模型。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/23 6:10:34

2026-06-22:不同频率的最小数对。用go语言,给定一个整数数组 nums,你需要从中找到两个不同的数 x 和 y,要求 x 比 y 小,而且它们在数组里出现的次数不一样。 在所有符合条件的组合

2026-06-22&#xff1a;不同频率的最小数对。用go语言&#xff0c;给定一个整数数组 nums&#xff0c;你需要从中找到两个不同的数 x 和 y&#xff0c;要求 x 比 y 小&#xff0c;而且它们在数组里出现的次数不一样。 在所有符合条件的组合里&#xff0c;先优先选择 x 尽可能小…

作者头像 李华
网站建设 2026/6/23 5:57:03

终极音频转换解决方案:fre:ac免费音频转换器完全指南

终极音频转换解决方案&#xff1a;fre:ac免费音频转换器完全指南 【免费下载链接】freac The fre:ac audio converter project 项目地址: https://gitcode.com/gh_mirrors/fr/freac 你是否曾经遇到过这样的烦恼&#xff1a;手机里的音乐格式电脑打不开&#xff0c;收藏的…

作者头像 李华
网站建设 2026/6/23 5:55:19

Hy-MT2混合指令调优:大模型翻译的工业级定制化实践

1. 项目概述&#xff1a;这不是又一个翻译工具&#xff0c;而是一次大模型落地逻辑的重新校准“Hy翻译”这个名字乍听平平无奇&#xff0c;像极了市面上那些套着AI外壳的网页翻译插件。但当你真正点开腾讯混元团队发布的Hy-MT2技术报告&#xff0c;翻到模型结构图里那个被加粗标…

作者头像 李华
网站建设 2026/6/23 5:49:17

NXP Kinetis FlexCAN驱动实战:从配置到eDMA优化的嵌入式通信指南

1. 项目概述如果你正在使用NXP的Kinetis系列微控制器开发汽车电子、工业控制或者任何需要高可靠实时通信的项目&#xff0c;那么FlexCAN模块几乎是你绕不开的核心外设。控制器局域网&#xff08;CAN&#xff09;总线以其卓越的抗干扰能力、多主仲裁机制和成熟的错误处理&#x…

作者头像 李华