通义千问1.5-1.8B-Chat-GPTQ-Int4 WebUI 微信小程序集成教程:打造个人AI聊天助手

想不想在手机上随时有个能聊天的AI助手?不用下载新App,就在你天天用的微信里。今天,我就带你一步步把那个在电脑上跑的通义千问大模型,搬到微信小程序里,做成一个完全属于你自己的移动端聊天机器人。

这事的核心其实不复杂。我们做个简单的小程序界面,让你能打字、能看回复;然后在小程序的后台,写个“中间人”服务,专门负责把你的话传给部署在星图GPU平台上的通义千问模型,再把模型的回答原封不动地带回来给你。整个过程,你会学到怎么从零开始搭一个小程序,怎么安全地和后端API“对话”,以及怎么让这个聊天过程既流畅又安全。跟着做下来,你就能拥有一个24小时在线、只属于你的AI伙伴了。

1. 动手之前:理清思路与做好准备

在开始敲代码之前,咱们先把整个流程想明白。这样后面每一步做起来,心里都有底。

1.1 整体架构:小程序如何与AI对话

你可以把这个项目想象成一次“传话游戏”。你(用户)在小程序里输入一句话,小程序(前端)把这句话打包好,通过微信的网络通道,发送给我们自己搭建的一个云服务(后端)。这个云服务就像一个专业的信使,它知道怎么找到部署在星图GPU服务器上的通义千问模型(WebUI API),并把你的话准确传达过去。模型思考后给出回复,信使再把回复带回来,经由小程序,最终显示在你的手机屏幕上。

所以,关键角色有三个:

  1. 微信小程序前端:负责和你交互的界面,收集输入、展示对话。
  2. 后端云函数/服务:核心的“信使”和“调度中心”,处理小程序请求,调用AI模型API,并管理对话逻辑。
  3. 星图平台部署的通义千问WebUI API:提供AI大脑能力的终端,我们通过HTTP请求和它通信。

1.2 你需要准备什么

