nodejs搭建chatgpt服务
5分钟快速搭建基于nodejs的chatgpt服务
写在前面
首先大家都知道,在国内是无法成功调用openapi的接口,甚至openai的官网都很难打开。所以如果想使用chatgpt的几乎是不可能,不过咱们可以抱着了解的心态去体验一下。
*阅读本文档您可以了解以下内容*
- eggjs框架
- egg-mysql的使用
- openai的实例创建聊天和图片
准备工作
- apiKey: 这是调用chatgpt的密钥。目前有两种获取方式:
- 通过openai的官网去申请,不过这需要一个国外的手机号。
- 去某宝直接购买,大概1块钱。
- 国外服务器: chatgpt必须用国外的服务器访问。可以:去各大厂购买云服务
就绪
- 准备环境,本文默认使用的centos7.6
- 开发环境,nodejs >=16,目前centos只能安装node16及以下版本,所以大家安装时需要注意,不要直接安装最新的nodejs。为了方便切换,可以安装nvm来管理node版本,
nvm install nodejs版本
,nvm use nodejs版本
- 安装eggjs,和所需插件:
npm init egg --type=simple
npm install
npm install egg-mysql
npm install openai
npm install egg-validate
说明: mysql是为了保存聊天上下文,当然你也可以保存在前端本地,validate是校验参数插件
数据库请自行创建,数据表的创建后面会写在代码中
修改配置文件
- 在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
};
- 在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