HarmonyOS应用<奇妙科学乐园>开发第2篇:Vibe Coding开发流程——从需求描述到应用上线

📖 引言
在上一篇《Agent与Skill框架重构——端侧AI能力全面下放》中,我们深入了解了HarmonyOS 7(API 26)带来的Agent智能体与标准化Skill开发体系。本篇将视角从框架原理转向实战工作流,系统讲解如何借助Vibe Coding理念与DevEco Code AI Agent工具,将自然语言需求转化为可运行的应用代码,并完成Skill的全流程开发、审核与多设备分发。
Vibe Coding(氛围编程)是2026年HarmonyOS生态最重要的开发范式变革——开发者用自然语言描述需求,AI Agent理解意图、拆解任务、生成代码、编译构建、本地调测,最终完成端到端交付。对于"奇妙科学乐园"这类儿童科普应用,Vibe Coding可以极大地加速科学问答Skill的开发、智能交互能力的接入以及多设备适配的效率。
通过本文,你将掌握:
- Vibe Coding的核心理念及其在HarmonyOS开发中的落地方式
- DevEco Code AI Agent的三种工作模式与完整开发流程
- Skill从创建、编码、配置到测试、审核、分发的全生命周期
- 小艺智能入口的接入方式与语音交互实现
- 用Vibe Coding为"奇妙科学乐园"快速开发智能问答Skill的完整实战
🎯 学习目标
完成本文后,你将能够:
- ✅ 理解Vibe Coding"意图即服务"的核心理念,区分传统编码与AI辅助编码的差异
- ✅ 使用DevEco Code AI Agent进行需求分析、代码生成、本地调测的完整开发闭环
- ✅ 独立完成Skill的SKILL.md编写、ArkTS入口脚本开发、module.json5配置
- ✅ 接入小艺智能入口,实现语音驱动的科学问答交互
- ✅ 掌握多设备适配策略与智能体市场审核分发流程
💡 需求分析
功能模块设计
本文涉及的知识点覆盖了从开发工具链到应用分发的完整链路,核心模块设计如下:
| 模块 | 功能描述 | 技术要点 |
|---|---|---|
| Vibe Coding理念 | 理解"意图即服务"开发范式,从自然语言到可运行代码 | DevEco Code Agent模式、GLM-5.1模型、Skill自动加载 |
| DevEco Code工作流 | 需求分析→代码生成→本地调测→优化的完整闭环 | Build/Plan/Goal三种Agent模式、DEVECO_HOME环境配置 |
| Skill开发全流程 | 创建→编码→配置→测试→审核→分发 | SKILL.md编写、ArkTS入口脚本、skillProfiles配置 |
| 小艺智能入口接入 | 系统级语音交互,用户一句话触发应用能力 | 系统智能体意图匹配、ExecuteResult回传、suggestion字段 |
| 实战:智能问答Skill | 为"奇妙科学乐园"开发科学问答Skill | 知识库查询、参数校验、错误兜底、触发场景定义 |
| 多设备适配与分发 | 手机/平板/车机等多终端适配与市场分发 | 智能体市场上架、设备能力预查询、版本管理 |
整体流程概览
Vibe Coding开发流程的核心链路可以概括为:
开发者自然语言描述需求
↓
DevEco Code AI Agent 理解意图、拆解任务
↓
自动生成 Skill 代码(SKILL.md + ArkTS入口 + 配置)
↓
本地编译构建、模拟器/真机调测
↓
优化完善 → 提交智能体市场审核
↓
审核通过 → 多设备分发上线
🛠️ 核心实现
步骤1: Vibe Coding理念——意图即服务
功能说明
Vibe Coding(氛围编程)是2026年最受关注的开发范式。其核心理念是:开发者用自然语言表达需求意图,AI Agent负责将意图转化为可运行代码。在HarmonyOS 7生态中,这一理念通过DevEco Code工具链和Skill开发体系完整落地。
与传统编码相比,Vibe Coding带来三个根本性变化:
| 维度 | 传统编码 | Vibe Coding |
|---|---|---|
| 需求表达 | 技术规格文档、UML图、伪代码 | 自然语言对话描述业务意图 |
| 代码生成 | 手动逐行编写 | AI理解意图后自动生成 |
| 调试修复 | 人工定位错误、查文档 | AI分析编译/运行日志自动修复 |
核心认知:Vibe Coding不等于"AI代写代码"
💡 重要提示:Vibe Coding不是让开发者"躺着不动",而是将开发者从重复性编码工作中解放出来,让精力集中在业务逻辑设计、用户体验打磨和场景创新上。
在HarmonyOS生态中,Vibe Coding有三个关键支撑:
- DevEco Code AI Agent:内置GLM-5.1免费模型,专门训练了ArkTS/ArkUI知识库
- 标准化Skill体系:SKILL.md声明式描述 + ArkTS薄适配层的开发模式
- 小艺智能入口:系统级AI智能体完成意图匹配,用户语音直达应用能力
代码示例:传统开发 vs Vibe Coding
以创建一个"科学问答Skill"为例,对比两种开发方式:
传统开发流程:
1. 查阅Skill开发文档,理解目录结构和配置规范
2. 手动创建 skills/science-qa/ 目录
3. 编写 SKILL.md(触发场景、参数契约、返回值契约)
4. 编写 ArkTS 入口脚本(参数解析、业务调用、结果回传)
5. 修改 module.json5 添加 skillProfiles
6. 编译、运行、调试
7. 出错后查文档、搜索、修复
预计耗时:2-3天
Vibe Coding流程:
1. 在 DevEco Code 中输入:
"为奇妙科学乐园创建一个科学问答Skill,
支持太空、自然、海洋等主题的智能问答,
能回答'太阳系有几颗行星'这类问题"
2. AI Agent 自动:
- 分析需求,确定Skill类型
- 生成 SKILL.md + ArkTS入口脚本 + module.json5配置
- 编译构建,推送到模拟器
3. 开发者审查生成的代码,用自然语言指出需要调整的地方
4. AI Agent 自动修改、重新构建
预计耗时:30分钟-2小时
步骤2: DevEco Code AI Agent工作流程
功能说明
DevEco Code是华为基于开源项目OpenCode扩展开发的HarmonyOS专属AI Agent工具。它内置GLM-5.1免费模型、5大鸿蒙专属Skill和3种Agent模式,支持代码生成、编译构建、错误修复、文档搜索全链路开发。
安装与配置
DevEco Code的安装只需一行命令:
# 全局安装 DevEco Code
npm install -g @deveco/deveco-code
# 验证安装
deveco --version
# 输出: 0.1.0 即安装成功
# 启动 TUI 交互界面
deveco
环境要求:
- Node.js >= 18(推荐22+)
- DevEco Studio 26.0.0+(API 26 Beta1)
- 华为开发者账号(登录后解锁GLM-5.1免费额度)
DEVECO_HOME环境变量配置:
# Windows PowerShell 设置环境变量
[System.Environment]::SetEnvironmentVariable('DEVECO_HOME', 'C:\Program Files\Huawei\DevEco Studio', 'User')
# 验证
echo $env:DEVECO_HOME
三种Agent模式
DevEco Code提供三种Agent模式,通过Tab键切换:
| Agent模式 | 适用场景 | 工作方式 |
|---|---|---|
| Build(默认) | 日常代码生成、修改、编译、推包 | 直接动手型,能调DevEco Studio工具链 |
| Plan | 复杂需求拆解、技术方案输出 | 先规划再行动,适合多页面工程 |
| Goal | 从需求描述到完整交付 | SDD五阶段端到端交付,最省心 |
五大内置Skill
DevEco Code内置的5个Skill会在对话中自动加载,这是它与通用AI工具(如ChatGPT)的本质区别:
| Skill名称 | 自动触发时机 | 作用 |
|---|---|---|
deveco-create-project |
说"创建项目"、"新建工程" | 标准模板创建ArkTS工程,自动检测API Level |
arkts-grammar-standards |
写或改.ets文件时 |
强制遵守ArkTS语法规范,拒绝any/as/模板字符串 |
arkts-runtime-fix |
出现运行时崩溃日志时 | 分析crash日志,给出修复方案 |
arkui-knowledge |
涉及UI组件布局时 | 调用ArkUI组件知识库,给出正确的组件用法 |
arkts-error-fixes |
编译报错时 | 针对常见ArkTS编译错误给出标准解法 |
完整代码:用DevEco Code创建Skill项目
以下是使用DevEco Code进行需求分析→代码生成的完整交互示例:
// =============================================
// DevEco Code 交互示例
// 在 DevEco Code TUI 中输入以下需求描述:
// =============================================
// 开发者输入:
// "帮我为奇妙科学乐园创建一个科学问答Skill,
// 支持以下功能:
// 1. 回答太空、自然、海洋、科技、人体、天气六大主题的科学问题
// 2. 支持语音触发,如'小艺,太阳系有几颗行星'
// 3. 对于不确定的问题,返回友好的提示语
// 4. Skill名称为 science-qa"
// AI Agent 会自动执行以下步骤:
// 1. 分析需求,确定Skill类型和功能范围
// 2. 生成目录结构和文件
// 3. 编写 SKILL.md(触发场景、参数契约、返回值契约)
// 4. 编写 ArkTS 入口脚本
// 5. 修改 module.json5 配置
// 6. 尝试编译构建
代码解析
1. 需求分析阶段——AI Agent如何理解意图
DevEco Code在接收到自然语言需求后,会通过以下步骤进行意图分析:
自然语言输入 → GLM-5.1语义理解 → 提取关键信息
↓
├── Skill名称: science-qa
├── 功能类型: 知识问答
├── 触发方式: 语音/文字
├── 主题范围: 太空/自然/海洋/科技/人体/天气
└── 错误策略: 友好提示语
2. 代码生成阶段——ArkTS语法规范自动遵守
与通用AI工具不同,DevEco Code内置的arkts-grammar-standards Skill会确保生成的代码符合ArkTS严格模式规范:
// ❌ 通用AI工具可能生成的代码(ArkTS严格模式报错)
// 使用了 any、as 类型断言、模板字符串等ArkTS禁止的语法
function queryScience(topic: any, question: string): any {
const result = data as Record<string, string>;
return `答案是:${result.answer}`;
}
// ✅ DevEco Code 生成的代码(完全符合ArkTS规范)
// 使用明确类型定义、Record类型、字符串拼接
function queryScience(
topic: string,
question: string
): ScienceAnswer | null {
const result: ScienceAnswer | null = ScienceDataService.search(topic, question);
if (result === null) {
return null;
}
return result;
}
3. 常用命令速查
deveco # 启动TUI对话界面(默认Build Agent + GLM-5.1)
deveco --agent plan # 以Plan模式启动(适合复杂需求拆解)
deveco --agent goal # 以Goal模式启动(端到端交付)
deveco --continue # 续接上次会话(保留上下文)
deveco models # 查看可用模型列表
deveco stats # 查看Token用量和费用统计
deveco session list # 查看历史会话列表
deveco upgrade # 升级到最新版本
步骤3: Skill开发全流程——创建→编码→配置→测试→审核→分发
功能说明
Skill是HarmonyOS 7引入的声明式能力外化机制。通过编写SKILL.md描述文件和ArkTS入口脚本,将应用内的业务功能对外开放,让系统AI智能体(小艺)可以"一句话调用"你的应用能力。
完整代码:Skill目录结构
entry/
├── skills/ ← 【固定目录名】Skill根目录
│ └── science-qa/ ← Skill名(三处一致)
│ ├── scripts/ ← 【固定目录名】脚本目录
│ │ └── ScienceQASkill.ets ← ArkTS入口脚本
│ └── SKILL.md ← 【固定文件名】描述文件
└── src/
└── main/
├── ets/
│ ├── service/
│ │ └── ScienceDataService.ets ← 应用内已有的业务服务
│ └── entryability/
│ └── EntryAbility.ets
├── module.json5 ← 在此注册skillProfiles
└── resources/
⚠️ 关键约束:Skill目录名
science-qa、SKILL.md中的name字段、module.json5中skillProfiles的name,三者必须完全一致,否则Skill注册失败。
完整代码:SKILL.md——Skill的灵魂文件
SKILL.md是系统AI智能体进行"意图→能力"匹配的唯一依据。以下是为"奇妙科学乐园"编写的完整SKILL.md:
---
name: science-qa
description: 提供儿童科学知识问答能力,覆盖太空、自然、海洋、科技、人体、天气六大主题,
响应"太阳系有几颗行星"、"水循环是怎么回事"、"人体有多少块骨骼"等科学类指令
---
## 触发场景
当用户询问**科学知识相关的问题**时调用。典型话术:
- "太阳系有几颗行星"
- "水循环是怎么回事"
- "人体有多少块骨骼"
- "为什么天空是蓝色的"
- "最大的海洋是什么"
- "闪电是怎么产生的"
- "光合作用是什么原理"
- "小艺讲个太空故事"
不调用的情况:
- 用户说"帮我设个闹钟"——这是系统工具功能,不是科学问答
- 用户说"今天天气怎么样"——这是天气查询,不是科学知识科普
- 用户说"播放一首歌"——这是媒体控制,与科学无关
- 用户说"打开微信"——这是应用启动,不是知识问答
- 用户问数学计算题"3加5等于几"——这是数学计算,不是科学知识
---
### 场景1:查询科学知识(queryScience)
#### 执行参数
exec-cli(command: ohos-arkTSScript --skillName 'science-qa'
--scriptPath 'scripts/ScienceQASkill.ets'
--functionName 'queryScience'
--args '{"arg1": "太空", "arg2": "太阳系有几颗行星"}'
)
参数Schema:
```json
{
"args": {
"type": "object",
"properties": {
"arg1": {
"type": "string",
"description": "主题分类,如太空、自然、海洋、科技、人体、天气"
},
"arg2": {
"type": "string",
"description": "具体的科学问题"
}
},
"required": ["arg2"]
}
}
执行返回值
// 1. 查询成功
{
"type": "result",
"status": "success",
"data": {
"topic": "太空",
"question": "太阳系有几颗行星",
"answer": "太阳系目前有8颗行星,按离太阳从近到远的顺序分别是:
水星、金星、地球、火星、木星、土星、天王星和海王星。",
"funFact": "冥王星在2006年被重新分类为矮行星哦!",
"difficulty": "简单"
}
}
// 2. 问题未找到
{
"type": "result",
"status": "failed",
"errCode": "ERR_NOT_FOUND",
"data": { "searchedQuestion": "宇宙的外面是什么" },
"suggestion": "这个问题目前超出了我的知识范围,
不过好奇心是科学探索的第一步!你可以问问关于太空、自然、
海洋、科技、人体或天气方面的问题哦。"
}
// 3. 参数缺失
{
"type": "result",
"status": "failed",
"errCode": "ERR_INVALID_PARAMS",
"errMsg": "question is required",
"suggestion": "请告诉我你想了解什么科学知识呢?"
}
#### 完整代码:ArkTS入口脚本(薄适配层)
```typescript
// entry/skills/science-qa/scripts/ScienceQASkill.ets
import { scriptManager } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 导入应用内已有的科学数据服务
import {
ScienceDataService,
type ScienceAnswer
} from '../../../src/main/ets/service/ScienceDataService';
/**
* 科学问答Skill入口类
* 每个public async方法对应SKILL.md中声明的一项能力
* 方法名必须与SKILL.md中的functionName严格一致
*/
export default class ScienceQASkill {
/**
* 查询科学知识
* @param info - 系统注入的脚本执行信息(含context和requestCode)
* @param argv - AI智能体传入的参数列表(按位置排列)
*/
public async queryScience(
info: scriptManager.ArkTSScriptInfo,
...argv: string[]
): Promise<void> {
// 解析参数:arg1为主题(可选),arg2为问题(必填)
const topic: string = argv.length > 0 ? argv[0].trim() : '';
const question: string = argv.length > 1 ? argv[1].trim() :
(argv.length > 0 ? argv[0].trim() : '');
// 参数校验:问题不能为空
if (question.length === 0) {
const payload: Record<string, Object> = {
'type': 'result',
'status': 'failed',
'errCode': 'ERR_INVALID_PARAMS',
'errMsg': 'question is required',
'suggestion': '请告诉我你想了解什么科学知识呢?'
};
await this.report(info, { code: -1, result: payload });
return;
}
// 调用应用内已有的业务服务查询答案
try {
const answer: ScienceAnswer | null =
ScienceDataService.search(topic, question);
if (answer === null) {
// 未找到匹配的答案
const payload: Record<string, Object> = {
'type': 'result',
'status': 'failed',
'errCode': 'ERR_NOT_FOUND',
'data': { 'searchedQuestion': question },
'suggestion': '这个问题目前超出了我的知识范围,' +
'不过好奇心是科学探索的第一步!' +
'你可以问问关于太空、自然、海洋、科技、人体或天气方面的问题哦。'
};
await this.report(info, { code: -1, result: payload });
return;
}
// 查询成功,构造结果回传
const data: Record<string, Object> = {
'topic': answer.topic,
'question': answer.question,
'answer': answer.content,
'funFact': answer.funFact,
'difficulty': answer.difficulty
};
const payload: Record<string, Object> = {
'type': 'result',
'status': 'success',
'data': data
};
await this.report(info, { code: 0, result: payload });
} catch (e) {
// 异常处理:分类捕获,输出可读中文错误信息
const err = e as BusinessError;
const payload: Record<string, Object> = {
'type': 'result',
'status': 'failed',
'errCode': 'ERR_INTERNAL',
'errMsg': err.message,
'suggestion': '查询科学知识时出了点问题,稍后再试试吧!'
};
await this.report(info, { code: -1, result: payload });
}
}
/**
* 统一封装结果回传方法
* 所有业务分支的结果回传都通过此方法
* @param info - 系统注入的脚本执行信息
* @param result - 执行结果(包含code、result、uris、flags)
*/
private async report(
info: scriptManager.ArkTSScriptInfo,
result: scriptManager.ExecuteResult
): Promise<void> {
try {
await scriptManager.completeArkTSScriptInApp(
info.context,
info.requestCode,
result
);
} catch (e) {
const err = e as BusinessError;
console.error(
`[ScienceQASkill] 回传结果失败, code: ${err.code}, message: ${err.message}`
);
}
}
}
代码解析
1. 入口脚本的"薄适配层"设计理念
Skill入口脚本的设计遵循"薄适配层"原则——它不承载业务逻辑,只做三件事:接参数 → 调业务 → 报结果。这意味着:
// ✅ 正确:薄适配层设计,复用已有业务代码
const answer: ScienceAnswer | null =
ScienceDataService.search(topic, question);
// ❌ 错误:在Skill入口脚本中直接写业务逻辑
const answer: string = '';
if (topic === '太空' && question.includes('行星')) {
answer = '太阳系有8颗行星...';
} else if (topic === '自然' && question.includes('水循环')) {
answer = '水循环是指...';
}
原理:
- 入口脚本只是AI与App之间的桥梁
- 业务逻辑应放在App内部的Service层中
- 这样做的好处是:原有业务代码一行都不用改,Skill只是增加了一个入口
2. 参数校验的重要性
AI智能体传进来的参数都是string类型,需要开发者自己校验:
// ✅ 正确:严格校验必填参数
const question: string = argv.length > 1 ? argv[1].trim() :
(argv.length > 0 ? argv[0].trim() : '');
if (question.length === 0) {
// 返回友好的错误提示(suggestion字段)
await this.report(info, { code: -1, result: payload });
return;
}
// ❌ 错误:不校验参数,直接使用
const question: string = argv[1]; // 可能越界或为空
const result = ScienceDataService.search(topic, question); // 查询结果不可控
3. suggestion字段——决定用户体验的关键
// ✅ 正确:suggestion使用儿童友好的语言
'suggestion': '这个问题目前超出了我的知识范围,' +
'不过好奇心是科学探索的第一步!' +
'你可以问问关于太空、自然、海洋、科技、人体或天气方面的问题哦。'
// ❌ 错误:suggestion是冰冷的系统语言
'suggestion': 'ERR_NOT_FOUND: 该问题无匹配结果'
完整代码:module.json5配置
// entry/src/main/module.json5
{
"module": {
"name": "entry",
"type": "entry",
// Skill注册配置
"skillProfiles": [
{
// 必须与目录名、SKILL.md的name三者一致
"name": "science-qa",
// 关联的Ability
"abilityName": "EntryAbility",
// 脚本路径(相对于 src/main/)
"srcEntries": [
"../../skills/science-qa/scripts/ScienceQASkill.ets"
]
}
],
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
// ...其他配置
}
],
"requestPermissions": [
// Skill运行所需的权限
]
}
}
步骤4: 小艺智能入口接入——系统级语音交互
功能说明
HarmonyOS 7的小艺智能体已经接入了超过2000个鸿蒙智能体和2100多项系统能力。开发者开发的Skill通过小艺搜索、系统导航条、小艺建议等系统级入口被用户触达。用户只需一句话,就能触发应用功能。
整体调用链路
理解小艺智能入口的工作链路,有助于正确编写SKILL.md:
用户语音/文字输入:"小艺,太阳系有几颗行星"
↓
系统智能体解析意图(NLU语义理解)
↓
匹配所有Skill的SKILL.md触发场景
↓
命中 science-qa Skill 的触发条件
↓
按 exec-cli 构造调用参数
--skillName 'science-qa'
--functionName 'queryScience'
--args '{"arg1": "太空", "arg2": "太阳系有几颗行星"}'
↓
调用 ArkTS 入口脚本 ScienceQASkill.ets
↓
解析参数 → 校验 → 调用 ScienceDataService.search()
↓
按返回值契约构造 ExecuteResult
↓
调用 completeArkTSScriptInApp 回传结果
↓
系统智能体将结果转换为自然语言回复用户
↓
用户听到:"太阳系目前有8颗行星,按离太阳从近到远的顺序分别是:
水星、金星、地球、火星、木星、土星、天王星和海王星。
冥王星在2006年被重新分类为矮行星哦!"
代码解析:触发场景的精准定义
SKILL.md中的触发场景定义直接决定Skill能否被正确调用。以下是关键技巧:
<!-- ✅ 正确:触发场景包含正向示例和反向排除 -->
## 触发场景
当用户询问科学知识相关的问题时调用:
- "太阳系有几颗行星" ← 正向示例
- "水循环是怎么回事" ← 正向示例
不调用的情况:
- "今天天气怎么样" ← 反向排除:天气查询≠科学知识
- "3加5等于几" ← 反向排除:数学计算≠科学知识
- "帮我打电话给妈妈" ← 反向排除:通信功能≠科学知识
<!-- ❌ 错误:触发场景过于宽泛 -->
## 触发场景
当用户问问题时调用。
→ 这会导致所有问题都触发这个Skill,误触发率极高
完整代码:核心接口速查
// Skill开发涉及的三个核心接口
// 1. ArkTSScriptInfo —— 入口函数的首参,系统注入
interface ArkTSScriptInfo {
context: UIAbilityContext; // 绑定的Ability上下文
requestCode: number; // 当前请求的唯一标识码
}
// 2. ExecuteResult —— 脚本执行结果
interface ExecuteResult {
code: number; // 结果码,0为成功,非0为失败
result?: Record<string, Object>; // 结果内容
uris?: Array<string>; // 需要授权给调用方的URI列表
flags?: number; // URI读写权限
}
// 3. completeArkTSScriptInApp —— 上报执行结果
// 不管成功还是失败,都必须调用此接口回传结果
// 否则系统侧会超时等待
await scriptManager.completeArkTSScriptInApp(
info.context, // Ability上下文
info.requestCode, // 请求标识码(必须原样传回)
result // ExecuteResult结果对象
);
步骤5: 实战——为"奇妙科学乐园"开发智能问答Skill
功能说明
本步骤将前面所有知识串联起来,完成"奇妙科学乐园"智能问答Skill的完整开发流程。我们将使用Vibe Coding方式,通过DevEco Code完成从需求描述到代码生成的全过程。
完整代码:ScienceDataService业务服务
在编写Skill入口脚本之前,先确认应用内已有的科学数据服务:
// entry/src/main/ets/service/ScienceDataService.ets
/**
* 科学数据服务
* 负责从本地数据源搜索科学知识答案
*/
export class ScienceDataService {
/**
* 搜索科学知识答案
* @param topic - 主题分类(太空、自然、海洋、科技、人体、天气)
* @param question - 用户提出的问题
* @returns 匹配的科学答案,未找到返回null
*/
static search(topic: string, question: string): ScienceAnswer | null {
// 从ScienceData单例获取全量数据
const allTopics: Topic[] = ScienceData.getInstance().getAllTopics();
// 遍历所有主题文章,匹配问题关键词
for (let i = 0; i < allTopics.length; i++) {
const currentTopic: Topic = allTopics[i];
// 主题匹配(可选)
if (topic.length > 0 && currentTopic.category !== topic) {
continue;
}
// 关键词匹配
if (this.isQuestionMatched(question, currentTopic)) {
return {
topic: currentTopic.category,
question: question,
content: currentTopic.content,
funFact: currentTopic.funFact,
difficulty: currentTopic.difficulty
};
}
}
return null;
}
/**
* 判断问题是否与文章内容匹配
* @param question - 用户问题
* @param topic - 文章数据
* @returns 是否匹配
*/
private static isQuestionMatched(question: string, topic: Topic): boolean {
// 提取问题中的关键词
const keywords: string[] = this.extractKeywords(question);
// 在文章标题和内容中搜索关键词
const searchField: string = topic.title + topic.content;
let matchCount: number = 0;
for (let i = 0; i < keywords.length; i++) {
if (searchField.includes(keywords[i])) {
matchCount++;
}
}
// 至少匹配一个关键词即认为匹配
return matchCount > 0;
}
/**
* 从问题中提取关键词
* @param question - 用户问题
* @returns 关键词数组
*/
private static extractKeywords(question: string): string[] {
// 去除常见疑问词,提取核心词
const stopWords: string[] = [
'的', '是', '什么', '为什么', '怎么', '多少',
'有', '能', '会', '可以', '吗', '呢', '啊', '吧'
];
let cleaned: string = question;
for (let i = 0; i < stopWords.length; i++) {
cleaned = cleaned.replace(new RegExp(stopWords[i], 'g'), '');
}
// 按空格分割关键词
return cleaned.split(' ').filter((word: string) => word.length > 0);
}
}
/**
* 科学答案数据模型
*/
export interface ScienceAnswer {
topic: string; // 主题分类
question: string; // 用户问题
content: string; // 答案内容
funFact: string; // 趣味冷知识
difficulty: string; // 难度等级
}
/**
* 文章数据模型(简化版)
*/
interface Topic {
title: string;
category: string;
content: string;
funFact: string;
difficulty: string;
}
Vibe Coding实战:用DevEco Code生成Skill
以下是完整的Vibe Coding交互过程,展示如何用自然语言驱动AI完成Skill开发:
=== 第1轮对话:创建Skill骨架 ===
开发者输入:
"为奇妙科学乐园创建一个科学问答Skill,
名称为 science-qa,关联 EntryAbility。
支持查询太空、自然、海洋、科技、人体、天气六大主题的科学知识。
对于找不到答案的问题,返回儿童友好的提示语。"
AI Agent执行:
- 创建 entry/skills/science-qa/ 目录结构
- 生成 SKILL.md(含触发场景、参数契约、返回值契约)
- 生成 scripts/ScienceQASkill.ets 入口脚本
- 修改 module.json5 添加 skillProfiles
- 执行编译验证
=== 第2轮对话:调整触发场景 ===
开发者输入:
"SKILL.md的触发场景需要更精确:
1. 排除数学计算类问题,比如'3加5等于几'
2. 排除天气查询类问题,比如'今天出门要带伞吗'
3. 增加'小艺讲个科学故事'的触发"
AI Agent执行:
- 读取当前 SKILL.md
- 修改触发场景部分,添加排除规则
- 保存文件,重新编译
=== 第3轮对话:优化返回结果 ===
开发者输入:
"queryScience的返回结果需要增加以下字段:
1. relatedTopics - 相关主题推荐(最多3个)
2. ageRange - 适合的年龄范围
同时更新SKILL.md的返回值契约"
AI Agent执行:
- 读取 ScienceQASkill.ets
- 在返回的data中添加 relatedTopics 和 ageRange 字段
- 同步更新 SKILL.md 的返回值Schema
- 编译验证通过
=== 第4轮对话:本地调测 ===
开发者输入:
"帮我模拟一个测试:用'太阳系有几颗行星'这个问问题调一下 science-qa Skill"
AI Agent执行:
- 在模拟器/真机上执行Skill测试
- 输出测试结果:
✅ 触发成功
✅ 参数解析正确 (topic="太空", question="太阳系有几颗行星")
✅ 查询成功,返回8颗行星的答案
✅ suggestion中包含冥王星冷知识
代码解析
1. Vibe Coding的迭代式开发模式
上面的交互展示了Vibe Coding的核心工作方式——对话式迭代开发。与传统开发的对比:
// 传统开发:手动修改 → 编译 → 运行 → 查看结果 → 再修改
// 每轮迭代需要 10-30 分钟
// Vibe Coding:自然语言描述 → AI自动修改 → 自动编译 → 自动测试
// 每轮迭代需要 30 秒 - 2 分钟
2. 分层返回结果设计
为"奇妙科学乐园"这类儿童应用设计返回结果时,需要考虑不同场景的数据结构:
// ✅ 正确:分场景设计返回结果
// 场景1:查询成功
const successPayload: Record<string, Object> = {
'type': 'result',
'status': 'success',
'data': {
'topic': '太空',
'question': '太阳系有几颗行星',
'answer': '太阳系目前有8颗行星...',
'funFact': '冥王星在2006年被重新分类为矮行星哦!',
'difficulty': '简单',
'relatedTopics': ['月球探索', '恒星知识', '宇宙奥秘'],
'ageRange': '4-10岁'
}
};
// 场景2:答案未找到(儿童友好提示)
const notFoundPayload: Record<string, Object> = {
'type': 'result',
'status': 'failed',
'errCode': 'ERR_NOT_FOUND',
'data': { 'searchedQuestion': '宇宙的外面是什么' },
'suggestion': '这个问题目前超出了我的知识范围,' +
'不过好奇心是科学探索的第一步!' +
'你可以问问关于太阳系、恐龙、海洋动物等问题哦!'
};
// ❌ 错误:所有场景返回相同的结构
// 不区分成功/失败,不提供suggestion提示
const badPayload: Record<string, Object> = {
'result': ScienceDataService.search(topic, question)
};
步骤6: 多设备适配与分发策略
功能说明
HarmonyOS的最大优势之一是"一次开发,多端部署"。当Skill开发完成并通过审核后,需要考虑如何在不同设备形态(手机、平板、车机、手表等)上正确运行和分发。
多设备适配原则
| 设备类型 | 屏幕特征 | 适配要点 | Skill调用方式 |
|---|---|---|---|
| 手机 | 4.7-6.9英寸 | 语音/文字触发,标准卡片展示 | 小艺语音、搜索栏 |
| 平板 | 10-12英寸 | 平行视界分栏,大屏沉浸展示 | 小艺语音、搜索栏、桌面卡片 |
| 车机 | 10-25英寸 | 语音优先,大字体高对比度 | 方向盘语音按键、中控语音 |
| 手表 | 1.4-2.0英寸 | 简短回答,关键信息优先 | 语音抬腕触发 |
完整代码:设备能力预查询
// entry/src/main/ets/utils/DeviceCapabilityUtil.ets
import { deviceInfo } from '@kit.BasicServicesKit';
/**
* 设备能力工具类
* 用于判断当前设备类型,适配不同设备形态
*/
export class DeviceCapabilityUtil {
/**
* 获取设备类型
* @returns 设备类型字符串
*/
static getDeviceType(): string {
const deviceType: string = deviceInfo.deviceType;
return deviceType;
}
/**
* 是否为大屏设备(平板、折叠屏)
* @returns 是否大屏
*/
static isLargeScreen(): boolean {
// 平板或折叠屏判定
const deviceType: string = this.getDeviceType();
return deviceType === 'tablet' || deviceType === '2in1';
}
/**
* 是否为可穿戴设备(手表)
* @returns 是否可穿戴
*/
static isWearable(): boolean {
return this.getDeviceType() === 'watch';
}
/**
* 是否为车机
* @returns 是否车机
*/
static isCarDevice(): boolean {
return this.getDeviceType() === 'car';
}
/**
* 根据设备类型获取适合的回答长度
* @returns 最大回答字符数
*/
static getMaxAnswerLength(): number {
if (this.isWearable()) {
return 100; // 手表:超短回答
} else if (this.isCarDevice()) {
return 200; // 车机:简短回答(语音播报)
} else if (this.isLargeScreen()) {
return 1000; // 平板:完整回答
} else {
return 500; // 手机:中等长度回答
}
}
}
完整代码:适配不同设备的Skill返回结果
// 在 ScienceQASkill.ets 的 queryScience 方法中添加设备适配逻辑
import { DeviceCapabilityUtil } from '../../../src/main/ets/utils/DeviceCapabilityUtil';
// 在查询成功后,根据设备类型裁剪答案
const maxLen: number = DeviceCapabilityUtil.getMaxAnswerLength();
let displayAnswer: string = answer.content;
if (displayAnswer.length > maxLen) {
displayAnswer = displayAnswer.substring(0, maxLen) + '...';
}
const data: Record<string, Object> = {
'topic': answer.topic,
'question': answer.question,
'answer': displayAnswer,
'funFact': DeviceCapabilityUtil.isWearable() ? '' : answer.funFact,
'difficulty': answer.difficulty,
'deviceType': deviceInfo.deviceType
};
智能体市场审核与分发
Skill开发完成后,通过以下流程上架到智能体市场:
开发完成 → 自测验证 → 提交审核 → 审核通过 → 市场上架 → 多端分发
审核要点:
1. Skill名称:不超过8个中文字符,直观表达功能
2. 触发场景:正向示例和反向排除都要清晰
3. 参数契约:args Schema完整,required标记必填项
4. 返回值契约:覆盖所有场景(成功、参数缺失、未找到、内部错误)
5. suggestion字段:必须包含,且使用用户友好的语言
6. 隐私政策:必须提供可访问的隐私政策链接
7. 内容合规:涉及AI生成内容需填写备案信息
8. 未成年人保护:面向儿童的应用需特别关注
代码解析
1. 审核常见驳回原因与规避
// ❌ 常见驳回原因1:Skill名称不规范
// "免费科学问答助手" → 包含"免费"营销词,会被驳回
// "AI科学王" → 名称过于抽象,无法直观表达功能
// ✅ 正确命名:"奇妙科学问答" → 清晰表达功能
// ❌ 常见驳回原因2:触发场景边界不清晰
// 只写了"当用户问问题时调用" → 误触发率极高
// ✅ 正确写法:
// "当用户询问科学知识相关的问题时调用"
// 并明确列出"不调用的情况"
// ❌ 常见驳回原因3:缺少隐私政策
// 没有配置隐私政策链接
// ✅ 正确做法:在智能体市场配置中关联有效的隐私政策URL
2. 版本管理策略
版本号规范:主版本号.次版本号.修订号(如 1.0.0 → 1.1.0 → 1.1.1)
变更类型 版本号变化 示例
新增功能 次版本号+1 1.0.0 → 1.1.0
Bug修复 修订号+1 1.1.0 → 1.1.1
重大架构调整 主版本号+1 1.1.0 → 2.0.0
⚠️ 常见问题与解决方案
问题1: Skill注册失败——三处名称不一致
现象:
Skill开发完成后,通过语音或文字触发时系统无响应,控制台也没有错误日志。
原因:
Skill目录名、SKILL.md中的name字段、module.json5中skillProfiles的name,三者不一致导致Skill注册失败。
错误代码:
// ❌ 错误:三处名称不一致
// 目录名: skills/science-qa/
// SKILL.md: name: science_qa ← 下划线
// module.json5: skillProfiles[0].name: "ScienceQA" ← 大驼峰
正确代码:
// ✅ 正确:三处名称完全一致
// 目录名: skills/science-qa/
// SKILL.md:
---
name: science-qa ← 与目录名一致
---
// module.json5:
"skillProfiles": [
{
"name": "science-qa" ← 与目录名、SKILL.md一致
}
]
规则/建议:
- Skill名称使用小写字母和短横线分隔(如
science-qa) - 开发时先确定名称,然后三处同步填写
- 编译前检查三处是否一致
问题2: 入口脚本方法名与SKILL.md不匹配
现象:
系统智能体能匹配到Skill,但执行时返回"function not found"错误。
原因:
ArkTS入口脚本中的public方法名与SKILL.md中的functionName不一致。
错误代码:
// ❌ 错误:方法名不一致
// SKILL.md中声明:
// functionName: 'queryScience'
// 入口脚本中:
export default class ScienceQASkill {
public async getScienceInfo( // ← 方法名不匹配
info: scriptManager.ArkTSScriptInfo,
...argv: string[]
): Promise<void> { }
}
正确代码:
// ✅ 正确:方法名与SKILL.md的functionName严格一致
// SKILL.md中声明:
// functionName: 'queryScience'
// 入口脚本中:
export default class ScienceQASkill {
public async queryScience( // ← 与SKILL.md一致
info: scriptManager.ArkTSScriptInfo,
...argv: string[]
): Promise<void> { }
}
规则/建议:
- 方法名严格匹配,区分大小写
- 建议在SKILL.md中写完functionName后,直接复制到入口脚本中
问题3: 未调用completeArkTSScriptInApp导致超时
现象:
Skill被触发并开始执行,但系统一直等待,最终超时返回"执行失败"。
原因:
某个代码分支(如异常处理、参数校验失败)忘记调用completeArkTSScriptInApp回传结果。
错误代码:
// ❌ 错误:参数校验分支没有回传结果
public async queryScience(
info: scriptManager.ArkTSScriptInfo,
...argv: string[]
): Promise<void> {
const question: string = argv.length > 1 ? argv[1].trim() : '';
if (question.length === 0) {
return; // ← 直接返回,没有调用completeArkTSScriptInApp
// 系统会一直等待直到超时
}
// ...正常逻辑
}
正确代码:
// ✅ 正确:所有分支都必须回传结果
public async queryScience(
info: scriptManager.ArkTSScriptInfo,
...argv: string[]
): Promise<void> {
const question: string = argv.length > 1 ? argv[1].trim() : '';
if (question.length === 0) {
const payload: Record<string, Object> = {
'type': 'result',
'status': 'failed',
'errCode': 'ERR_INVALID_PARAMS',
'suggestion': '请告诉我你想了解什么科学知识呢?'
};
await this.report(info, { code: -1, result: payload }); // ← 必须回传
return;
}
// ...正常逻辑
}
规则/建议:
- 封装统一的
report()方法,所有分支都通过它回传结果 - 不管成功还是失败,都必须调用
completeArkTSScriptInApp - 每次新增错误分支时,检查是否调用了回传方法
问题4: argv参数越界导致运行时异常
现象:
某些用户提问能正常回答,但某些提问会导致Skill崩溃。
原因:
AI智能体传入的参数数量不固定,直接按索引访问argv数组导致越界。
错误代码:
// ❌ 错误:直接按索引访问,可能越界
public async queryScience(
info: scriptManager.ArkTSScriptInfo,
...argv: string[]
): Promise<void> {
// 假设argv[0]是topic,argv[1]是question
const topic: string = argv[0].trim(); // ← argv[0]可能不存在
const question: string = argv[1].trim(); // ← argv[1]可能不存在
}
正确代码:
// ✅ 正确:安全地访问argv,做好长度判断和默认值处理
public async queryScience(
info: scriptManager.ArkTSScriptInfo,
...argv: string[]
): Promise<void> {
// 安全获取参数,带默认值
const topic: string = argv.length > 0 ? argv[0].trim() : '';
const question: string = argv.length > 1 ? argv[1].trim() :
(argv.length > 0 ? argv[0].trim() : '');
// 进一步校验
if (question.length === 0) {
// 返回参数缺失错误
}
}
规则/建议:
- argv是string数组,AI传参数量不固定
- 访问前务必判断length
- 对第一个参数同时做topic和question的兜底处理
问题5: DevEco Code生成的代码不符合ArkTS严格模式
现象:
DevEco Code生成的Skill代码在编译时报ArkTS严格模式错误,如"Spread operator is not allowed"。
原因:
GLM-5.1模型虽然内置了ArkTS语法规范Skill,但在复杂场景下可能仍然生成不符合严格模式的代码。
错误代码:
// ❌ DevEco Code可能生成的代码(ArkTS严格模式不兼容)
// 1. 使用了展开运算符
const result = { ...baseData, ...extraData };
// 2. 使用了 any 类型
const data: any = JSON.parse(jsonStr);
// 3. 使用了模板字符串
const msg = `答案是:${answer}`;
// 4. 使用了 as 类型断言
const result = data as ScienceAnswer;
正确代码:
// ✅ ArkTS严格模式兼容的写法
// 1. 展开运算符 → 使用Object.assign或显式赋值
const result: Record<string, Object> = Object.assign({}, baseData, extraData);
// 2. any → 使用明确类型或unknown + 类型守卫
const data: ScienceAnswer | null = ScienceDataService.parseJSON(jsonStr);
// 3. 模板字符串 → 使用字符串拼接
const msg: string = '答案是:' + answer;
// 4. as类型断言 → 使用类型守卫函数
function isScienceAnswer(obj: unknown): obj is ScienceAnswer {
return obj !== null &&
typeof obj === 'object' &&
'topic' in obj &&
'content' in obj;
}
if (isScienceAnswer(data)) {
// 安全使用
}
规则/建议:
- 收到编译错误后,在DevEco Code中直接粘贴错误信息,AI会自动修复
arkts-grammar-standardsSkill会在生成时自动检查大部分语法问题- 对于复杂类型转换,建议使用类型守卫函数替代
as
📝 本章小结
核心知识点
本文详细讲解了Vibe Coding开发流程——从需求描述到应用上线,主要包括:
1. Vibe Coding理念
- 核心是"意图即服务",用自然语言表达需求,AI自动生成代码
- 不是替代开发者,而是将精力从重复编码中释放到业务创新上
- DevEco Code是HarmonyOS生态Vibe Coding的核心工具
2. DevEco Code AI Agent工作流
- 三种Agent模式:Build(日常开发)、Plan(需求拆解)、Goal(端到端交付)
- 五大内置Skill:项目创建、语法规范、运行时修复、ArkUI知识、错误修复
- 完整链路:需求分析 → 代码生成 → 编译构建 → 本地调测 → 优化
3. Skill开发全流程
- SKILL.md:Skill的灵魂文件,定义触发场景和参数/返回值契约
- ArkTS入口脚本:薄适配层设计,只做"接参数→调业务→报结果"
- module.json5:skillProfiles配置,三处名称必须一致
- 关键接口:ArkTSScriptInfo、ExecuteResult、completeArkTSScriptInApp
4. 小艺智能入口接入
- 系统智能体完成意图匹配 → 参数构造 → 脚本调用 → 结果回传 → 自然语言回复
- suggestion字段决定用户体验,必须使用友好语言
5. 多设备适配与分发
- 设备能力预查询,根据设备类型调整回答长度和内容
- 智能体市场审核要点:名称规范、触发边界、隐私政策、内容合规
最佳实践总结
✅ Skill名称三处一致
// 目录名: skills/science-qa/
// SKILL.md: name: science-qa
// module.json5: skillProfiles[0].name: "science-qa"
✅ 薄适配层设计——复用已有业务代码
// Skill入口脚本只做桥接,不写业务逻辑
const answer: ScienceAnswer | null =
ScienceDataService.search(topic, question);
✅ 统一的report方法——所有分支都回传结果
// 不管成功还是失败,都通过report方法统一回传
await this.report(info, { code: 0, result: successPayload });
await this.report(info, { code: -1, result: errorPayload });
✅ 儿童友好的suggestion提示
// 面向儿童应用,suggestion使用鼓励性、引导性的语言
'suggestion': '这个问题目前超出了我的知识范围,' +
'不过好奇心是科学探索的第一步!'
✅ DevEco Code Vibe Coding交互技巧
# 先用Plan模式拆解复杂需求
deveco --agent plan
# 再用Build模式执行代码生成
deveco --agent build
# 编译错误直接粘贴给AI自动修复
下一步预告
在下一篇文章《A2A跨应用智能体互通——实现多软件联动复杂任务》中,我们将:
- 🎨 深入了解A2A(Agent to Agent)跨应用通信机制
- 📚 实现端侧A2A和云侧A2A的双向通道
- 🏷️ 通过多Agent协同编排,完成"一句话查天气→推荐户外活动→自动添加日历"的跨应用任务链
🔗 相关链接
- 项目源码: Atomgit仓库
- DevEco Code 开源地址: gitcode.com/openharmony-sig/deveco-code
- HarmonyOS 官方文档: developer.harmonyos.com
- 小艺开放平台: 小艺智能体开发指南
💡 提示: 建议结合项目源码和DevEco Code工具同步阅读本文,动手实践效果更好!可以尝试用DevEco Code为"奇妙科学乐园"创建一个简单的Skill,体验Vibe Coding的完整开发流程。
更多推荐



所有评论(0)