简介:这份资源面向希望快速上手微信小程序接入大语言模型的开发者与学习者,提供一套可直接运行的AI聊天页面源码,解决从零搭建对话界面并对接LLM的入门门槛问题。压缩包共6个文件,约386KB,包含wxml页面结构、wxss样式、js逻辑、json配置以及用户与机器人头像图片,覆盖聊天界面渲染与消息交互的核心环节。借助这套页面,读者可以体验小程序端发起AI对话的完整流程,并灵活设定AI人设,例如儿童教育领域专家、商城客服或法律顾问,同时调整回复语气、提出个性化要求,用于教学演示、客服原型或垂直场景验证。目前已有980人学习,适合具备基础小程序开发能力、想低成本验证AI对话产品的读者参考复用。
1. 从一份聊天页源码说起:AI 小程序到底能跑出什么效果
前阵子帮一个做本地生活服务的朋友看他的小程序,需求很朴素:在微信里加一个能对话的 AI 助手,用户问营业时间、问套餐、问附近门店,后台接一个大语言模型来回答。他手上只有一份「微信小程序聊天页面 + AI 大语言模型对接」的源码包,页面能打开,但消息发出去没反应,控制台一片红。我花了一个晚上把这份源码从头到尾拆了一遍,发现它其实是个挺典型的骨架:前端是完整的聊天 UI(消息气泡、输入框、滚动到底、加载态),后端是一层薄薄的服务端转发,把用户输入拼成 prompt 再打给 LLM 接口,把返回的文本流式或整段吐回小程序。这类项目最适合两类人:一是想快速给自己的小程序加个 AI 对话入口的开发者,二是想学「小程序前端 + 服务端 + 大模型 API」这条完整链路怎么串起来的新手。它不复杂,但该有的坑一个不少,下面按「能干什么 → 怎么跑 → 怎么改」的顺序拆给你看。
2. 项目定位与技术栈:小程序前端、服务端转发、LLM 接口三段式
2.1 这个项目解决的是「最后一公里」问题
大语言模型本身不稀奇,难的是把它塞进微信小程序的运行环境里。小程序有域名白名单、有 request 并发限制、有包体积约束,还不能直接在前端放 API Key——一旦写进小程序代码,反编译一下密钥就裸奔了。所以这类项目的核心价值不在「调模型」,而在「怎么安全、稳定地把模型能力送到用户手机上」。这份源码的架构就是围绕这个目标搭的:小程序端只负责渲染和交互,所有跟模型相关的密钥、prompt 拼接、上下文管理都放在服务端。你拿到的不是一个大而全的框架,而是一条最小可用的链路,改起来心里有数。
从技术栈看,前端是原生微信小程序(WXML + WXSS + JS),没有引入 uni-app 或 Taro 这类跨端框架,好处是依赖少、调试直接,坏处是你想移植到其他端得自己动手。服务端部分常见做法是 Node.js + Express 或 Koa,也有用 Python Flask/FastAPI 的版本,取决于源码包里的具体实现;它对外暴露一个 HTTP 接口,内部再去调 LLM 的对话补全 API。数据层通常很轻,聊天记录要么只存前端内存,要么落一张简单的表,这个项目大概率是前者,因为它的定位是「聊天页面」而不是「聊天系统」。
2.2 三段式链路拆开看
把一次对话拆成时间线,大概是这样的:用户在小程序输入框敲字 → 点击发送 → 前端把消息 push 进本地消息数组并立即渲染自己的气泡 → 同时发起wx.request到你的服务端 → 服务端校验参数、拼 system prompt 和历史上下文 → 调用 LLM 接口 → 拿到回复后返回 JSON → 前端把 AI 气泡 push 进数组并滚动到底。这条链路里,前端是「乐观更新」,也就是先显示自己的消息再等回复,体验上更顺;服务端是「无状态转发」,每次请求自带上下文;LLM 接口是「外部依赖」,它的延迟和稳定性直接决定用户体验。
这里有个设计取舍值得说:上下文放前端还是放服务端。这份源码常见做法是前端维护一个messages数组,每次请求把最近 N 条一起发给服务端,服务端不再存会话。好处是服务端无状态、好扩容、部署简单;坏处是请求体随对话变长而变大,而且用户清缓存就丢历史。如果你要做多轮长对话,这个方案迟早要换成服务端会话 + 数据库,但作为起步骨架,它够用且透明。
2.3 目录结构长什么样
原生小程序的目录基本是固定的,拿到源码先认这几个位置:
project/ ├── miniprogram/ # 小程序前端 │ ├── pages/ │ │ └── chat/ # 聊天页:wxml/wxss/js/json 四件套 │ ├── utils/ │ │ └── request.js # 封装 wx.request,统一错误处理 │ ├── app.js # 全局配置、启动逻辑 │ └── app.json # 页面注册、窗口样式 ├── server/ # 服务端(Node 或 Python) │ ├── index.js # 入口,起 HTTP 服务 │ ├── routes/chat.js # 对话接口 │ └── services/llm.js # 调 LLM 的封装 └── README.md认目录的目的是快速定位改动点:改 UI 去pages/chat,改请求逻辑去utils/request.js,改模型调用去server/services/llm.js,改接口协议去server/routes/chat.js。这四个位置基本覆盖了 90% 的二次开发需求。
3. 跑起来:从配置域名到发出第一条消息
3.1 环境准备与依赖安装
先把两端分开跑。小程序端需要微信开发者工具,导入miniprogram目录即可,AppID 用测试号也行,但要注意测试号不支持某些能力,正式联调建议用真实 AppID。服务端看源码语言:Node 版本进server目录执行npm install,Python 版本执行pip install -r requirements.txt。依赖通常就几个:HTTP 框架、请求库(axios 或 requests)、dotenv 读环境变量。装完先别急着启动,把环境变量配好。
# server/.env 示例,密钥绝不进代码仓库 LLM_API_KEY=your_key_here LLM_BASE_URL=https://api.example-llm.com/v1 LLM_MODEL=chat-model-name PORT=3000LLM_BASE_URL和LLM_MODEL这两个参数是二次开发时最常改的:换供应商只改 base url 和 model 名,业务代码不用动。PORT决定服务端监听端口,本地调试默认 3000 就行。注意.env一定要进.gitignore,我见过太多把密钥提交上去的翻车案例。
3.2 服务端启动与接口自测
启动命令一般是npm run dev或node index.js,Python 则是python app.py或uvicorn main:app --reload。起来之后先用 curl 自测,别一上来就开小程序,否则前端报错你分不清是网络问题还是服务端问题。
curl -X POST http://localhost:3000/api/chat \ -H "Content-Type: application/json" \ -d '{"messages":[{"role":"user","content":"你好"}]}'这个请求体结构是关键:messages是标准对话数组,每条含role(user/assistant/system)和content。服务端拿到后通常会补一条 system 消息在最前面,用来约束 AI 的人设和回答范围。如果 curl 返回 200 且有reply字段,说明服务端链路通了;如果返回 401,查密钥;返回 404,查路由路径是否和前端一致;返回 500,看服务端日志里 LLM 接口的原始报错。
3.3 小程序端联调与域名白名单
小程序有个硬规矩:wx.request只能请求已配置的合法域名,且必须是 HTTPS。本地调试时在开发者工具里勾选「不校验合法域名」可以绕过,但真机预览和上线必须配。所以联调阶段建议先用内网穿透把本地服务暴露成一个 HTTPS 地址,填到小程序后台的 request 合法域名里。这一步是新手最容易卡住的地方,现象是开发者工具正常、真机一片失败,原因就是域名没配或证书不被信任。
前端发请求的核心代码大概长这样:
// utils/request.js function chat(messages) { return new Promise((resolve, reject) => { wx.request({ url: `${BASE_URL}/api/chat`, // BASE_URL 指向你的服务端 method: 'POST', data: { messages }, // 带上最近 N 条上下文 timeout: 30000, // 大模型响应慢,超时要放宽 success: (res) => { if (res.statusCode === 200 && res.data.reply) { resolve(res.data.reply); } else { reject(new Error(res.data.error || '服务异常')); } }, fail: reject }); }); }timeout设 30 秒是有讲究的:大模型首字延迟加上生成长文本,10 秒经常不够,默认的 60 秒又太长让用户干等。BASE_URL建议抽成配置,开发、测试、生产三套环境各一个值,别硬编码在页面里。success里做了状态码和业务字段双重判断,是因为有些服务端异常也会返回 200,只判状态码会漏。
3.4 聊天页的渲染逻辑
聊天页的 JS 里核心是一个messages数组和一个inputValue。发送时先 push 用户消息、清空输入框、置loading为 true,然后调chat(),回来后 push AI 消息、置loading为 false、调scrollToBottom。WXML 里用wx:for渲染气泡,根据role决定左右对齐和头像。滚动到底常用scroll-into-view绑定最后一条消息的 id,比手动算scrollTop稳。加载态用一个「正在输入…」的占位气泡,比转圈更符合聊天场景的直觉。
4. 核心模块拆解:一次对话从输入到返回的完整链路
4.1 前端消息状态管理
聊天页最容易写乱的就是状态。这份源码的做法是把「消息列表」和「请求状态」分开:messages只管数据,loading、inputValue各管各的。发送函数大致如下:
// pages/chat/chat.js Page({ data: { messages: [], inputValue: '', loading: false }, onInput(e) { this.setData({ inputValue: e.detail.value }); }, async onSend() { const text = this.data.inputValue.trim(); if (!text || this.data.loading) return; // 空消息和重复发送都拦掉 const userMsg = { role: 'user', content: text, id: Date.now() }; this.setData({ messages: [...this.data.messages, userMsg], inputValue: '', loading: true }); this.scrollToBottom(); try { // 只带最近 10 条,控制请求体大小 const context = this.data.messages.slice(-10); const reply = await chat(context); this.setData({ messages: [...this.data.messages, { role: 'assistant', content: reply, id: Date.now() + 1 }], loading: false }); } catch (err) { this.setData({ loading: false }); wx.showToast({ title: '发送失败,请重试', icon: 'none' }); } this.scrollToBottom(); } });几个参数值得说:slice(-10)是上下文窗口的简化控制,条数太多会让 token 成本飙升、响应变慢,太少又记不住前文,10 条是个经验值,按你的模型上下文长度调。Date.now()做 id 在快速连发时可能撞,严谨点用自增计数器。loading期间禁止再次发送,避免并发请求把消息顺序打乱——这是聊天类页面最常见的玄学 bug 来源。
4.2 服务端接口与 prompt 组装
服务端这一层看着薄,但它是安全边界。核心逻辑是:校验入参 → 注入 system prompt → 调 LLM → 归一化返回。以 Node 为例:
// server/routes/chat.js router.post('/api/chat', async (req, res) => { const { messages } = req.body; if (!Array.isArray(messages) || messages.length === 0) { return res.status(400).json({ error: 'messages 不能为空' }); } // 限制条数和单条长度,防止被刷 const safe = messages.slice(-10).map(m => ({ role: ['user', 'assistant'].includes(m.role) ? m.role : 'user', content: String(m.content).slice(0, 2000) })); const payload = [ { role: 'system', content: SYSTEM_PROMPT }, // 人设和边界在这里定 ...safe ]; try { const reply = await callLLM(payload); res.json({ reply }); } catch (e) { console.error('LLM error:', e.message); res.status(502).json({ error: '模型服务暂时不可用' }); } });SYSTEM_PROMPT是整个项目里最该花时间打磨的字符串,它决定 AI 是「客服」还是「闲聊搭子」。slice(0, 2000)是防滥用:不限制的话,有人塞一篇长文进来,token 费用直接起飞。role白名单过滤是防注入,避免前端伪造 system 消息覆盖你的人设。错误统一返回 502 而不是把上游报错原样透出,是为了不泄露供应商信息。
4.3 LLM 调用封装与超时重试
调外部模型接口的封装要处理三件事:超时、重试、返回结构差异。不同供应商的返回字段名不一样,有的在choices[0].message.content,有的在data.reply,封装层要抹平这个差异,让上层只认一个reply字符串。
// server/services/llm.js async function callLLM(messages, retry = 1) { try { const resp = await axios.post( `${process.env.LLM_BASE_URL}/chat/completions`, { model: process.env.LLM_MODEL, messages, temperature: 0.7 }, { headers: { Authorization: `Bearer ${process.env.LLM_API_KEY}` }, timeout: 25000 } ); return resp.data.choices[0].message.content; } catch (e) { if (retry > 0 && e.code === 'ECONNABORTED') { return callLLM(messages, retry - 1); // 只对超时重试一次 } throw e; } }temperature控制回答的发散程度,客服场景调低到 0.3 更稳,创意场景调高。重试只对超时做、且只重试一次,是因为模型调用不是幂等的,重试多了既费钱又可能重复计费;对 4xx 错误重试毫无意义。timeout设 25 秒略小于前端 30 秒,保证前端先拿到服务端的错误响应而不是自己先超时。
5. 避坑与排查:上线前必须过的五道坎
5.1 真机请求全失败,开发者工具却正常
现象:模拟器里对话正常,扫码真机预览每条消息都提示发送失败。原因几乎都是合法域名没配或配了但证书链不完整。小程序真机强制校验 HTTPS 和域名白名单,工具里的「不校验」开关对真机无效。解决:把服务端部署到有正规证书的域名,在小程序后台「开发管理 → 服务器域名」里把该域名加进 request 合法域名,改完重新编译并清一次缓存。内网穿透的临时域名也要加,且它经常变,别指望一劳永逸。
5.2 消息发出去了,AI 一直不回也不报错
现象:用户气泡出来了,加载态一直转,最后超时。原因通常是服务端调 LLM 时卡住,而前端timeout比服务端还短,或者服务端根本没设超时。排查顺序:先看服务端日志有没有收到请求,再看调 LLM 那一步的耗时,最后看是不是模型供应商限流。解决:服务端 axios 必须显式设timeout,前端超时要比服务端大 5 秒左右;对限流错误(429)做友好提示而不是静默失败。
5.3 多轮对话「失忆」,AI 不记得上一句
现象:问「它多少钱」,AI 反问「你指什么」。原因多半是前端每次只发了当前这一条,没带历史;或者服务端把messages截断得太狠。解决:确认前端发送的是messages数组而非单个字符串,确认slice(-N)的 N 够用,确认服务端没有在组装时把历史丢掉。还有一种隐蔽情况:前端 push 消息用了this.data.messages.push()而不是setData新数组,导致数据没同步,发出去的上下文是旧的。
5.4 密钥泄露与费用失控
现象:账单突然暴涨,或者代码仓库里搜到了 API Key。原因:密钥写死在前端、.env没进.gitignore、接口没有频率限制被人脚本刷。解决:密钥只放服务端环境变量;接口加简单的按 IP 或按用户维度的限流(比如每分钟 20 次);对单次请求的messages条数和总字符数做上限;上线前在仓库里全局搜一遍 key 的特征串。这是血泪经验,别等账单来了才补。
5.5 长回复被截断或页面卡顿
现象:AI 回答到一半没了,或者长消息渲染时页面明显掉帧。原因:模型max_tokens设太小导致截断;长文本一次性setData渲染大量节点导致卡顿。解决:按场景调大max_tokens;超长回复考虑分段返回或折叠展示;setData只更新变化的那条消息而不是整个数组,能明显减轻渲染压力。
6. 二次开发:把骨架改成你自己的 AI 助手
拿到这份源码,真正的工作量在「改」而不是「跑」。我一般按三步走:先换人设,再换交互,最后补能力。换人设就是改SYSTEM_PROMPT,把「你是一个乐于助人的助手」改成你业务里的角色,比如「你是某门店的在线客服,只回答营业时间、地址、套餐三类问题,其他问题引导用户致电」。这一步改完,AI 的回答风格立刻不一样,成本最低、收益最明显。
换交互是改前端。常见需求有三个:加「重新生成」按钮(把最后一条 assistant 消息删掉重发)、加「停止生成」(需要服务端支持流式返回,前端用wx.request的enableChunked接收分块)、加「清空对话」(重置messages数组)。流式返回是体验分水岭,但改造量不小,建议先把非流式跑稳再动。
补能力是接业务数据。纯模型回答容易编造,正确做法是「检索增强」:用户提问后,服务端先查你的知识库(哪怕是几张数据库表),把查到的内容拼进 system prompt 再调模型。改造点在routes/chat.js组装 payload 之前插一步检索,示意如下:
// 检索增强的插入点 const knowledge = await searchKnowledge(lastUserContent); // 查你的业务数据 const payload = [ { role: 'system', content: `${SYSTEM_PROMPT}\n参考资料:${knowledge}` }, ...safe ];searchKnowledge的实现按你的数据形态定:结构化数据用 SQL 模糊匹配,文档用向量检索。关键是给检索结果加一句「若参考资料中没有答案,请如实说不知道」,否则模型会把不相关资料硬编进回答。
验证方法上,我习惯准备一组固定问题做回归:正常问题、边界问题(超范围)、恶意输入(让它忽略指令)、空输入、超长输入,每次改完 prompt 或检索逻辑都跑一遍,看回答是否还在预期内。这套用例不用多,十条就够挡住大部分回归。
最后说个我踩过的坑:早期我图省事,把上下文条数设成 20,结果用户聊久了每次请求都带着一大坨历史,token 费用翻倍、响应还变慢,用户以为卡死了。从那以后我每次接这类项目,都强制把「上下文窗口大小」和「单次请求字符上限」当成两个必填参数写进配置,上线前用长对话压一遍。希望这份拆解能帮你少走几步弯路,把这份聊天页源码真正变成能用的东西。
本文还有配套的精品资源,点击获取