Phi-3 Forest Laboratory 实战:微信小程序集成AI对话功能

张开发
2026/4/16 6:06:17 15 分钟阅读

分享文章

Phi-3 Forest Laboratory 实战:微信小程序集成AI对话功能
Phi-3 Forest Laboratory 实战微信小程序集成AI对话功能最近有不少朋友在问怎么把自己部署的大模型能力快速搬到微信小程序里做个自己的AI小助手。确实小程序用起来方便用户不用下载点开就用是个很好的落地场景。今天我就以微软的轻量级模型 Phi-3 为例手把手带你走一遍这个流程。我们假设你已经用类似星图GPU平台这样的服务把 Phi-3 的 API 服务部署好了现在要做的就是让微信小程序能和这个“大脑”对话。整个过程会涉及到小程序怎么发起请求、后端怎么安全地接收和处理、以及怎么把模型“打字”一样的流式回复流畅地展示在小程序页面上。如果你对小程序开发有点基础跟着做下来一个下午就能跑通。1. 动手之前理清思路与准备工作在开始写代码之前咱们先把整个流程的架子搭好心里有个数。核心思路其实不复杂用户在小程序里输入问题小程序把这个问题打包成一个网络请求发送给你部署好的 Phi-3 模型 APIAPI 处理完把答案流式地传回来小程序再把这些字一个个“吐”出来显示给用户。听起来简单但有几个关键点需要提前准备好1.1 确认你的后端API服务这是整个项目的基石。你需要一个已经正常运行的、能够接受 HTTP 请求并返回流式响应的 Phi-3 模型服务。通常这个服务会提供一个类似https://your-api-domain.com/v1/chat/completions的接口。接口规范确保你的服务遵循 OpenAI 兼容的 API 格式这会让我们前端的调用变得非常通用和简单。请求体和响应体的结构应该是标准的。流式支持这是实现“打字机效果”的关键。你的服务必须支持stream: true参数并以 Server-Sent Events (SSE) 格式返回数据。网络可达确保这个 API 地址能从公网访问因为微信小程序会从用户的手机直接向它发起请求。1.2 配置微信小程序开发环境如果你还没弄过需要先去微信公众平台注册一个小程序账号拿到你的AppID。然后把微信开发者工具安装好创建一个新的小程序项目把这个 AppID 填进去。创建项目时模板选“不使用云服务”就行因为我们用自己的后端。1.3 理解关键的技术点小程序网络请求小程序用的是wx.request或它的升级版wx.requestTask。但默认的wx.request不支持流式响应SSE。所以我们需要一点“魔法”来让它支持。域名白名单微信小程序出于安全考虑不允许随意请求外网地址。你必须在小程序管理后台的“开发设置”里把你后端 API 的域名比如your-api-domain.com配置到request合法域名列表中。这是必做的一步否则请求会失败。流式响应处理后端返回的数据是一段一段的我们需要像接水管一样把这些数据块拼接起来并实时更新到页面上。好了思路理清了准备工作也到位了接下来我们进入实战环节。2. 构建小程序前端发起对话请求我们先从小程序端做起构建一个简单的聊天界面并实现最核心的请求发送功能。2.1 搭建基础聊天界面我们先在pages/index/index.wxml里写个简单的界面有个输入框和发送按钮下面用来显示对话。!-- pages/index/index.wxml -- view classcontainer !-- 聊天记录区域 -- scroll-view scroll-y scroll-with-animation scroll-into-view{{toView}} classchat-list block wx:for{{messages}} wx:keyid view classmessage {{item.role}} view classavatar{{item.role user ? 我 : AI}}/view view classbubble{{item.content}}/view /view /block /scroll-view !-- 输入区域 -- view classinput-area input value{{inputValue}} bindinputonInput placeholder请输入您的问题... confirm-typesend bindconfirmsendMessage classinput / button bindtapsendMessage classsend-btn disabled{{isLoading}} {{isLoading ? 思考中... : 发送}} /button /view /view样式文件index.wxss稍微美化一下这里就不全贴了主要是设置一下气泡、滚动区域和输入框的样式。在index.js的 data 里我们初始化一些数据// pages/index/index.js Page({ data: { messages: [], // 存储所有消息 inputValue: , // 输入框内容 isLoading: false, // 是否正在加载 toView: , // 用于滚动到底部 }, onInput(e) { this.setData({ inputValue: e.detail.value }); }, // ... 其他函数待会填充 })2.2 实现流式请求函数这是前端最核心的部分。由于小程序的wx.request原生不支持 SSE我们需要用wx.connectSocket来模拟或者使用一个更聪明的办法利用wx.request返回的RequestTask对象监听onChunkReceived事件注意这个 API 可能需要基础库版本较高或者用 beta 版本。为了兼容性和简单起见我这里演示一个使用fetch和ReadableStream的变通方案但需要提醒小程序原生不支持fetch。更通用的方法是封装一个支持流式的request方法。我们可以利用wx.request设置responseType: ‘text’并在返回头中看到Transfer-Encoding: chunked时手动处理分块数据。但这个过程比较底层。为了快速实现一个常见的实践是在后端做一层适配将 SSE 流转换成 WebSocket 或者一个返回 JSON 数组的普通 HTTP 接口。这里为了教程清晰我们假设后端提供了一个非流式但可用的接口先实现基础功能然后再讨论流式升级。我们先实现一个基础的、非流式的请求函数// 在 index.js 中或者新建一个 utils/api.js const API_BASE_URL https://your-api-domain.com; // 替换成你的真实地址 const API_PATH /v1/chat/completions; function sendChatRequest(messages, onSuccess, onFail) { // 显示加载状态 this.setData({ isLoading: true }); wx.request({ url: API_BASE_URL API_PATH, method: POST, header: { Content-Type: application/json, // 如果需要 API Key在这里添加 // Authorization: Bearer YOUR_API_KEY }, data: { model: phi-3, // 根据你的模型名称修改 messages: messages, stream: false, // 先关闭流式确保能收到完整响应 max_tokens: 500 }, success: (res) { if (res.statusCode 200 res.data.choices res.data.choices[0].message) { const aiMessage res.data.choices[0].message; onSuccess onSuccess(aiMessage); } else { onFail onFail(请求失败: ${res.statusCode}); } }, fail: (err) { onFail onFail(网络错误: ${err.errMsg}); }, complete: () { this.setData({ isLoading: false }); } }); }然后我们在页面的sendMessage函数中调用它// pages/index/index.js 中的 sendMessage 函数 sendMessage() { const text this.data.inputValue.trim(); if (!text || this.data.isLoading) return; const userMessage { role: user, content: text, id: Date.now() }; // 更新UI添加用户消息 this.setData({ messages: [...this.data.messages, userMessage], inputValue: , toView: msg-${userMessage.id} }); // 构建发送给API的消息历史 const messagesForAPI this.data.messages.concat(userMessage).map(msg ({ role: msg.role, content: msg.content })); // 调用请求函数 sendChatRequest.call(this, messagesForAPI, (aiMessage) { // 成功回调 const newAiMsg { ...aiMessage, id: Date.now() 1 }; this.setData({ messages: [...this.data.messages, newAiMsg], toView: msg-${newAiMsg.id} }); }, (errorMsg) { // 失败回调 wx.showToast({ title: errorMsg, icon: none }); // 也可以把错误信息显示在聊天框 const errorMsgObj { role: assistant, content: 抱歉出错了: ${errorMsg}, id: Date.now() 1 }; this.setData({ messages: [...this.data.messages, errorMsgObj] }); } ); }到这一步一个最基础的、非流式的小程序 AI 对话功能就完成了。用户输入点击发送等待片刻就能看到 AI 的完整回复。3. 升级体验实现流式响应与打字机效果非流式的体验像是等对方写完一封信再寄给你而流式体验像是打电话对方一边说你一边听。要提升体验我们必须实现流式。3.1 改造前端以处理流式数据正如之前提到的在小程序里直接处理 SSE 流比较麻烦。一个更可行的架构是后端适配层你的 Phi-3 服务后端或者一个中间件不直接向小程序返回 SSE而是将流式数据通过WebSocket推送给小程序或者将流聚合成一个完整的响应后再返回这就失去了“流式”意义。使用云函数中转在小程序云开发环境中云函数可以原生支持fetch和ReadableStream处理 SSE 流非常方便。然后云函数通过 WebSocket 或 HTTP 长轮询将数据块推给小程序前端。考虑到教程的通用性我们采用一个简化但有效的方案在后端将流式数据缓存并提供一个轮询接口。前端快速轮询这个接口获取最新的回复片段。这不是最优解但能清晰演示原理。假设后端提供了这样一个接口POST /v1/chat/completions开启一个流式任务并返回一个task_id然后通过GET /v1/chat/task/{task_id}可以轮询获取到当前已生成的所有文本。前端逻辑需要修改// 修改后的 sendMessage 函数简化版逻辑 async sendMessage() { const text this.data.inputValue.trim(); if (!text) return; const userMessage { role: user, content: text, id: Date.now() }; this.setData({ messages: [...this.data.messages, userMessage], inputValue: , isLoading: true }); // 1. 创建流式任务 const startRes await wx.request({ url: API_BASE_URL /v1/chat/completions, method: POST, data: { model: phi-3, messages: [...this.data.messages.map(m({role:m.role,content:m.content})), userMessage], stream: true }, header: { Content-Type: application/json } }); const taskId startRes.data.task_id; // 2. 在界面上先添加一个空的AI消息气泡用于后续更新内容 const aiMessageId Date.now() 1; const aiMessagePlaceholder { role: assistant, content: , id: aiMessageId }; this.setData({ messages: [...this.data.messages, aiMessagePlaceholder] }); // 3. 轮询获取结果 const pollInterval setInterval(async () { const pollRes await wx.request({ url: ${API_BASE_URL}/v1/chat/task/${taskId}, }); if (pollRes.data.status completed) { clearInterval(pollInterval); this.setData({ isLoading: false }); // 更新最终内容 this.updateMessageContent(aiMessageId, pollRes.data.full_content); } else if (pollRes.data.status streaming) { // 更新部分内容实现打字机效果 this.updateMessageContent(aiMessageId, pollRes.data.current_content); } else if (pollRes.data.status failed) { clearInterval(pollInterval); this.setData({ isLoading: false }); this.updateMessageContent(aiMessageId, 生成失败: ${pollRes.data.error}); } }, 300); // 每300毫秒轮询一次 } // 用于更新特定消息内容的函数 updateMessageContent(messageId, newContent) { const messages this.data.messages; const index messages.findIndex(msg msg.id messageId); if (index ! -1) { messages[index].content newContent; this.setData({ messages, toView: msg-${messageId} }); } }这个方案里updateMessageContent函数会不断更新同一个消息气泡的内容视觉上就形成了逐字输出的效果。轮询间隔可以根据实际情况调整。3.2 后端服务的相应调整你的 Phi-3 后端服务需要配合上述前端逻辑进行改造POST /v1/chat/completions接口收到请求后不直接流式响应而是启动一个后台任务来运行模型推理将生成的文本片段写入一个缓存如 Redis并立即返回{“task_id”: “xxx”}。新增GET /v1/chat/task/{task_id}接口从缓存中读取该任务当前已生成的全部文本并返回状态streaming,completed,failed和内容。这样前端通过轮询这个状态接口就能模拟出流式接收的效果。虽然增加了网络请求次数但避开了小程序处理 SSE 的复杂性是一种实用的折中方案。4. 关键细节与避坑指南在实际开发中你肯定会遇到一些坑。这里我总结几个最常见的域名配置与 HTTPS微信小程序要求请求的域名必须备案且支持 HTTPS。确保你的 API 地址是https://开头并且已经在小程序后台的request合法域名列表中正确配置。TLS 版本需要支持 TLS 1.2 及以上。超时设置大模型推理可能耗时较长。小程序默认的wx.request超时时间可能不够。你可以在请求时设置timeout参数单位毫秒比如timeout: 600001分钟。对于流式/轮询方案更要注意超时逻辑。并发与性能避免用户快速连续发送消息导致前端状态混乱或后端压力过大。可以通过加载状态isLoading禁用发送按钮或者使用请求队列。错误处理要友好网络错误、服务端错误、模型生成错误都要捕获并在前端给用户明确的提示而不是一个空白或卡死界面。内容安全如果你的小程序对公众开放务必在后端对用户的输入和模型的输出进行内容安全过滤避免产生违规内容这既是平台要求也是负责任的做法。体验优化在等待响应时可以显示一个加载动画。对于流式响应如果回传速度很快逐字输出可能显得“卡顿”可以改为按词或短句更新提升流畅感。5. 回顾与展望走完这一趟你会发现把自有大模型接入微信小程序核心就是打通前后端的数据通道。前端负责收集用户输入、展示友好界面、处理流式数据后端负责承载模型、处理逻辑、提供稳定 API。我们今天采用的“轮询模拟流式”方案在体验和实现复杂度上取得了不错的平衡特别适合作为第一个可运行的版本。当你把这个基础版本跑通后就可以根据实际需求去优化了。比如追求更实时的体验可以研究在小程序里使用WebSocket来接收后端真正的数据流如果考虑更复杂的业务逻辑可以引入小程序云开发作为中间层让云函数去处理复杂的流式请求再通过云数据库或云调用将数据推给前端。Phi-3 这类轻量模型响应速度快资源消耗相对小非常适合集成到小程序这种轻量级平台中。你可以基于这个框架扩展出各种有趣的应用比如法律咨询助手、编程答疑机器人、创意文案生成器等等。关键是先让第一个对话跑起来然后再慢慢打磨细节添加历史记录、多轮对话管理、上下文长度控制等功能。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章