用一杯「WeStore 拿铁」的下单流程,把小程序 AI 开发模式的原子接口、原子组件、SKILL 封装和联动闭环讲清楚。文中代码均来自官方示例,可直接对照复制。
一、这是个什么模式
微信悄悄上线了小程序 AI 开发模式(beta,内测中) 。它和我们熟悉的"在小程序里调个 AI 模型生成文本"完全不是一回事------它提供的是一整套智能化的运行环境和开发框架 :开发者把小程序的功能抽象成「原子接口」和「原子组件」,封装成 SKILL,然后小程序 AI 就能在用户对话时,自己推理该调哪个接口、渲染哪个组件,最终把任务执行完。
一句话区别:过去是"用户点按钮 → 小程序执行",现在是"用户说一句话 → AI 调接口、渲卡片、推进流程"。
官方放了一个完整示例 wechat-miniprogram/ai-mode-demo,做的就是一个虚构咖啡品牌 WeStoreCafe 的点单场景。这篇就以它为样本,把最核心的 SKILL 联动机制拆开讲。
注意:当前处于内测阶段,相关代码暂不开放提审,切勿合入正式版本提交审核。需先在「微信公众平台 - 基础功能 - AI 能力」申请「开发模式」,并用申请过的 AppID 跑 demo。
二、四个核心概念
官方框架建立在四个概念上,理解它们是看懂后面一切的前提。
小程序 MCP :向小程序 AI 暴露"可调用能力"的一套协议。它和标准 MCP 不同,针对小程序特点做了适配------开发者只要按规范提供完整的 SKILL,AI 就能正确推理并执行对应接口,无需关心协议细节。
原子接口:最小执行单元,封装单一业务功能,有标准化的输入参数和输出结构,运行在微信客户端的独立 JS 环境里。
原子组件:原子接口的可视化单元,把接口返回的结构化数据渲染成对话流里的 GUI 卡片。
SKILL :一个完整场景任务的能力封装。一个小程序可以有多个 SKILL,每个 SKILL 包含三样东西------业务说明 SKILL.md、能力声明 mcp.json、以及原子接口与原子组件的实现。
三、三方运行架构
运行时涉及三个角色,职责分得很清楚:
| 角色 | 职责 |
|---|---|
| 客户端运行时 | 微信客户端提供的独立运行环境,执行原子接口、渲染原子组件 |
| 小程序 AI 后台 | 加载 SKILL,结合用户请求做 LLM 推理,下发接口调用或渲染指令 |
| 第三方服务 | 开发者自己的服务,在原子接口实现里被调用,完成真实数据交互 |
客户端和 AI 后台之间通过小程序 MCP 通信,协议细节开发者不用管,只要把 SKILL 写完整。
这里有个必须警惕的运行环境细节 :原子接口、原子组件、实时动态原子组件分别运行在三个不同的执行上下文 里,权限集不同,且不共享全局变量。另外原子组件用的是自研卡片渲染引擎(glass-easel 框架),WXSS 支持范围和小程序的 WebView/Skyline 有差异------别拿小程序那套 CSS 习惯直接套。
四、SKILL 的目录结构
来看 demo 里 skills/drink-skill/ 的组织方式:
bash
skills/
└── drink-skill/
├── SKILL.md # 业务说明(给 AI 看的"说明书")
├── mcp.json # 原子接口能力声明(类似 tool schema)
├── index.js # 接口注册入口
├── apis/ # 原子接口实现
│ ├── getRecommendedDrinks.js
│ ├── searchDrinks.js
│ ├── selectDrink.js
│ ├── confirmSku.js
│ ├── saveAddress.js
│ ├── payOrder.js
│ └── ...
└── components/ # 原子组件(卡片)
├── recommended-drinks/
├── drink-detail-card/
├── order-confirm-card/
├── pay-success-card/
└── store-status-card/
它在 app.json 里通过 agent.skills 注册,并作为独立分包存在:
json
{
"subPackages": [
{ "root": "skills", "pages": [], "independent": true }
],
"agent": {
"skills": [
{
"name": "drink",
"description": "WeStoreCafe 点单场景:查询推荐饮品、选择规格、填写收货地址、下单支付",
"path": "skills/drink-skill"
}
],
"pageMetadata": "page-meta.json"
}
}
接口注册用 wx.modelContext.createSkill,注意 path 要和 app.json 里的 agent.skills[].path 一致:
javascript
// skills/drink-skill/index.js
const getRecommendedDrinks = require('./apis/getRecommendedDrinks.js')
const selectDrink = require('./apis/selectDrink.js')
// ...
const skill = wx.modelContext.createSkill('skills/drink-skill')
skill.registerAPI('getRecommendedDrinks', getRecommendedDrinks)
skill.registerAPI('selectDrink', selectDrink)
skill.registerAPI('confirmSku', confirmSku)
skill.registerAPI('payOrder', payOrder)
// ... 注册所有原子接口,name 需与 mcp.json 声明一致
五、重点:SKILL 联动是怎么实现的
这是整个模式最有价值的部分。所谓"联动",本质是一个三层闭环:Agent 调接口 → 接口返回数据 → 卡片渲染 → 用户点击回灌 Agent → Agent 调下一个接口。下面逐层拆。
第一层:用 SKILL.md + mcp.json 双重约束 AI
AI 不能随便调接口。mcp.json 用 schema 约束单个接口怎么调 ,SKILL.md 用自然语言约束接口之间的顺序和铁律。
mcp.json 里每个接口的 description 都写死了前置条件和严禁场景。比如推荐接口:
json
{
"name": "getRecommendedDrinks",
"description": "获取推荐饮品列表。调用前置条件:用户表达想喝饮品但未指定具体商品名(如「想喝点什么」)。【严禁场景】用户已说出具体商品名(如「来杯拿铁」)时,禁止调用本接口,应调用 searchDrinks。",
"inputSchema": { ... },
"outputSchema": { ... },
"_meta": { "ui": { "componentPath": "components/recommended-drinks/index" } }
}
SKILL.md 则用一张流程图 + "跨接口铁律"约束顺序,核心几条:
- AI 不能跳过
selectDrink直接调confirmSku------必须先有有效的drinkId; - AI 不能跳过
confirmSku直接调payOrder------必须先有 confirmed 状态的订单; payOrder未返回成功前,禁止向用户宣布"已支付成功" ;drinkId/orderId必须来自上游接口返回的原值,禁止从"那个 3 号"这类自然语言推断。
这套约束让接口调用有了顺序性和状态依赖,AI 不会乱调、不会跳步、不会编造关键 ID。
第二层:接口返回数据分三层(精髓)
每个原子接口返回的数据被刻意分成三层,这是省 token + 防上下文污染的关键设计。看 selectDrink 的实现:
javascript
// skills/drink-skill/apis/selectDrink.js
async function selectDrink({ drinkId } = {}) {
const drink = findDrink(drinkId)
if (!drink) {
// 失败分支:堵死错误退路 + 给出正确出口
return {
isError: true,
content: [{
type: 'text',
text: `未找到 drinkId=${drinkId}。禁止编造 ID 再次调用,禁止从用户语言推断。正确出口:调用 getRecommendedDrinks 或 searchDrinks 获取有效 drinkId。`
}]
}
}
return {
isError: false,
// ① content:事实陈述 + 给 AI 的下一步指令(AI 可见)
content: [{
type: 'text',
text: `已加载饮品「${drink.name}」详情。接下来展示详情卡片,引导用户点击"直接下单"。禁止 AI 主动调用 confirmSku 跳过卡片展示。`
}],
// ② structuredContent:供 AI 理解屏幕内容的结构化数据(AI 可见,不含图片)
structuredContent: {
drinkId: drink.id,
name: drink.name,
price: drink.price,
specOptions // 精简版规格
},
// ③ _meta:纯渲染数据(AI 不可见,只给组件用)
_meta: {
imageUrl: drink.imageUrl,
skuSchema: drink.skuSchema // 完整 schema
}
}
}
三层各司其职:
content:事实陈述 + 给 AI 的下一步行动指令,AI 可见。structuredContent:让 AI 理解"屏幕上现在是什么"的结构化语义数据,AI 可见,但不含图片 URL、完整 schema 等渲染细节。_meta:图片、完整 SKU schema 等纯渲染数据,AI 完全看不到,只给卡片组件用。
好处:AI 的上下文不会被一堆图片 URL 污染,既省 token 又让推理更聚焦。
第三层:sendFollowUpMessage 回灌闭环(最巧妙的一跳)
用户在卡片上点击,组件不在前端自己消化,而是把这个动作"回灌"给 AI------模拟成一条新的用户消息 + 一个接口调用指令。看推荐卡片组件:
kotlin
// skills/drink-skill/components/recommended-drinks/index.js
Component({
lifetimes: {
created() {
this._modelCtx = wx.modelContext.getContext(this)
this._viewCtx = wx.modelContext.getViewContext(this)
const { NotificationType } = wx.modelContext
// 监听接口返回,拿数据渲染
this._modelCtx.on(NotificationType.Result, (data) => {
const result = data?.result || {}
const sc = result.structuredContent || {}
const meta = result._meta || {}
// 渲染用 structuredContent + _meta(图片从 _meta 补)
const viewItems = meta.viewItems || sc.items || []
this.setData({ items: viewItems.slice(0, 3), total: sc.total })
})
}
},
methods: {
onTapItem(e) {
const item = e.currentTarget.dataset.item
// 关键:点击 → 回灌为新一轮输入,并精确带上 drinkId
this._modelCtx.sendFollowUpMessage({
content: [
{ type: 'text', text: `选择${item.name}` },
{ type: 'api/call', data: { name: 'selectDrink', arguments: { drinkId: item.drinkId } } }
]
})
}
}
})
这一跳解决了第一层埋下的难题------"禁止 AI 编造 drinkId"。用户点了"拿铁"卡片,等价于对 AI 说"选择拿铁",并直接携带了正确的 drinkId 。ID 不是 AI 猜的,是点击时精确带上的。组件初始化时通过 wx.modelContext.getContext(this) 拿上下文,监听 NotificationType.Result 接收数据渲染,点击时用 sendFollowUpMessage 把下一步发回去------一来一回,闭环成立。
联动闭环全景
把三层串起来,一次完整点单的数据流:
scss
用户说"想喝点什么"
→ AI 按 mcp.json 约束选中 getRecommendedDrinks
→ 接口返回 content(指令) + structuredContent(数据) + _meta(图片)
→ 推荐卡片渲染(读 structuredContent + _meta)
→ 用户点击某款 → sendFollowUpMessage 回灌「选择X + 调 selectDrink(drinkId)」
→ AI 调 selectDrink → 详情卡片
→ 用户点"直接下单" → 组件回灌 confirmSku
→ 订单确认卡(无地址则展示地址空态)
→ 用户点地址区 → wx.chooseAddress / 半屏填写 → saveAddress 自动续跑订单
→ 用户点"确认下单" → payOrder → 支付成功卡片
六、完整交互时序(官方运行机制)
官方文档给了一个简化的"点拿铁少糖"时序,串起三方角色,能帮你建立整体认知:
css
用户「点一杯 WeStore 拿铁,少糖」
│ 上行消息
▼
[AI 后台] 加载 SKILL → LLM 推理:调用 confirmOrder
│ 下发接口调用
▼
[客户端] 启动运行环境、下载分包 → 执行原子接口(wx.login 取身份)
│
▼
[第三方] 创建订单 → 回传订单信息
│ 回传结果
▼
[AI 后台] LLM 推理:下发渲染指令
│
▼
[客户端] 渲染原子组件,展示订单 → 用户点击「确认支付」
│ 上行消息:确认支付
▼
[AI 后台] LLM 推理:调用 payOrder
│ 下发接口调用
▼
[客户端] wx.requestPayment 拉起收银台
│
▼
[第三方] 创建预支付 → 查询支付状态 → 回传
│
▼
[AI 后台] 下发渲染指令 → [客户端] 渲染支付结果卡片
可以看到:LLM 推理在 AI 后台,接口执行和卡片渲染在客户端,真实业务数据在第三方服务。三方通过小程序 MCP 协同。
七、接入步骤
- 在「微信公众平台 - 基础功能 - AI 能力」或「微信开发者助手 - 微信AI管理」中,接入模式选「开发模式」申请开通。
- 下载安装微信开发者工具 Nightly(Electron Build 最新版) ------普通正式版不支持调试本模式。
- 把功能抽象成原子接口 + 原子组件,封装成 SKILL,在
app.json的agent.skills注册。 - 用申请了「开发模式」的 AppID 导入 demo,编译运行,通过 AI 对话界面体验。
八、踩坑与最佳实践
结合官方文档和 demo 代码,几条值得记住的要点:
接口要给"错误出口" :接口失败时别只报错,要明确告诉 AI "正确的下一步该调谁"(如上面 selectDrink 的失败分支),引导 AI 自我纠正,而不是让它瞎试。
枚举值用英文、禁止中文 label :demo 里规格值统一用 ice/hot/normal 等英文枚举,避免 AI 在中文 label 和实际值之间混淆。
绑定卡片必须出卡片 :成功返回且绑定了组件的接口,必须展示卡片,禁止把商品名、价格等以 markdown 列表纯文本展开------这是 demo 明确写进 SKILL.md 的输出形态铁律。
三个执行上下文不共享全局变量:原子接口、原子组件、实时动态组件互相隔离,需要传状态用 storage 或接口返回,别指望全局变量。
WXSS 有差异 :卡片渲染引擎只支持部分 CSS 选择器(推荐用类选择器),尺寸单位支持 rpx/px/vw 等,写卡片样式前先查官方附录的支持范围。
登录态可复用 :本模式下用户登录身份与原小程序一致,可通过 storage 共享登录凭证,也能用 wx.login/wx.getPhoneNumber 走登录流程。
写在最后
这套模式真正可复用的,不是点单业务本身,而是它的四条设计约定:
- 用 schema + 自然语言双重约束 AI ------
mcp.json管参数和前置条件,SKILL.md管顺序和铁律; - 返回数据分三层 ------
content(指令)/structuredContent(语义)/_meta(渲染),控制 AI 能看到什么; - 用
sendFollowUpMessage回灌打通"点击 → 下一步" ,让精确参数随点击传递; - 错误返回给出口,引导 AI 自我纠正。
任何想接入这套模式的业务------预约、查询、表单、交易------都可以照着这四条 + demo 的目录结构去套。剩下的,就是把你自己的业务拆成一个个"原子"。
参考资料:
- 官方能力介绍:developers.weixin.qq.com/miniprogram...
- 官方运行机制:developers.weixin.qq.com/miniprogram...
- 官方示例 demo:github.com/wechat-mini...