# 接入方式

小程序 AI 开发模式(以下简称此模式)提供了一套智能化的运行环境和开发框架,开发者可根据自身业务要求,参考本文档,将小程序的功能抽象为原子接口和原子组件,并封装成 SKILL,供小程序 AI 调用。

# 一、全局提示词

全局提示词可以用来整体说明此模式下提供的服务范围,提供相关的背景知识、行为逻辑和回答风格,引导小程序 AI 生成猜你想问的内容等等。

另一方面,一个小程序可能存在多个 SKILLs,多个 SKILLs 的关联关系及相关介绍也可以在全局提示词上说明,以帮助模型选择合适的 SKILL 和原子接口来为用户提供服务。

通常,全局提示词是一个名为 AGENTS.md 的文件,在小程序 AI 中,你可以这样指定它的路径:

// app.json
{
  "agent": {
    "skills": [],
    "instruction": "path/to/AGENTS.md",
  }
}

instruction 字段目前非必填,文件大小为最大 10000 字节。

# 二、SKILL 声明

一个小程序可声明多个 SKILLsSKILL 必须封装在一个独立分包里,而一个独立分包可以放置多个 SKILLs,且需要全局开启按需注入 lazyCodeLoading

app.json 中增加 agent 字段,声明 SKILL 列表(agent.skills 字段),每个 SKILL 的字段说明如下:

字段 必填 说明
name 名称
description SKILL 简要说明
path SKILL 分包路径(绝对路径)
// app.json
{
  "lazyCodeLoading": "requiredComponents",
  "subPackages": [
    {
      "root": "path/to/pkg",
      "independent": true,
      "pages": []
    }
  ],
  "agent": {
    "skills": [
      {
        "name": "weather",
        "description": "查询天气业务",
        "path": "path/to/pkg/weather-skill"
      },
      {
        "name": "shopping",
        "description": "购物业务",
        "path": "path/to/pkg/shopping-skill"
      }
    ]
  }
}

目前一个小程序最多可以声明 30 个 SKILL。

# 三、SKILL 封装

SKILL 包含业务说明(SKILL.md)、模型可调用能力的声明(mcp.json)、原子接口(apis)与原子组件(components)的实现,其目录结构如下:

文件 说明 固定目录结构
SKILL.md SKILL 的详细说明,只支持单文件,不支持引用其他 md 文件 是,最大长度 16000 字节
index.js 注册当前 SKILL 所涉及的所有原子接口
mcp.json 模型可调用能力的声明(当前 SKILL 所涉及的所有原子接口声明) 是,最大长度 24000 字节
components/ 当前 SKILL 所涉及的原子组件代码实现的所在目录 否,仅作为建议
apis/ 当前 SKILL 所涉及的原子接口代码实现的所在目录 否,仅作为建议

目前计算 mcp.json 的长度时,会除去所有的 outputSchema 字段及空格、换行符后再计算。

|-- path/to/pkg/weather-skill
| |-- SKILL.md
| |-- mcp.json
| |-- components/
| |-- apis/
| |-- index.js

# 3.1 模型可调用能力的声明

mcp.json 包含所有原子接口的 Schema 声明,其中 componentPath 表示该原子接口返回的结果可由原子组件展示卡片。inputSchemaoutputSchema 需遵循 JSON Schema 规范。

属性 是否必填 说明
name 标识符,跟 index.js 中导出的原子接口函数名一致
description 原子接口的功能描述
inputSchema 原子接口的入参,需为对象格式
outputSchema 建议填 原子接口返回的 structuredContent对应的 schema
_meta 可指定渲染的原子组件,componentPath 相对于 SKILL 目录,如 { "ui": { "componentPath": "path/to/comp" } }

下面是只有一个 getWeather 的原子接口的声明示例:

{
  "apis": [
    {
      "name": "getWeather",
      "description": "查询当前位置或某地的未来一段时间的天气",
      "_meta": {
        "ui": {
          "componentPath": "path/to/comp"
        }
      },
      "inputSchema": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "要查询天气的地点,如城市名、行政区名或经纬度。不传则默认取当前位置",
          },
          "days": {
            "type": "number",
            "description": "预报天数,范围1-15,默认7",
          }
        },
        "required": [
          "days"
        ]
      },
      "outputSchema": {}
    }
  ]
}

# 处理图片/文件内容

小程序 AI 的输入框支持了多模态上传,用户可以发送图片/文件给小程序 AI。如果需要处理用户上传的图片/文件,在原子接口的声明中,在 inputSchema 定义的字段中填写 "format": "image" | "file"`,小程序 AI 会调用相应的原子接口,并将接收到的图片/文件传给对应字段。

{
  "name": "EditPhoto",
  "description": "帮用户 P 图",
  "inputSchema": {
    "type": "object",
    "properties": {
      "imagePath": {
        "type": "string",
        "description": "本地图片路径",
        "format": "image"
      },
      "query": { "type": "string", "description": "用户的 P 图需求" }
    },
    "required": [ "imagePath", "query" ]
  }
}

# 3.2 原子接口实现

以原子接口 getWeather 为例,其返回值说明如下:

属性 类型 必填
isError boolean 否,默认为 false
content ContentBlock[] 是,返回给 LLM 的文本内容,不超过 200 KB
structuredContent { [key: string]: unknown } 否,返回给 LLM 的结构化数据,不超过 200 KB
_meta { [key: string]: unknown } 否,对 LLM 不可见,可携带元数据,不超过 200 KB

其中 isErrorstructuredContentcontent 三个字段的数据是会作为 LLM 的上下文供 LLM 理解,而 _meta 内容对 LLM 不可见,用于携带私有数据,传递给原子组件。

isError: true 时,将不进行 GUI 卡片渲染,structuredContent 将被忽略,content 常用于异常流程的引导。

isError: false 时,且该原子接口有绑定原子组件,小程序 AI 将倾向于渲染 GUI 卡片,structuredContent 数据会传给原子组件用来渲染(便于模型理解 GUI 卡片的内容),content 用于后续流程引导。比如,如需强制出 GUI 卡片时,可在 content 里追加引导 :「接下来为用户展示 XXX UI 卡片」。

ContentBlock[] 目前只支持 TextContent,其定义如下:

interface TextContent {
  type: "text";
  text: string;
}

下面是原子接口 getWeather 的代码实现示例:

async function getWeather({ location, days }) {
  // 原子接口内部实现,此处省略
  return {
    isError: false,
    content: [{ type: "text", text: "已为您查询到北京未来3天天气" }],
    structuredContent: {
      location: "北京",
      forecasts: [
        { date: "2026-04-23", dayWeather: "晴", nightWeather: "晴", tempHigh: 28, tempLow: 14, wind: "北风2级" },
        { date: "2026-04-24", dayWeather: "多云", nightWeather: "阴", tempHigh: 25, tempLow: 13, wind: "东南风3级" },
        { date: "2026-04-25", dayWeather: "小雨", nightWeather: "中雨", tempHigh: 20, tempLow: 12, wind: "东风4级" }
      ]
    },
  }
}

# 3.3 注册原子接口

path/to/pkg/weather-skill/index.js 进行注册,当小程序 AI 发起原子接口调用时,就会在注册列表中找到原子接口的函数入口并执行:

const getWeather = require('./apis/getWeather')

const skill = wx.modelContext.createSkill('/path/to/pkg/weather-skill')
// name 需与定义文件中的一致
skill.registerAPI('getWeather', getWeather)

# 中间件机制

为了方便在多个原子接口同时处理一些公共逻辑,我们提供了原子接口中间件的机制,可用于统一登录态,统一上报和错误监听等场景。

  • wx.modelContext.createSkill(skillPath: string) // 创建skill
    • skill.use(Middleware) // 注册中间件
    • skill.registerAPI(name, handler) // 注册原子接口,等同于 wx.modelContext.registerAPI('name', handler)
interface MiddlewareContext {
  name: string                  // 当前执行的原子接口名
  skillPath: string             // 当前中间件执行时所在的skill
  arguments: Record<string, any>  // 原子接口的入参
}
type Middleware = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void>

示例:

const skill = wx.modelContext.createSkill('pages/weather')
skill.registerAPI('getWeather', getWeather)
skill.use(async (ctx, next) => {
  // 1. 统一登录(仅首次)
  const token = wx.getStorageSync('token')
  if (!token) {
    const { code } = await wx.login()
    const res = await wx.request({ url: '...', data: { code } })
    wx.setStorageSync('token', res.data.token)
  }
  // 2. 上报:记录开始时间
  const start = Date.now()
  try {
    await next()  // 执行实际 handler
    // 上报成功
    reportLog({
      name: ctx.name,
      cost: Date.now() - start,
    })
  } catch (err) {
    // 3. 统一错误监听
    reportError({
      name: ctx.name,
      error: err,
      cost: Date.now() - start,
    })
    throw e
}) 

核心语义:

  • 每次原子接口调用都会执行中间件
  • next() 前原子接口调用的前置逻辑,next() 后是后置逻辑,try/catch 包裹可捕获错误
  • 整个中间件链与原子接口执行时间共享超时上限(当前为 300s)
  • 支持多个中间件:按注册顺序形成链,外层 -> 内层 -> handler -> 内层 -> 外层
  • 中间件函数为 async 类型,运行时 await 整个链路

# 3.4 原子组件实现

# 3.4.1 原子组件定义

原子组件用于承接原子接口返回的结构化数据的展示。在编码上,其与小程序的自定义组件一致,不过需要注意的是,原子组件不可声明为虚拟组件。

通过 wx.modelContext.getContext / getViewContext 获取原子组件渲染时需要的尺寸、数据等信息。

Component({
  lifetimes: {
    created() {
      const modelCtx = wx.modelContext.getContext(this);
      const { NotificationType } = wx.modelContext;
      modelCtx.on(NotificationType.Input, (data) => {
        // 原子接口的入参数据
      });
      modelCtx.on(NotificationType.Result, (data) => {
        // 原子接口的出参数据
      });

      const viewCtx = wx.modelContext.getViewContext(this);
      const { minHeight, maxHeight, width } = viewCtx.getDimensions();
      viewCtx.on(NotificationType.Overflow, (data) => {
        // data.contentHeight: 内容总高
        // data.overflowHeight: 溢出高度
        // data.maxHeihgt: 当前卡片容器的硬性阈值
        // 如果给原子组件的 root element 设置 maxHeight 和 overflow: hidden 则无法触发
      });
    }
  }
})

# 3.4.2 关联小程序页面(必须)

原子组件渲染出来的 GUI 卡片上方有个标题栏,其右上方提供进入小程序的入口,该入口需配置与原子组件关联的小程序页面,是必填项。通过此方式进入小程序的场景值为 1442 或 1443。

实现上,分为两步:

1、在 mcp.json 声明原子组件所对应的小程序页面的完整 path(固定 path):

{
  "apis": [],
  "components": [
    {
      "path": "path/to/comp",
      "relatedPage": "/path/to/related-page"
    }
  ]
}

2、在原子组件渲染时,动态设置页面的 query 参数:

Component({
  lifetimes: {
    created() {
      const modelCtx = wx.modelContext.getContext(this)
      this._viewCtx = wx.modelContext.getViewContext(this)

      modelCtx.on('result', (data) => {
        const { orderId } = data.result.structuredContent
        this.setData({ orderId })
        // 若只传 query,路径由 mcp.json 中的 components[].relatedPage 决定
        this._viewCtx.setRelatedPage({ query: `orderId=${orderId}` })
        // 若原子组件可能与多个小程序页面关联,也可传入 path 参数
        this._viewCtx.setRelatedPage({ path: '', query: `orderId=${orderId}` })
      })
    }
  },
  methods: {
    onSpecChange(e) {
      const spec = e.detail.value
      this.setData({ selectedSpec: spec })
      // 状态变化后更新 query 参数
      this._viewCtx.setRelatedPage({
        query: `orderId=${this.data.orderId}&spec=${spec}`
      })
    }
  }
})

# 3.4.3 原子组件的约束

原子组件的运行环境与原子接口隔离,处于不同的上下文中,其可调用的接口能力也与原子接口有所不同。

原子组件存在以下特性与限制:

  • 原子组件的渲染区域有限,其宽度随屏幕宽度变化,其最小高度是宽高比 4:1,最大高度是宽高比 1:1,不超出最小最大高度时,随内容自动撑高
  • 初始化时决定卡片高度,后续不可再改变高度
  • 仅支持 tap 点击、Image load、image error 事件,不支持其他交互事件
  • 默认不支持网络请求和云开发接口,需声明为「实时动态组件」
  • 默认不支持定时器接口,如 setTimeout、setInterval,需声明为「实时动态组件」
  • 不支持打开小程序接口
  • 不支持动画
  • 禁止上下滚动(即禁止使用 overflow-y)
  • 支持点击后帮用户上行一条文本消息(ModelContext.sendFollowUpMessage),效果等同于用户语音发送一条文本消息
  • 支持点击后打开半屏页面(见下节)

# 3.4.4 实时动态组件

对于需要展示实时动态内容的合理的场景,可在 mcp.json 声明需要实时动态能力(支持 wx.request、定时器等接口)

注意:此能力需要单独审核(正常提审即可),请声明使用场景,非必要场景不建议使用

{
  "apis": [],
  "components": [
    {
      "path": "path/to/comp",
      "permissions": {
        "scope.dynamic": {
          "desc": "声明使用场景"
        }
      }
    }
  ]
}

# 3.4.5 原子组件过期态

支持通过接口形式,将已经渲染出来的原子组件置为过期态,防止用户再次点击。

1、组件过期声明

mcp.jsoncomponents 字段下声明:

字段 类型 必填 说明
path string 组件路径(相对于 skill 目录)
expirable boolean 声明该组件渲染的卡片是否可被过期,默认 false
expiredText string 过期后显示的文案,默认 “服务已过期”
{
  "apis": [
    {
      "name": "searchRides",
      "description": "搜索打车方案",
      "_meta": { "ui": { "componentPath": "components/ride-card/index" } },
      "inputSchema": { ... }
    }
  ],
  "components": [
    {
      "path": "components/ride-card/index",
      "expirable": true,
      "expiredText": "店铺已关闭"
    }
  ]
}

2、组件过期接口

提供两种粒度的过期 API:

方式一:wx.modelContext.expireAllCards()

wx.modelContext.expireAllCards(): Promise<{ errMsg: string }>

可在原子接口/组件里调用。所有声明了 expirable: true 的组件(包括自身),将被标记为过期并渲染蒙层。

同时支持按 componentPath 过滤,使所有相同的 componentPath 的原子组件或最近一个特定的 componentPath 的原子组件过期:

// 过期特定组件的卡片(绝对路径)
wx.modelContext.expireAllCards({
  componentPaths: ['packageA/weather-skill/components/weather-card/index']
})

// 只过期最近一张匹配的卡片
wx.modelContext.expireAllCards({
  componentPaths: ['packageA/weather-skill/components/weather-card/index'],
  match: 'latest'
})

方式二:wx.modelContext.getViewContext(this).expirePreviousCards()

const viewCtx = wx.modelContext.getViewContext(this)
viewCtx.expirePreviousCards(): Promise<{ errMsg: string }>

只能在原子组件里调用,之前渲染过的原子组件,声明了 expirable: true 的将被标记为过期并渲染蒙层。

同时支持按 componentPath 过滤,使所有相同的 componentPath 的原子组件或最近一个特定的 componentPath 的原子组件过期:

// 过期特定组件的卡片(绝对路径)
viewCtx.expirePreviousCards({
  componentPaths: ['packageA/weather-skill/components/weather-card/index']
})

// 只过期最近一张匹配的卡片
viewCtx.expirePreviousCards({
  componentPaths: ['packageA/weather-skill/components/weather-card/index'],
  match: 'latest'
})

3、组件过期事件

原子组件被置为过期时,会收到过期事件,此时原子组件内部可以做一些必要的清理。

Component({
  lifetimes: {
    created() {
      const { NotificationType } = wx.modelContext;
      const viewCtx = wx.modelContext.getViewContext(this);

      viewCtx.on(NotificationType.Expire, (event) => {
        console.log('卡片已过期', event.expiredText)
        // 停止轮询、禁用按钮等清理逻辑
      })
    }
  }
})

# 3.5 半屏页面

半屏页面是原子组件内容的延伸,是个非必要流程,只有当需要展示更多详情信息,或者需要用户补充信息时,才使用半屏页面能力,通过点击原子组件上的某个按钮打开半屏页面。

半屏页面的环境可以认为是与小程序的环境一致,可以用已有的小程序页面来加载,但不允许任何跳转,包括所有跳出半屏的接口、页面路由接口、广告相关接口组件。

当用户需要进行下一步时,应是点击半屏里的某个按钮,触发上行一段文本消息,此时会同时关闭半屏页面回到小程序 AI 界面,小程序 AI 继续处理新的用户请求。

注:半屏打开小程序页面的场景值为 1433 或 1434

注:通过 wx.getDetailPageCloseButtonBoundingClientRect 接口适配左上角关闭按钮

# 3.5.1 半屏页面实现

在原子组件内,响应点击并打开半屏页面(原子接口内不可调用):

<view> xxx </view>
<view bind:tap="showDetail">查看未来 15 天的天气</view>
Component({
  lifetimes: {
    created() {
    }
  },
  methods: {
    showDetail() {
      const viewCtx = wx.modelContext.getViewContext(this);
      viewCtx.openDetailPage({
        url: '/packageA/pages/weather-detail?foo=bar'
      })
    }
  }
})

在半屏页面中,代用户上行一条文本消息(同时会关闭半屏页面):

注意:需要以用户第一人称的角度来发送这条消息

Page({
  onTap() {
    const ctx = wx.modelContext.getContext()
    ctx.sendFollowUpMessage({
      content: [
        {
          // 必须传
          type: 'text',
          text: '选择拿铁'
        },
        {
          // 指定下一步应该调用哪个原子接口,若不传,则由模型推理
          type: 'api/call',
          data: {
            name: 'selectGoods',
            arguments: {}
          }
        }
      ]
    })
  }
})

若半屏页面是 web-view 加载的 h5 页面,则上行文本消息的方式是:

// 前置 wx.config()
wx.ready(function () {
    WeixinJSBridge.invoke('invokeMiniProgramAPI', {
      name: 'sendFollowUpMessage',
      arg: {
        content: [
          {
            // 必须传
            type: 'text',
            text: '选择拿铁'
          },
          {
            // 指定下一步应该调用哪个原子接口,若不传,则由模型推理
            type: 'api/call',
            data: {
              name: 'selectGoods',
              arguments: {}
            }
          }
        ]
      }
    }, function (res) {})
})

# 3.5.2 半屏页面的约束

半屏页面的执行环境与小程序页面一致,但可调用的接口能力有所不同。半屏页面存在以下限制:

  • 所有跳出半屏去往公众号、视频号、其他小程序、表情、问一问、地图 App 的接口
  • 所有页面路由接口
  • 所有广告相关的接口组件

# 3.5.3 半屏页面预加载

为了给用户提供更好的体验,我们提供了预加载半屏页面的能力,若当前原子组件存在调用拉起半屏页面的逻辑,开发者可以在原子组件创建后调用 preloadDetailPage

Component({
  lifetimes: {
    attached() {
      const viewCtx = wx.modelContext.getViewContext(this);
      viewCtx.preloadDetailPage({
        url: '/packageA/pages/weather-detail?foo=bar'
      })
    }
  },
})

# 四、通过文字链拉起小程序

小程序 AI 可以在回复用户的文本消息中带上小程序短链,供用户点击打开小程序页面。

该能力需要开发者提供页面元数据,小程序 AI 将根据用户问答的上下文,在必要的时候生成小程序短链发送给用户。短链上的文本由模型根据聊天上下文、页面 name、页面 description、页面 query 来动态生成。通过此方式进入小程序的场景值为 1435 或1436。

注意:通过文字链拉起小程序为兜底策略,原则上用户流程需在小程序 AI 内闭环,核心功能使用文字链可能会影响审核或触发召回降权,建议开发者谨慎使用。

页面元数据定义

app.json 里增加 pageMetadata 字段

// app.json
{
  "agent": {
    "skills": [],
    "pageMetadata": "path/to/page-meta.json",
  }
}

page-meta.json 中定义了页面的功能和所需 query。 query 为一个标准的 object 格式 JSON-Schema。

字段 必填 功能
path 页面路径,可带固定的 query 信息
name 页面标题
description 页面的功能描述
query 页面 query
// page-meta.json
{
  "pages": [
    {
      "path": "pages/home/home",
      "name": "首页",
      "description": "展示最新的内容和推荐"
    },
    {
      "path": "pages/detail/detail",
      "name": "商品详情",
      "description": "展示特定商品的信息",
      "query": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "商品的唯一标识符"
          }
        },
        "required": [
          "id"
        ]
      }
    }
  ]
}

目前 page-meta.json 的最大长度为 8000 字节。

# 五、小程序与小程序 AI 交互

我们提供了一些能力,方便在小程序环境下能与小程序 AI 进行交互

# 1、打开小程序 AI 界面

现有的小程序页面内可通过 wx.openAgent 接口打开小程序 AI 界面,支持以下参数:

参数 类型 说明
followUpMessage string 自动帮用户上行一条文本消息(支持中)
context string 可携带当前页面上下文,便于模型理解以响应用户询问

当用户从胶囊入口打开小程序 AI 界面时,会触发 wx.onAgentOpen 事件,事件回调里返回数据,同样能实现帮用户上行一条文本消息以及携带当前页面上下文:

function callback() {
  return { followUpMessage, context }
}
wx.onAgentOpen(callback)
wx.offAgentOpen(callback)

同时,为了更好的兼容,我们提供了 wx.checkIsSupportAgent() 接口,判断当前设备/用户支持打开小程序 AI 界面,再调用 wx.openAgent()

wx.checkIsSupportAgent({
  success(res) {
    if (res.isSupport) wx.openAgent()
  }
})

# 2、返回小程序 AI 界面

当用户通过「文字链」或者「卡片右上角的关联页面入口」进入小程序后,小程序可通过 wx.navigateBackAgent 返回小程序 AI 界面,可携带数据给到模型,让小程序 AI 继续完成用户的意图。该接口支持以下参数:

参数 类型 说明
followUpMessage object 自动帮用户上行一条文本消息,可携带 api/call
context string 可携带当前页面上下文,便于模型理解以响应用户询问
wx.navigateBackAgent({
  followUpMessage: {
    content: [
      {
        // 必须传
        type: 'text',
        text: '选择拿铁'
      },
      {
        // 指定下一步应该调用哪个原子接口,若不传,则由模型推理
        type: 'api/call',
        data: {
          name: 'selectGoods',
          arguments: {} // 长度限制 1000 个字符
        }
      }
    ]
  },
  context: '',
})