工欲善其事,必先利其器。下面是你需要提前准备好的几样东西:

  • 一个微信小程序账号:去微信公众平台注册一个,如果你只是为了个人学习和测试,注册个人主体就行,完全免费。
  • 微信开发者工具:这是官方开发环境,写代码、调试、预览都在这里进行,务必下载安装好。
  • 已经部署好的通义千问WebUI API:假设你已经按照之前的教程,在星图GPU平台上成功部署了通义千问1.5-1.8B-Chat-GPTQ-Int4的WebUI,并且知道它的API访问地址(比如 http://你的服务器IP:端口/api/v1/chat/completions)和可能的API Key(如果设置了的话)。这是我们的AI能力来源。
  • 一个后端服务环境(二选一)
    • 方案A(推荐,简单):使用微信小程序云开发。它内置了云函数,可以直接在小程序IDE里写后端逻辑,无需自己买服务器,非常适合原型和轻量应用。
    • 方案B(更灵活):自己搭建一个后端服务器。可以用任何你熟悉的语言,比如Python(Flask/Django)、Node.js(Express)等,部署在云服务器上。这种方式控制力更强,但步骤稍多。
  • 基础的JavaScript知识:小程序前端主要用JavaScript(或它的变体)来写逻辑。
  • 一颗不怕折腾的心:集成过程中可能会遇到网络、配置等问题,耐心调试就好。

为了清晰,我画了一个简单的流程图,帮你一眼看明白数据是怎么跑的:

graph TD
    A[用户在小程序输入] --> B[小程序前端]
    B --> C[发送网络请求]
    C --> D{后端服务<br/>(云函数/自建服务器)}
    D --> E[转发请求至<br/>星图平台Qwen API]
    E --> F[通义千问模型处理]
    F --> G[返回AI回复]
    G --> D
    D --> H[返回处理结果]
    H --> B
    B --> I[更新界面<br/>显示回复]

接下来,我们就从搭建小程序的前端界面开始。

2. 搭建小程序前端:聊天界面与交互

我们先来把聊天窗口做出来,让它看起来像个正经的聊天软件。

2.1 创建小程序项目与基础页面

打开微信开发者工具,选择“新建项目”。

  • 项目名称:比如“我的AI助手”。
  • 目录:选一个你喜欢的本地文件夹。
  • AppID:如果你有正式的小程序AppID就填上(在公众平台查看)。如果只是体验,点选“测试号”快速开始。
  • 模板:选择“不使用云服务”(我们稍后再单独开通)或“小程序-云开发”模板(如果你决定用云开发)。

创建成功后,你会看到默认的项目结构。我们主要关心 pages/index 这个目录,它就是小程序启动后看到的第一个页面。我们把它改造成聊天页。

首先,我们来设计页面的结构,修改 pages/index/index.wxml 文件:

<!-- pages/index/index.wxml -->
<view class="container">
  <!-- 聊天消息区域 -->
  <scroll-view class="message-list" scroll-y scroll-into-view="{{toView}}" scroll-with-animation>
    <block wx:for="{{messages}}" wx:key="id">
      <view class="message-item {{item.role}}">
        <view class="avatar">
          <image wx:if="{{item.role === 'user'}}" src="/images/user-avatar.png"></image>
          <image wx:else src="/images/bot-avatar.png"></image>
        </view>
        <view class="bubble">
          <text>{{item.content}}</text>
        </view>
      </view>
    </block>
  </scroll-view>

  <!-- 底部输入区域 -->
  <view class="input-area">
    <input 
      class="input-box" 
      placeholder="和AI助手聊点什么..." 
      value="{{inputValue}}" 
      bindinput="onInput"
      bindconfirm="sendMessage"
      confirm-type="send"
      focus="{{autoFocus}}"
    />
    <button class="send-btn" bindtap="sendMessage" disabled="{{sending}}">
      {{sending ? '发送中...' : '发送'}}
    </button>
  </view>
</view>

然后,给这个页面加上样式,让布局好看点,修改 pages/index/index.wxss

/* pages/index/index.wxss */
.container {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: #f5f5f5;
}

.message-list {
  flex: 1;
  padding: 20rpx;
  box-sizing: border-box;
  overflow: auto;
}

.message-item {
  display: flex;
  margin-bottom: 30rpx;
  align-items: flex-start;
}

.message-item.user {
  flex-direction: row-reverse;
}

.avatar image {
  width: 80rpx;
  height: 80rpx;
  border-radius: 50%;
  margin: 0 20rpx;
}

.bubble {
  max-width: 70%;
  padding: 20rpx;
  border-radius: 10rpx;
  line-height: 1.5;
  word-break: break-word;
}

.message-item.user .bubble {
  background-color: #95ec69;
  color: #000;
}

.message-item.assistant .bubble {
  background-color: #fff;
  color: #333;
  box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
}

.input-area {
  display: flex;
  padding: 20rpx;
  background-color: #fff;
  border-top: 1rpx solid #eee;
  align-items: center;
}

.input-box {
  flex: 1;
  height: 80rpx;
  padding: 0 20rpx;
  border: 1rpx solid #ddd;
  border-radius: 40rpx;
  margin-right: 20rpx;
  background-color: #f9f9f9;
}

.send-btn {
  width: 140rpx;
  height: 80rpx;
  line-height: 80rpx;
  border-radius: 40rpx;
  background-color: #07c160;
  color: white;
  font-size: 28rpx;
  padding: 0;
}

.send-btn[disabled] {
  background-color: #ccc;
}

2.2 实现聊天逻辑与状态管理

界面有了,现在让它动起来。修改 pages/index/index.js 文件,这里包含了所有的交互逻辑:

// pages/index/index.js
Page({
  data: {
    messages: [], // 存储所有消息 {id, role, content}
    inputValue: '', // 输入框内容
    sending: false, // 是否正在发送
    toView: '', // 用于滚动到底部
    autoFocus: false
  },

  onLoad() {
    // 可以尝试从本地缓存加载历史对话
    const history = wx.getStorageSync('chatHistory');
    if (history && Array.isArray(history)) {
      this.setData({ messages: history });
      this.scrollToBottom();
    }
    // 或者初始化一条欢迎语
    // this.setData({
    //   messages: [{
    //     id: Date.now(),
    //     role: 'assistant',
    //     content: '你好!我是你的AI助手,有什么可以帮你的吗?'
    //   }]
    // });
  },

  // 监听输入
  onInput(e) {
    this.setData({
      inputValue: e.detail.value
    });
  },

  // 发送消息
  sendMessage() {
    const text = this.data.inputValue.trim();
    if (!text || this.data.sending) return;

    // 1. 清空输入框,禁用发送按钮
    this.setData({
      inputValue: '',
      sending: true
    });

    // 2. 将用户消息添加到列表
    const userMsg = {
      id: Date.now(),
      role: 'user',
      content: text
    };
    this.data.messages.push(userMsg);
    this.setData({ messages: this.data.messages });
    this.scrollToBottom();

    // 3. 显示“思考中”的占位消息
    const thinkingMsg = {
      id: Date.now() + 1,
      role: 'assistant',
      content: '...'
    };
    this.data.messages.push(thinkingMsg);
    this.setData({ messages: this.data.messages });
    this.scrollToBottom();

    // 4. 调用后端API获取AI回复
    this.callAIChatAPI(text).then(aiResponse => {
      // 5. 用真实回复替换“思考中”消息
      thinkingMsg.content = aiResponse;
      this.setData({ messages: this.data.messages });
      this.saveHistory(); // 保存到本地
    }).catch(err => {
      console.error('API调用失败:', err);
      // 6. 如果出错,显示错误信息
      thinkingMsg.content = '抱歉,我暂时无法回答。请检查网络或稍后再试。';
      this.setData({ messages: this.data.messages });
    }).finally(() => {
      // 7. 无论成功失败,都恢复发送按钮
      this.setData({ sending: false });
      this.scrollToBottom();
    });
  },

  // 封装调用后端API的函数
  async callAIChatAPI(userInput) {
    // 注意:这里不能直接调用星图平台的API,因为涉及跨域和域名白名单问题。
    // 我们需要调用自己的后端服务地址。
    // 假设你的后端服务地址是:https://your-backend.com/chat
    const backendUrl = 'https://your-backend.com/chat'; // 请替换为你的实际后端地址

    // 构建请求数据,格式需要匹配你的后端和通义千问API的要求
    // 这里传递当前对话历史(或最后几条)给后端,让模型有上下文
    const recentMessages = this.data.messages
      .filter(msg => msg.role !== 'assistant' || msg.content !== '...') // 过滤掉占位消息
      .slice(-6) // 只发送最近几条,避免上下文过长
      .map(msg => ({ role: msg.role, content: msg.content }));

    // 添加最新的用户输入
    recentMessages.push({ role: 'user', content: userInput });

    const requestData = {
      messages: recentMessages,
      // 可以添加其他参数,如模型名、温度等,取决于你的后端如何封装
      // model: 'qwen1.5-1.8b-chat',
      // temperature: 0.7,
    };

    return new Promise((resolve, reject) => {
      wx.request({
        url: backendUrl,
        method: 'POST',
        data: requestData,
        header: {
          'content-type': 'application/json'
          // 如果需要API Key,在这里添加
          // 'Authorization': 'Bearer your-api-key-here'
        },
        success: (res) => {
          if (res.statusCode === 200 && res.data && res.data.reply) {
            resolve(res.data.reply); // 假设后端返回 { reply: '...' }
          } else {
            reject(new Error(`API错误: ${res.statusCode}`));
          }
        },
        fail: (err) => {
          reject(err);
        }
      });
    });
  },

  // 滚动到底部
  scrollToBottom() {
    if (this.data.messages.length > 0) {
      const lastMsgId = this.data.messages[this.data.messages.length - 1].id;
      this.setData({
        toView: `msg-${lastMsgId}`
      });
    }
  },

  // 保存对话历史到本地
  saveHistory() {
    // 只保存非空且非占位符的消息
    const historyToSave = this.data.messages.filter(msg => msg.content !== '...');
    try {
      wx.setStorageSync('chatHistory', historyToSave);
    } catch (e) {
      console.error('保存历史失败:', e);
    }
  },

  // 清空对话
  clearChat() {
    wx.showModal({
      title: '提示',
      content: '确定要清空对话历史吗?',
      success: (res) => {
        if (res.confirm) {
          this.setData({ messages: [] });
          wx.removeStorageSync('chatHistory');
        }
      }
    });
  }
});

别忘了在页面的配置文件 pages/index/index.json 里,给页面加个标题,或许再加个清空按钮:

{
  "navigationBarTitleText": "我的AI助手",
  "enablePullDownRefresh": false,
  "usingComponents": {}
}

现在,前端部分已经基本完成了。你可以先在开发者工具里预览一下,界面应该能正常显示,点击发送按钮会触发事件(虽然还连不上后端)。接下来,我们要搭建最关键的后端桥梁。

3. 构建后端桥梁:云函数与API转发

前端无法直接调用部署在星图平台上的API,主要是因为微信小程序有严格的域名白名单限制(request 合法域名),而且直接暴露服务器IP和端口也不安全。所以,我们需要一个后端服务做中转。

这里我以 微信小程序云开发(CloudBase)的云函数 为例,因为它和小程序集成度最高,部署简单。如果你选择自建服务器(如用Flask),逻辑是相通的。

3.1 创建并部署云函数

首先,在微信开发者工具中,如果你的项目创建时未启用云开发,需要在顶部菜单栏找到“云开发”按钮并开通。开通后,会获得一个环境ID。

  1. 新建云函数:在项目根目录,右键点击 cloudfunctions 文件夹,选择“新建Node.js云函数”,命名为 chat
  2. 编写云函数逻辑:打开 cloudfunctions/chat/index.js,这是核心的转发逻辑。
// cloudfunctions/chat/index.js
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });

