更新记录

1.2.1(2026-04-25)

支持各大小程序 支持http sse模式和websocket模式

v1.0.0(2026-04-21)

  • 支持 iOS 与 Android 平台
  • 提供 openAIStream,用于 OpenAI 兼容聊天流式请求
  • 提供 streamRequest,用于通用 SSE 流式请求
  • 支持 abortStreamabortRequest 取消请求
  • 已适配 DashScope 兼容 OpenAI 接口的流式调用方式
  • 提供完整 README、TypeScript 类型说明与示例

平台兼容性

uni-app(3.8.0)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
× × × 5.0 15 ×
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
× - × ×

uni-app x(3.8.0)

Chrome Safari Android iOS 鸿蒙 微信小程序
× × 5.0 15 × ×

vm-openai

vm-openai 是一个 uni-app / uni-app x 插件,用于发起 OpenAI 兼容流式 AI 请求。

核心说明:vm-openai 同时支持原生 HTTP chunk/SSE 和 WebSocket 两种流式模式。业务侧统一调用同一套 API,插件内部会根据 http(s)://ws(s):// 协议自动选择对应传输实现,并对不同小程序平台和 App 端的差异做兼容处理。

当前提供两个 API:

  • openAIStream 面向 OpenAI 兼容聊天接口,直接返回增量文本
  • streamRequest 面向通用 SSE 接口,原样返回 event / id / data

如果你希望直接复用聊天界面,可以搭配 vm-openai-ui 一起使用。

流式协议支持

平台 HTTP chunk/SSE WebSocket 协议选择
App iOS / Android 支持 支持 http(s)://ws(s):// 自动选择
微信小程序 支持 支持 http(s)://ws(s):// 自动选择
百度智能小程序 支持 支持 http(s)://ws(s):// 自动选择
抖音小程序 不支持 支持 baseURL / url 需要传 ws://wss://
支付宝小程序 不支持 支持 baseURL / url 需要传 ws://wss://
小红书小程序 不支持 支持 baseURL / url 需要传 ws://wss://
京东小程序 不支持 支持 baseURL / url 需要传 ws://wss://
飞书小程序 不支持 支持 baseURL / url 需要传 ws://wss://
快手小程序 不支持 支持 baseURL / url 需要传 ws://wss://
QQ 小程序 不支持 支持 baseURL / url 需要传 ws://wss://

如果使用 WebSocket 流式输出,可以参考服务端转接器项目 vm-openai-ws-proxy。该项目用于接收 vm-openai 的 WebSocket 请求,在服务端调用 OpenAI 兼容 /chat/completions,再把 SSE 流式响应转发回 WebSocket,同时避免在小程序端暴露真实 API Key。

导入方式

业务侧统一从插件根入口导入。插件内部会按平台条件编译分发到 App UTS 或对应小程序适配层:

import {
  openAIStream,
  abortStream,
  streamRequest,
  abortRequest
} from '@/uni_modules/vm-openai'

TypeScript 导入方式:

import {
  openAIStream,
  abortStream,
  streamRequest,
  abortRequest
} from '@/uni_modules/vm-openai'
import type {
  VMOpenAIChatMessage,
  VMOpenAISSECompleteEvent,
  VMOpenAISSEError,
  VMOpenAISSEEvent,
  VMOpenAIStreamChatOptions,
  VMOpenAIStreamChunkEvent,
  VMOpenAIStreamCompleteEvent,
  VMOpenAIStreamError,
  VMOpenAISSEOptions
} from '@/uni_modules/vm-openai/utssdk/interface.uts'

API 选择

API 适合场景
openAIStream OpenAI 兼容 /chat/completions,直接消费增量文本
streamRequest 通用 SSE 请求,业务层自己处理 data

快速开始

下面示例演示一个页面里最常见的用法:发送用户输入、流式累积模型回复、支持主动取消。

import {
  openAIStream,
  abortStream
} from '@/uni_modules/vm-openai'

