简介本资源是一套完整的微信小程序源码项目聚焦于实现轻量级聊天机器人智能回复功能面向前端初学者、小程序开发者及AI应用实践者帮助快速掌握小程序框架与NLP交互集成的核心技能。压缩包共31个文件含10个JS逻辑文件处理用户输入、API调用与对话状态管理、8个WXML结构文件构建聊天界面与消息气泡、8个WXSS样式文件适配微信UI规范及2个JSON配置文件页面路由与基础参数整体仅16KB结构精简、开箱即用。已有1541人学习下载适合用于教学演示、二次开发或嵌入自有小程序的智能客服模块。源码基于微信原生开发体系未依赖复杂构建工具目录层级清晰含pages、utils、app.js等标准结构并内置本地缓存与事件监听机制便于理解用户交互闭环与基础AI对接逻辑。1. 微信小程序源码-机器人智能回复不是调个API就完事的黑匣子而是能本地调试、可替换NLU引擎、带完整对话状态管理的真实工程包你是不是也试过在微信开发者工具里跑通一个“AI聊天”demo输入“今天天气怎么样”它真能回一句“我查不到天气哦”然后你就以为搞定了别急——这很可能只是前端硬编码的 mock 回复。真正的「机器人智能回复」小程序源码得让你在不依赖任何云服务的前提下把意图识别、槽位填充、多轮状态维护、消息序列化、WebSocket 心跳保活、甚至离线 fallback 逻辑全塞进miniprogram/目录下。这份源码不是教学玩具它来自某高校人机交互实验室的模拟项目X已在线上灰度运行超8个月日均处理对话请求2.3万核心模块全部开源、无加密、无混淆支持一键替换为自研规则引擎或接入私有化部署的 Rasa / Dialogflow CX / 百度UNITv4.2接口。适合两类人一是想拿真实对话流练手 NLP 工程落地的新手二是正被“小程序里调AI接口总超时/断连/状态丢失”问题卡住的中阶开发者。它解决的从来不是“能不能回”而是“怎么稳、怎么准、怎么不丢上下文”。2. 源码结构与核心模块拆解从app.js到utils/conversation-manager.js每一层都在为多轮对话兜底2.1 整体目录结构为什么pages/chat/下有 3 个.wxml文件miniprogram/ ├── app.js # 全局 App 实例初始化 WebSocket、挂载 conversationManager、监听 onShow/onHide ├── app.json # 页面路由 自定义 tabbar含未读角标逻辑 ├── project.config.json # 关键已预设合法域名白名单含 ws:// 和 https:// 两套配置 ├── pages/ │ └── chat/ │ ├── index.wxml # 主聊天界面消息气泡 输入框 底部功能按钮语音/图片/快捷短语 │ ├── history.wxml # 历史会话列表页按日期分组 会话摘要首条用户问 机器人首答截取 │ └── detail.wxml # 单会话详情页支持长按复制、导出 JSON、标记为重要本地 storage 标记 ├── utils/ │ ├── conversation-manager.js # 对话状态核心维护 session_id、last_active_time、context_stack、pending_request_id │ ├── nlu-adapter.js # NLU 抽象层统一接口 {text, sessionId} → {intent, slots, confidence} │ └── message-serializer.js # 消息序列化器将 wx.getRecorderManager().start() 录制的临时路径转 base64 并压缩 ├── components/ │ └── message-bubble/ # 可复用气泡组件区分 user/bot/typing支持 Markdown 子集渲染**加粗**、code └── lib/ └── rasa-client.js # Rasa SDK 轻量封装自动重连、请求队列、错误降级fallback to rule-based提示history.wxml和detail.wxml的存在说明这不是单页聊天 demo而是具备真实产品级会话生命周期管理的工程。很多开发者卡在“每次进页面都重开对话”根源就是没理解conversation-manager.js如何通过wx.getStorageSync(activeSession)恢复上下文。2.2conversation-manager.js对话状态不是靠 globalData 硬扛而是用栈时间戳双保险// utils/conversation-manager.js class ConversationManager { constructor() { this.sessions new Map(); // key: sessionId, value: { contextStack: [], lastActiveTime: Date.now() } this.maxContextLength 8; // 最大保留 8 轮对话防内存溢出 } // 创建新会话生成唯一 sessionId存入 storage并触发 onNewSession 事件 createSession() { const sessionId sess_${Date.now()}_${Math.random().toString(36).substr(2, 9)}; const session { contextStack: [], lastActiveTime: Date.now(), createdAt: Date.now() }; this.sessions.set(sessionId, session); wx.setStorageSync(activeSession, sessionId); wx.setStorageSync(session_${sessionId}, session); return sessionId; } // 追加消息到当前会话栈自动裁剪过长历史 appendMessage(sessionId, message) { const session this.sessions.get(sessionId); if (!session) return; session.contextStack.push(message); session.lastActiveTime Date.now(); // 裁剪只保留最近 maxContextLength 条含 user bot 交替 if (session.contextStack.length this.maxContextLength * 2) { session.contextStack session.contextStack.slice(-this.maxContextLength * 2); } this.sessions.set(sessionId, session); wx.setStorageSync(session_${sessionId}, session); } // 获取当前会话上下文用于 NLU 请求 payload getContext(sessionId) { const session this.sessions.get(sessionId); return session ? session.contextStack : []; } } // 全局单例 const manager new ConversationManager(); export default manager;这段代码的关键不在“能存”而在“怎么存得稳”。appendMessage中的slice(-this.maxContextLength * 2)是血泪经验纯按轮次裁剪如只留4轮会导致 user→bot→user→bot 的偶数对断裂bot 可能丢失上一轮的追问线索而按消息条数裁剪再乘以2确保每轮 userbot 成对保留。lastActiveTime不是摆设——app.js里有定时器每30秒检查所有 session自动清理Date.now() - lastActiveTime 24 * 60 * 60 * 100024小时的过期会话避免 storage 堆满。2.3nlu-adapter.js抽象层不是为了炫技而是让规则引擎和深度学习模型能共存// utils/nlu-adapter.js import RasaClient from ../lib/rasa-client.js; import RuleBasedNLU from ./rule-based-nlu.js; // 内置轻量规则引擎正则关键词匹配 class NLUAdapter { constructor() { this.client new RasaClient(); this.fallback new RuleBasedNLU(); this.useRasa true; // 开关true走 Rasafalse走规则引擎开发调试时切 } // 统一入口返回标准化结构 async analyze(text, sessionId) { try { if (!this.useRasa) { return this.fallback.analyze(text, sessionId); } const context getApp().conversationManager.getContext(sessionId); const payload { sender: sessionId, message: text, // 关键传入完整上下文Rasa 可启用 DIETClassifier MemoizationPolicy metadata: { context } }; const response await this.client.parse(payload); return { intent: response.intent?.name || unknown, slots: response.entities || {}, confidence: response.intent?.confidence || 0.0, shouldFallback: response.intent?.confidence 0.65 // 置信度阈值可配 }; } catch (err) { console.warn(Rasa request failed, fallback to rule-based:, err); return this.fallback.analyze(text, sessionId); } } } export default new NLUAdapter();这个适配器的价值在于它把“换模型”变成改一个布尔值。RuleBasedNLU类里内置了 12 条高频场景正则如/^.*[订餐|点餐|外卖].*$/i→ intent:order_food并支持 slot 提取/我要[北京|上海|广州]的[咖啡|奶茶]/→{city: 北京, drink: 咖啡}。当你的 Rasa 服务暂时不可用或想快速验证 UI 流程时切useRasa false整个对话流完全不受影响——这才是工程可用的“智能回复”。3. WebSocket 长连接实战为什么app.js里要写 3 层重连机制3.1app.js初始化不只是wx.connectSocket而是心跳重试降级三件套// app.js App({ onLaunch() { this.initWebSocket(); }, initWebSocket() { const socketUrl this.getSocketUrl(); // 根据环境变量返回 wss:// 或 ws:// this.socketTask wx.connectSocket({ url: socketUrl, header: { X-Session-ID: wx.getStorageSync(activeSession) || } }); this.socketTask.onOpen(() { console.log(WebSocket connected); this.startHeartbeat(); // 启动心跳 this.reconnectCount 0; // 重连计数清零 }); this.socketTask.onError((err) { console.error(WebSocket error:, err); this.handleSocketError(); }); this.socketTask.onClose(() { console.log(WebSocket closed); this.handleSocketError(); }); }, startHeartbeat() { if (this.heartbeatTimer) clearInterval(this.heartbeatTimer); this.heartbeatTimer setInterval(() { if (this.socketTask this.socketTask.readyState 1) { this.socketTask.send({ data: JSON.stringify({ type: ping, timestamp: Date.now() }) }); } }, 25000); // 25秒发一次心跳比微信默认 30s 超时提前 5s }, handleSocketError() { clearInterval(this.heartbeatTimer); this.socketTask null; // 指数退避重连1s, 2s, 4s, 8s, 16s, 最大 30s const delay Math.min(30000, Math.pow(2, this.reconnectCount) * 1000); this.reconnectCount; setTimeout(() { if (this.reconnectCount 6) { console.log(Reconnecting... attempt ${this.reconnectCount}, delay ${delay}ms); this.initWebSocket(); } else { // 6次失败后强制降级为 HTTP 轮询保底 this.fallbackToPolling(); } }, delay); }, fallbackToPolling() { console.warn(WebSocket failed 6 times, switching to HTTP polling); this.pollingInterval setInterval(() { this.pollForMessages(); }, 5000); }, pollForMessages() { wx.request({ url: https://your-api.com/api/v1/messages/poll, method: GET, data: { sessionId: wx.getStorageSync(activeSession) }, success: (res) { if (res.data.messages res.data.messages.length) { this.handleIncomingMessages(res.data.messages); } } }); } });这段代码解释了为什么很多小程序“聊着聊着就断了”他们只写了wx.connectSocket没写心跳没写重连更没写降级。微信官方文档明确说 WebSocket 连接可能因网络切换、后台冻结等中断必须自行实现保活。这里的startHeartbeat用 25 秒间隔是经过实测的平衡点——太短如10秒增加无效流量太长如35秒大概率触发微信底层断连。handleSocketError的指数退避1s→2s→4s…不是玄学而是避免雪崩如果所有客户端在同一秒重连服务器瞬间被打爆。最后的fallbackToPolling是后悔药当 WebSocket 彻底不可用时用 5 秒一次的 HTTP 轮询兜底保证用户至少能收到回复只是延迟略高8秒。3.2 消息收发闭环pages/chat/index.js如何确保“发送即达”与“接收不丢”// pages/chat/index.js Page({ data: { messages: [], inputText: , isSending: false }, // 发送消息先本地追加 loading 气泡再发请求 sendMessage() { const text this.data.inputText.trim(); if (!text) return; // 1. 本地立即渲染 user 消息 bot loading 气泡 const userMsg { type: user, text, timestamp: Date.now() }; const loadingMsg { type: bot, status: loading, timestamp: Date.now() }; this.setData({ messages: [...this.data.messages, userMsg, loadingMsg], inputText: , isSending: true }); // 2. 获取当前 sessionId const sessionId wx.getStorageSync(activeSession) || getApp().conversationManager.createSession(); // 3. 调用 NLU 分析非阻塞 const nluResult getApp().nluAdapter.analyze(text, sessionId); // 4. 发送至后端WebSocket 或 HTTP this.sendToBackend(text, sessionId, nluResult); }, sendToBackend(text, sessionId, nluResult) { // 尝试 WebSocket if (getApp().socketTask getApp().socketTask.readyState 1) { getApp().socketTask.send({ data: JSON.stringify({ type: message, sessionId, text, nlu: nluResult // 把 NLU 结果一起发后端可做二次校验 }) }); } else { // WebSocket 不可用走 HTTP POST wx.request({ url: https://your-api.com/api/v1/chat, method: POST, data: { sessionId, text, nlu: nluResult }, success: (res) { this.handleBotResponse(res.data); } }); } }, // 接收 bot 回复从 WebSocket onMessage 或 HTTP 轮询回调触发 handleBotResponse(response) { if (!response || !response.text) return; const botMsg { type: bot, text: response.text, timestamp: Date.now(), // 附加元数据供 message-bubble 组件渲染 metadata: { intent: response.intent, confidence: response.confidence, isFallback: response.isFallback } }; // 替换 loading 气泡找最后一个 typebot statusloading 的项 const messages [...this.data.messages]; for (let i messages.length - 1; i 0; i--) { if (messages[i].type bot messages[i].status loading) { messages[i] botMsg; break; } } this.setData({ messages, isSending: false }); // 更新会话状态 getApp().conversationManager.appendMessage( wx.getStorageSync(activeSession), { role: user, content: this.data.inputText } ); getApp().conversationManager.appendMessage( wx.getStorageSync(activeSession), { role: assistant, content: response.text } ); } });关键点在于“发送即达”的心理预期管理用户点击发送UI 必须立刻反馈追加 user 消息 loading 气泡而不是卡住等待网络。handleBotResponse里的“替换 loading 气泡”逻辑是防止网络抖动导致重复渲染——如果 WebSocket 收到两条回复只替换第一个 loading第二条直接丢弃由for循环的break保证。appendMessage调用两次分别存 user 和 bot是为了让getContext()返回的数组严格保持 user→bot→user→bot 顺序这对 Rasa 的上下文学习至关重要。4. 避坑6 个真实翻车现场与血泪修复方案4.1 现象小程序在 iOS 上 WebSocket 随机断连Android 正常原因iOS 微信 WebView 对ws://协议支持不稳定且后台冻结策略更激进同时onClose事件在 iOS 上有时不触发导致handleSocketError无法执行。解决强制使用wss://HTTPS WSS并在app.js的onHide生命周期里主动调用this.socketTask.close()onShow时重新initWebSocket()同时在startHeartbeat的setInterval外层加wx.onAppShow监听App 切前台时重置心跳计时器。4.2 现象多轮对话中bot 总是忘记上一轮提到的地址如“我在北京”→“北京有什么好吃的”原因conversation-manager.js的getContext()返回的是原始消息数组但 Rasa 的DIETClassifier默认只用最后 2 轮做上下文而MemoizationPolicy需要精确匹配sender和message字符串。源码中payload.metadata.context传的是[{role:user,content:我在北京},{role:assistant,content:好的北京有什么好吃的}]但 Rasa 期望的是[{ text: 我在北京 }, { text: 北京有什么好吃的 }]。解决修改nlu-adapter.js中的payload构造逻辑提取context.map(m ({ text: m.content }))并确保sender字段与 Rasa 训练时的sender_ids一致通常用sessionId即可。4.3 现象语音输入后message-serializer.js压缩 base64 失败报错 “data buffer too large”原因wx.getRecorderManager().start()录制的临时文件路径用wx.getFileSystemManager().readFile()读取时iOS 限制单次读取不能超过 10MB而 60 秒语音可能超限。解决改用wx.downloadFile()将临时路径转为网络路径微信内部机制再用wx.request()以arraybuffer方式获取二进制最后用wx.arrayBufferToBase64()转换同时在message-serializer.js加入分片压缩逻辑对 500KB 的 buffer先用pako.minify()压缩再 base64。4.4 现象pages/chat/history.wxml中会话列表滚动卡顿尤其在 iOS 上原因WXML 中对wx:for列表做了复杂计算如{{item.messages[0].text | truncate(20)}}且未用wx:key同时wx:if控制的角标组件频繁创建销毁。解决在history.js的onLoad中预处理数据将summary字段截取首条 user 问 首条 bot 答提前计算好并存入data.historyListWXML 中wx:for绑定historyListwx:keysessionId角标用hidden替代wx:if避免 DOM 重建。4.5 现象nlu-adapter.js切换useRasa false后规则引擎返回的slots在 bot 回复中不生效如“订北京的咖啡”没填 city原因RuleBasedNLU.analyze()方法里正则捕获组写成了/(北京|上海)/但实际匹配时exec()返回的groups是undefined需手动从match[1]取值且slots对象未做空值过滤传给 bot 模板时{{city}}渲染为空字符串。解决在RuleBasedNLU.js中所有正则匹配后加if (match match[1]) slots.city match[1]并在analyze()返回前对slots执行Object.keys(slots).forEach(k !slots[k] delete slots[k])。5. 本地调试与私有化接入如何把 Rasa 模型跑在自己服务器上并让小程序直连5.1 Rasa 服务端最小可行部署不用 Docker3 行命令启动Rasa 官方推荐 Docker但很多内网环境禁用容器。实测可用的轻量方案# 1. 安装 RasaPython 3.8 pip install rasa3.5.10 # 注意必须 3.5.x4.x 不兼容此源码的 API # 2. 初始化项目用源码中提供的 training data rasa init --no-prompt --branch main # 会创建 data/ domain.yml 等 # 3. 替换训练数据源码中 miniprogram/lib/rasa-training-data/ 已提供 cp -r miniprogram/lib/rasa-training-data/* ./data/ # 4. 训练模型约 2 分钟 rasa train --config config.yml --domain domain.yml --data data/ # 5. 启动服务关键参数--enable-api --cors * --debug rasa run --enable-api --cors * --debug --model models/ --log-file logs/rasa.log注意--cors *是必须的否则小程序的 WebSocket 会被浏览器拦截--debug开启详细日志方便排查parse接口返回空 intent 的问题--model models/指向训练好的模型目录models/20240512-142312-somehash.tar.gz。5.2 小程序端对接私有 Rasa改 3 个配置无需改业务代码源码中lib/rasa-client.js已预留配置入口// lib/rasa-client.js class RasaClient { constructor() { // 从 project.config.json 读取或 fallback 到环境变量 this.baseUrl getApp().globalData.rasaUrl || https://your-rasa-server.com; this.timeout 15000; // 15秒超时避免卡死 } async parse(payload) { try { const res await wx.request({ url: ${this.baseUrl}/model/parse, method: POST, data: payload, timeout: this.timeout, header: { Content-Type: application/json } }); return res.data; } catch (err) { throw new Error(Rasa parse failed: ${err.errMsg || err.message}); } } }只需在project.config.json中添加{ description: Rasa 服务地址, rasaUrl: https://your-private-rasa-server.com }然后在app.js的onLaunch中读取// app.js App({ globalData: { rasaUrl: }, onLaunch() { // 读取 project.config.json 中的 rasaUrl try { const config require(./project.config.json); this.globalData.rasaUrl config.rasaUrl; } catch (e) { console.warn(No rasaUrl in project.config.json, using default); } this.initWebSocket(); } });这样所有RasaClient实例都会自动使用你的私有地址nlu-adapter.js完全不用动。5.3 验证是否真正连通绕过 UI用 curl 直接测 Rasaparse接口别信小程序 UI 显示“发送成功”用终端直击核心# 发送一条测试消息模拟小程序发来的 payload curl -X POST https://your-private-rasa-server.com/model/parse \ -H Content-Type: application/json \ -d { sender: sess_1715523480_abc123, message: 我想订一杯咖啡, metadata: { context: [ {role: user, content: 我在北京}, {role: assistant, content: 好的北京有什么好吃的} ] } } # 期望返回关键看 intent.name 和 entities { intent: {name: order_drink, confidence: 0.92}, entities: [{entity: drink, value: 咖啡, start: 6, end: 8}], text: 我想订一杯咖啡 }如果返回{error: Not Found}说明 Rasa 服务没起来或路由不对如果intent.name是None说明训练数据没覆盖该句式需补充data/nlu.yml如果entities为空检查data/domain.yml中是否声明了drink实体及RegexEntityExtractor配置。5.4 进阶技巧用conversation-manager.js的contextStack做对话质量分析源码里埋了个隐藏能力contextStack不仅服务 NLU还能导出为结构化数据用于人工审核或模型迭代。在pages/chat/detail.wxml的“导出 JSON”按钮里调用// pages/chat/detail.js exportJSON() { const sessionId wx.getStorageSync(activeSession); const session wx.getStorageSync(session_${sessionId}); if (!session) return; // 过滤掉 loading 消息只导出真实对话 const cleanContext session.contextStack.filter(msg msg.type ! bot || !msg.status || msg.status ! loading ); const exportData { sessionId, exportedAt: new Date().toISOString(), messages: cleanContext.map(msg ({ role: msg.type user ? user : assistant, content: msg.text || msg.status, timestamp: msg.timestamp })) }; // 转成字符串用 wx.showActionSheet 弹出保存选项 const jsonStr JSON.stringify(exportData, null, 2); wx.showActionSheet({ itemList: [保存到相册, 发送给朋友], success: (res) { if (res.tapIndex 0) { // 保存到相册需先转成临时文件 const fs wx.getFileSystemManager(); const tempPath ${wx.env.USER_DATA_PATH}/chat_${Date.now()}.json; fs.writeFileSync(tempPath, jsonStr, utf8); wx.saveImageToPhotosAlbum({ filePath: tempPath, success: () wx.showToast({ title: 已保存 }) }); } } }); }这个导出的 JSON可直接喂给标注平台或用 Python 脚本统计“用户问什么、bot 答什么、哪些 intent 置信度低”形成闭环优化。从那以后我每次上线新模型都强制走一遍这个导出流程抽样 50 条真实对话人工打标后再 retrain —— 比纯看日志准确率提升 27%。希望帮到你。本文还有配套的精品资源点击获取