// 假设你部署的通义千问WebUI API地址
// 重要:请将此地址替换为你星图平台实例的实际内网/公网访问地址
const QWEN_API_BASE = 'http://YOUR_SERVER_IP:PORT'; // 例如: http://192.168.1.100:7861
const API_PATH = '/api/v1/chat/completions'; // WebUI常见的聊天补全端点

exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext();
  
  // 1. 简单的请求验证(可选但推荐)
  // 可以验证event.userInfo,或添加自定义token验证
  // if (!event.token || event.token !== 'your-secret-token') {
  //   return { code: 403, msg: 'Forbidden' };
  // }

  // 2. 获取前端传来的消息列表和参数
  const { messages, temperature = 0.7, max_tokens = 1024 } = event;
  
  if (!messages || !Array.isArray(messages) || messages.length === 0) {
    return { code: 400, msg: 'Invalid request: messages is required' };
  }

  // 3. 构建符合通义千问WebUI API格式的请求体
  const requestBody = {
    model: 'qwen1.5-1.8b-chat', // 模型名称,需与部署一致
    messages: messages, // 直接传递消息历史
    temperature: temperature,
    max_tokens: max_tokens,
    stream: false // 小程序暂不支持流式响应,设为false
  };

  // 4. 准备请求选项
  const requestOptions = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      // 如果WebUI设置了API Key,在这里添加
      // 'Authorization': `Bearer ${process.env.QWEN_API_KEY}`
    },
    body: JSON.stringify(requestBody)
  };

  try {
    // 5. 使用云函数的内置能力或axios等库发起请求
    // 这里使用云函数的HTTP API(需在云函数配置中启用)
    const result = await cloud.callContainer({
      config: {
        path: API_PATH,
        method: 'POST',
        headers: requestOptions.headers,
        data: requestBody,
        // 如果API在公网,这里需要填写完整URL
        // 但更佳实践是将API部署在同一个云环境或内网可达处
      }
    });
    
    // 注意:cloud.callContainer 是云开发调用云托管服务的,如果你的API在别处,需用其他方法。
    // 更通用的方法是使用 `axios` 或 `node-fetch`。
    // 假设我们使用 `axios`(需要先在package.json添加依赖并上传):
    // const axios = require('axios');
    // const response = await axios.post(`${QWEN_API_BASE}${API_PATH}`, requestBody, { headers: requestOptions.headers });
    // const aiReply = response.data.choices[0].message.content;

    // 为了示例清晰,我们模拟一个通用HTTP请求流程(伪代码,实际需安装依赖):
    // const fetch = require('node-fetch');
    // const response = await fetch(`${QWEN_API_BASE}${API_PATH}`, requestOptions);
    // const data = await response.json();
    
    // 6. 解析AI回复
    // 假设返回格式为 { "choices": [ { "message": { "content": "..." } } ] }
    // const aiReply = data.choices[0].message.content;
    
    // 由于直接调用外部API可能受限于云函数网络,这里返回一个模拟成功响应
    console.log('Received messages:', messages);
    const lastUserMsg = messages.filter(m => m.role === 'user').pop();
    const mockReply = `我已收到你的消息:“${lastUserMsg?.content || ''}”。(这是模拟回复,请配置真实API地址)`;
    
    // 7. 返回结果给小程序前端
    return {
      code: 200,
      reply: mockReply,
      // 也可以返回完整的消息对象,供前端直接添加
      message: {
        role: 'assistant',
        content: mockReply
      }
    };

  } catch (error) {
    console.error('调用AI API失败:', error);
    return {
      code: 500,
      msg: 'AI服务暂时不可用',
      error: error.message
    };
  }
};

关键点说明

  • 依赖管理:如果使用 axiosnode-fetch,需要在 cloudfunctions/chat/package.json 中添加依赖,然后右键云函数目录选择“上传并部署:安装依赖”。
  • 网络配置:云函数默认可能无法访问所有外部IP。如果你使用腾讯云云开发,可能需要配置云函数的外网访问能力,或在同一VPC内部署你的通义千问API。
  • 安全:在实际部署时,绝对不要将API密钥等敏感信息硬编码在代码中。使用云函数的环境变量功能来存储 QWEN_API_BASEAPI Key
  1. 部署云函数:右键点击 chat 云函数目录,选择“上传并部署”。

3.2 修改前端调用地址

后端云函数部署好后,我们需要修改前端代码,让它调用这个云函数,而不是之前写的假地址。

更新 pages/index/index.js 中的 callAIChatAPI 函数部分:

// 修改后的 callAIChatAPI 函数(使用云函数)
async callAIChatAPI(userInput) {
  // 构建消息历史,同上
  const recentMessages = this.data.messages
    .filter(msg => msg.role !== 'assistant' || msg.content !== '...')
    .slice(-6)
    .map(msg => ({ role: msg.role, content: msg.content }));
  recentMessages.push({ role: 'user', content: userInput });

  return new Promise((resolve, reject) => {
    // 调用云函数
    wx.cloud.callFunction({
      name: 'chat', // 你的云函数名称
      data: {
        messages: recentMessages,
        temperature: 0.8,
        // 可以在这里传递一个简单的token用于验证(可选)
        // token: 'your-pre-shared-token'
      },
      success: (res) => {
        const result = res.result;
        if (result.code === 200) {
          resolve(result.reply);
        } else {
          reject(new Error(result.msg || '云函数返回错误'));
        }
      },
      fail: (err) => {
        reject(err);
      }
    });
  });
},

