微信小程序 AI 开发实战:4 种接入方案与 5 个踩坑记录
直连、云函数代理、长连接流式推送、端侧小模型——4 种方案的生产环境实测对比,附域名白名单、SSE 适配、密钥安全等 5 个真实踩坑记录。
去年底我们帮一个电商客户把客服小程序接入了通义千问,上线第一周就遇到两个意外:用户输入「我要退款」后 AI 洋洋洒洒回了 800 字,小程序直接白屏;另一个问题是客服账号的密钥被反编译从源码里扒了出来,当月账单多了 ¥2,400。这两个事故逼着我们系统地梳理了小程序端 AI 集成的每一种路径和每一个坑——下面是所有踩过的坑和验证过的方案。
为什么小程序端接入大模型比 Web 端麻烦得多
Web 端接 OpenAI 或通义千问无非一个 fetch 请求,配上 CORS 头就完事。小程序端有三道硬门槛:
- 域名白名单:只允许向
request 合法域名列表中的地址发请求,且必须是 HTTPS + ICP 备案域名。海外厂商的域名不在国内备案,直连会被拦截。 - 请求体上限:
wx.request单次 data 约 1MB,对长上下文场景(如丢一整个 PDF 进去)直接不够用。 - 无标准 SSE 支持:浏览器端
EventSource在小程序里不存在,流式输出必须走长连接或手动解析分块响应。
这三条决定了你不能直接把 Web 端的集成代码搬到小程序里。下面逐一拆解 4 种可行路径。
4 种方案一览
| 方案 | 延迟 | 开发复杂度 | 流式输出 | 月成本(千次调用) | 适用场景 |
|---|---|---|---|---|---|
| ① 直连模型厂商 | 低(200-800ms) | 低 | ❌ 不支持 | ¥30-80 | 内部工具 / 非流式问答 |
| ② 云函数代理中转 | 中(400-1500ms) | 中 | ✅ 需改造 | ¥50-120 | 生产环境 / 需要安全管控 |
| ③ 长连接流式推送 | 低(首字 < 500ms) | 高 | ✅ 原生支持 | ¥80-200 | ChatBot / 长文本生成 |
| ④ 端侧小模型 | 极低(本地推理) | 高 | ✅ 无需网络 | ¥0(无调用费) | 离线场景 / 敏感数据不出端 |
方案①:直连模型厂商——最简路径与隐藏成本
如果你的小程序是内部工具(比如团队自用的代码审查助手),直连是最快的路径。把目标地址加入域名白名单,然后用 wx.request 直接调:
wx.request({
url: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
method: 'POST',
header: {
'Authorization': 'Bearer sk-xxxx',
'Content-Type': 'application/json'
},
data: {
model: 'qwen-plus',
messages: [{ role: 'user', content: '你好' }]
},
success(res) {
console.log(res.data.choices[0].message.content);
}
});
但这里有三个硬伤:第一,dashscope.aliyuncs.com 确实在国内备案,但 OpenAI、Anthropic、Kimi 等厂商的服务域名不在微信白名单生态内,直连会被 request:fail 拦截。第二,密钥硬编码在源码里,小程序包体虽然经过编译混淆,但用 wxapkg 解包工具几秒就能还原明文。第三,不支持流式——用户要等完整响应返回后才能看到内容,体验很差。
结论:直连只适合内部工具 + 非流式场景 + 国内备案厂商(通义千问、文心一言、DeepSeek)。上线给外部用户的,往下看。
方案②:云函数代理中转——灵活性与可控性的平衡
这是目前我们在生产环境使用最多的模式。核心思路:小程序不直接调模型厂商,而是调自己的云函数,由云函数在后端代发请求并返回结果。架构如下:
小程序 ──wx.request──▶ 云函数(Node.js 运行环境)
│
├── 读取密钥(环境变量)
├── 调用模型厂商
├── 日志 / 用量记录
└── 返回结果给小程序
云函数端代码示例(微信云开发):
// cloud/functions/aiChat/index.js
exports.main = async (event) => {
const { messages, model = 'qwen-plus' } = event;
const apiKey = process.env.DASHSCOPE_KEY;
const response = await fetch(
'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ model, messages }),
}
);
const data = await response.json();
return { content: data.choices[0].message.content };
};
这个方案把密钥锁在云函数环境变量里,客户端接触不到。同时可以做用量审计、内容审核、请求限流。代价是增加一跳网络延迟(通常 100-300ms),以及冷启动时间(首次调用约 200-500ms)。可通过「常驻实例」消除冷启动,月费增加 ¥30-60。
配合流式改造:如果需要流式输出,云函数可以先请求模型 SSE 端点,将分块内容通过长连接推送给小程序——这就是方案③要解决的问题。
方案③:长连接流式推送——让 AI 像打字一样回复
用户对 AI Chat 的心理预期是「逐字蹦出来」而不是「等 5 秒突然刷一屏」。小程序没有 EventSource,但 wx.connectSocket 是完整可用的。整个链路需要三层配合:
- 云函数 接收请求后,以流模式调用模型厂商(带
stream: true),每收到一个 chunk 就通过长连接推送给小程序。 - 小程序端 监听
onMessage回调,把每个 delta(增量文本)拼到界面上。 - 连接管理:用户切走页面或网络波动时,需要心跳保活 + 自动重连。
// 小程序端建立长连接
const task = wx.connectSocket({
url: 'wss://your-server.com/ai-stream',
});
task.onMessage((msg) => {
const chunk = JSON.parse(msg.data);
this.appendToChat(chunk.delta);
});
task.onClose(() => {
setTimeout(() => this.reconnect(), 1000);
});
这里最大的坑是 SSE 到长连接的协议转换。模型厂商返回的是 SSE 格式(data: {"choices":[{"delta":{"content":"你"}}]}),而小程序只能消费长连接消息。需要在云函数端做一个轻量代理:读 SSE 流 → 解析每一帧 → 转成 JSON → send 推送。我们踩过一个坑:SSE 的一行可能被 TCP 拆成两个包到达,直接 split('\n') 会截断一行 JSON,导致解析失败。正确做法是用缓冲区累积后再按 \n\n 分割。
方案④:端侧小模型——离线场景的最后拼图
微信从 2024 年起在小程序里逐步开放了端侧推理能力(wx.createInferenceSession),支持运行量化后的 ONNX 模型。适合的场景很窄但很关键:
- 弱网 / 离线环境:比如野外巡检、地下车库信号盲区里的设备故障诊断助手。
- 敏感数据不出端:医疗问诊、企业内部文档问答等场景,数据不能离开用户手机。
- 极低延迟要求:拍照 OCR、实时语音转文字等轻量任务。
限制也很明显:模型体积受包体 20MB 上限约束(分包后单包 ≤ 2MB 的模型比较现实),推理速度取决于设备,中低端机型跑一个 1B 参数的模型可能需要 3-8 秒。目前更实用的做法是 端侧做前置过滤 + 云端做深度推理——比如 Qwen 端侧版先判断意图和内容安全,需要深度理解时再走方案②或③上云。
5 个真实踩坑记录
坑 1:域名白名单——「request:fail url not in domain list」
这是最多人卡住的第一关。所有网络请求的目标域名必须在「小程序后台 → 开发 → 开发设置 → 服务器域名」中配置,且必须是 HTTPS + ICP 备案。各模型厂商的域名(如 api.moonshot.cn、api.deepseek.com)虽然备案了,但需要手动添加。海外厂商的域名没有 ICP 备案,无法直接添加。
绕过方式只有一个:部署自己的代理域名(方案②),让小程序只访问你的已备案域名,后端再转发。
坑 2:流式输出的 SSE 适配——数据被截断
即使走方案②或③,服务端收到模型的 SSE 流后,数据帧边界是随机的。一个 data: {...} 行可能被 TCP 分片成两次到达。直接用 split('\n') 处理会得到半个 JSON,JSON.parse 直接抛异常。
正确处理:维护一个 buffer 字符串,每次收到新 chunk 先 append,然后按 \n\n 分割,完整帧移出处理,不完整的留在 buffer 等下一次数据到达。
坑 3:Token 安全——密钥从源码里泄露
小程序编译产物是 .wxapkg 格式,GitHub 上有多个解包工具可以把它还原成可读的 JS 和 JSON。如果你把密钥写在代码里(哪怕是 const 常量),上线后 24 小时内就可能被人扒出来。
底线:密钥绝对不能出现在小程序端代码中。至少走方案②放在云函数环境变量里。更严格的做法是用临时凭证 + 自动轮换,配合频率限制,即使泄露也能快速止损。
坑 4:内容审核合规——微信 + 模型厂商双重审核
小程序上线涉及两块内容安全:
- 微信侧:涉及 UGC(用户输入也算)的小程序必须接入微信内容安全接口(
security.msgSecCheck)。用户发一条消息,你得先调这个接口检测是否违规,通过了才能转发给 LLM。 - 模型侧:各家厂商都有自己的安全策略,会拒答涉政涉黄问题并返回特定错误码。你的小程序必须处理这些错误码,给用户一个体面的「这个问题我无法回答」而不是白屏。
实际开发中最常见的问题是:微信审核通过的内容,模型拒答了,反之亦然。需要在两套审核规则之间做一层映射,把错误码统一成用户友好的提示。
坑 5:弱网环境降级——超时与重试策略
小程序运行在移动网络下,地铁、电梯、地下车库场景的网络质量远不如桌面端。单次推理响应可能 5-30 秒,弱网下连接直接超时。
我们最后采用的策略:
- 超时分级:
wx.request设 15 秒超时;超时后自动重试一次,第二次还超时就降级为预设的本地快捷回复。 - 请求队列:弱网下不丢弃用户消息,按顺序排队,恢复网络后逐一发送。
- 离线话术:准备 10-20 条高频问题的离线答案(如「如何退款」「营业时间」),在断网时作为兜底。
常见问题
Q1:小程序能直接调 OpenAI 的服务吗?
不能直连。OpenAI 的域名没有 ICP 备案,无法加入服务器域名白名单。必须通过你自己的已备案服务器或云函数做代理转发。
Q2:流式输出必须用长连接吗?有更简单的方案吗?
如果对实时性要求不高,可以用 wx.request 启用 enableChunked(基础库 2.20.1+),在 onChunkReceived 回调中逐块处理响应。这个方案比长连接简单很多,但只支持接收分块、不支持双向通信。如果你的场景只是 AI 输出流式文本,enableChunked 通常够用。
Q3:云函数冷启动延迟有多少?
未预热的云函数首次调用约 200-500ms 冷启动延迟。高频函数可以开启「常驻实例」(付费),将冷启动降到 0。预算有限的话,可用定时触发器每 5 分钟调一次来保持热实例。
Q4:端侧小模型在小程序里实际可用吗?
微信已开放 wx.createInferenceSession,支持 ONNX 量化模型。但受限于包体 20MB 上限和移动端算力,实际能跑的是参数量 ≤ 500M 的轻量模型。适合意图识别、文本分类、简单问答,不适合复杂推理。
Q5:密钥安全除了放环境变量还有什么加强手段?
三个层次:基础层是环境变量(防源码泄露);进阶层是临时 Token + HMAC 签名——客户端拿到 5 分钟过期的临时凭证,配合时间戳和签名校验;最高层是独立密钥管理服务(如阿里云 KMS / HashiCorp Vault),云函数运行时动态拉取,用完即焚。
总结
小程序接入大模型不是「能不能」的问题——4 种方案里总有一款适合你。内部非流式工具用直连就够了;外部用户的 ChatBot 走云函数代理 + 流式推送是目前最成熟的组合;离线或强隐私场景下端侧方案虽然受限但方向明确。真正卡人的往往是那 5 个工程细节:域名白名单、SSE 分帧、密钥泄露、双重审核、弱网降级——这些在原型阶段看不出问题,上线一周后才会集中爆发。
如果你正在做小程序 AI 集成的技术选型,欢迎查看我们的案例或通过联系页面与我们讨论具体场景。
