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

发布时间:2026/6/23 6:19:27

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.json5的module标签下加上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}fromkit.AbilityKit;import{BusinessError}fromkit.BasicServicesKit;import{WeatherService,WeatherInfo,ForecastResult}from../../../src/main/ets/service/WeatherService;2.2 定义入口类用export default导出一个类类里每个public async方法对应SKILL.md里声明的一项能力方法名必须和SKILL.md的functionName严格一致第一个参数类型固定为scriptManager.ArkTSScriptInfoexportdefaultclassWeatherSkill{publicasyncqueryWeather(info:scriptManager.ArkTSScriptInfo,...argv:string[]):Promisevoid{// 具体实现见下文}publicasyncqueryForecast(info:scriptManager.ArkTSScriptInfo,...argv:string[]):Promisevoid{// 同理}}2.3 解析和校验参数AI智能体传进来的参数都在argv里按位置排列咱们得自己做校验// queryWeather需要城市名日期可选constcity:stringargv.length0?argv[0].trim():;constdate:stringargv.length1?argv[1].trim():;if(city.length0){// 城市都没传直接走错误分支constpayload:Recordstring,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:stringargv.length0?argv[0].trim():;if(city.length0){constpayload:Recordstring,Object{type:result,status:failed,errCode:ERR_INVALID_PARAMS,errMsg:city is required,suggestion:请告诉我你想查哪个城市的预报};awaitthis.report(info,{code:-1,result:payload});return;}constdays:numberargv.length1?parseInt(argv[1],10):7;if(days1||days15){constpayload:Recordstring,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[]):Promisevoid{constcity:stringargv.length0?argv[0].trim():;constdate:stringargv.length1?argv[1].trim():;if(city.length0){constpayload:Recordstring,Object{type:result,status:failed,errCode:ERR_INVALID_PARAMS,errMsg:city is required,suggestion:你想查哪个城市的天气呢};awaitthis.report(info,{code:-1,result:payload});return;}try{constweather:WeatherInfoWeatherService.getCurrentWeather(city,date);constdata:Recordstring,Object{city:weather.city,temperature:weather.temperature,condition:weather.condition,humidity:weather.humidity,wind:weather.wind};constpayload:Recordstring,Object{type:result,status:success,data:data};awaitthis.report(info,{code:0,result:payload});}catch(e){consterreasBusinessError;if(err.code404){constpayload:Recordstring,Object{type:result,status:failed,errCode:ERR_NOT_FOUND,data:{searchedCity:city},suggestion:暂时没有找到${city}的天气数据};awaitthis.report(info,{code:-1,result:payload});}else{constpayload:Recordstring,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):Promisevoid{try{awaitscriptManager.completeArkTSScriptInApp(info.context,info.requestCode,result);}catch(e){consterreasBusinessError;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的namedescription要简洁这是AI做初次筛选的关键依据。3.2 触发场景用自然语言写帮AI搞清楚什么时候该调我、什么时候不该调我## 触发场景 当用户询问**某个城市的天气或预报**时调用。典型话术 - 北京今天天气怎么样 - 明天上海热不热 - 深圳未来一周天气预报 - 广州下雨了吗 不调用的情况 - 用户说帮我设个明天7点的闹钟——这是闹钟功能跟天气无关 - 用户说今天适合跑步吗——这是运动建议除非明确提到天气查询 - 用户说这张天空照片真好看——这是社交评价不是查天气 - 用户说帮我关空调——这是智能家居控制不是天气查询划重点边界说明特别重要不写清楚的话AI很容易误触发。比如今天适合出门吗这种话如果你的Skill只查天气不做出行建议就要明确排除。3.3 能力1queryWeather的参数契约### 场景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 能力1queryWeather的返回值契约 先把所有可能的返回结果列出来 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 能力2queryForecast的参数契约### 场景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列表、flagsURI读写权限 | | ArkTSScriptInfo | 入口函数的首参包含 requestCode请求标识和 contextAbility上下文 | | completeArkTSScriptInApp(context, requestCode, result) | 上报执行结果Promise异步回调 | ExecuteResult 的完整结构 typescript interface ExecuteResult { code: number; // 结果码0为成功 result?: Recordstring, Object; // 脚本执行结果 uris?: Arraystring; // 需授权给调用方的URI列表 flags?: number; // URI读写权限 }五、开发避坑指南总结几个容易踩的坑名字一致性Skill目录名、SKILL.md的name字段、module.json5的skillProfiles里的name三个地方必须完全一样少一个下划线都不行否则注册不上。方法名严格匹配入口脚本里的public方法名必须跟SKILL.md里的functionName一模一样大小写都别搞错。第一个参数类型固定入口方法的第一个参数必须是scriptManager.ArkTSScriptInfo这是系统注入的上下文不能换、不能省。argv是string数组AI传进来的参数全是string需要自己做类型转换比如parseInt同时做好校验和容错。必须调用completeArkTSScriptInApp不管成功还是失败都必须调用这个接口回传结果否则系统侧会一直等着超时后认为执行失败。suggestion字段很重要失败时一定要填suggestion这是AI转述给用户的提示语写得好用户体验就好写得烂用户就一脸懵。触发场景要写清楚边界SKILL.md里不调用的情况一定要认真写否则你的Skill会被AI在各种奇怪的场景下误触发。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 267.0编写Skill相关接口起始版本为26.0.0仅支持Stage模型。

相关新闻