export default {
  data() {
    return {
      inputText: '',
      answerText: '',
      loading: false
    }
  },
  methods: {
    sendMessage() {
      const content = this.inputText.trim()
      if (!content || this.loading) {
        return
      }

      this.answerText = ''
      this.loading = true

      openAIStream({
        apiKey: 'YOUR_API_KEY',
        baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
        model: 'qwen-plus',
        messages: [
          {
            role: 'system',
            content: '你是一个有帮助的助手。'
          },
          {
            role: 'user',
            content
          }
        ],
        onChunk: (res) => {
          this.answerText += res.deltaText
        },
        onError: (err) => {
          this.loading = false
          uni.showToast({
            title: err.message || '请求失败',
            icon: 'none'
          })
        },
        onComplete: () => {
          this.loading = false
        }
      })
    },
    stopMessage() {
      abortStream()
    }
  }
}

Vue3 组合式写法:

<script setup lang="ts">
import { ref } from 'vue'
import {
  openAIStream,
  abortStream
} from '@/uni_modules/vm-openai'
import type {
  VMOpenAIChatMessage,
  VMOpenAIStreamChunkEvent,
  VMOpenAIStreamCompleteEvent,
  VMOpenAIStreamError,
  VMOpenAIStreamOpenEvent
} from '@/uni_modules/vm-openai/utssdk/interface.uts'

const inputText = ref('')
const answerText = ref('')
const loading = ref(false)

function sendMessage() {
  const content = inputText.value.trim()
  if (!content || loading.value) {
    return
  }

  const messages: VMOpenAIChatMessage[] = [
    {
      role: 'system',
      content: '你是一个有帮助的助手。'
    },
    {
      role: 'user',
      content
    }
  ]

  answerText.value = ''
  loading.value = true

  openAIStream({
    apiKey: 'YOUR_API_KEY',
    baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
    model: 'qwen-plus',
    messages,
    onOpen(res: VMOpenAIStreamOpenEvent) {
      console.log('stream opened:', res.statusCode)
    },
    onChunk(res: VMOpenAIStreamChunkEvent) {
      answerText.value += res.deltaText
    },
    onError(err: VMOpenAIStreamError) {
      loading.value = false
      uni.showToast({
        title: err.message || '请求失败',
        icon: 'none'
      })
    },
    onComplete(res: VMOpenAIStreamCompleteEvent) {
      loading.value = false
      if (res.cancelled) {
        console.log('stream cancelled')
      }
    }
  })
}

function stopMessage() {
  abortStream()
}
</script>

使用 WebSocket 转接器时,只需要把 baseURL 换成 WebSocket 地址,回调写法保持不变:

let answerText = ''

openAIStream({
  apiKey: 'dummy',
  baseURL: 'wss://your-domain.example.com/openai-stream',
  model: 'qwen-plus',
  messages: [
    { role: 'user', content: '你好' }
  ],
  onOpen(res) {
    console.log('websocket opened:', res.statusCode) // WebSocket 连接成功时通常为 101
  },
  onChunk(res) {
    answerText += res.deltaText
    console.log('delta:', res.deltaText)
    console.log('answer:', answerText)
  },
  onError(err) {
    console.log('websocket stream error:', err.statusCode, err.message, err.raw)
  },
  onComplete(res) {
    if (res.cancelled) {
      console.log('websocket stream cancelled')
      return
    }
    console.log('websocket stream complete:', answerText)
  }
})

// 需要主动停止时调用:
// abortStream()

说明:如果 API Key 已保存在 vm-openai-ws-proxy 服务端,客户端的 apiKey 可传占位值;真实 Key 不需要下发到小程序端。 WebSocket 服务端正常结束时需要返回 [DONE]{ "type": "done" },客户端才会触发 onComplete;连接失败或服务端返回 { "type": "error" } 时会触发 onError

openAIStream

最小示例:

openAIStream({
  apiKey: 'YOUR_API_KEY',
  baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
  model: 'qwen-plus',
  messages: [
    { role: 'user', content: '你是谁?' }
  ],
  onOpen(res) {
    console.log('statusCode =', res.statusCode)
  },
  onChunk(res) {
    console.log('deltaText =', res.deltaText)
  },
  onError(err) {
    console.log('error =', err.message, err.statusCode, err.raw)
  },
  onComplete(res) {
    console.log('done =', res.cancelled)
  }
})

