nodejs搭建chatgpt服务

5分钟快速搭建基于nodejs的chatgpt服务

写在前面

首先大家都知道,在国内是无法成功调用openapi的接口,甚至openai的官网都很难打开。所以如果想使用chatgpt的几乎是不可能,不过咱们可以抱着了解的心态去体验一下。


*阅读本文档您可以了解以下内容*
  • eggjs框架
  • egg-mysql的使用
  • openai的实例创建聊天和图片

准备工作

  • apiKey: 这是调用chatgpt的密钥。目前有两种获取方式:
    1. 通过openai的官网去申请,不过这需要一个国外的手机号。
    2. 去某宝直接购买,大概1块钱。
  • 国外服务器: chatgpt必须用国外的服务器访问。可以:去各大厂购买云服务

就绪

  • 准备环境,本文默认使用的centos7.6
  • 开发环境,nodejs >=16,目前centos只能安装node16及以下版本,所以大家安装时需要注意,不要直接安装最新的nodejs。为了方便切换,可以安装nvm来管理node版本,nvm install nodejs版本nvm use nodejs版本
  • 安装eggjs,和所需插件:
    1. npm init egg --type=simple
    2. npm install
    3. npm install egg-mysql
    4. npm install openai
    5. npm install egg-validate

说明: mysql是为了保存聊天上下文,当然你也可以保存在前端本地,validate是校验参数插件


数据库请自行创建,数据表的创建后面会写在代码中

修改配置文件

  1. config/config.default.js中添加
config.security = {
  csrf: {
    enable: false,
    ignoreJSON: true
  },
  domainWhiteList: ['*']
}

config.cors = {
  origin: '*',
  allowMethods: 'GET,HEAD,POST,DELETE,PATCH'
}

config.mysql = {
  client: {
    host: 'localhost',
    port: '3306',
    user: 'root',
    password: 'Chat9527', // 数据库密码
    database: 'chatDB', // 数据库名称
  },
  app: true,
  agent: false,
}

config.validate = {
  convert: true,
  widelyUndefined: true,
  async formatter(ctx, error) {
    ctx.status = 400;
    ctx.body = {
      code: 'INVALID_PARAM',
      errors: error.errors,
    };
  },
}

config.constant = {
  API_KEY: 'sk-xxxx', // 你的apiKey
};
  1. config/plugin.js中添加
cors: {
  enable: true,
  package: 'egg-cors'
},
validate: {
  enable: true,
  package: 'egg-validate'
},
mysql: {
  enable: true,
  package: 'egg-mysql',
}

api接口

分析目前只有两个接口,一个是openai的聊天,另一个是生成图片
具体代码如下:

module.exports = app => {
  const { router, controller } = app;

  app.beforeStart(async () => {
    const ctx = app.createAnonymousContext();
    await ctx.app.mysql.query(`
      CREATE TABLE IF NOT EXISTS user_contexts (
        id INT AUTO_INCREMENT PRIMARY KEY,
        user_id VARCHAR(255) NOT NULL,
        memory INT(11) DEFAULT NULL,
        affinity INT(11) DEFAULT NULL,
        context TEXT NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
      )
    `);

  });
  router.post('/creatChat', controller.chat.createChat);
  router.post('/creatImg', controller.chat.creatImg);
};

user_id 表示访问服务的用户;context 为聊天的记录;memory和affinity为保留字段可以不要。

openai使用

  • nodejs中使用new openai({ opentions })来创建实例;
  • 使用实例.chat.completions.create({ messages, model: "gpt-3.5-turbo", });来创建一个聊天

具体代码如下:app/controller/chat.js

const { Controller } = require('egg');
const OpenAI = require('openai');

class ChatController extends Controller {
  // 创建图片
  async creatImg() {
    const { ctx } = this;
    const { prompt, n=1, size="1024x1024" } = ctx.request.body;
    const openai = new OpenAI({
      apiKey: this.app.config.constant.API_KEY,
    });
    const response = await openai.images.generate({ prompt, n, size });
    ctx.body = {
      data: response.data
    };
  }
  
