简介:面向微信小程序开发者和人工智能对话初学者,这份智能机器人小程序源码可帮助快速掌握页面搭建、消息交互与机器人服务对接方法。压缩包共19个文件,整体体积仅15KB,其中包含5个逻辑脚本文件、4个样式表文件、3个页面结构文件、2个配置文件及若干图片素材,分别承担页面渲染、交互逻辑、全局配置和界面展示职责。资源已有1819人学习下载,受到小程序入门开发者欢迎,适合用于个人练习、课程设计或项目起步。源码内部包含工具模块、业务页面、全局配置和图片目录,目录结构清晰完整,可帮助开发者理解机器人对话页面的数据流与组件写法。拿到后可直接对照学习界面布局与事件绑定,并根据实际机器人服务替换对应接口,快速完成二次开发与功能扩展。
1. 微信小程序智能机器人:先定链路,再写源码
微信小程序里的智能机器人,最容易被低估的不是模型接口的返回质量,而是会话怎么维持。小程序切后台再回来,页面栈随时可能被系统回收,如果机器人只靠前端内存里的数组记上下文,聊不了几句就前言不搭后语。一套能落到生产环境的"源码",应当先把消息链路定下来:用户输入、云处理、历史持久化、结果渲染,四段各司其职,再谈机器人应答逻辑本身。这篇内容面向正在做客服问答、AI 陪聊或企业内助理类小程序的人,目标是让你在没有独立服务器的情况下,用云函数加数据库把机器人挂起来,并清楚每个参数为什么这样设、出问题时先看哪个环节。
2. 微信小程序智能机器人的技术选型与消息链路设计
2.1 三种机器人实现方式:规则匹配、云函数接模型、自建服务端
微信小程序智能机器人常见有三条路线,区别主要在"回答从哪来、会话状态放哪里"。
规则匹配最简单,把常见问题写进一个 JSON 映射表,前端命中就直接返回固定文案,缺陷是只能回答预设问题,换个说法就失效。云函数接大模型 API 是当前大多数项目的主力方案,未命中的问题由模型泛化回答,会话状态写入云数据库,主要成本就是接口调用费。自建服务端再走一层 WebSocket 或 HTTP 长连接,适合需要主动推送、实时人机协同的场景,但要有服务器运维能力。
三种方式的取舍可以看这张表:
| 实现方式 | 运维成本 | 平均响应 | 上下文维持 | 适合场景 |
|---|---|---|---|---|
| 纯前端规则匹配 | 无 | < 300ms | 前端变量 | 固定 FAQ、活动答疑 |
| 云函数 + LLM API | 低 | 1~5s | 数据库会话 | 通用智能客服、陪聊、企业助理 |
| 自建服务 + WebSocket | 高 | < 500ms | 内存 / Redis | 高并发实时对话、人工接管 |
选型时我一般把"团队有没有人愿意长期盯后端"作为第一判断标准。没人盯服务器就优先云函数,因为云函数天然带鉴权、日志和扩缩容,消息积压了也容易排查。自建服务虽然响应快,没有冷启动问题,但压测、监控、白名单都要自己做,一个小程序前期往往撑不起这个成本。
2.2 用会话 ID 把无状态请求串联成上下文
云函数每次调用都是独立进程,内存里不保留任何连接,所以微信小程序智能机器人的"记忆"必须显式地存在数据库里。前端在进入聊天页时生成一个会话 ID,后续每次请求都带上它,云函数用openid + sessionId去查历史记录,才能拼出上下文。
// 前端生成会话 ID 的常见写法 const sessionId = `s_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;这段代码用时间戳加随机字符串拼一个不可预测的 ID,目的是避免不同会话之间串话。注意不要用用户昵称或自增数字作为 ID,前者有隐私风险,后者容易被遍历出别人的聊天记录。openid则由云函数通过cloud.getWXContext()自动拿到,前端不传,天然不可伪造,数据隔离从源头就成立了。
再往下是云函数里根据 sessionId 读取历史。每次取最近 10 轮就够,大模型的上下文窗口有限,把三个月前的对话全塞进去既浪费 token 又会让回复偏离当前话题。
const historyRes = await db.collection('chat_history') .where({ openid: OPENID, sessionId }) .orderBy('createdAt', 'asc') .limit(10) .get();这里按createdAt升序取 10 条,是为了拿到从旧到新的完整对话流;如果降序取,后面还要 reverse,反而多一次数组翻转。
2.3 消息链路与数据流设计
把前面几段串起来,一条完整的消息链路是:用户在小程序输入框按下发送,前端先把用户消息插入消息列表做回显,然后调用云函数;云函数校验参数后读取历史,拼装模型所需的消息数组,请求大模型接口;拿到回复后把本轮问答写进数据库,再把回答返回前端;前端把回答更新到对应气泡里。链路中任何一环慢,都会表现为"机器人不回话"。
chat_history集合里的一条记录结构如下,字段含义一并列出:
{ "_id": "auto", "openid": "oX8xxx", "sessionId": "s_1712700000000_abc", "question": "你们营业时间是几点", "answer": "早上9点到晚上9点", "createdAt": 1712700000000 }_id由数据库自动生成,createdAt用毫秒时间戳方便排序。这里把 question 和 answer 拆开存,而不是存一个对话对象数组,是为了后面做数据统计时可以直接按 question 聚合,找出问得最多的十个问题。设计数据结构时多留一个维度,比事后迁移省事得多。
3. 用云开发搭建智能机器人后端的最小源码
3.1 云函数初始化与依赖声明
微信小程序智能机器人后端,我习惯用一个云函数robot承担全部对话逻辑。它只做三件事:读历史、调模型、写记录,不掺入业务无关的代码,后续要加功能也容易拆。
云函数的package.json至少需要两个依赖:wx-server-sdk用于访问数据库和获取用户身份,axios用于发起对大模型接口的 HTTP 请求。
{ "name": "robot", "version": "1.0.0", "main": "index.js", "dependencies": { "wx-server-sdk": "~2.6.3", "axios": "^1.6.0" } }然后初始化环境:
const cloud = require('wx-server-sdk'); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); const db = cloud.database();cloud.DYNAMIC_CURRENT_ENV的意思是运行在哪个云开发环境里就用哪个环境,不必把环境 ID 硬编码进代码。这样同一个云函数在测试环境和生产环境之间切换时,只需要改部署目标,不用改任何一行源码。
3.2 云函数主逻辑:读取历史、调用模型、写回数据库
下面是index.js的完整源码。代码里已经把注释写清,部署到云开发控制台后,填入环境变量即可运行。
// cloudfunctions/robot/index.js const cloud = require('wx-server-sdk'); const axios = require('axios'); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); const db = cloud.database(); const _ = db.command; exports.main = async (event) => { const { question, sessionId } = event; const { OPENID } = cloud.getWXContext(); // 入参校验:长度控制在 500 字内,避免恶意大文本 if (!question || !sessionId || question.length > 500) { return { code: 400, msg: '参数不合法或消息超过 500 字' }; } // 1. 读最近 10 轮历史 const historyRes = await db.collection('chat_history') .where({ openid: OPENID, sessionId }) .orderBy('createdAt', 'asc') .limit(10) .get(); // 2. 组装模型消息数组 const messages = [ { role: 'system', content: '你是微信小程序里的智能机器人,回答尽量口语化、简短。' } ]; historyRes.data.forEach((item) => { messages.push({ role: 'user', content: item.question }); messages.push({ role: 'assistant', content: item.answer }); }); messages.push({ role: 'user', content: question }); // 3. 调用大模型接口 const llmRes = await axios.post( process.env.LLM_API_URL, { model: process.env.LLM_MODEL || 'llm-default', messages: messages, temperature: 0.7 }, { headers: { Authorization: `Bearer ${process.env.LLM_API_KEY}` }, timeout: 10000 } ); const answer = llmRes.data.choices[0].message.content; // 4. 写回数据库 await db.collection('chat_history').add({ data: { openid: OPENID, sessionId: sessionId, question: question, answer: answer, createdAt: Date.now() } }); return { code: 0, data: { answer: answer } }; };这段代码的逻辑顺序是固定的:先校验入参,再拉历史,调模型,最后落库。之所以把"写回历史"放在模型调用之后,是为了保证数据库里不会出现只有问题没有回答的残缺记录;如果模型接口超时抛出异常,云函数会直接报错,前端走兜底文案,不会污染历史记录。
几个参数说明一下。temperature控制回答随机性,0 到 1 之间,0.7 是通用值,客服场景可以调到 0.3 以下让回答更稳定;timeout设 10 秒,比前端wx.cloud.callFunction的默认超时稍短,模型超时会优先在云函数侧暴露,前端不会长时间卡在"正在思考"。
3.3 数据库集合设计与索引
chat_history集合的最小字段集如下表,再往后要加统计、反馈字段,也是在这个结构上扩展:
| 字段 | 类型 | 说明 |
|---|---|---|
_id | string | 数据库自动生成的记录 ID |
openid | string | 用户唯一标识,隔离数据用 |
sessionId | string | 会话标识,关联同一用户的多轮对话 |
question | string | 用户输入原文 |
answer | string | 机器人返回原文 |
createdAt | number | 毫秒时间戳 |
数据量上来后,查询性能取决于索引。打开云开发控制台的数据库面板,在"索引管理"里给chat_history新建一个组合索引:openid升序、sessionId升序、createdAt升序。这个索引能同时满足按用户隔离、按会话拉历史和按时间排序三个条件,避免集合大了以后扫全表。
3.4 环境变量与密钥管理
模型接口的地址、密钥和模型名不要写进代码,部署云函数时在控制台的"配置-环境变量"里设置即可。
LLM_API_URL=https://your-model-endpoint/v1/chat/completions LLM_API_KEY=sk-your-key LLM_MODEL=your-model-name密钥写进前端是最常见的泄露方式。微信小程序源码包本质上是可以被解包的资源,网上甚至有工具能还原出 js、wxss 和云函数目录结构,只要前端代码里出现 Key,就等于把接口额度公开给了所有人。环境变量只存在于云端,小程序端即使拿到云函数名字,也无法读出它的配置。
4. 微信小程序端对话交互与气泡渲染源码
4.1 页面结构:消息列表、输入框与滚动定位
前端源码的核心在页面结构。用scroll-view做消息容器,配合scroll-into-view绑定最后一条消息的 ID,每次发消息和收消息后页面会自动滚到底部。
<!-- pages/chat/chat.wxml --> <view class="chat-page"> <scroll-view class="message-list" scroll-y="true" scroll-into-view="{{lastMsgId}}" scroll-with-animation="true" > <view wx:for="{{messages}}" wx:key="id" id="msg_{{item.id}}" class="row {{item.role}}" bindlongpress="onCopy" >// pages/chat/chat.js Page({ data: { messages: [], inputValue: '', isLoading: false, lastMsgId: '', sessionId: '' }, onLoad() { this.setData({ sessionId: `s_${Date.now()}_${Math.random().toString(36).slice(2, 10)}` }); }, onInput(e) { this.setData({ inputValue: e.detail.value }); }, async sendMessage() { const { inputValue, sessionId, isLoading, messages } = this.data; const content = inputValue.trim(); if (!content || isLoading) return; const userMsg = { id: `u_${Date.now()}`, role: 'user', content }; const botId = `b_${Date.now()}`; this.setData({ messages: [...messages, userMsg, { id: botId, role: 'assistant', content: '正在思考…' }], inputValue: '', isLoading: true, lastMsgId: `msg_${botId}` }); try { const res = await wx.cloud.callFunction({ name: 'robot', data: { question: content, sessionId } }); const answer = res.result.data.answer; const finalList = this.data.messages.map((item) => item.id === botId ? { ...item, content: answer } : item ); this.setData({ messages: finalList, lastMsgId: `msg_${botId}` }); } catch (err) { const fallbackList = this.data.messages.map((item) => item.id === botId ? { ...item, content: '网络开小差了,请重试' } : item ); this.setData({ messages: fallbackList, lastMsgId: `msg_${botId}` }); } finally { this.setData({ isLoading: false }); } }, onCopy(e) { wx.setClipboardData({ data: e.currentTarget.dataset.content || '' }); } });isLoading是一个前端锁。用户在等待期间连续点发送,第二次进入sendMessage时会被if (!content || isLoading)挡掉,避免同时发起多个云函数请求。占位消息的好处是让用户感知到机器人已经收到消息,而不是界面毫无响应;失败分支把占位消息替换成"网络开小差了,请重试",比起直接 toast 错误码,对普通用户友好得多。
wx.setClipboardData在用户长按消息时复制原文,这是聊天类小程序的基本操作。注意它随后会弹一个"内容已复制"的提示,如果不想让默认提示出现,可以在回调里调用wx.hideToast()。
4.3 setData 性能与消息列表清理
小程序每次setData都会做一次虚拟 DOM diff,消息数组越长,渲染开销越大。微信官方对单个页面setData的数据量限制是 1MB,聊天页几十条消息占不满,但超过 100 条后滚动会出现明显卡顿,尤其是在低端安卓机上。
| 消息条数 | 实际表现 | 处理方式 |
|---|---|---|
| 30 条以内 | 流畅 | 当前方案即可 |
| 30~100 条 | 低端机开始掉帧 | 追加时截断,只保留最近 50 条 |
| 100 条以上 | 明显卡顿、白屏 | 分页加载,上滑加载历史 |
常用做法是在每次追加消息后检查messages长度,超过 50 条就把最前面的 20 条截掉,只保留最近 30 条参与渲染;更彻底的做法是把历史消息分页,用户上滑到顶部时再加载更早的记录。分页需要引入"拉取上一页"的参数,代码复杂度会上一个台阶,前期用截断法就够了。刚进入页面时如果想显示欢迎语或上次加载失败提示,可以在 onLoad 里先压入一条系统消息,占位和文案都由这条消息承载,评分和反馈按钮也挂在同一条系统消息下面。
5. 微信小程序智能机器人关键参数与异常处理
5.1 超时设置:云函数、模型接口、前端三层都要调
超时是智能机器人最常踩的坑。三层链路各自有默认超时,任一环节时间不匹配,都会出现"前端显示请求失败、后端其实已经写库成功"的双写问题。
| 配置项 | 默认值 | 建议值 | 说明 |
|---|---|---|---|
| 云函数超时时间 | 3 秒 | 20 秒 | 在云函数配置里调整,给模型响应留余量 |
| 模型接口 axios timeout | 无 | 10 秒 | 第 3 章代码里的timeout: 10000 |
| 前端 callFunction timeout | 15 秒 | 20 秒 | wx.cloud.callFunction的可选参数 |
云函数默认超时只有 3 秒,这是后端异常最常见的来源。模型响应超过 3 秒,云函数直接超时终止,前端会收到FUNCTION_TIMEOUT错误,但数据库里那条chat_history可能已经写入,因为写库发生在返回之前。处理方式有两个:一是把云函数超时调到 20 秒,让单次请求走完整流程;二是在写库前判断模型调用是否成功,失败就提前 return,不留残记录。第 3 章的代码已经用 try/catch 规避了残记录,部署后记得去控制台把超时时间也改掉。
5.2 限流与重复请求拦截
机器人接口一旦在群里扩散,短时间涌入的并发请求会同时打爆数据库和模型接口的配额。前端isLoading只能挡同一台设备的重复点击,挡不住不同用户的同时请求,所以云函数侧也要有限流。
// 云函数内加一个简单限流:同一用户 1 分钟最多 10 次 const start = Date.now() - 60 * 1000; const countRes = await db.collection('chat_history') .where({ openid: OPENID, createdAt: _.gt(start) }) .count(); if (countRes.total >= 10) { return { code: 429, msg: '消息发送太快,请稍后再试' }; }这段代码放在第 3 章的入参校验之后、读历史之前。_.gt(start)是数据库指令里的"大于",表示只统计最近 60 秒创建的记录;count()只做聚合,开销很小。如果对准确性要求更高,可以换成每 10 秒限 3 次的滑动窗口,但那是另一个量级的复杂度,初期用分钟级计数已经足够挡住大部分刷接口行为。
5.3 内容安全检测与敏感词兜底
涉及 UGC 内容的小程序必须具备内容安全能力。云函数里可以直接调用微信内容安全接口,不需要额外申请 AppSecret,因为云函数天然拿到了小程序身份。
// 云函数内对用户输入做内容安全检测 try { const checkRes = await cloud.openapi.security.msgSecCheck({ content: question, openid: OPENID }); if (checkRes.errCode !== 0) { return { code: 4400, msg: '内容包含违规信息' }; } } catch (err) { // 检测接口偶发异常时不阻断主流程,记录日志即可 }msgSecCheck检测的是用户输入,不是模型输出。模型输出仍然可能越界,建议在返回 answer 之前也套一层同样的检测;但模型输出的检测结果不应直接展示给用户,而是改成"这句话我没法回答,换个说法试试"。这样可以防止模型生成的回答触发告警,也给自己留了一层审计记录。
6. 智能机器人项目跑通到落地的三个进阶技巧
6.1 慢响应异步化:先回"排队中",再轮询结果
模型接口一旦超过 15 秒,用户几乎没有耐心等。压测中常见用户会在等待期反复点发送,触发更多请求。建议在云函数里做一个异步化改造:云函数收到请求后把任务写入chat_task集合,状态标记为pending,立即返回"收到,正在排队";小程序端每 2 秒轮询一次任务状态,返回done后再读取结果。
// 轮询用的查询条件,status 为 done 且 id 匹配 const taskRes = await db.collection('chat_task') .doc(taskId) .get(); if (taskRes.data.status === 'done') { // 用真实回答更新对话气泡 }异步化之后,云函数超时压力转移到轮询侧,用户等待体验也变成"已进入队列"。这个改造适合机器人接入复杂模型、或回答里需要拼接外部数据的场景。
6.2 高频问题加缓存:一样的问法,别再花一次 token
常见问题往往集中在几百条内。可以在chat_history里按question做聚合查询,把出现次数排名前 50 的问题导出到qa_cache集合;云函数先查缓存,命中就直接返回,没命中再走模型。命中率超过 30% 时,模型接口费用能省下三分之一以上,响应时间也会降到毫秒级。缓存记录的字段就三列:归一化后的问法、标准回答、上次命中时间。归一化规则先把标点和空格去掉,再做大小写折叠,能挡住大部分换着标点问同一件事的情况。
6.3 收集"点踩"数据,为人工接管留入口
机器人答得不好是常态,但产品能不能变好,取决于有没有数据。在消息气泡上增加"踩一下"按钮,点击后往feedback集合写一条记录,包含sessionId、question、answer和openid。每周跑一次聚合,找出被点踩次数最多的 top 20 问题,再决定是进缓存、调提示词还是转人工客服。这个动作看起来很小,却是机器人从"演示源码"走向"可持续运营"的标志。再接人工客服入口时,不要让用户留下手机号再等待,直接把当前sessionId传给客服工作台,客服能看到完整对话历史,机器人转人工才真正接得上。
本文还有配套的精品资源,点击获取