TypeScript 示例:

const messages: VMOpenAIChatMessage[] = [
  { role: 'user', content: '你是谁?' }
]

const options: VMOpenAIStreamChatOptions = {
  apiKey: 'YOUR_API_KEY',
  baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
  model: 'qwen-plus',
  messages,
  onChunk(res: VMOpenAIStreamChunkEvent) {
    console.log(res.deltaText)
  },
  onError(err: VMOpenAIStreamError) {
    console.log(err.message)
  },
  onComplete(res: VMOpenAIStreamCompleteEvent) {
    console.log(res.cancelled)
  }
}

openAIStream(options)

入参

参数 类型 必填 说明
apiKey string API Key
model string 模型名,例如 qwen-plus
messages Array<VMOpenAIChatMessage> 对话消息数组
baseURL string 服务根地址,HTTP 协议会自动补 /chat/completions;App、微信/百度小程序传 ws:// / wss:// 时直接走 WebSocket;抖音/支付宝/小红书/京东/飞书/快手/QQ 小程序端需直接传 ws:// / wss:// 地址
url string 完整请求地址,优先级高于 baseURL
timeout number 超时时间,单位毫秒
temperature number 采样温度
top_p number Top P
max_tokens number 最大输出 token
presence_penalty number presence penalty
frequency_penalty number frequency penalty
onLog (res) => void 调试日志回调
onOpen (res) => void 连接建立成功回调
onChunk (res) => void 增量文本回调
onError (err) => void 错误回调
onComplete (res) => void 结束回调

messages 元素结构:

字段 类型 必填 说明
role string 一般为 system / user / assistant
content string 文本内容

对应 TypeScript 类型:

type VMOpenAIChatMessage = {
  role: string
  content: string
}

回调出参

onLog(res)

字段 类型 说明
message string 调试日志文本

onOpen(res)

字段 类型 说明
statusCode number HTTP 状态码;WebSocket 连接成功时为 101

onChunk(res)

字段 类型 说明
event string SSE 事件名,可能为空
raw string 当前 chunk 的原始 data
deltaText string 当前新增文本

onError(err)

字段 类型 说明
statusCode number HTTP 状态码;WebSocket 连接失败前可能为 0
message string 错误信息
raw string 原始错误内容

onComplete(res)

字段 类型 说明
statusCode number HTTP 状态码;WebSocket 连接成功时为 101
cancelled boolean 是否主动取消
text string 当前实现不建议作为主输出使用

说明:

  • 如果传 HTTP baseURL,插件会自动补 /chat/completions
  • App 支持 http(s):// 原生 chunk/SSE 和 ws(s):// WebSocket,会按协议自动分发;不支持的协议会触发 onError
  • 微信小程序支持 http(s):// 原生 chunk/SSE 和 ws(s):// WebSocket,会按协议自动分发;不支持的协议会触发 onError
  • 百度小程序支持 http(s):// 原生 chunk/SSE 和 ws(s):// WebSocket,会按协议自动分发;不支持的协议会触发 onError
  • 抖音/支付宝/小红书/京东/飞书/快手/QQ 小程序端只支持 WebSocket 流式输出,baseURL 需要直接传 ws:// / wss:// 地址,不会自动转换协议
  • 完整文本建议业务侧在 onChunk 中自行累积

streamRequest

最小示例:

streamRequest({
  url: 'https://example.com/sse',
  method: 'POST',
  headers: {
    Authorization: 'Bearer xxx',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    stream: true
  }),
  onOpen(res) {
    console.log('statusCode =', res.statusCode)
  },
  onEvent(res) {
    console.log('event =', res.event)
    console.log('id =', res.id)
    console.log('data =', res.data)
  },
  onError(err) {
    console.log('error =', err.message, err.statusCode, err.raw)
  },
  onComplete(res) {
    console.log('done =', res.cancelled)
  }
})

TypeScript 示例:

const options: VMOpenAISSEOptions = {
  url: 'https://example.com/sse',
  method: 'POST',
  headers: {
    Authorization: 'Bearer xxx',
    'Content-Type': 'application/json'
  } as UTSJSONObject,
  body: JSON.stringify({ stream: true }),
  onEvent(res: VMOpenAISSEEvent) {
    console.log(res.event, res.id, res.data)
  },
  onError(err: VMOpenAISSEError) {
    console.log(err.message)
  },
  onComplete(res: VMOpenAISSECompleteEvent) {
    console.log(res.cancelled)
  }
}

streamRequest(options)

OpenAI 兼容 SSE 解析示例:

let answerText = ''

streamRequest({
  url: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    model: 'qwen-plus',
    stream: true,
    messages: [
      { role: 'user', content: '写一个一句话笑话' }
    ]
  }),
  onEvent(res) {
    if (!res.data || res.data === '[DONE]') {
      return
    }

    try {
      const payload = JSON.parse(res.data)
      const deltaText = payload.choices &&
        payload.choices[0] &&
        payload.choices[0].delta &&
        payload.choices[0].delta.content

      if (deltaText) {
        answerText += deltaText
        console.log(answerText)
      }
    } catch (error) {
      console.log('SSE data parse failed:', res.data)
    }
  },
  onError(err) {
    console.log('streamRequest error:', err.message)
  },
  onComplete() {
    console.log('streamRequest complete:', answerText)
  }
})

入参

参数 类型 必填 说明
url string 请求地址;App、微信/百度小程序支持 http(s)://ws(s):// 自动分发;抖音/支付宝/小红书/京东/飞书/快手/QQ 小程序端需直接传 ws:// / wss:// 地址
method string 请求方法,默认 GET
headers object 请求头
body string 请求体字符串
timeout number 超时时间,单位毫秒
onLog (res) => void 调试日志回调
onOpen (res) => void 连接建立成功回调
onEvent (res) => void SSE 事件回调
onError (err) => void 错误回调
onComplete (res) => void 结束回调

回调出参

onLog(res)

字段 类型 说明
message string 调试日志文本

onOpen(res)

字段 类型 说明
statusCode number HTTP 状态码;WebSocket 连接成功时为 101

onEvent(res)

字段 类型 说明
event string SSE 事件名
id string SSE 事件 ID
data string SSE 事件数据

onError(err)

字段 类型 说明
statusCode number HTTP 状态码;WebSocket 连接失败前可能为 0
message string 错误信息
raw string 原始错误内容

onComplete(res)

字段 类型 说明
statusCode number HTTP 状态码;WebSocket 连接成功时为 101
cancelled boolean 是否主动取消

说明:

  • streamRequest 不会帮你做 OpenAI JSON 解码
  • 如果服务端返回的是 OpenAI 兼容 SSE,请在业务层自己解析 res.data
  • App 会根据 streamRequest.url 协议选择 HTTP SSE 或 WebSocket,不支持的协议会触发 onError
  • 微信小程序会根据 streamRequest.url 协议选择 request 或 WebSocket,不支持的协议会触发 onError
  • 百度小程序会根据 streamRequest.url 协议选择 request 或 WebSocket,不支持的协议会触发 onError
  • 抖音/支付宝/小红书/京东/飞书/快手/QQ 小程序端只支持 WebSocket,streamRequest.url 需要直接传 ws:// / wss:// 地址

取消请求

API 返回值 说明
abortStream() void 取消当前 openAIStream 请求
abortRequest() void 取消当前 streamRequest 请求

取消后,onComplete 会回调:

onComplete(res) {
  if (res.cancelled) {
    console.log('request cancelled')
  }
}

DashScope / qwen-plus 示例

openAIStream({
  apiKey: 'YOUR_DASHSCOPE_API_KEY',
  baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
  model: 'qwen-plus',
  messages: [
    { role: 'user', content: '你好' }
  ],
  onChunk(res) {
    console.log(res.deltaText)
  }
})

建议

场景 建议
OpenAI 兼容聊天接口 优先使用 openAIStream
通用 SSE 接口 使用 streamRequest
生产环境 不要直接暴露长期有效 API Key

隐私、权限声明

1. 本插件需要申请的系统权限列表:

网络请求、WebSocket 连接

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率: