通义千问1.5-1.8B-Chat-GPTQ-Int4与微信小程序开发结合:打造个人AI聊天助手

想不想拥有一个属于自己的、随时可以聊天的AI助手?把它装进微信里,不用下载新App,点开就能用。今天,我们就来动手实现这个想法。整个过程不复杂,你不需要是资深的AI专家或全栈工程师,只要跟着步骤走,就能把一个轻量级的通义千问大模型,和一个简洁的微信小程序前端连接起来,做成一个可以分享给朋友的个人智能聊天工具。

这个方案的核心思路很清晰:我们把计算密集的模型推理工作,交给部署在云端的专业平台来处理;而我们自己,则专注于打造一个用户友好、交互流畅的微信小程序界面。这样一来,你既能享受到大模型的智能,又无需操心复杂的服务器运维和昂贵的算力成本。下面,我就带你一步步走完全流程,从模型部署到小程序编码,最后实现一个能打字、能“思考”、能流畅回复的聊天应用。

1. 准备工作:模型部署与接口获取

在开始写小程序代码之前,我们需要先把AI大脑——也就是通义千问模型——准备好,并拿到与它对话的“钥匙”(API接口)。

1.1 部署通义千问模型后端

首先,我们需要一个地方来运行模型。这里我们选择在星图镜像广场上,一键部署一个已经优化好的通义千问模型镜像。这个镜像里的模型是 Qwen1.5-1.8B-ChatGPTQ-Int4 量化版本。简单解释一下,1.8B是指它有18亿参数,属于“小而美”的模型,在保证不错对话能力的同时,对资源要求不高;GPTQ-Int4是一种模型压缩技术,能把模型“瘦身”到原来的四分之一左右,让它在推理时速度更快、占用内存更少,非常适合我们这种个人项目。

部署过程非常简单,基本上就是“点几下”的事情:

  1. 访问星图镜像广场,搜索“通义千问”或“Qwen1.5”。
  2. 找到带有“GPTQ-Int4”或“量化”标签的1.8B-Chat版本镜像。
  3. 点击“部署”或“运行”,平台通常会让你选择一下基础配置(比如CPU/GPU型号、内存大小)。对于1.8B-Int4这个模型,选择一款基础的GPU实例就完全够用了,成本也很低。
  4. 确认部署后,等待几分钟,实例就会启动完成。

部署成功后,最关键的是获取模型的 API访问地址。这个地址通常是平台分配的一个URL,比如 https://your-instance-id.region.example.com/v1。请务必记下这个地址,我们的小程序后面就要和这个地址通信。

1.2 理解后端API格式

模型跑起来后,它会提供一个兼容 OpenAI API格式 的接口。这意味着,我们调用它的方式,和调用ChatGPT的官方API非常相似,大大降低了学习成本。

主要用到的接口是 /v1/chat/completions,我们向它发送一个HTTP POST请求。请求体(body)是一个JSON对象,核心结构如下:

{
  "model": "qwen1.5-1.8b-chat", // 指定模型,有时可省略
  "messages": [
    {"role": "system", "content": "你是一个乐于助人的AI助手。"}, // 系统指令,设定AI角色
    {"role": "user", "content": "你好,请介绍一下你自己。"} // 用户当前的问题
  ],
  "stream": true, // 是否启用流式输出。强烈建议设为true,用户体验更好。
  "max_tokens": 1024 // 限制AI回复的最大长度
}

stream: true 时,后端返回的不是一个完整的JSON,而是一个 数据流(Server-Sent Events, SSE)。AI会一个字一个字地生成回复,并通过这个流实时推送给前端。我们的小程序需要有能力处理这种流式数据,并实时显示到聊天界面上。

好了,后台服务已经就绪,API钥匙也拿到了。接下来,我们进入激动人心的部分——打造小程序的界面和逻辑。

2. 构建微信小程序前端

微信小程序提供了丰富的组件和API,让我们能快速构建出美观且功能强大的应用界面。我们主要需要两个页面:一个聊天主界面,一个可能用到的设置页面。这里我们聚焦于核心的聊天功能。

2.1 项目初始化与页面结构

首先,在微信开发者工具中创建一个新的小程序项目。然后,我们设计聊天页面的基本结构,编辑 pages/chat/chat.wxml 文件:

<!-- pages/chat/chat.wxml -->
<view class="chat-container">
  <!-- 聊天消息列表区域 -->
  <scroll-view class="message-list" scroll-y scroll-into-view="{{scrollToView}}" scroll-with-animation>
    <block wx:for="{{messages}}" wx:key="id">
      <!-- 用户消息 -->
      <view wx:if="{{item.role === 'user'}}" class="message user-message">
        <view class="avatar user-avatar">我</view>
        <view class="bubble">{{item.content}}</view>
      </view>
      <!-- AI消息 -->
      <view wx:elif="{{item.role === 'assistant'}}" class="message assistant-message">
        <view class="avatar assistant-avatar">AI</view>
        <view class="bubble">
          <text>{{item.content}}</text>
          <!-- 加载动画,仅在AI思考时显示 -->
          <view wx:if="{{item.loading}}" class="typing-indicator">
            <text>.</text><text>.</text><text>.</text>
          </view>
        </view>
      </view>
    </block>
  </scroll-view>

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

这个结构清晰地区分了用户消息和AI消息,并为AI消息预留了加载状态指示器。scroll-view 确保了新消息出现时能自动滚动到底部。

2.2 样式美化

光有结构不够,还得好看。我们来添加一些样式,编辑 pages/chat/chat.wxss

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

.message-list {
  flex: 1;
  padding: 20rpx;
  box-sizing: border-box;
  overflow: hidden; /* 重要,确保滚动区域正确 */
}

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

.user-message {
  justify-content: flex-end;
}

.avatar {
  width: 80rpx;
  height: 80rpx;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 28rpx;
  color: white;
  flex-shrink: 0;
  margin: 0 20rpx;
}

.user-avatar {
  background-color: #07c160; /* 微信绿色 */
  order: 1; /* 用户头像在右边 */
}

.assistant-avatar {
  background-color: #576b95; /* 深蓝色 */
}

.bubble {
  max-width: 65%;
  padding: 20rpx 28rpx;
  border-radius: 12rpx;
  line-height: 1.5;
  font-size: 32rpx;
  word-break: break-word;
}

.user-message .bubble {
  background-color: #95ec69;
  color: #000;
  border-top-right-radius: 0;
}

.assistant-message .bubble {
  background-color: #fff;
  color: #333;
  border-top-left-radius: 0;
  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}

.typing-indicator {
  display: inline-flex;
}
.typing-indicator text {
  animation: blink 1.4s infinite;
  margin-left: 2rpx;
}
.typing-indicator text:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator text:nth-child(3) { animation-delay: 0.4s; }

@keyframes blink {
  0%, 100% { opacity: 0.2; }
  50% { opacity: 1; }
}

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

.input-box {
  flex: 1;
  border: 1rpx solid #ddd;
  border-radius: 40rpx;
  padding: 20rpx 30rpx;
  font-size: 32rpx;
  margin-right: 20rpx;
  background-color: #f9f9f9;
}

.send-btn {
  background-color: #07c160;
  color: white;
  border-radius: 40rpx;
  padding: 0 40rpx;
  height: 80rpx;
  line-height: 80rpx;
  font-size: 32rpx;
}
.send-btn[disabled] {
  background-color: #cccccc;
}

现在,界面应该看起来清爽多了,有了聊天气泡和友好的视觉反馈。

2.3 实现核心聊天逻辑

界面搭好了,接下来是让它们“动”起来。编辑 pages/chat/chat.js,这是我们小程序的“大脑”。

首先,在 data 中定义需要的数据:

// pages/chat/chat.js
Page({
  data: {
    messages: [], // 存储所有聊天消息
    inputValue: '', // 输入框内容
    isLoading: false, // 是否正在请求中(禁用发送按钮)
    scrollToView: '', // 控制滚动到底部的视图ID
    autoFocus: true // 自动聚焦输入框
  },
  
  onLoad: function() {
    // 页面加载时,可以初始化一条欢迎语
    this.setData({
      messages: [{
        id: this.generateId(),
        role: 'assistant',
        content: '你好!我是你的个人AI助手,基于通义千问模型。有什么可以帮你的吗?'
      }]
    });
    // 稍后滚动到底部
    setTimeout(() => this.scrollToBottom(), 100);
  },
  
  generateId: function() {
    return 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
  },
  
  // 输入框内容变化
  onInput: function(e) {
    this.setData({
      inputValue: e.detail.value
    });
  },
  
  // 发送消息
  sendMessage: function() {
    const userInput = this.data.inputValue.trim();
    if (!userInput || this.data.isLoading) return;
    
    // 1. 清空输入框
    this.setData({ inputValue: '' });
    
    // 2. 将用户消息添加到列表
    const userMessage = {
      id: this.generateId(),
      role: 'user',
      content: userInput
    };
    const newMessages = [...this.data.messages, userMessage];
    this.setData({ messages: newMessages });
    
    // 3. 添加一个初始为空的AI消息,并显示加载动画
    const assistantMessageId = this.generateId();
    const assistantMessage = {
      id: assistantMessageId,
      role: 'assistant',
      content: '',
      loading: true
    };
    this.setData({ 
      messages: [...newMessages, assistantMessage],
      isLoading: true 
    });
    
    // 4. 滚动到底部,并开始请求AI回复
    this.scrollToBottom();
    this.requestAIResponse(userInput, assistantMessageId);
  },

上面的代码处理了用户发送消息的本地逻辑。接下来是最关键的一步:如何与部署好的通义千问模型API进行通信,并处理流式响应。

2.4 对接后端API与处理流式响应

微信小程序提供了 wx.requestwx.connectSocket 等网络API。对于流式响应,我们通常使用 wx.request 并监听 onChunkReceived 事件(基础库2.14.0以上),或者使用更通用的 WebSocket。这里我们使用 wx.request 的流式支持,因为它更简单直观。

chat.js 中继续添加方法:

  // 请求AI回复
  requestAIResponse: function(userInput, assistantMessageId) {
    const that = this;
    // 注意:这里需要替换成你实际部署的API地址
    const apiUrl = 'https://your-deployed-instance.com/v1/chat/completions';
    // 可以在小程序管理后台设置服务器域名,并将此地址加入request合法域名
    
    const requestTask = wx.request({
      url: apiUrl,
      method: 'POST',
      header: {
        'Content-Type': 'application/json',
        // 如果部署的镜像需要API Key,请在此处添加
        // 'Authorization': 'Bearer your-api-key-here'
      },
      data: {
        model: 'qwen1.5-1.8b-chat',
        messages: [
          // 可以保留对话历史,让AI有上下文记忆
          // ...this.data.messages.slice(0, -1).map(msg => ({role: msg.role, content: msg.content})),
          // 为了简单演示,我们只发送最新的用户消息和系统指令
          {role: 'system', content: '你是一个友好且乐于助人的AI助手。回答应简洁明了。'},
          {role: 'user', content: userInput}
        ],
        stream: true, // 开启流式输出
        max_tokens: 1024
      },
      responseType: 'text', // 流式响应需要设置为text
      enableChunked: true, // 启用分块传输
      
      success(res) {
        // 对于流式请求,success回调在连接建立时即触发,实际数据在onChunkReceived
        console.log('连接建立成功', res.statusCode);
      },
      
      fail(err) {
        console.error('请求失败', err);
        that.handleRequestError(assistantMessageId, '请求失败,请检查网络或配置。');
      },
      
      // 接收数据流块
      onChunkReceived: function(res) {
        // res.data 是字符串,可能包含多个SSE格式的数据行
        const chunks = res.data.split('\n\n').filter(line => line.trim());
        
        let fullText = '';
        for (const chunk of chunks) {
          if (chunk.startsWith('data: ')) {
            const dataStr = chunk.substring(6); // 去掉 'data: '
            if (dataStr === '[DONE]') {
              // 流式传输结束
              that.setData({
                isLoading: false
              });
              that.scrollToBottom();
              break;
            }
            try {
              const dataObj = JSON.parse(dataStr);
              const deltaContent = dataObj.choices[0]?.delta?.content || '';
              fullText += deltaContent;
              
              // 实时更新UI中对应的AI消息内容
              that.updateAssistantMessage(assistantMessageId, fullText);
            } catch (e) {
              console.error('解析流数据失败', e, chunk);
            }
          }
        }
      }
    });
    
    // 可选:保存requestTask以便在页面卸载时中断请求
    this.requestTask = requestTask;
  },
  
  // 更新指定ID的AI消息内容
  updateAssistantMessage: function(messageId, newContent) {
    const messages = this.data.messages.map(msg => {
      if (msg.id === messageId) {
        return { ...msg, content: newContent, loading: false };
      }
      return msg;
    });
    this.setData({ messages });
    // 每次更新都尝试滚动到底部
    this.scrollToBottom();
  },
  
  // 处理请求错误
  handleRequestError: function(messageId, errorMsg) {
    const messages = this.data.messages.map(msg => {
      if (msg.id === messageId) {
        return { ...msg, content: `抱歉,出错了: ${errorMsg}`, loading: false };
      }
      return msg;
    });
    this.setData({ 
      messages,
      isLoading: false 
    });
  },
  
  // 滚动到底部
  scrollToBottom: function() {
    const lastMessage = this.data.messages[this.data.messages.length - 1];
    if (lastMessage) {
      this.setData({
        scrollToView: lastMessage.id
      });
    }
  },
  
  onUnload: function() {
    // 页面卸载时,中断可能还在进行的网络请求
    if (this.requestTask) {
      this.requestTask.abort();
    }
  }
});

这段代码是核心。它创建了一个到我们后端API的请求,并开启了流式传输。每当后端传回一个新的数据块(AI生成的一个词或几个词),onChunkReceived 回调就会被触发,我们解析出新的文本内容,并立即更新到小程序的聊天界面上。这样用户就能看到AI是“一个字一个字”思考并回复的,体验非常流畅。

3. 项目配置与上线前检查

代码写完了,但在真机上运行前,还有几步关键的配置。

3.1 配置小程序网络权限

微信小程序对网络请求有严格的安全要求。你需要在小程序管理后台配置 服务器域名

  1. 登录微信公众平台,进入你的小程序管理后台。
  2. 找到 开发 -> 开发管理 -> 开发设置
  3. 服务器域名 部分,找到 request合法域名 列表。
  4. 点击“修改”,将你部署通义千问模型的API地址(例如 https://your-instance-id.region.example.com)添加进去。注意:不能带路径(如 /v1),只能配置到域名或主域名。
  5. 保存并提交。可能需要几分钟生效。

3.2 处理可能的CORS问题

如果你在调试时发现网络请求失败,并提示CORS(跨域)错误,这通常是因为后端服务没有正确配置响应头。由于我们使用的是星图平台的镜像,大多数预置镜像已经处理好了CORS。如果遇到问题,你可能需要检查或调整后端的CORS配置,确保允许你的小程序域名(servicewechat.com)进行跨域访问。这通常需要在部署镜像时,设置相应的环境变量或修改后端代码。

3.3 用户体验优化点

一个基本可用的聊天助手已经完成了。但我们可以让它更好用:

  • 对话历史持久化:使用小程序的本地存储 wx.setStorageSync,在每次对话更新时保存 messages,并在 onLoad 时读取,这样用户关闭小程序再打开,聊天记录还在。
  • 清除对话:在页面添加一个按钮,调用 wx.showModal 确认后,清空 data 中的 messages 和本地存储。
  • 错误处理与重试:网络请求失败时,除了显示错误信息,还可以提供一个“重试”按钮,重新发送上一条用户消息。
  • 复制与分享:长按AI回复的气泡,可以弹出菜单选项,允许用户复制回复内容或分享某段有趣的对话。
  • 模型参数前端化:可以做一个简单的设置页,让用户自己调整 max_tokens(生成长度)或 temperature(创造性),保存在本地。

4. 总结与展望

跟着上面的步骤走一遍,你应该已经拥有了一个运行在自己手机微信里的、功能完整的个人AI聊天助手。它前端界面简洁友好,后端智能由轻量高效的通义千问模型驱动,中间通过标准的API进行通信。整个项目麻雀虽小,五脏俱全,涵盖了现代AI应用的核心链路:模型服务化、API接口调用、前端交互与流式响应处理。

实际体验下来,这个1.8B的量化模型在普通对话、知识问答、简单文案生成等场景下,反应速度很快,效果也足够令人满意,对于个人学习和日常助手用途来说绰绰有余。最重要的是,整个开发和部署成本非常低,你无需购买昂贵的显卡,也无需搭建复杂的运维环境。

当然,这只是一个起点。你可以基于这个框架做很多扩展:比如接入不同的模型(在星图镜像广场尝试其他有趣的模型),增加语音输入输出,设计更复杂的多轮对话场景,或者把它改造成一个专业领域的问答工具。希望这个实践能为你打开一扇门,让你感受到将大模型能力融入自己创意中的乐趣。


获取更多AI镜像

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

Logo

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

更多推荐