  // 创建聊天
  async createChat() {
    const { ctx } = this;
    const { request, service } = ctx;
    const { prompt, userId } = request.body;
    try {
      ctx.validate(this.indexRule, request.body);

      const openai = new OpenAI({
        apiKey: this.app.config.constant.API_KEY,
      });

      const userContext = await service.chat.getUserContext(userId);
      const messages = this.buildMessageList(userContext, prompt);

      const response = await openai.chat.completions.create({
        messages,
        model: "gpt-3.5-turbo",
      });

      const assistantResponse = response.choices[0].message.content.trim();
      const updatedUserContext = this.updateUserContext(userContext, prompt, assistantResponse);
      service.chat.updateUserContext(userId, updatedUserContext);

      const shouldEndConversation = this.isConversationEnding(assistantResponse);
      if (shouldEndConversation) {
        service.chat.deleteUserContext(userId);
        ctx.body = { code: 0, message: "拜拜!" };
      } else {
        ctx.body = { code: 0, message: assistantResponse };
      }

    } catch (err) {
      ctx.status = 442;
      ctx.body = {
        code: -1,
        message: '系统错误',
        error: err.errors,
      }
    }
  }

  // 构建消息列表
  buildMessageList = (userContext, prompt) => {
    return [
      ...userContext,
      { role: "system", content: "You are a helpful assistant that answers questions." },
      { role: "user", content: prompt }
    ];
  }

  // 更新用户上下文
  updateUserContext = (userContext, prompt, assistantResponse) => {
    return [
      ...userContext,
      { role: "user", content: prompt },
      { role: "assistant", content: assistantResponse }
    ];
  }

  // 判断是否结束聊天
  isConversationEnding(response) {
    const keywords = ["再见", "退出", "滚", "拜拜"];
    return keywords.some(keyword => response.includes(keyword));
  }

  // 定义参数校验规则
  get indexRule() {
    return {
      userId: {
        type: 'string',
        required: true,
        format: /^[A-Za-z0-9]+$/,
        message: 'userId不能为空,且只能包含字母和数字',
      },
      prompt: {
        type: 'string',
        required: true,
        max: 300,
        message: 'content不能为空,且长度不能超过300',
      },
    };
  }
}

module.exports = ChatController;
  • app/service/chat.js里是数据的存储逻辑,如下:
const { Service } = require('egg');

class ChatService extends Service {

  // 获取用户上下文
  async getUserContext(userId) {
    const result = await this.app.mysql.select('user_contexts', {
      where: { user_id: userId },
      orders: [['created_at', 'desc']],
      limit: 1,
    });
    return result[0] ? JSON.parse(result[0].context) : [];
  }

  // 更新用户上下文
  async updateUserContext(userId, context) {
    const existingData = await this.app.mysql.get('user_contexts', { user_id: userId });
  
    const record = {
      user_id: userId,
      context: JSON.stringify(context),
      created_at: new Date(),
    };

    if (existingData) {
      await this.app.mysql.update('user_contexts', record, {
        where: { user_id: userId },
      });
    } else {
      await this.app.mysql.insert('user_contexts', record);
    }

  }

  // 删除用户上下文
  async deleteUserContext(userId) {
    await this.app.mysql.delete('user_contexts', {
      user_id: userId
    });
  }
}

module.exports = ChatService;

一切完成就绪

  • 使用 npm run dev 来运行服务器
  • 用Postman 访问 ip:7001/creatChat,参数 userId: '1' , prompt: '你好'
  • 使用 npm start 来后台运行服务
  • 使用 npm stop 来停止服务

花费了不止5分钟?

  • 直接克隆项目 git clone https://github.com/812781385/chatgptService.git
  • npm i
  • npm run dev
posted @ 2023-09-14 19:03  大耳朵小虎  阅读(108)  评论(0编辑  收藏  举报