手写第一个 AI 技能
场景
你想把某个业务功能封装成 AI 可调用的 SKILL,但没有现成的模板可以用,需要从头手写一个。本文以"天气查询"为例,从脚手架到完整 SKILL,走通全流程。
前置条件
- 已完成 从头构建 AI 小程序,项目可在开发者工具中运行
- 微信开发者工具 Nightly 版 已安装
- 基础库版本 ≥ 3.16.1
- Node.js ≥ 18
实现步骤
第 1 步:创建 SKILL 脚手架
# 在项目根目录下执行
npx mp-skills create weather-skill
预期输出:
📦 已创建 Skill: weather-skill
ok miniprogram/skills/weather-skill/
ok cloudbaserc.json — 云资源声明(云函数配置 + 数据库集合)
ok mcp.json — 定义 API 接口
ok SKILL.md — 编排业务流程
ok index.js — 注册入口
ok apis/ — 原子接口实现
ok components/ — 原子组件
ok 已自动注册到 app.json agent.skills
生成的文件结构:
miniprogram/skills/weather-skill/
├── SKILL.md # 业务说明 — AI 引擎读此判断触发时机
├── mcp.json # 原子接口声明 — 定义参数和返回值
├── index.js # 入口 — 注册原子接口
├── cloudbaserc.json # 云资源声明(可选,按需修改)
├── apis/
│ └── greet.js # 默认示例接口,稍后替换为 getWeather.js
└── components/
└── greeting-card/ # 默认示例组件,稍后替换为 weather-card
create 命令同时自动更新了 app.json 的 agent.skills 和 subPackages,无需手动配置。
生成的文件包含默认的示例代码(apis/greet.js、components/greeting-card/),后续步骤会替换为天气查询的实战代码。
第 2 步:编写 SKILL.md
SKILL.md 告诉 AI 引擎这个技能何时触发。编写 miniprogram/skills/weather-skill/SKILL.md:
# 天气查询
## 触发场景
用户想查询天气时。典型提问:
- "今天北京天 气怎么样"
- "明天会下雨吗"
- "这周末上海的温度"
- "查一下深圳未来三天的天气"
## 不适用范围
- 询问日期、时间等非天气信息
- 需要图片/视频等多媒体内容
第 3 步:配置 mcp.json
mcp.json 声明原子接口的输入输出。编写 miniprogram/skills/weather-skill/mcp.json:
{
"apis": [
{
"name": "getWeather",
"description": "查询指定地点的天气信息。用户提供城市名或位置时调用。需传入 location 和可选的 days 参数。",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "查询地点,如城市名(北京、上海)或区名。取值来源:用户原话中的地点信息。"
},
"days": {
"type": "number",
"description": "预报天数,范围 1-7,不传默认 1。"
}
},
"required": ["location"]
},
"outputSchema": {
"type": "object",
"properties": {
"location": { "type": "string" },
"forecasts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"date": { "type": "string" },
"weather": { "type": "string" },
"tempHigh": { "type": "number" },
"tempLow": { "type": "number" }
}
}
}
}
},
"_meta": {
"ui": { "componentPath": "components/weather-card/index" }
}
}
],
"components": [
{
"path": "components/weather-card/index",
"relatedPage": "/pages/index/index"
}
]
}
第 4 步:实现原子接口
编写 miniprogram/skills/weather-skill/apis/getWeather.js:
async function getWeather({ location, days = 1 }) {
// 校验参数
if (!location) {
return {
isError: true,
content: [{ type: 'text', text: '缺少地点参数,请提供要查询天气的城市名。' }]
}
}
// 调用第三方天气 API 或云函数
try {
const res = await wx.request({
url: 'https://api.example.com/weather',
data: { city: location, days: Math.min(days, 7) }
})
return {
isError: false,
content: [{ type: 'text', text: `已查询到 ${location} 未来 ${days} 天天气` }],
structuredContent: {
location,
forecasts: res.data.forecasts.map(f => ({
date: f.date,
weather: f.weather,
tempHigh: f.tempMax,
tempLow: f.tempMin
}))
}
}
} catch (err) {
return {
isError: true,
content: [{ type: 'text', text: `查询天气失败:${err.message}。请稍后重试。` }]
}
}
}
module.exports = getWeather
返回值说明:
| 字段 | 说明 |
|---|---|
isError | 是否出错。false 表示成功,true 表示失败 |
content | 返回给 AI 引擎的文本,用于引导后续对话 |
structuredContent | 返回给原子组件渲染的结构化数据 |
第 5 步:创建原子组件
组件用于展示天气卡片。框架已生成默认 components/greeting-card/ 目录,将其替换为 components/weather-card/,包含 4 个文件:
index.wxml:
<view class="card">
<text class="title">{{location}} 天气预报</text>
<view wx:for="{{forecasts}}" wx:key="date" class="day-row">
<text class="date">{{item.date}}</text>
<text class="weather">{{item.weather}}</text>
<text class="temp">{{item.tempLow}}°~{{item.tempHigh}}°</text>
</view>
</view>
index.js:
Component({
lifetimes: {
created() {
const modelCtx = wx.modelContext.getContext(this)
modelCtx.on(wx.modelContext.NotificationType.Result, (data) => {
const sc = data?.result?.structuredContent || {}
this.setData({
location: sc.location || '',
forecasts: sc.forecasts || []
})
})
}
}
})
index.json:
{
"component": true,
"usingComponents": {}
}
index.wxss:
.card { padding: 16px; background: #fff; border-radius: 12px; }
.title { font-size: 16px; font-weight: 600; margin-bottom: 12px; display: block; }
.day-row { display: flex; justify-content: space-between; padding: 8px 0; }
.date { font-size: 14px; color: #333; }
.weather { font-size: 14px; color: #666; }
.temp { font-size: 14px; color: #1677ff; }
第 6 步:注册原子接口
编写 miniprogram/skills/weather-skill/index.js:
const getWeather = require('./apis/getWeather')
function registerAPIs() {
const skill = wx.modelContext.createSkill('skills/weather-skill')
skill.registerAPI('getWeather', getWeather)
}
registerAPIs()
registerAPI 的第一个参数须与 mcp.json 中的 apis[].name 完全一致。
注意:框架生成的
index.js中createSkill('skills/my-skill')使用了占位路径,需要手动改为'skills/weather-skill'。
第 7 步:validate 校验
npx mp-skills validate
检查通过后进入下一步,失败则根据错误提示修复。
第 8 步:在开发者工具中验证
# macOS
/Applications/wechatwebdevtools.app/Contents/MacOS/cli open --project /your/path/to/project
切换到「小程序 AI 编译」模式:
- 左侧 SKILL 列表中应出现
weather-skill - 选中后右侧显示
getWeather接口 - 填入参数
{"location": "北京", "days": 3}执行 - 查看返回的天气卡片渲染效果
验证清单
-
npx mp-skills validate全部通过 - 开发者工具中
getWeather接口可正常调用 - 天气数据通过
weather-card组件正确渲染 - 缺少
location参数时返回错误提示
一个 SKILL 包含哪些文件
miniprogram/skills/weather-skill/
├── SKILL.md # 业务说明 — AI 引擎判断触发时机
├── mcp.json # 接口声明 — 定义参数和返回值格式
├── index.js # 入口文件 — 注册所有原子接口
├── apis/ # 原子接口实现目录
│ └── getWeather.js
└── components/ # 原子组件目录
└── weather-card/
├── index.js
├── index.json
├── index.wxml
└── index.wxss
常见问题
skill.registerAPI 报错 name 不匹配
- 检查
mcp.json中apis[].name是否与代码中的名称一致
原子接口调用了但不返回结果
- 检查
api函数是否module.exports导出 - 检查
index.js中require路径是否正确
组件不显示数据
- 检查
mcp.json中componentPath是否指向正确的组件路径 - 检查组件
index.js中structuredContent字段名是否与原子接口返回的一致