img

📖 引言

在上一篇《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有三个关键支撑:

  1. DevEco Code AI Agent:内置GLM-5.1免费模型,专门训练了ArkTS/ArkUI知识库
  2. 标准化Skill体系:SKILL.md声明式描述 + ArkTS薄适配层的开发模式
  3. 小艺智能入口:系统级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中skillProfilesname,三者必须完全一致,否则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中skillProfilesname,三者不一致导致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-standards Skill会在生成时自动检查大部分语法问题
  • 对于复杂类型转换,建议使用类型守卫函数替代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协同编排,完成"一句话查天气→推荐户外活动→自动添加日历"的跨应用任务链

🔗 相关链接


💡 提示: 建议结合项目源码和DevEco Code工具同步阅读本文,动手实践效果更好!可以尝试用DevEco Code为"奇妙科学乐园"创建一个简单的Skill,体验Vibe Coding的完整开发流程。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