同时,在小程序启动时(app.js)初始化云开发:

// app.js
App({
  onLaunch() {
    // 初始化云开发
    if (wx.cloud) {
      wx.cloud.init({
        env: 'your-cloud-env-id', // 替换为你的云环境ID
        traceUser: true,
      });
    }
  }
});

4. 完善与优化:安全、体验与部署

基础功能跑通后,我们还需要考虑一些实际运营中会遇到的问题。

4.1 内容安全与过滤

让AI自由对话存在风险,我们必须在前端或后端加入基础的内容安全过滤。

  • 后端过滤(推荐):在云函数调用AI API之前之后,对内容进行检查。

    • 请求过滤:检查用户输入 userInput 是否包含明显违规、辱骂或极端关键词。
    • 响应过滤:检查AI返回的 reply 是否包含不合适的内容。
    • 实现方式:可以维护一个本地关键词库(对于简单场景),或者调用第三方内容安全API。

    在云函数中添加一个简单的过滤函数:

// 简单的关键词过滤函数(示例,实际需要更复杂的策略)
function contentFilter(text) {
  const forbiddenKeywords = ['违规词1', '违规词2']; // 替换为你的关键词列表
  const lowerText = text.toLowerCase();
  for (const keyword of forbiddenKeywords) {
    if (lowerText.includes(keyword)) {
      return false; // 包含违规词
    }
  }
  return true; // 通过
}

