ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

微信小程序智能机器人开发:接口选型、上下文管理与流式对话

微信小程序智能机器人开发:接口选型、上下文管理与流式对话 简介一个基于微信小程序的智能机器人完整源码包面向小程序初学者、前端开发者及对对话交互感兴趣的编程爱好者用来快速搭建具备基础问答能力的聊天机器人界面与逻辑。资源共包含19个文件压缩包约15KB其中js负责页面交互与机器人对话逻辑wxml和wxss搭建页面结构与样式json用于页面配置另有png和jpg图片素材完善界面视觉整体结构紧凑适合直接导入微信开发者工具运行调试。已有1819人学习下载可用于课程设计、毕业设计或作为二次开发的基础模板。通过阅读源码能理解小程序项目的基本目录组织、页面数据绑定、事件处理及utils工具模块的用法并在此基础上扩展自然语言处理或接入第三方智能对话API实现更丰富的功能。1. 智能机器人小程序先别急着找现成源码想清楚“智能”从哪来打开微信搜“智能机器人”一半是固定关键词回复的套壳另一半是对话到一半就断片的半成品。真正可用的微信小程序智能机器人重量从来不在那几十 KB 的页面源码里而在三件事大模型接口怎么接、对话上下文怎么管、上线前的内容安全怎么过。第一件决定机器人聪明不聪明第二件决定聊不聊得下去第三件决定能不能过审、会不会翻车。下面按这个顺序推进先做接口选型再给出一套可复现的对话页面和云函数代码然后处理上下文裁剪和线上参数最后落到流式回答。适合做毕业设计、接外包或快速验证产品 demo 的人拿到源码能两小时内跑通已经是老手的也能从参数和排错细节里找到可以迁移的东西。2. 微信小程序智能机器人的接口选型云开发还是自建后端2.1 三种接入路径的取舍小程序里调大模型常见做法就是三条路微信云开发云函数、自建后端、第三方聚合平台。判断标准就一条你手上有没有一台长期在跑的服务器。没有就老老实实选云开发——微信开发者工具里点“云开发”开通环境request 合法域名、https 证书、备案全都替你省了一份源码拿来能直接跑通常默认走的就是这条路。有服务器且要接私有知识库、要做 prompt 深度调优就自建后端前端照样是 wx.request只是多一层域名配置的活。聚合平台适合只交页面 demo、不关心数据落地的场景但真要发版token 成本和链路可控性都是隐患。对比项云开发云函数自建后端聚合 SaaS合法域名配置不需要request/socket 都要配不需要用户身份getWXContext() 直接拿 OPENIDcode2session 换 openid看平台能力冷启动延迟有通常百毫秒级无看部署无prompt 可控性中高低典型场景课设、demo、轻客服企业应用、知识库问答展示页面选型上我一般会多问一句这个小程序要不要发布上线。只要提交审核就绕不开内容安全校验云开发在这个环节可以把 msgSecCheck 和云函数放同一套环境里调比自建后端少一次跨域和 token 交换。这也是推荐从云开发起步的第二个原因——不是它性能最好而是它把微信生态内的工程杂事收口了。2.2 云开发的初始化与第一个可运行的云函数开通云开发后在项目里建 cloudfunctions/chat 目录右键“创建并部署云端安装依赖”。先写 ai.js统一封装对外的模型请求const axios require(axios) const API_URL process.env.AI_API_URL const API_KEY process.env.AI_API_KEY // 兼容 /chat/completions 协议的模型接口都能过这个函数 async function chat(messages, options {}) { const { data } await axios.post( API_URL /chat/completions, { model: options.model || glm-4-flash, messages, temperature: options.temperature ?? 0.7, max_tokens: options.max_tokens ?? 1024, stream: false }, { headers: { Authorization: Bearer ${API_KEY} }, timeout: 28000 } ) return data.choices[0].message.content } module.exports { chat }这段代码说明三件事。第一model 默认值写成你实际开通的那个glm-4-flash 这类轻量模型在微信小程序场景里响应快、成本低做客服问答足够。第二API_URL 和 API_KEY 放进云开发控制台的“环境变量”不要写死在源码里源码只要在 GitHub 上公开过key 就等于丢了放在环境变量里还能一键轮换。第三timeout 设 28 秒是因为云函数默认执行超时只有 3 秒后面要手动调大具体看第 5 章。然后是云函数入口 index.jsconst cloud require(wx-server-sdk) const { chat } require(./ai) cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) exports.main async (event) { const { message } event const { OPENID } cloud.getWXContext() // 免登录直接拿用户身份 const reply await chat([ { role: system, content: 你是微信小程序里的智能助手回答不超过 120 字 }, { role: user, content: message } ]) return { reply } }cloud.getWXContext() 是云开发对开发者最友好的设计前端完全不用传 openid用户换了设备、切了微信号服务端拿到的身份都不会串。system prompt 在这里直接写死方便快速跑通做到第 4 章的会话管理时再把这一层挪到消息数组里动态拼。2.3 自建后端时的域名和登录态参数如果最终走自建后端前端几个参数要一次配对。小程序后台“开发管理-开发设置-服务器域名”里request 合法域名必须是备案过的 https 域名且不能带路径和端口上线后改域名要走配置生效流程通常要 5 分钟左右调试期经常有人在这里白等半小时。用户识别走 code2sessionwx.login() 拿到 code后端用 code 换 openid 和 session_key再把自定义 token 下发前端后续 wx.request 的 header 带 Authorization 即可。参数上有一个容易漏的code 是一次性的5 分钟内有效且一个 code 只能换一次 session重复换会直接报 40029。用 debugger 看不出来要用 charles 这类抓包工具看实际响应 code才能确认是不是这个问题。3. 从输入框到气泡微信小程序对话页面的可运行实现3.1 页面结构与消息流的滚动定位对话页就两个区域上部的 scroll-view 消息列表下部的输入栏。新手最容易踩的坑是列表不会自动滚到底部新消息被挤到屏幕外。解法是用 scroll-into-view 配合一个恒定存在的尾部锚点scroll-view classmsg-list scroll-y scroll-into-view{{toView}} view wx:for{{messages}} wx:keyindex classmsg {{item.role}} view classbubble{{item.content}}/view /view view idbottom-anchor/view /scroll-view每次 setData 完消息数组后把 toView 设成 bottom-anchorscroll-view 就会把锚点滚进可视区。注意 scroll-into-view 只有滚动内容超过一屏时才有效果所以锚点 id 要放在最后一个消息的外面而不是挂在最后一条消息上否则内容不满一屏时会因为找不到锚点而报渲染层告警。3.2 前端调用云函数的完整逻辑页面里最核心的发送逻辑长这样Page({ data: { messages: [], inputText: , loading: false, sessionId: , toView: }, onLoad() { if (!wx.cloud) { wx.showToast({ title: 基础库版本过低, icon: none }) return } wx.cloud.init({ env: bot-demo-1a2b3c }) // 换成你自己的环境 ID this.setData({ sessionId: Date.now().toString(36) }) }, onInput(e) { this.setData({ inputText: e.detail.value }) }, async sendMessage() { const text this.data.inputText.trim() if (!text || this.data.loading) return const next this.data.messages.concat({ role: user, content: text }) this.setData({ messages: next, inputText: , loading: true, toView: bottom-anchor }) try { const res await wx.cloud.callFunction({ name: chat, data: { message: text, sessionId: this.data.sessionId } }) const reply res.result.reply this.setData({ messages: this.data.messages.concat({ role: assistant, content: reply }), toView: bottom-anchor }) } catch (e) { this.setData({ messages: this.data.messages.concat({ role: system, content: 服务繁忙请稍后再试 }), toView: bottom-anchor }) } finally { this.setData({ loading: false }) } } })逻辑上有几个细节值得说。loading 是双保险button 的 disabled 绑定它函数开头也判一次防止用户连点发送导致消息乱序。错误时往消息流里插一条 role 为 system 的占位气泡而不是 wx.showToast因为 toast 一闪而过用户看到的是“发了消息没回应”。sessionId 用时间戳生成只用于后面对话轮次的隔离真正的身份维度是云函数里的 OPENID服务端拼。3.3 uniapp 工程怎么复用这套代码如果不用原生微信小程序而是用 uniapp 在 HBuilderX 里开发这套逻辑只改两处 APIwx.cloud.callFunction 换成 uniCloud 的对应调用页面路径和 tabBar 配置走 manifest.json 的小程序面板。需要注意的是uniCloud 环境和微信云开发是两个体系云函数代码不能直接复制但页面层的消息渲染、滚动锚点、loading 状态管理是 100% 复用。碰到小程序端调用云函数报 env not found先查两件事init 里的 env 是不是云开发控制台首页那个环境 ID云函数是否部署到了同一个环境。4. 机器人的上下文与记忆控制成本的关键参数全在这4.1 无状态接口与“记性”的实现大模型的对话接口本身是无状态的你发一条它回一条它不知道上一条说了什么。所谓智能机器人能记住前面聊了什么全靠前端把历史消息重新塞回请求。这是源码里最容易被忽略的部分很多模板代码只传当前 message导致聊到第三句就开始答非所问用户体感是“这机器人很傻”。正确做法是在云函数里维护一个 history 数组结构就是 OpenAI 兼容协议的 messages 格式system 在最前user 和 assistant 交替排列。前端每次发消息时把整个 messages 数组传给云函数而不是只传 text服务端透传给模型exports.main async (event) { const { messages } event const history messages.slice(-10) // 只取最近 10 条防止超长 const reply await chat(history) return { reply } }slice(-10) 是最简单的保险但它是按条数截不是按 token 截。如果某条消息特别长10 条照样能撑爆上下文窗口所以还要有下一层的预算控制。4.2 滑动窗口与 token 预算messages 不能无限累积原因有两个模型上下文窗口有限超出会被截断计费按 token 算历史越长每轮越贵。常用做法是滑动窗口加 token 预算双层控制const MAX_TOKENS 4000 function estimateTokens(text) { // 中文粗略估算1 个汉字约 0.6 token英文 1 词约 1.3 token const chinese (text.match(/[\u4e00-\u9fa5]/g) || []).length const other text.length - chinese return Math.ceil(chinese * 0.6 other * 0.25) } function buildContext(messages) { const budget [] let total 0 for (let i messages.length - 1; i 0; i--) { const cost estimateTokens(messages[i].content) if (total cost MAX_TOKENS) break budget.unshift(messages[i]) total cost } return budget }从最新一条往前累加直到预算上限。这样长对话里最早的几句会被淘汰但最近五轮的关键信息都保留。参数 MAX_TOKENS 是保留历史的预算不是模型输出的上限两者别混。模型输出上限由 max_tokens 控制一般客服场景设 300 到 600 就够设太大既贵又容易跑题。4.3 记忆参数表与云端持久化参数推荐值作用调参方向temperature0.6~0.8回答随机性客服求稳调 0.3闲聊调 0.9max_tokens300~600单次回复长度需要写长文再放大MAX_TOKENS历史3000~4000记忆保留量越大越准但越贵历史条数上限10~20 条防止消息无限增长结合 MAX_TOKENS 一起调要不要把会话持久化到数据库取决于场景。临时对话直接放内存就行云函数实例销毁即丢但要做“用户隔天回来还能续聊”就得在云函数里把 messages 存到云数据库const db cloud.database() async function loadHistory(openid, sessionId) { const res await db.collection(sessions) .where({ openid, sessionId }) .orderBy(updatedAt, desc) .limit(1) .get() return res.data[0]?.messages || [] }这里有一个性能点要注意每轮对话都全量读写 messages 字段数据长到 50 条以上时一次 get 可能 300ms而且云数据库按读次数计费。常见做法是只保存最近 20 条或者把完整记忆丢到对象存储里数据库只存索引。个人项目用前一种就够别为不存在的性能问题过度设计。5. 上线前必改的配置与 5 个高频坑5.1 云函数超时、内存与并发云函数默认执行超时是 3 秒但大模型接口普遍 3~30 秒不改必然白屏。打开云开发控制台找到 chat 云函数在配置页把超时时间从 3 改成 30内存从 256 改成 512。改完立刻生效不用重新部署代码——改的是运行时配置部署的是代码包两者是两回事很多人重新部署了代码却没动配置超时依旧。如果走自建后端则对应调整 nginx 的 proxy_read_timeout前端 wx.request 默认超时 60 秒瓶颈通常在代理层而不是客户端。5.2 内容安全msgSecCheck 必须开小程序审核对对话类应用的内容安全抓得很紧。云环境里可以直接调微信的内容安全能力在云函数里加一段校验AI 的回复在返回前端前也过一遍async function securityCheck(content, openid) { try { const res await cloud.openapi.security.msgSecCheck({ version: 2, scene: 2, openid, content }) return res.result.suggest pass } catch (e) { return false } }这段代码需要在云开发控制台“更多-开放接口”里开通 security.msgSecCheck 权限否则会报 permissions error 或配额超限。scene 参数按场景填0 是资料1 是评论2 是论坛发帖对话类填 2 最稳。校验不通过时不要返回原始内容统一回一句“内容涉及敏感信息请换个说法”。另外注意 msgSecCheck 单次 content 上限是 2500 字节超长要截断后分两段校验两条都过才算过。5.3 源码安全反编译与密钥泄露发布过的小程序资源包都可以从 CDN 拉下来再反编译一键反编译类工具不少wxml 和 js 基本能还原大半。这不是说代码一定会被偷走而是说前端包里不能放任何密钥、内部接口地址和业务规则。常做的三层防护调用密钥全部放云函数环境变量敏感逻辑写在云函数侧前端只做展示正式环境的接口地址不写死在页面里从云函数返回或用配置中心下发。反编译只能拿到壳拿不到云函数这是云开发架构在小程序安全上一个很实际的收益。真要防篡改微信公众平台后台可以开“小程序代码上传保护”把上传阶段的代码混淆打开但这只是加大破解成本不是根治。6. 进阶把流式回答搬进小程序体验才像真机器人6.1 为什么 wx.request 做不了原生打字机大模型的 streamtrue 返回的是 SSE而 wx.request 拿到的是完整响应体不会分段触发回调前端只能干等 5~10 秒后一次性出字。想做出“边出边显示”的效果常见做法是 WebSocket 转发或轮询。轮询实现简单但每 300ms 打一次云函数按量计费心疼我一般用 WebSocket 直连中转服务云函数负责签发 token客户端拿 token 连 socket。6.2 WebSocket 转发骨架服务端 Node.js 代码const { WebSocketServer } require(ws) const axios require(axios) const wss new WebSocketServer({ port: 3000 }) wss.on(connection, (ws) { ws.on(message, async (raw) { const { message, history } JSON.parse(raw) const resp await axios.post( process.env.AI_API_URL /chat/completions, { model: glm-4-flash, messages: [...history, { role: user, content: message }], stream: true }, { responseType: stream, headers: { Authorization: Bearer ${process.env.AI_API_KEY} } } ) // SSE 数据按行推流一个 chunk 里可能含多条 data resp.data.on(data, (chunk) { const content parseSSE(chunk.toString()) if (content) ws.send(JSON.stringify({ type: chunk, content })) }) }) }) function parseSSE(chunk) { const lines chunk.split(\n) let result for (const line of lines) { if (!line.startsWith(data:)) continue const json line.slice(5).trim() if (json [DONE]) continue try { result JSON.parse(json).choices[0].delta.content || } catch (e) { // 半包解析失败直接跳过不中断流 } } return result || null }客户端对应逻辑const socket wx.connectSocket({ url: wss://your-domain.com/chat, header: { Authorization: getApp().globalData.token } }) socket.onMessage((res) { const { type, content } JSON.parse(res.data) if (type chunk) { this.setData({ streamingReply: this.data.streamingReply content }) } })最后两个连接细节。WebSocket 用的是 socket 合法域名要在小程序后台单独配置配完同样要等生效。服务端要给 delta.content 为空的帧兜底很多模型在结束时会发一个只有 finish_reason 的片段不加判空前端末尾就会多一个 undefined 或 null 字符串。把这两个点接住流式链路才算真正闭环机器人从“等十秒吐一整段”变成“边说边出”体验上的差距是质变的。本文还有配套的精品资源点击获取
返回列表