feat: openai base url

This commit is contained in:
archer
2023-04-25 20:02:35 +08:00
parent ce68791c3c
commit fb08f61eb5
21 changed files with 47 additions and 385 deletions

View File

@@ -1,7 +1,6 @@
# proxy
AXIOS_PROXY_HOST=127.0.0.1
AXIOS_PROXY_PORT_FAST=7890
AXIOS_PROXY_PORT_NORMAL=7890
AXIOS_PROXY_PORT=7890
queueTask=1
parentUrl=https://hostname/api/openapi/startEvents
# email
@@ -16,6 +15,7 @@ aliTemplateCode=SMS_xxx
TOKEN_KEY=xxx
# openai
OPENAIKEY=sk-xxx
OPENAI_BASE_URL=https://api.openai.com/v1
# db
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/test?authSource=admin
PG_HOST=0.0.0.0

View File

@@ -11,8 +11,10 @@ Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接
```bash
# proxy不需要代理可忽略
AXIOS_PROXY_HOST=127.0.0.1
AXIOS_PROXY_PORT_FAST=7890
AXIOS_PROXY_PORT_NORMAL=7890
AXIOS_PROXY_PORT=7890
# 中转方案,修改 openai 的 base url
OPENAI_BASE_URL=https://api.openai.com/v1
# 是否开启队列任务。 1-开启0-关闭请求parentUrl去执行任务,单机时直接填1
queueTask=1
parentUrl=https://hostname/api/openapi/startEvents
# email参考 nodeMail 获取参数
@@ -181,24 +183,17 @@ services:
restart: always
container_name: fast-gpt
environment:
# 代理(不需要代理,可去掉下面三个参数)
- AXIOS_PROXY_HOST=127.0.0.1
- AXIOS_PROXY_PORT_FAST=7890
- AXIOS_PROXY_PORT_NORMAL=7890
# 邮箱
- AXIOS_PROXY_PORT=7890
- MY_MAIL=xxxx@qq.com
- MAILE_CODE=xxxx
# 阿里云短信
- aliAccessKeyId=xxxx
- aliAccessKeySecret=xxxx
- aliSignName=xxxxx
- aliTemplateCode=SMS_xxxx
# 登录 key
- TOKEN_KEY=xxxx
# 是否开启队列任务。 1-开启0-关闭请求parentUrl去执行任务,单机时直接填1
- queueTask=1
- parentUrl=https://hostname/api/openapi/startEvents
# db
- MONGODB_URI=mongodb://username:passsword@0.0.0.0:27017/?authSource=admin
- MONGODB_NAME=xxx
- PG_HOST=0.0.0.0
@@ -206,7 +201,6 @@ services:
- PG_USER=xxx
- PG_PASSWORD=xxx
- PG_DB_NAME=xxx
# openai 账号
- OPENAIKEY=sk-xxxxx
nginx:
image: nginx:alpine3.17

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { getOpenAIApi, authChat } from '@/service/utils/auth';
import { httpsAgent, openaiChatFilter } from '@/service/utils/tools';
import { axiosConfig, openaiChatFilter } from '@/service/utils/tools';
import { ChatItemType } from '@/types/chat';
import { jsonRes } from '@/service/response';
import { PassThrough } from 'stream';
@@ -88,7 +88,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
{
timeout: 40000,
responseType: 'stream',
httpsAgent: httpsAgent(!userApiKey)
...axiosConfig
}
);

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authChat } from '@/service/utils/auth';
import { httpsAgent, systemPromptFilter, openaiChatFilter } from '@/service/utils/tools';
import { axiosConfig, systemPromptFilter, openaiChatFilter } from '@/service/utils/tools';
import { ChatItemType } from '@/types/chat';
import { jsonRes } from '@/service/response';
import { PassThrough } from 'stream';
@@ -150,7 +150,7 @@ ${
{
timeout: 40000,
responseType: 'stream',
httpsAgent: httpsAgent(!userApiKey)
...axiosConfig
}
);

View File

@@ -3,7 +3,7 @@ import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/tools';
import axios from 'axios';
import { httpsAgent } from '@/service/utils/tools';
import { axiosConfig } from '@/service/utils/tools';
/**
* 读取网站的内容
@@ -22,7 +22,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const data = await axios
.get(url, {
httpsAgent: httpsAgent(false)
httpsAgent: axiosConfig.httpsAgent
})
.then((res) => res.data as string);

View File

@@ -1,11 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { Chat, Model, Training, connectToDatabase } from '@/service/mongo';
import { Chat, Model, connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/tools';
import { getUserApiOpenai } from '@/service/utils/openai';
import { TrainingStatusEnum } from '@/constants/model';
import { TrainingItemType } from '@/types/training';
import { httpsAgent } from '@/service/utils/tools';
import { PgClient } from '@/service/pg';
/* 获取我的模型 */
@@ -47,31 +43,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
modelId
});
// 查看是否正在训练
const training: TrainingItemType | null = await Training.findOne({
modelId,
status: TrainingStatusEnum.pending
});
// 如果正在训练需要删除openai上的相关信息
if (training) {
const { openai } = await getUserApiOpenai(userId);
// 获取训练记录
const tuneRecord = await openai.retrieveFineTune(training.tuneId, {
httpsAgent: httpsAgent(false)
});
// 删除训练文件
openai.deleteFile(tuneRecord.data.training_files[0].id, { httpsAgent: httpsAgent(false) });
// 取消训练
openai.cancelFineTune(training.tuneId, { httpsAgent: httpsAgent(false) });
}
// 删除对应训练记录
await Training.deleteMany({
modelId
});
// 删除模型
await Model.deleteOne({
_id: modelId,

View File

@@ -1,52 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Training } from '@/service/mongo';
import { authToken } from '@/service/utils/tools';
// 关闭next默认的bodyParser处理方式
export const config = {
api: {
bodyParser: false
}
};
/* 获取模型训练记录 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { authorization } = req.headers;
if (!authorization) {
throw new Error('无权操作');
}
const { modelId } = req.query;
if (!modelId) {
throw new Error('参数错误');
}
await authToken(authorization);
await connectToDatabase();
/* 获取 modelId 下的 training 记录 */
const records = await Training.find({
modelId
});
jsonRes(res, {
data: records
});
} catch (err: any) {
/* 清除上传的文件,关闭训练记录 */
// @ts-ignore
if (openai) {
// @ts-ignore
uploadFileId && openai.deleteFile(uploadFileId);
// @ts-ignore
trainId && openai.cancelFineTune(trainId);
}
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,106 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Model, Training } from '@/service/mongo';
import { authToken } from '@/service/utils/tools';
import { getUserApiOpenai } from '@/service/utils/openai';
import type { ModelSchema } from '@/types/mongoSchema';
import { TrainingItemType } from '@/types/training';
import { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model';
import { OpenAiTuneStatusEnum } from '@/service/constants/training';
import { httpsAgent } from '@/service/utils/tools';
/* 更新训练状态 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { authorization } = req.headers;
if (!authorization) {
throw new Error('无权操作');
}
const { modelId } = req.query as { modelId: string };
if (!modelId) {
throw new Error('参数错误');
}
const userId = await authToken(authorization);
await connectToDatabase();
// 获取模型
const model = await Model.findById<ModelSchema>(modelId);
if (!model || model.status !== 'training') {
throw new Error('模型不在训练中');
}
// 查询正在训练中的训练记录
const training: TrainingItemType | null = await Training.findOne({
modelId,
status: 'pending'
});
if (!training) {
throw new Error('找不到训练记录');
}
// 用户的 openai 实例
const { openai } = await getUserApiOpenai(userId);
// 获取 openai 的训练情况
const { data } = await openai.retrieveFineTune(training.tuneId, {
httpsAgent: httpsAgent(false)
});
// console.log(data);
if (data.status === OpenAiTuneStatusEnum.succeeded) {
// 删除训练文件
openai.deleteFile(data.training_files[0].id, { httpsAgent: httpsAgent(false) });
// 更新模型状态和模型内容
await Model.findByIdAndUpdate(modelId, {
status: ModelStatusEnum.running,
updateTime: new Date(),
service: {
...model.service,
trainId: data.fine_tuned_model, // 训练完后,再次训练和对话使用的 model 是一样的
chatModel: data.fine_tuned_model
}
});
// 更新训练数据
await Training.findByIdAndUpdate(training._id, {
status: TrainingStatusEnum.succeed
});
return jsonRes(res, {
data: '模型微调完成'
});
}
/* 取消微调 */
if (data.status === OpenAiTuneStatusEnum.cancelled) {
// 删除训练文件
openai.deleteFile(data.training_files[0].id, { httpsAgent: httpsAgent(false) });
// 更新模型
await Model.findByIdAndUpdate(modelId, {
status: ModelStatusEnum.running,
updateTime: new Date()
});
// 更新训练数据
await Training.findByIdAndUpdate(training._id, {
status: TrainingStatusEnum.canceled
});
return jsonRes(res, {
data: '模型微调已取消'
});
}
jsonRes(res, {
data: '模型还在训练中'
});
} catch (err: any) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,130 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Model, Training } from '@/service/mongo';
import formidable from 'formidable';
import { authToken } from '@/service/utils/tools';
import { getUserApiOpenai } from '@/service/utils/openai';
import { join } from 'path';
import fs from 'fs';
import type { ModelSchema } from '@/types/mongoSchema';
import type { OpenAIApi } from 'openai';
import { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model';
import { httpsAgent } from '@/service/utils/tools';
// 关闭next默认的bodyParser处理方式
export const config = {
api: {
bodyParser: false
}
};
/* 上传文件,开始微调 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
let openai: OpenAIApi, trainId: string, uploadFileId: string;
try {
const { authorization } = req.headers;
if (!authorization) {
throw new Error('无权操作');
}
const { modelId } = req.query;
if (!modelId) {
throw new Error('参数错误');
}
const userId = await authToken(authorization);
await connectToDatabase();
// 获取模型的状态
const model = await Model.findById<ModelSchema>(modelId);
if (!model || model.status !== 'running') {
throw new Error('模型正忙');
}
// const trainingType = model.service.modelType
const trainingType = model.service.trainId; // 目前都默认是 openai text-davinci-03
// 获取用户的 API Key 实例化后的对象
const user = await getUserApiOpenai(userId);
openai = user.openai;
// 接收文件并保存
const form = formidable({
uploadDir: join(process.cwd(), 'public/trainData'),
keepExtensions: true
});
const { files } = await new Promise<{
fields: formidable.Fields;
files: formidable.Files;
}>((resolve, reject) => {
form.parse(req, (err, fields, files) => {
if (err) return reject(err);
resolve({ fields, files });
});
});
const file = files.file;
// 上传文件到 openai
// @ts-ignore
const uploadRes = await openai.createFile(
// @ts-ignore
fs.createReadStream(file.filepath),
'fine-tune',
{ httpsAgent: httpsAgent(false) }
);
uploadFileId = uploadRes.data.id; // 记录上传文件的 ID
// 开始训练
const trainRes = await openai.createFineTune(
{
training_file: uploadFileId,
model: trainingType,
suffix: model.name,
n_epochs: 4
},
{ httpsAgent: httpsAgent(false) }
);
trainId = trainRes.data.id; // 记录训练 ID
// 创建训练记录
await Training.create({
serviceName: 'openai',
tuneId: trainId,
status: TrainingStatusEnum.pending,
modelId
});
// 修改模型状态
await Model.findByIdAndUpdate(modelId, {
$inc: {
trainingTimes: +1
},
updateTime: new Date(),
status: ModelStatusEnum.training
});
jsonRes(res, {
data: 'start training'
});
} catch (err: any) {
/* 清除上传的文件,关闭训练记录 */
// @ts-ignore
if (openai) {
// @ts-ignore
uploadFileId && openai.deleteFile(uploadFileId, { httpsAgent: httpsAgent(false) });
// @ts-ignore
trainId && openai.cancelFineTune(trainId, { httpsAgent: httpsAgent(false) });
}
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,8 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase, Model } from '@/service/mongo';
import { getOpenAIApi } from '@/service/utils/auth';
import { httpsAgent, openaiChatFilter, authOpenApiKey } from '@/service/utils/tools';
import { ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum } from 'openai';
import { axiosConfig, openaiChatFilter, authOpenApiKey } from '@/service/utils/tools';
import { ChatItemType } from '@/types/chat';
import { jsonRes } from '@/service/response';
import { PassThrough } from 'stream';
@@ -101,7 +100,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
{
timeout: 40000,
responseType: isStream ? 'stream' : 'json',
httpsAgent: httpsAgent(true)
...axiosConfig
}
);

View File

@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase, Model } from '@/service/mongo';
import { getOpenAIApi } from '@/service/utils/auth';
import { authOpenApiKey } from '@/service/utils/tools';
import { httpsAgent, openaiChatFilter, systemPromptFilter } from '@/service/utils/tools';
import { axiosConfig, openaiChatFilter, systemPromptFilter } from '@/service/utils/tools';
import { ChatItemType } from '@/types/chat';
import { jsonRes } from '@/service/response';
import { PassThrough } from 'stream';
@@ -120,7 +120,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
{
timeout: 180000,
httpsAgent: httpsAgent(true)
...axiosConfig
}
);
@@ -196,7 +196,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
{
timeout: 180000,
responseType: isStream ? 'stream' : 'json',
httpsAgent: httpsAgent(true)
...axiosConfig
}
);

View File

@@ -1,12 +1,11 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase, Model } from '@/service/mongo';
import {
httpsAgent,
axiosConfig,
systemPromptFilter,
authOpenApiKey,
openaiChatFilter
} from '@/service/utils/tools';
import { ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum } from 'openai';
import { ChatItemType } from '@/types/chat';
import { jsonRes } from '@/service/response';
import { PassThrough } from 'stream';
@@ -172,7 +171,7 @@ ${
{
timeout: 180000,
responseType: isStream ? 'stream' : 'json',
httpsAgent: httpsAgent(true)
...axiosConfig
}
);

View File

@@ -119,31 +119,27 @@ const InputDataModal = ({
pb={2}
>
<Box flex={2} mr={[0, 4]} mb={[4, 0]} h={['230px', '100%']}>
<Box h={'30px'}></Box>
<Box h={'30px'}>{'匹配的知识点'}</Box>
<Textarea
placeholder={
'相关问题,可以输入多个问法, 最多 1000 字。例如:\n1. laf 是什么?\n2. laf 可以做什么?\n3. laf怎么用'
}
maxLength={1000}
placeholder={'匹配的知识点。这部分内容会被搜索,请把控内容的质量。最多 1000 字。'}
maxLength={2000}
resize={'none'}
h={'calc(100% - 30px)'}
{...register(`q`, {
required: '相关问题,可以回车输入多个问法'
required: true
})}
/>
</Box>
<Box flex={3} h={['330px', '100%']}>
<Box h={'30px'}></Box>
<Box h={'30px'}></Box>
<Textarea
placeholder={
'知识点,最多 2000 字。例如:\n1. laf是一个云函数开发平台。\n2. laf 什么都能做。\n3. 下面是使用 laf 的例子: ……'
'补充知识。这部分内容不会被搜索,但会作为"匹配的知识点"的内容补充,你可以讲一些细节的内容填写在这里。最多 2000 字。'
}
maxLength={2000}
resize={'none'}
h={'calc(100% - 30px)'}
{...register(`a`, {
required: '知识点'
})}
{...register('a')}
/>
</Box>
</Box>

View File

@@ -204,8 +204,8 @@ const ModelDataCard = ({ modelId }: { modelId: string }) => {
<Table variant={'simple'} w={'100%'}>
<Thead>
<Tr>
<Th>{'匹配内容(问题)'}</Th>
<Th></Th>
<Th>{'匹配的知识点'}</Th>
<Th></Th>
<Th></Th>
<Th></Th>
</Tr>

View File

@@ -1,6 +1,6 @@
import { DataItem } from '@/service/mongo';
import { getOpenAIApi } from '@/service/utils/auth';
import { httpsAgent } from '@/service/utils/tools';
import { axiosConfig } from '@/service/utils/tools';
import { getOpenApiKey } from '../utils/openai';
import type { ChatCompletionRequestMessage } from 'openai';
import { DataItemSchema } from '@/types/mongoSchema';
@@ -80,8 +80,8 @@ export async function generateAbstract(next = false): Promise<any> {
]
},
{
timeout: 120000,
httpsAgent: httpsAgent(!userApiKey)
timeout: 180000,
...axiosConfig
}
);

View File

@@ -1,6 +1,6 @@
import { SplitData } from '@/service/mongo';
import { getOpenAIApi } from '@/service/utils/auth';
import { httpsAgent } from '@/service/utils/tools';
import { axiosConfig } from '@/service/utils/tools';
import { getOpenApiKey } from '../utils/openai';
import type { ChatCompletionRequestMessage } from 'openai';
import { ChatModelEnum } from '@/constants/model';
@@ -97,7 +97,7 @@ A2:
},
{
timeout: 180000,
httpsAgent: httpsAgent(!userApiKey)
...axiosConfig
}
)
.then((res) => {

View File

@@ -31,21 +31,11 @@ export async function connectToDatabase(): Promise<void> {
generateVector(true);
// 创建代理对象
if (
process.env.AXIOS_PROXY_HOST &&
process.env.AXIOS_PROXY_PORT_FAST &&
process.env.AXIOS_PROXY_PORT_NORMAL
) {
global.httpsAgentFast = tunnel.httpsOverHttp({
if (process.env.AXIOS_PROXY_HOST && process.env.AXIOS_PROXY_PORT) {
global.httpsAgent = tunnel.httpsOverHttp({
proxy: {
host: process.env.AXIOS_PROXY_HOST,
port: +process.env.AXIOS_PROXY_PORT_FAST
}
});
global.httpsAgentNormal = tunnel.httpsOverHttp({
proxy: {
host: process.env.AXIOS_PROXY_HOST,
port: +process.env.AXIOS_PROXY_PORT_NORMAL
port: +process.env.AXIOS_PROXY_PORT
}
});
}

View File

@@ -8,7 +8,8 @@ import mongoose from 'mongoose';
export const getOpenAIApi = (apiKey: string) => {
const configuration = new Configuration({
apiKey
apiKey,
basePath: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'
});
return new OpenAIApi(configuration, undefined);

View File

@@ -2,7 +2,7 @@ import type { NextApiResponse } from 'next';
import type { PassThrough } from 'stream';
import { createParser, ParsedEvent, ReconnectInterval } from 'eventsource-parser';
import { getOpenAIApi } from '@/service/utils/auth';
import { httpsAgent } from './tools';
import { axiosConfig } from './tools';
import { User } from '../models/user';
import { formatPrice } from '@/utils/user';
import { embeddingModel } from '@/constants/model';
@@ -85,7 +85,7 @@ export const openaiCreateEmbedding = async ({
},
{
timeout: 60000,
httpsAgent: httpsAgent(isPay)
...axiosConfig
}
)
.then((res) => ({

View File

@@ -84,9 +84,10 @@ export const authOpenApiKey = async (req: NextApiRequest) => {
}
};
/* 代理 */
export const httpsAgent = (fast: boolean) =>
fast ? global.httpsAgentFast : global.httpsAgentNormal;
/* openai axios config */
export const axiosConfig = {
httpsAgent: global.httpsAgent
};
/* delete invalid symbol */
const simplifyStr = (str: string) =>

View File

@@ -11,8 +11,7 @@ declare global {
var generatingAbstract: boolean;
var generatingVector: boolean;
var QRCode: any;
var httpsAgentFast: Agent;
var httpsAgentNormal: Agent;
var httpsAgent: Agent;
interface Window {
['pdfjs-dist/build/pdf']: any;