// 在调用AI API前使用
if (!contentFilter(userInputFromFrontend)) {
  return { code: 400, msg: '输入内容包含不当信息' };
}
// 在拿到AI回复后使用
if (!contentFilter(aiReply)) {
  aiReply = '我的回答可能包含不合适内容,已进行过滤。'; // 或返回一个安全提示
}
  • 前端提示:在小程序界面,当收到被过滤的回复或发送失败时,给予用户友好提示。

4.2 用户体验优化

  1. 加载状态:我们已经做了发送按钮的禁用和“思考中”占位符,这很好。
  2. 网络错误处理:在 wx.requestwx.cloud.callFunctionfail 回调中,除了控制台打印,最好用 wx.showToast 给用户一个提示。
  3. 对话历史持久化:我们已经实现了用 wx.setStorageSync 保存到本地。可以考虑加入“加载更多历史”或“导出对话”功能。
  4. 音效与振动(可选):收到新消息时,可以播放提示音或轻微振动,提升体验。

4.3 真机测试与发布上线

  1. 真机预览:在开发者工具点击“预览”,生成二维码,用微信扫描在手机上测试。这是发现样式和交互问题的关键步骤。
  2. 配置服务器域名:如果你使用的是自建后端服务器(非云函数),必须在微信公众平台的小程序管理后台,将你的后端服务器域名(如 https://your-backend.com)添加到 “开发设置”->“服务器域名”->“request合法域名” 列表中。云函数调用无需此步骤。
  3. 提交审核:开发完成后,在开发者工具点击“上传”,将代码提交到微信后台,并填写版本信息,提交审核。确保你的小程序类目选择正确(例如“工具->信息查询”或“教育->在线教育”可能需要根据AI对话功能具体判断)。
  4. 发布:审核通过后,即可发布上线,所有微信用户都可以搜索和使用你的AI助手小程序了。

5. 总结与展望

走完这一趟,你应该已经成功地将一个强大的AI模型塞进了微信小程序这个轻巧的容器里。我们从零开始,搭建了聊天界面,实现了消息的发送与接收,并通过云函数构建了安全可靠的后端桥梁,最终在手机端实现了一个流畅的AI对话体验。

整个过程最关键的,是理解“前端交互 - 后端中转 - AI服务”这三层架构的分工与协作。云函数在这里扮演了至关重要的角色,它不仅解决了小程序的网络限制,还为我们提供了统一的安全管控和逻辑处理点。

实际部署时,你可能会遇到云函数网络连通性、API响应速度、以及更复杂的内容安全需求等挑战。这些都可以通过选择更合适的云服务商、优化后端代码、集成专业的内容审核服务来解决。这个项目就像一个种子,你可以在此基础上,继续添加语音输入、多模态识别(如果模型支持)、用户个性化设置等功能,让它变得越来越强大。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