This commit is contained in:
Archer
2023-11-09 09:46:57 +08:00
committed by GitHub
parent 661ee79943
commit 8bb5588305
402 changed files with 9899 additions and 5967 deletions

View File

@@ -1,47 +0,0 @@
import { connectionMongo, type Model } from '@fastgpt/service/common/mongo';
const { Schema, model, models } = connectionMongo;
import { BillSchema as BillType } from '@/types/common/bill';
import { BillSourceMap } from '@/constants/user';
const BillSchema = new Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
},
appName: {
type: String,
default: ''
},
appId: {
type: Schema.Types.ObjectId,
ref: 'model',
required: false
},
time: {
type: Date,
default: () => new Date()
},
total: {
type: Number,
required: true
},
source: {
type: String,
enum: Object.keys(BillSourceMap),
required: true
},
list: {
type: Array,
default: []
}
});
try {
BillSchema.index({ userId: 1 });
BillSchema.index({ time: 1 }, { expireAfterSeconds: 90 * 24 * 60 * 60 });
} catch (error) {
console.log(error);
}
export const Bill: Model<BillType> = models['bill'] || model('bill', BillSchema);

View File

@@ -0,0 +1,15 @@
import { POST } from '@fastgpt/service/common/api/plusRequest';
export const postTextCensor = (data: { text: string }) =>
POST<{ code?: number; message: string }>('/common/censor/text_baidu', data)
.then((res) => {
if (res?.code === 5000) {
return Promise.reject(res);
}
})
.catch((err) => {
if (err?.code === 5000) {
return Promise.reject(err.message);
}
return Promise.resolve('');
});

View File

@@ -1,5 +1,5 @@
import { ChatItemType } from '@/types/chat';
import { ChatRoleEnum } from '@/constants/chat';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import type { NextApiResponse } from 'next';
import { countMessagesTokens, countPromptTokens } from '@/global/common/tiktoken';
import { adaptRole_Chat2Message } from '@/utils/common/adapt/message';

View File

@@ -1,15 +1,17 @@
import {
defaultAudioSpeechModels,
defaultChatModels,
defaultCQModels,
defaultExtractModels,
defaultQAModels,
defaultQGModels,
defaultVectorModels
} from '@/constants/model';
} from '@fastgpt/global/core/ai/model';
export const getChatModel = (model?: string) => {
return (
(global.chatModels || defaultChatModels).find((item) => item.model === model) ||
global.chatModels?.[0] ||
defaultChatModels[0]
);
};
@@ -50,6 +52,14 @@ export const getVectorModel = (model?: string) => {
);
};
export function getAudioSpeechModel(model?: string) {
return (
global.audioSpeechModels.find((item) => item.model === model) ||
global.audioSpeechModels?.[0] ||
defaultAudioSpeechModels[0]
);
}
export enum ModelTypeEnum {
chat = 'chat',
qa = 'qa',

View File

@@ -0,0 +1,70 @@
import { getAIApi } from '@fastgpt/service/core/ai/config';
export type GetVectorProps = {
model: string;
input: string | string[];
};
// text to vector
export async function getVectorsByText({
model = 'text-embedding-ada-002',
input
}: GetVectorProps) {
try {
if (typeof input === 'string' && !input) {
return Promise.reject({
code: 500,
message: 'input is empty'
});
} else if (Array.isArray(input)) {
for (let i = 0; i < input.length; i++) {
if (!input[i]) {
return Promise.reject({
code: 500,
message: 'input array is empty'
});
}
}
}
// 获取 chatAPI
const ai = getAIApi();
// 把输入的内容转成向量
const result = await ai.embeddings
.create({
model,
input
})
.then(async (res) => {
if (!res.data) {
return Promise.reject('Embedding API 404');
}
if (!res?.data?.[0]?.embedding) {
console.log(res?.data);
// @ts-ignore
return Promise.reject(res.data?.err?.message || 'Embedding API Error');
}
return {
tokenLen: res.usage.total_tokens || 0,
vectors: await Promise.all(res.data.map((item) => unityDimensional(item.embedding)))
};
});
return result;
} catch (error) {
console.log(`Embedding Error`, error);
return Promise.reject(error);
}
}
function unityDimensional(vector: number[]) {
if (vector.length > 1536) return Promise.reject('向量维度不能超过 1536');
let resultVector = vector;
const vectorLen = vector.length;
const zeroVector = new Array(1536 - vectorLen).fill(0);
return resultVector.concat(zeroVector);
}

View File

@@ -0,0 +1,168 @@
import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
import { getVectorsByText } from '@/service/core/ai/vector';
import { PgClient } from '@fastgpt/service/common/pg';
import { delay } from '@/utils/tools';
import {
DatasetDataItemType,
PgDataItemType,
PgRawDataItemType
} from '@fastgpt/global/core/dataset/type';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
export async function formatPgRawData(data: PgRawDataItemType) {
return {
id: data.id,
q: data.q,
a: data.a,
teamId: data.team_id,
tmbId: data.tmb_id,
datasetId: data.dataset_id,
collectionId: data.collection_id
};
}
/* get */
export async function getDatasetPgData({ id }: { id: string }): Promise<PgDataItemType> {
const { rows } = await PgClient.select<PgRawDataItemType>(PgDatasetTableName, {
fields: ['id', 'q', 'a', 'team_id', 'tmb_id', 'dataset_id', 'collection_id'],
where: [['id', id]],
limit: 1
});
const row = rows[0];
if (!row) return Promise.reject('Data not found');
return formatPgRawData(row);
}
export async function getPgDataWithCollection({
pgDataList
}: {
pgDataList: PgRawDataItemType[];
}): Promise<DatasetDataItemType[]> {
const collections = await MongoDatasetCollection.find(
{
_id: { $in: pgDataList.map((item) => item.collection_id) }
},
'_id name datasetId metadata'
).lean();
return pgDataList.map((item) => {
const collection = collections.find(
(collection) => String(collection._id) === item.collection_id
);
return {
id: item.id,
q: item.q,
a: item.a,
datasetId: collection?.datasetId || '',
collectionId: item.collection_id,
sourceName: collection?.name || '',
sourceId: collection?.metadata?.fileId || collection?.metadata?.rawLink
};
});
}
type Props = {
q: string;
a?: string;
model: string;
};
/**
* update a or a
*/
export async function updateData2Dataset({ dataId, q, a = '', model }: Props & { dataId: string }) {
const { vectors = [], tokenLen = 0 } = await (async () => {
if (q) {
return getVectorsByText({
input: [q],
model
});
}
return { vectors: [[]], tokenLen: 0 };
})();
await PgClient.update(PgDatasetTableName, {
where: [['id', dataId]],
values: [
{ key: 'a', value: a.replace(/'/g, '"') },
...(q
? [
{ key: 'q', value: q.replace(/'/g, '"') },
{ key: 'vector', value: `[${vectors[0]}]` }
]
: [])
]
});
return {
vectors,
tokenLen
};
}
/* insert data to pg */
export async function insertData2Dataset({
teamId,
tmbId,
datasetId,
collectionId,
q,
a = '',
model
}: Props & {
teamId: string;
tmbId: string;
datasetId: string;
collectionId: string;
}) {
if (!q || !datasetId || !collectionId || !model) {
return Promise.reject('q, datasetId, collectionId, model is required');
}
const { vectors, tokenLen } = await getVectorsByText({
model,
input: [q]
});
let retry = 2;
async function insertPg(): Promise<string> {
try {
const { rows } = await PgClient.insert(PgDatasetTableName, {
values: [
[
{ key: 'vector', value: `[${vectors[0]}]` },
{ key: 'team_id', value: String(teamId) },
{ key: 'tmb_id', value: String(tmbId) },
{ key: 'q', value: q },
{ key: 'a', value: a },
{ key: 'dataset_id', value: datasetId },
{ key: 'collection_id', value: collectionId }
]
]
});
return rows[0].id;
} catch (error) {
if (--retry < 0) {
return Promise.reject(error);
}
await delay(500);
return insertPg();
}
}
const insertId = await insertPg();
return {
insertId,
tokenLen,
vectors
};
}
/**
* delete data by collectionIds
*/
export async function delDataByCollectionId({ collectionIds }: { collectionIds: string[] }) {
const ids = collectionIds.map((item) => String(item));
return PgClient.delete(PgDatasetTableName, {
where: [`collection_id IN ('${ids.join("','")}')`]
});
}

View File

@@ -1,7 +1,11 @@
import { PgDatasetTableName } from '@/constants/plugin';
import { getVector } from '@/pages/api/openapi/plugin/vector';
import { PgClient } from '@/service/pg';
import { delay } from '@/utils/tools';
import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
import {
SearchDataResponseItemType,
SearchDataResultItemType
} from '@fastgpt/global/core/dataset/type';
import { PgClient } from '@fastgpt/service/common/pg';
import { getVectorsByText } from '../../ai/vector';
import { getPgDataWithCollection } from './controller';
/**
* Same value judgment
@@ -27,99 +31,6 @@ export async function hasSameValue({
}
}
type Props = {
userId: string;
q: string;
a?: string;
model: string;
};
export async function insertData2Dataset({
userId,
datasetId,
collectionId,
q,
a = '',
model,
billId
}: Props & {
datasetId: string;
collectionId: string;
billId?: string;
}) {
if (!q || !datasetId || !collectionId || !model) {
return Promise.reject('q, datasetId, collectionId, model is required');
}
const { vectors } = await getVector({
model,
input: [q],
userId,
billId
});
let retry = 2;
async function insertPg(): Promise<string> {
try {
const { rows } = await PgClient.insert(PgDatasetTableName, {
values: [
[
{ key: 'vector', value: `[${vectors[0]}]` },
{ key: 'user_id', value: userId },
{ key: 'q', value: q },
{ key: 'a', value: a },
{ key: 'dataset_id', value: datasetId },
{ key: 'collection_id', value: collectionId }
]
]
});
return rows[0].id;
} catch (error) {
if (--retry < 0) {
return Promise.reject(error);
}
await delay(500);
return insertPg();
}
}
return insertPg();
}
/**
* update a or a
*/
export async function updateData2Dataset({
dataId,
userId,
q,
a = '',
model
}: Props & { dataId: string }) {
const { vectors = [] } = await (async () => {
if (q) {
return getVector({
userId,
input: [q],
model
});
}
return { vectors: [[]] };
})();
await PgClient.update(PgDatasetTableName, {
where: [['id', dataId], 'AND', ['user_id', userId]],
values: [
{ key: 'a', value: a.replace(/'/g, '"') },
...(q
? [
{ key: 'q', value: q.replace(/'/g, '"') },
{ key: 'vector', value: `[${vectors[0]}]` }
]
: [])
]
});
}
/**
* count one collection amount of total data
*/
@@ -148,18 +59,46 @@ export async function countCollectionData({
return values;
}
/**
* delete data by collectionIds
*/
export async function delDataByCollectionId({
userId,
collectionIds
export async function searchDatasetData({
text,
model,
similarity = 0,
limit,
datasetIds = []
}: {
userId: string;
collectionIds: string[];
text: string;
model: string;
similarity?: number;
limit: number;
datasetIds: string[];
}) {
const ids = collectionIds.map((item) => String(item));
return PgClient.delete(PgDatasetTableName, {
where: [['user_id', userId], 'AND', `collection_id IN ('${ids.join("','")}')`]
const { vectors, tokenLen } = await getVectorsByText({
model,
input: [text]
});
const results: any = await PgClient.query(
`BEGIN;
SET LOCAL hnsw.ef_search = ${global.systemEnv.pgHNSWEfSearch || 100};
select id, q, a, collection_id, (vector <#> '[${
vectors[0]
}]') * -1 AS score from ${PgDatasetTableName} where dataset_id IN (${datasetIds
.map((id) => `'${String(id)}'`)
.join(',')}) AND vector <#> '[${vectors[0]}]' < -${similarity} order by vector <#> '[${
vectors[0]
}]' limit ${limit};
COMMIT;`
);
const rows = results?.[2]?.rows as SearchDataResultItemType[];
const collectionsData = await getPgDataWithCollection({ pgDataList: rows });
const searchRes: SearchDataResponseItemType[] = collectionsData.map((item, index) => ({
...item,
score: rows[index].score
}));
return {
searchRes,
tokenLen
};
}

View File

@@ -1,17 +1,16 @@
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { pushQABill } from '@/service/common/bill/push';
import { pushQABill } from '@/service/support/wallet/bill/push';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant';
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import { sendInform } from '@/pages/api/user/inform/send';
import { authBalanceByUid } from '@fastgpt/service/support/user/auth';
import { sendOneInform } from '../support/user/inform/api';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type { ChatCompletionRequestMessage } from '@fastgpt/global/core/ai/type.d';
import { addLog } from '../utils/tools';
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
import { addLog } from '@fastgpt/service/common/mongo/controller';
import { splitText2Chunks } from '@/global/common/string/tools';
import { replaceVariable } from '@/global/common/string/tools';
import { Prompt_AgentQA } from '@/global/core/prompt/agent';
import { pushDataToDatasetCollection } from '@/pages/api/core/dataset/data/pushData';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { authTeamBalance } from '../support/permission/auth/bill';
const reduceQueue = () => {
global.qaQueueLen = global.qaQueueLen > 0 ? global.qaQueueLen - 1 : 0;
@@ -21,45 +20,92 @@ export async function generateQA(): Promise<any> {
if (global.qaQueueLen >= global.systemEnv.qaMaxProcess) return;
global.qaQueueLen++;
let trainingId = '';
let userId = '';
// get training data
const {
data,
text,
done = false,
error = false
} = await (async () => {
try {
const data = (
await MongoDatasetTraining.findOneAndUpdate(
{
mode: TrainingModeEnum.qa,
lockTime: { $lte: new Date(Date.now() - 10 * 60 * 1000) }
},
{
lockTime: new Date()
}
).select({
_id: 1,
userId: 1,
teamId: 1,
tmbId: 1,
datasetId: 1,
datasetCollectionId: 1,
q: 1,
model: 1,
billId: 1,
prompt: 1
})
)?.toJSON();
// task preemption
if (!data) {
return {
done: true
};
}
return {
data,
text: data.q
};
} catch (error) {
console.log(`Get Training Data error`, error);
return {
error: true
};
}
})();
if (done) {
reduceQueue();
global.vectorQueueLen <= 0 && console.log(`【索引】任务完成`);
return;
}
if (error || !data) {
reduceQueue();
return generateQA();
}
// auth balance
try {
await authTeamBalance(data.teamId);
} catch (error) {
// send inform and lock data
try {
sendOneInform({
type: 'system',
title: '索引生成任务中止',
content:
'由于账号余额不足,索引生成任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。',
tmbId: data.tmbId
});
console.log('余额不足,暂停向量生成任务');
await MongoDatasetTraining.findById(data._id, {
lockTime: new Date('2999/5/5')
});
} catch (error) {}
reduceQueue();
return generateQA();
}
try {
const data = await MongoDatasetTraining.findOneAndUpdate(
{
mode: TrainingModeEnum.qa,
lockTime: { $lte: new Date(Date.now() - 10 * 60 * 1000) }
},
{
lockTime: new Date()
}
).select({
_id: 1,
userId: 1,
datasetCollectionId: 1,
q: 1,
model: 1,
prompt: 1,
billId: 1
});
// task preemption
if (!data) {
reduceQueue();
global.qaQueueLen <= 0 && console.log(`【QA】任务完成`);
return;
}
trainingId = data._id;
userId = String(data.userId);
await authBalanceByUid(userId);
const startTime = Date.now();
// request LLM to get QA
const text = data.q;
const messages: ChatCompletionRequestMessage[] = [
const messages: ChatMessageItemType[] = [
{
role: 'user',
content: data.prompt
@@ -84,7 +130,8 @@ export async function generateQA(): Promise<any> {
// get vector and insert
await pushDataToDatasetCollection({
userId,
teamId: data.teamId,
tmbId: data.tmbId,
collectionId: data.datasetCollectionId,
data: qaArr,
mode: TrainingModeEnum.index,
@@ -97,10 +144,11 @@ export async function generateQA(): Promise<any> {
console.log(`split result length: `, qaArr.length);
console.log('生成QA成功time:', `${(Date.now() - startTime) / 1000}s`);
// 计费
// add bill
if (qaArr.length > 0) {
pushQABill({
userId: data.userId,
teamId: data.teamId,
tmbId: data.tmbId,
totalTokens,
billId: data.billId
});
@@ -114,36 +162,30 @@ export async function generateQA(): Promise<any> {
reduceQueue();
// log
if (err?.response) {
console.log('openai error: 生成QA错误');
console.log(err.response?.status, err.response?.statusText, err.response?.data);
addLog.info('openai error: 生成QA错误', {
status: err.response?.status,
stateusText: err.response?.statusText,
data: err.response?.data
});
} else {
console.log(err);
addLog.error(getErrText(err, '生成 QA 错误'));
}
// message error or openai account error
if (err?.message === 'invalid message format') {
await MongoDatasetTraining.findByIdAndRemove(trainingId);
}
// 账号余额不足,删除任务
if (userId && err === ERROR_ENUM.insufficientQuota) {
sendInform({
type: 'system',
title: 'QA 任务中止',
content:
'由于账号余额不足,索引生成任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。',
userId
if (
err?.message === 'invalid message format' ||
err.response?.data?.error?.type === 'invalid_request_error' ||
err?.code === 500
) {
addLog.info('invalid message format', {
text
});
console.log('余额不足,暂停向量生成任务');
await MongoDatasetTraining.updateMany(
{
userId
},
{
lockTime: new Date('2999/5/5')
}
);
try {
await MongoDatasetTraining.findByIdAndUpdate(data._id, {
lockTime: new Date('2998/5/5')
});
} catch (error) {}
return generateQA();
}

View File

@@ -1,11 +1,11 @@
import { insertData2Dataset } from '../core/dataset/data/utils';
import { getVector } from '@/pages/api/openapi/plugin/vector';
import { insertData2Dataset } from '@/service/core/dataset/data/controller';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant';
import { sendInform } from '@/pages/api/user/inform/send';
import { addLog } from '../utils/tools';
import { sendOneInform } from '../support/user/inform/api';
import { addLog } from '@fastgpt/service/common/mongo/controller';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { authTeamBalance } from '@/service/support/permission/auth/bill';
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
const reduceQueue = () => {
global.vectorQueueLen = global.vectorQueueLen > 0 ? global.vectorQueueLen - 1 : 0;
@@ -16,68 +16,114 @@ export async function generateVector(): Promise<any> {
if (global.vectorQueueLen >= global.systemEnv.vectorMaxProcess) return;
global.vectorQueueLen++;
let trainingId = '';
let userId = '';
let dataItems: {
q: string;
a: string;
} = {
q: '',
a: ''
};
// get training data
const {
data,
dataItem,
done = false,
error = false
} = await (async () => {
try {
const data = (
await MongoDatasetTraining.findOneAndUpdate(
{
mode: TrainingModeEnum.index,
lockTime: { $lte: new Date(Date.now() - 1 * 60 * 1000) }
},
{
lockTime: new Date()
}
).select({
_id: 1,
userId: 1,
teamId: 1,
tmbId: 1,
datasetId: 1,
datasetCollectionId: 1,
q: 1,
a: 1,
model: 1,
billId: 1
})
)?.toJSON();
// task preemption
if (!data) {
return {
done: true
};
}
return {
data,
dataItem: {
q: data.q.replace(/[\x00-\x08]/g, ' '),
a: data.a?.replace(/[\x00-\x08]/g, ' ') || ''
}
};
} catch (error) {
console.log(`Get Training Data error`, error);
return {
error: true
};
}
})();
if (done) {
reduceQueue();
global.vectorQueueLen <= 0 && console.log(`【索引】任务完成`);
return;
}
if (error || !data) {
reduceQueue();
return generateVector();
}
// auth balance
try {
await authTeamBalance(data.teamId);
} catch (error) {
// send inform and lock data
try {
sendOneInform({
type: 'system',
title: '索引生成任务中止',
content:
'由于账号余额不足,索引生成任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。',
tmbId: data.tmbId
});
console.log('余额不足,暂停向量生成任务');
await MongoDatasetTraining.findById(data._id, {
lockTime: new Date('2999/5/5')
});
} catch (error) {}
reduceQueue();
return generateVector();
}
// create vector and insert
try {
const data = await MongoDatasetTraining.findOneAndUpdate(
{
mode: TrainingModeEnum.index,
lockTime: { $lte: new Date(Date.now() - 1 * 60 * 1000) }
},
{
lockTime: new Date()
}
)
.select({
_id: 1,
userId: 1,
datasetId: 1,
datasetCollectionId: 1,
q: 1,
a: 1,
model: 1,
billId: 1
})
.lean();
// task preemption
if (!data) {
reduceQueue();
global.vectorQueueLen <= 0 && console.log(`【索引】任务完成`);
return;
}
trainingId = data._id;
userId = String(data.userId);
dataItems = {
q: data.q.replace(/[\x00-\x08]/g, ' '),
a: data.a?.replace(/[\x00-\x08]/g, ' ') || ''
};
// insert data 2 pg
await insertData2Dataset({
userId,
// insert data to pg
const { tokenLen } = await insertData2Dataset({
teamId: data.teamId,
tmbId: data.teamId,
datasetId: data.datasetId,
collectionId: data.datasetCollectionId,
q: dataItems.q,
a: dataItems.a,
q: dataItem.q,
a: dataItem.a,
model: data.model
});
// push bill
pushGenerateVectorBill({
teamId: data.teamId,
tmbId: data.teamId,
tokenLen: tokenLen,
model: data.model,
billId: data.billId
});
// delete data from training
await MongoDatasetTraining.findByIdAndDelete(data._id);
// console.log(`生成向量成功: ${data._id}`);
reduceQueue();
generateVector();
} catch (err: any) {
@@ -97,48 +143,20 @@ export async function generateVector(): Promise<any> {
// message error or openai account error
if (
err?.message === 'invalid message format' ||
err.response?.data?.error?.type === 'invalid_request_error'
err.response?.data?.error?.type === 'invalid_request_error' ||
err?.code === 500
) {
addLog.info('invalid message format', {
dataItems
dataItem
});
try {
await MongoDatasetTraining.findByIdAndUpdate(trainingId, {
await MongoDatasetTraining.findByIdAndUpdate(data._id, {
lockTime: new Date('2998/5/5')
});
} catch (error) {}
return generateVector();
}
// err vector data
if (err?.code === 500) {
await MongoDatasetTraining.findByIdAndDelete(trainingId);
return generateVector();
}
// 账号余额不足,暂停任务
if (userId && err === ERROR_ENUM.insufficientQuota) {
try {
sendInform({
type: 'system',
title: '索引生成任务中止',
content:
'由于账号余额不足,索引生成任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。',
userId
});
console.log('余额不足,暂停向量生成任务');
await MongoDatasetTraining.updateMany(
{
userId
},
{
lockTime: new Date('2999/5/5')
}
);
} catch (error) {}
return generateVector();
}
setTimeout(() => {
generateVector();
}, 1000);

View File

@@ -1,16 +0,0 @@
export const startSendInform = async () => {
if (global.sendInformQueue.length === 0 || global.sendInformQueueLen > 0) return;
global.sendInformQueueLen++;
try {
const fn = global.sendInformQueue[global.sendInformQueue.length - 1];
await fn();
global.sendInformQueue.pop();
global.sendInformQueueLen--;
startSendInform();
} catch (error) {
global.sendInformQueueLen--;
startSendInform();
}
};

View File

@@ -1,125 +0,0 @@
import { Types, connectionMongo } from '@fastgpt/service/common/mongo';
import fs from 'fs';
import fsp from 'fs/promises';
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import type { GSFileInfoType } from '@/types/common/file';
enum BucketNameEnum {
dataset = 'dataset'
}
export class GridFSStorage {
readonly type = 'gridfs';
readonly bucket: `${BucketNameEnum}`;
readonly uid: string;
constructor(bucket: `${BucketNameEnum}`, uid: string) {
this.bucket = bucket;
this.uid = String(uid);
}
Collection() {
return connectionMongo.connection.db.collection(`${this.bucket}.files`);
}
GridFSBucket() {
return new connectionMongo.mongo.GridFSBucket(connectionMongo.connection.db, {
bucketName: this.bucket
});
}
async save({
path,
filename,
metadata = {}
}: {
path: string;
filename: string;
metadata?: Record<string, any>;
}) {
if (!path) return Promise.reject(`filePath is empty`);
if (!filename) return Promise.reject(`filename is empty`);
const stats = await fsp.stat(path);
if (!stats.isFile()) return Promise.reject(`${path} is not a file`);
metadata.userId = this.uid;
// create a gridfs bucket
const bucket = this.GridFSBucket();
const stream = bucket.openUploadStream(filename, {
metadata,
contentType: metadata?.contentType
});
// save to gridfs
await new Promise((resolve, reject) => {
fs.createReadStream(path)
.pipe(stream as any)
.on('finish', resolve)
.on('error', reject);
});
return String(stream.id);
}
async findAndAuthFile(id: string): Promise<GSFileInfoType> {
if (!id) {
return Promise.reject(`id is empty`);
}
// create a gridfs bucket
const bucket = this.GridFSBucket();
// check if file exists
const files = await bucket.find({ _id: new Types.ObjectId(id) }).toArray();
const file = files.shift();
if (!file) {
return Promise.reject(`file not found`);
}
if (file.metadata?.userId !== this.uid) {
return Promise.reject(ERROR_ENUM.unAuthFile);
}
return {
id: String(file._id),
filename: file.filename,
contentType: file.metadata?.contentType,
encoding: file.metadata?.encoding,
uploadDate: file.uploadDate,
size: file.length
};
}
async delete(id: string) {
await this.findAndAuthFile(id);
const bucket = this.GridFSBucket();
await bucket.delete(new Types.ObjectId(id));
return true;
}
async deleteFilesByDatasetId(datasetId: string) {
if (!datasetId) return;
const collection = this.Collection();
return collection.deleteMany({
'metadata.datasetId': String(datasetId)
});
}
async download(id: string) {
await this.findAndAuthFile(id);
const bucket = this.GridFSBucket();
const stream = bucket.openDownloadStream(new Types.ObjectId(id));
const buf: Buffer = await new Promise((resolve, reject) => {
const buffers: Buffer[] = [];
stream.on('data', (data) => buffers.push(data));
stream.on('error', reject);
stream.on('end', () => resolve(Buffer.concat(buffers)));
});
return buf;
}
}

View File

@@ -1,74 +0,0 @@
import { connectionMongo, type Model } from '@fastgpt/service/common/mongo';
const { Schema, model, models } = connectionMongo;
import { AppSchema as AppType } from '@/types/mongoSchema';
const AppSchema = new Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
},
name: {
type: String,
required: true
},
type: {
type: String,
default: 'advanced',
enum: ['basic', 'advanced']
},
avatar: {
type: String,
default: '/icon/logo.svg'
},
intro: {
type: String,
default: ''
},
updateTime: {
type: Date,
default: () => new Date()
},
share: {
topNum: {
type: Number,
default: 0
},
isShare: {
type: Boolean,
default: false
},
isShareDetail: {
// share model detail info. false: just show name and intro
type: Boolean,
default: false
},
intro: {
type: String,
default: '',
maxlength: 150
},
collection: {
type: Number,
default: 0
}
},
modules: {
type: Array,
default: []
},
inited: {
type: Boolean
},
// 弃
chat: Object
});
try {
AppSchema.index({ updateTime: -1 });
AppSchema.index({ 'share.collection': -1 });
} catch (error) {
console.log(error);
}
export const App: Model<AppType> = models['app'] || model('app', AppSchema);

View File

@@ -1,83 +0,0 @@
import { connectionMongo, type Model } from '@fastgpt/service/common/mongo';
const { Schema, model, models } = connectionMongo;
import { ChatSchema as ChatType } from '@/types/mongoSchema';
import { ChatRoleMap, TaskResponseKeyEnum } from '@/constants/chat';
import { ChatSourceMap } from '@/constants/chat';
const ChatSchema = new Schema({
chatId: {
type: String,
require: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
},
appId: {
type: Schema.Types.ObjectId,
ref: 'model',
required: true
},
updateTime: {
type: Date,
default: () => new Date()
},
title: {
type: String,
default: '历史记录'
},
customTitle: {
type: String,
default: ''
},
top: {
type: Boolean
},
variables: {
type: Object,
default: {}
},
source: {
type: String,
enum: Object.keys(ChatSourceMap),
required: true
},
shareId: {
type: String
},
isInit: {
type: Boolean,
default: false
},
content: {
type: [
{
obj: {
type: String,
required: true,
enum: Object.keys(ChatRoleMap)
},
value: {
type: String,
default: ''
},
[TaskResponseKeyEnum.responseData]: {
type: Array,
default: []
}
}
],
default: []
}
});
try {
ChatSchema.index({ userId: 1 });
ChatSchema.index({ updateTime: -1 });
ChatSchema.index({ appId: 1 });
} catch (error) {
console.log(error);
}
export const Chat: Model<ChatType> = models['chat'] || model('chat', ChatSchema);

View File

@@ -1,70 +0,0 @@
import { connectionMongo, type Model } from '@fastgpt/service/common/mongo';
const { Schema, model, models } = connectionMongo;
import { ChatItemSchema as ChatItemType } from '@/types/mongoSchema';
import { ChatRoleMap, TaskResponseKeyEnum } from '@/constants/chat';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
const ChatItemSchema = new Schema({
dataId: {
type: String,
require: true,
default: () => nanoid()
},
chatId: {
type: String,
require: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
},
appId: {
type: Schema.Types.ObjectId,
ref: 'model',
required: true
},
time: {
type: Date,
default: () => new Date()
},
obj: {
type: String,
required: true,
enum: Object.keys(ChatRoleMap)
},
value: {
type: String,
default: ''
},
userFeedback: {
type: String
},
adminFeedback: {
type: {
datasetId: String,
collectionId: String,
dataId: String,
q: String,
a: String
}
},
[TaskResponseKeyEnum.responseData]: {
type: Array,
default: []
}
});
try {
ChatItemSchema.index({ time: -1 });
ChatItemSchema.index({ userId: 1 });
ChatItemSchema.index({ appId: 1 });
ChatItemSchema.index({ chatId: 1 });
ChatItemSchema.index({ userFeedback: 1 });
} catch (error) {
console.log(error);
}
export const ChatItem: Model<ChatItemType> =
models['chatItem'] || model('chatItem', ChatItemSchema);

View File

@@ -1,15 +1,15 @@
import { adaptChat2GptMessages } from '@/utils/common/adapt/message';
import { ChatContextFilter } from '@/service/common/tiktoken';
import type { moduleDispatchResType, ChatItemType } from '@/types/chat';
import { ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat';
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatRoleEnum, TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type { ClassifyQuestionAgentItemType } from '@/types/app';
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/module/type.d';
import { SystemInputEnum } from '@/constants/app';
import { FlowNodeSpecialInputKeyEnum } from '@fastgpt/global/core/module/node/constant';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import { replaceVariable } from '@/global/common/string/tools';
import { Prompt_CQJson } from '@/global/core/prompt/agent';
import { FunctionModelItemType } from '@/types/model';
import { FunctionModelItemType } from '@fastgpt/global/core/ai/model.d';
import { getCQModel } from '@/service/core/ai/model';
type Props = ModuleDispatchProps<{
@@ -88,7 +88,7 @@ ${systemPrompt}
const filterMessages = ChatContextFilter({
messages,
maxTokens: cqModel.maxToken
maxTokens: cqModel.maxContext
});
const adaptMessages = adaptChat2GptMessages({ messages: filterMessages, reserveId: false });

View File

@@ -1,14 +1,14 @@
import { adaptChat2GptMessages } from '@/utils/common/adapt/message';
import { ChatContextFilter } from '@/service/common/tiktoken';
import type { moduleDispatchResType, ChatItemType } from '@/types/chat';
import { ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat';
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatRoleEnum, TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type { ContextExtractAgentItemType } from '@/types/app';
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
import { ContextExtractEnum } from '@/constants/flow/flowField';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import { Prompt_ExtractJson } from '@/global/core/prompt/agent';
import { replaceVariable } from '@/global/common/string/tools';
import { FunctionModelItemType } from '@/types/model';
import { FunctionModelItemType } from '@fastgpt/global/core/ai/model.d';
type Props = ModuleDispatchProps<{
history?: ChatItemType[];
@@ -98,7 +98,7 @@ async function functionCall({
];
const filterMessages = ChatContextFilter({
messages,
maxTokens: extractModel.maxToken
maxTokens: extractModel.maxContext
});
const adaptMessages = adaptChat2GptMessages({ messages: filterMessages, reserveId: false });

View File

@@ -1,14 +1,15 @@
import type { NextApiResponse } from 'next';
import { ChatContextFilter } from '@/service/common/tiktoken';
import type { ChatItemType, moduleDispatchResType } from '@/types/chat';
import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat';
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { textAdaptGptResponse } from '@/utils/adapt';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { countModelPrice } from '@/service/common/bill/push';
import { ChatModelItemType } from '@/types/model';
import { postTextCensor } from '@/web/common/plusApi/censor';
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import { countModelPrice } from '@/service/support/wallet/bill/utils';
import type { ChatModelItemType } from '@fastgpt/global/core/ai/model.d';
import { postTextCensor } from '@/service/common/censor';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant';
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { countMessagesTokens, sliceMessagesTB } from '@/global/common/tiktoken';
@@ -99,7 +100,6 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
maxToken,
filterMessages
});
// console.log(messages);
// FastGPT temperature range: 1~10
temperature = +(modelConstantsData.maxTemperature * (temperature / 10)).toFixed(2);
@@ -281,7 +281,7 @@ function getChatMessages({
const filterMessages = ChatContextFilter({
messages,
maxTokens: Math.ceil(model.maxToken - 300) // filter token. not response maxToken
maxTokens: Math.ceil(model.maxContext - 300) // filter token. not response maxToken
});
const adaptMessages = adaptChat2GptMessages({ messages: filterMessages, reserveId: false });
@@ -300,13 +300,13 @@ function getMaxTokens({
model: ChatModelItemType;
filterMessages: ChatProps['inputs']['history'];
}) {
const tokensLimit = model.maxToken;
/* count response max token */
const tokensLimit = model.maxContext;
/* count response max token */
const promptsToken = countMessagesTokens({
messages: filterMessages
});
maxToken = maxToken + promptsToken > tokensLimit ? tokensLimit - promptsToken : maxToken;
maxToken = promptsToken + model.maxResponse > tokensLimit ? tokensLimit - promptsToken : maxToken;
return {
max_tokens: maxToken

View File

@@ -1,17 +1,11 @@
import { PgClient } from '@/service/pg';
import type { moduleDispatchResType } from '@/types/chat';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { getVector } from '@/pages/api/openapi/plugin/vector';
import { countModelPrice } from '@/service/common/bill/push';
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import { countModelPrice } from '@/service/support/wallet/bill/utils';
import type { SelectedDatasetType } from '@/types/core/dataset';
import type {
SearchDataResponseItemType,
SearchDataResultItemType
} from '@fastgpt/global/core/dataset/type';
import { PgDatasetTableName } from '@/constants/plugin';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import { ModelTypeEnum } from '@/service/core/ai/model';
import { getDatasetDataItemInfo } from '@/pages/api/core/dataset/data/getDataById';
import { searchDatasetData } from '@/service/core/dataset/data/utils';
type DatasetSearchProps = ModuleDispatchProps<{
datasets: SelectedDatasetType;
@@ -28,7 +22,8 @@ export type KBSearchResponse = {
export async function dispatchDatasetSearch(props: Record<string, any>): Promise<KBSearchResponse> {
const {
user,
teamId,
tmbId,
inputs: { datasets = [], similarity = 0.4, limit = 5, userChatInput }
} = props as DatasetSearchProps;
@@ -42,34 +37,15 @@ export async function dispatchDatasetSearch(props: Record<string, any>): Promise
// get vector
const vectorModel = datasets[0]?.vectorModel || global.vectorModels[0];
const { vectors, tokenLen } = await getVector({
const { searchRes, tokenLen } = await searchDatasetData({
text: userChatInput,
model: vectorModel.model,
input: [userChatInput]
similarity,
limit,
datasetIds: datasets.map((item) => item.datasetId)
});
// search kb
const results: any = await PgClient.query(
`BEGIN;
SET LOCAL hnsw.ef_search = ${global.systemEnv.pgHNSWEfSearch || 100};
select id, q, a, dataset_id, collection_id, (vector <#> '[${
vectors[0]
}]') * -1 AS score from ${PgDatasetTableName} where user_id='${
user._id
}' AND dataset_id IN (${datasets
.map((item) => `'${item.datasetId}'`)
.join(',')}) AND vector <#> '[${vectors[0]}]' < -${similarity} order by vector <#> '[${
vectors[0]
}]' limit ${limit};
COMMIT;`
);
const rows = results?.[2]?.rows as SearchDataResultItemType[];
const collectionsData = await getDatasetDataItemInfo({ pgDataList: rows });
const searchRes: SearchDataResponseItemType[] = collectionsData.map((item, index) => ({
...item,
score: rows[index].score
}));
return {
isEmpty: searchRes.length === 0 ? true : undefined,
unEmpty: searchRes.length > 0 ? true : undefined,

View File

@@ -1,12 +1,303 @@
export * from './init/history';
export * from './init/userChatInput';
export * from './chat/oneapi';
export * from './dataset/search';
export * from './tools/answer';
export * from './tools/http';
export * from './tools/runApp';
export * from './agent/classifyQuestion';
export * from './agent/extract';
export * from './plugin/run';
export * from './plugin/runInput';
export * from './plugin/runOutput';
import { NextApiResponse } from 'next';
import { SystemInputEnum, SystemOutputEnum } from '@/constants/app';
import { RunningModuleItemType } from '@/types/app';
import { ModuleDispatchProps } from '@/types/core/chat/type';
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
import { UserType } from '@fastgpt/global/support/user/type';
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import { replaceVariable } from '@/global/common/string/tools';
import { responseWrite } from '@fastgpt/service/common/response';
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
import { initModuleType } from '@/constants/flow';
import { dispatchHistory } from './init/history';
import { dispatchChatInput } from './init/userChatInput';
import { dispatchChatCompletion } from './chat/oneapi';
import { dispatchDatasetSearch } from './dataset/search';
import { dispatchAnswer } from './tools/answer';
import { dispatchClassifyQuestion } from './agent/classifyQuestion';
import { dispatchContentExtract } from './agent/extract';
import { dispatchHttpRequest } from './tools/http';
import { dispatchAppRequest } from './tools/runApp';
import { dispatchRunPlugin } from './plugin/run';
import { dispatchPluginInput } from './plugin/runInput';
import { dispatchPluginOutput } from './plugin/runOutput';
/* running */
export async function dispatchModules({
res,
chatId,
modules,
user,
teamId,
tmbId,
params = {},
variables = {},
stream = false,
detail = false
}: {
res: NextApiResponse;
chatId?: string;
modules: ModuleItemType[];
user: UserType;
teamId: string;
tmbId: string;
params?: Record<string, any>;
variables?: Record<string, any>;
stream?: boolean;
detail?: boolean;
}) {
variables = {
...getSystemVariable({ timezone: user.timezone }),
...variables
};
const runningModules = loadModules(modules, variables);
// let storeData: Record<string, any> = {}; // after module used
let chatResponse: ChatHistoryItemResType[] = []; // response request and save to database
let chatAnswerText = ''; // AI answer
let runningTime = Date.now();
function pushStore(
{ inputs = [] }: RunningModuleItemType,
{
answerText = '',
responseData
}: {
answerText?: string;
responseData?: ChatHistoryItemResType | ChatHistoryItemResType[];
}
) {
const time = Date.now();
if (responseData) {
if (Array.isArray(responseData)) {
chatResponse = chatResponse.concat(responseData);
} else {
chatResponse.push({
...responseData,
runningTime: +((time - runningTime) / 1000).toFixed(2)
});
}
}
runningTime = time;
const isResponseAnswerText =
inputs.find((item) => item.key === SystemInputEnum.isResponseAnswerText)?.value ?? true;
if (isResponseAnswerText) {
chatAnswerText += answerText;
}
}
function moduleInput(
module: RunningModuleItemType,
data: Record<string, any> = {}
): Promise<any> {
const checkInputFinish = () => {
return !module.inputs.find((item: any) => item.value === undefined);
};
const updateInputValue = (key: string, value: any) => {
const index = module.inputs.findIndex((item: any) => item.key === key);
if (index === -1) return;
module.inputs[index].value = value;
};
const set = new Set();
return Promise.all(
Object.entries(data).map(([key, val]: any) => {
updateInputValue(key, val);
if (!set.has(module.moduleId) && checkInputFinish()) {
set.add(module.moduleId);
// remove switch
updateInputValue(SystemInputEnum.switch, undefined);
return moduleRun(module);
}
})
);
}
function moduleOutput(
module: RunningModuleItemType,
result: Record<string, any> = {}
): Promise<any> {
pushStore(module, result);
return Promise.all(
module.outputs.map((outputItem) => {
if (result[outputItem.key] === undefined) return;
/* update output value */
outputItem.value = result[outputItem.key];
/* update target */
return Promise.all(
outputItem.targets.map((target: any) => {
// find module
const targetModule = runningModules.find((item) => item.moduleId === target.moduleId);
if (!targetModule) return;
return moduleInput(targetModule, { [target.key]: outputItem.value });
})
);
})
);
}
async function moduleRun(module: RunningModuleItemType): Promise<any> {
if (res.closed) return Promise.resolve();
if (stream && detail && module.showStatus) {
responseStatus({
res,
name: module.name,
status: 'running'
});
}
// get fetch params
const params: Record<string, any> = {};
module.inputs.forEach((item: any) => {
params[item.key] = item.value;
});
const props: ModuleDispatchProps<Record<string, any>> = {
res,
stream,
detail,
variables,
outputs: module.outputs,
user,
teamId,
tmbId,
inputs: params
};
const dispatchRes: Record<string, any> = await (async () => {
const callbackMap: Record<string, Function> = {
[FlowNodeTypeEnum.historyNode]: dispatchHistory,
[FlowNodeTypeEnum.questionInput]: dispatchChatInput,
[FlowNodeTypeEnum.answerNode]: dispatchAnswer,
[FlowNodeTypeEnum.chatNode]: dispatchChatCompletion,
[FlowNodeTypeEnum.datasetSearchNode]: dispatchDatasetSearch,
[FlowNodeTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
[FlowNodeTypeEnum.contentExtract]: dispatchContentExtract,
[FlowNodeTypeEnum.httpRequest]: dispatchHttpRequest,
[FlowNodeTypeEnum.runApp]: dispatchAppRequest,
[FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin,
[FlowNodeTypeEnum.pluginInput]: dispatchPluginInput,
[FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput
};
if (callbackMap[module.flowType]) {
return callbackMap[module.flowType](props);
}
return {};
})();
const formatResponseData = (() => {
if (!dispatchRes[TaskResponseKeyEnum.responseData]) return undefined;
if (Array.isArray(dispatchRes[TaskResponseKeyEnum.responseData]))
return dispatchRes[TaskResponseKeyEnum.responseData];
return {
...dispatchRes[TaskResponseKeyEnum.responseData],
moduleName: module.name,
moduleType: module.flowType
};
})();
return moduleOutput(module, {
[SystemOutputEnum.finish]: true,
...dispatchRes,
[TaskResponseKeyEnum.responseData]: formatResponseData
});
}
// start process width initInput
const initModules = runningModules.filter((item) => initModuleType[item.flowType]);
await Promise.all(initModules.map((module) => moduleInput(module, params)));
// focus running pluginOutput
const pluginOutputModule = runningModules.find(
(item) => item.flowType === FlowNodeTypeEnum.pluginOutput
);
if (pluginOutputModule) {
await moduleRun(pluginOutputModule);
}
return {
[TaskResponseKeyEnum.answerText]: chatAnswerText,
[TaskResponseKeyEnum.responseData]: chatResponse
};
}
/* init store modules to running modules */
function loadModules(
modules: ModuleItemType[],
variables: Record<string, any>
): RunningModuleItemType[] {
return modules.map((module) => {
return {
moduleId: module.moduleId,
name: module.name,
flowType: module.flowType,
showStatus: module.showStatus,
inputs: module.inputs
.filter((item) => item.connected) // filter unconnected target input
.map((item) => {
if (typeof item.value !== 'string') {
return {
key: item.key,
value: item.value
};
}
// variables replace
const replacedVal = replaceVariable(item.value, variables);
return {
key: item.key,
value: replacedVal
};
}),
outputs: module.outputs
.map((item) => ({
key: item.key,
answer: item.key === TaskResponseKeyEnum.answerText,
value: undefined,
targets: item.targets
}))
.sort((a, b) => {
// finish output always at last
if (a.key === SystemOutputEnum.finish) return 1;
if (b.key === SystemOutputEnum.finish) return -1;
return 0;
})
};
});
}
/* sse response modules staus */
export function responseStatus({
res,
status,
name
}: {
res: NextApiResponse;
status?: 'running' | 'finish';
name?: string;
}) {
if (!name) return;
responseWrite({
res,
event: sseResponseEventEnum.moduleStatus,
data: JSON.stringify({
status: 'running',
name
})
});
}
/* get system variable */
export function getSystemVariable({ timezone }: { timezone: string }) {
return {
cTime: getSystemTime(timezone)
};
}

View File

@@ -1,5 +1,5 @@
import { SystemInputEnum } from '@/constants/app';
import { ChatItemType } from '@/types/chat';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
export type HistoryProps = ModuleDispatchProps<{
maxContext: number;

View File

@@ -1,12 +1,12 @@
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import { dispatchModules } from '@/pages/api/v1/chat/completions';
import { dispatchModules } from '../index';
import {
FlowNodeSpecialInputKeyEnum,
FlowNodeTypeEnum
} from '@fastgpt/global/core/module/node/constant';
import { getOnePluginDetail } from '@fastgpt/service/core/plugin/controller';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { moduleDispatchResType } from '@/types/chat';
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
type RunPluginProps = ModuleDispatchProps<{
[FlowNodeSpecialInputKeyEnum.pluginId]: string;
@@ -19,11 +19,6 @@ type RunPluginResponse = {
export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPluginResponse> => {
const {
res,
variables,
user,
stream,
detail,
inputs: { pluginId, ...data }
} = props;
@@ -31,19 +26,15 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
return Promise.reject('Input is empty');
}
const plugin = await getOnePluginDetail({ id: pluginId, userId: user._id });
const plugin = await MongoPlugin.findOne({ _id: pluginId });
if (!plugin) {
return Promise.reject('Plugin not found');
}
const { responseData, answerText } = await dispatchModules({
res,
...props,
modules: plugin.modules,
user,
variables,
params: data,
stream,
detail
params: data
});
const output = responseData.find((item) => item.moduleType === FlowNodeTypeEnum.pluginOutput);

View File

@@ -1,5 +1,5 @@
import { TaskResponseKeyEnum } from '@/constants/chat';
import { moduleDispatchResType } from '@/types/chat';
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
export type PluginOutputProps = ModuleDispatchProps<{

View File

@@ -1,4 +1,5 @@
import { sseResponseEventEnum, TaskResponseKeyEnum } from '@/constants/chat';
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { responseWrite } from '@fastgpt/service/common/response';
import { textAdaptGptResponse } from '@/utils/adapt';
import type { ModuleDispatchProps } from '@/types/core/chat/type';

View File

@@ -1,6 +1,6 @@
import { TaskResponseKeyEnum } from '@/constants/chat';
import { HttpPropsEnum } from '@/constants/flow/flowField';
import { moduleDispatchResType } from '@/types/chat';
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
export type HttpRequestProps = ModuleDispatchProps<{
[HttpPropsEnum.url]: string;
@@ -14,13 +14,15 @@ export type HttpResponse = {
export const dispatchHttpRequest = async (props: Record<string, any>): Promise<HttpResponse> => {
const {
chatId,
variables,
inputs: { url, ...body }
} = props as HttpRequestProps;
const requestBody = {
variables,
...body
...body,
chatId,
variables
};
try {

View File

@@ -1,10 +1,11 @@
import { moduleDispatchResType, ChatItemType } from '@/types/chat';
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import { SelectAppItemType } from '@fastgpt/global/core/module/type';
import { dispatchModules } from '@/pages/api/v1/chat/completions';
import { App } from '@/service/mongo';
import { dispatchModules } from '../index';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { responseWrite } from '@fastgpt/service/common/response';
import { ChatRoleEnum, TaskResponseKeyEnum, sseResponseEventEnum } from '@/constants/chat';
import { ChatRoleEnum, TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { textAdaptGptResponse } from '@/utils/adapt';
type Props = ModuleDispatchProps<{
@@ -18,21 +19,20 @@ type Response = {
[TaskResponseKeyEnum.history]: ChatItemType[];
};
export const dispatchAppRequest = async (props: Record<string, any>): Promise<Response> => {
export const dispatchAppRequest = async (props: Props): Promise<Response> => {
const {
res,
variables,
user,
stream,
detail,
inputs: { userChatInput, history = [], app }
} = props as Props;
} = props;
if (!userChatInput) {
return Promise.reject('Input is empty');
}
const appData = await App.findOne({
const appData = await MongoApp.findOne({
_id: app.id,
userId: user._id
});
@@ -52,16 +52,12 @@ export const dispatchAppRequest = async (props: Record<string, any>): Promise<Re
}
const { responseData, answerText } = await dispatchModules({
res,
...props,
modules: appData.modules,
user,
variables,
params: {
history,
userChatInput
},
stream,
detail
}
});
const completeMessages = history.concat([

View File

@@ -1,25 +1,27 @@
import { startQueue } from './utils/tools';
import { PRICE_SCALE } from '@fastgpt/global/common/bill/constants';
import { initPg } from './pg';
import { PRICE_SCALE } from '@fastgpt/global/support/wallet/bill/constants';
import { initPg } from '@fastgpt/service/common/pg';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { connectMongo } from '@fastgpt/service/common/mongo/init';
import { hashStr } from '@fastgpt/global/common/string/tools';
import { getInitConfig, initGlobal } from '@/pages/api/system/getInitData';
import { createDefaultTeam } from '@fastgpt/service/support/user/team/controller';
import { exit } from 'process';
/**
* connect MongoDB and init data
*/
export async function connectToDatabase(): Promise<void> {
await connectMongo({
export function connectToDatabase(): Promise<void> {
return connectMongo({
beforeHook: () => {
initGlobal();
getInitConfig();
},
afterHook: async () => {
await initRootUser();
afterHook: () => {
initPg();
// start queue
startQueue();
return initRootUser();
}
});
}
@@ -31,6 +33,9 @@ async function initRootUser() {
});
const psw = process.env.DEFAULT_ROOT_PSW || '123456';
let rootId = rootUser?._id || '';
// init root user
if (rootUser) {
await MongoUser.findOneAndUpdate(
{ username: 'root' },
@@ -40,12 +45,15 @@ async function initRootUser() {
}
);
} else {
await MongoUser.create({
const { _id } = await MongoUser.create({
username: 'root',
password: hashStr(psw),
balance: 999999 * PRICE_SCALE
});
rootId = _id;
}
// init root team
await createDefaultTeam({ userId: rootId, maxSize: 1 });
console.log(`root user init:`, {
username: 'root',
@@ -53,10 +61,6 @@ async function initRootUser() {
});
} catch (error) {
console.log('init root user error', error);
exit(1);
}
}
export * from './models/chat';
export * from './models/chatItem';
export * from './models/app';
export * from './common/bill/schema';

View File

@@ -1,205 +0,0 @@
import { Pool } from 'pg';
import type { QueryResultRow } from 'pg';
import { PgDatasetTableName } from '@/constants/plugin';
import { DatasetSpecialIdEnum } from '@fastgpt/global/core/dataset/constant';
export const connectPg = async (): Promise<Pool> => {
if (global.pgClient) {
return global.pgClient;
}
global.pgClient = new Pool({
connectionString: process.env.PG_URL,
max: Number(process.env.DB_MAX_LINK || 5),
keepAlive: true,
idleTimeoutMillis: 60000,
connectionTimeoutMillis: 20000
});
global.pgClient.on('error', (err) => {
console.log(err);
global.pgClient?.end();
global.pgClient = null;
connectPg();
});
try {
await global.pgClient.connect();
console.log('pg connected');
return global.pgClient;
} catch (error) {
global.pgClient = null;
return connectPg();
}
};
type WhereProps = (string | [string, string | number])[];
type GetProps = {
fields?: string[];
where?: WhereProps;
order?: { field: string; mode: 'DESC' | 'ASC' | string }[];
limit?: number;
offset?: number;
};
type DeleteProps = {
where: WhereProps;
};
type ValuesProps = { key: string; value?: string | number }[];
type UpdateProps = {
values: ValuesProps;
where: WhereProps;
};
type InsertProps = {
values: ValuesProps[];
};
class Pg {
private getWhereStr(where?: WhereProps) {
return where
? `WHERE ${where
.map((item) => {
if (typeof item === 'string') {
return item;
}
const val = typeof item[1] === 'number' ? item[1] : `'${String(item[1])}'`;
return `${item[0]}=${val}`;
})
.join(' ')}`
: '';
}
private getUpdateValStr(values: ValuesProps) {
return values
.map((item) => {
const val =
typeof item.value === 'number'
? item.value
: `'${String(item.value).replace(/\'/g, '"')}'`;
return `${item.key}=${val}`;
})
.join(',');
}
private getInsertValStr(values: ValuesProps[]) {
return values
.map(
(items) =>
`(${items
.map((item) =>
typeof item.value === 'number'
? item.value
: `'${String(item.value).replace(/\'/g, '"')}'`
)
.join(',')})`
)
.join(',');
}
async select<T extends QueryResultRow = any>(table: string, props: GetProps) {
const sql = `SELECT ${
!props.fields || props.fields?.length === 0 ? '*' : props.fields?.join(',')
}
FROM ${table}
${this.getWhereStr(props.where)}
${
props.order
? `ORDER BY ${props.order.map((item) => `${item.field} ${item.mode}`).join(',')}`
: ''
}
LIMIT ${props.limit || 10} OFFSET ${props.offset || 0}
`;
const pg = await connectPg();
return pg.query<T>(sql);
}
async count(table: string, props: GetProps) {
const sql = `SELECT COUNT(${props?.fields?.[0] || '*'})
FROM ${table}
${this.getWhereStr(props.where)}
`;
const pg = await connectPg();
return pg.query(sql).then((res) => Number(res.rows[0]?.count || 0));
}
async delete(table: string, props: DeleteProps) {
const sql = `DELETE FROM ${table} ${this.getWhereStr(props.where)}`;
const pg = await connectPg();
return pg.query(sql);
}
async update(table: string, props: UpdateProps) {
if (props.values.length === 0) {
return {
rowCount: 0
};
}
const sql = `UPDATE ${table} SET ${this.getUpdateValStr(props.values)} ${this.getWhereStr(
props.where
)}`;
const pg = await connectPg();
return pg.query(sql);
}
async insert(table: string, props: InsertProps) {
if (props.values.length === 0) {
return {
rowCount: 0,
rows: []
};
}
const fields = props.values[0].map((item) => item.key).join(',');
const sql = `INSERT INTO ${table} (${fields}) VALUES ${this.getInsertValStr(
props.values
)} RETURNING id`;
const pg = await connectPg();
return pg.query<{ id: string }>(sql);
}
async query<T extends QueryResultRow = any>(sql: string) {
const pg = await connectPg();
return pg.query<T>(sql);
}
}
export const PgClient = new Pg();
/**
* Update data file_id
*/
export const updateDataFileId = async ({
oldFileId,
userId,
newFileId = DatasetSpecialIdEnum.manual
}: {
oldFileId: string;
userId: string;
newFileId?: string;
}) => {
await PgClient.update(PgDatasetTableName, {
where: [['file_id', oldFileId], 'AND', ['user_id', userId]],
values: [{ key: 'file_id', value: newFileId }]
});
return newFileId;
};
export async function initPg() {
try {
await connectPg();
await PgClient.query(`
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE IF NOT EXISTS ${PgDatasetTableName} (
id BIGSERIAL PRIMARY KEY,
vector VECTOR(1536) NOT NULL,
user_id VARCHAR(50) NOT NULL,
dataset_id VARCHAR(50) NOT NULL,
collection_id VARCHAR(50) NOT NULL,
q TEXT NOT NULL,
a TEXT
);
CREATE INDEX IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 24, ef_construction = 48);
`);
console.log('init pg successful');
} catch (error) {
console.log('init pg error', error);
}
}

View File

@@ -1,96 +0,0 @@
import { sseResponseEventEnum } from '@/constants/chat';
import { NextApiResponse } from 'next';
import { proxyError, ERROR_RESPONSE, ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import { addLog } from './utils/tools';
import { clearCookie } from '@fastgpt/service/support/user/auth';
import { responseWrite } from '@fastgpt/service/common/response';
export interface ResponseType<T = any> {
code: number;
message: string;
data: T;
}
export const jsonRes = <T = any>(
res: NextApiResponse,
props?: {
code?: number;
message?: string;
data?: T;
error?: any;
}
) => {
const { code = 200, message = '', data = null, error } = props || {};
const errResponseKey = typeof error === 'string' ? error : error?.message;
// Specified error
if (ERROR_RESPONSE[errResponseKey]) {
// login is expired
if (errResponseKey === ERROR_ENUM.unAuthorization) {
clearCookie(res);
}
return res.json(ERROR_RESPONSE[errResponseKey]);
}
// another error
let msg = '';
if ((code < 200 || code >= 400) && !message) {
msg = error?.response?.statusText || error?.message || '请求错误';
if (typeof error === 'string') {
msg = error;
} else if (proxyError[error?.code]) {
msg = '网络连接异常';
} else if (error?.response?.data?.error?.message) {
msg = error?.response?.data?.error?.message;
} else if (error?.error?.message) {
msg = error?.error?.message;
}
addLog.error(`response error: ${msg}`, error);
}
res.status(code).json({
code,
statusText: '',
message: message || msg,
data: data !== undefined ? data : null
});
};
export const sseErrRes = (res: NextApiResponse, error: any) => {
const errResponseKey = typeof error === 'string' ? error : error?.message;
// Specified error
if (ERROR_RESPONSE[errResponseKey]) {
// login is expired
if (errResponseKey === ERROR_ENUM.unAuthorization) {
clearCookie(res);
}
return responseWrite({
res,
event: sseResponseEventEnum.error,
data: JSON.stringify(ERROR_RESPONSE[errResponseKey])
});
}
let msg = error?.response?.statusText || error?.message || '请求错误';
if (typeof error === 'string') {
msg = error;
} else if (proxyError[error?.code]) {
msg = '网络连接异常';
} else if (error?.response?.data?.error?.message) {
msg = error?.response?.data?.error?.message;
} else if (error?.error?.message) {
msg = error?.error?.message;
}
addLog.error(`sse error: ${msg}`, error);
responseWrite({
res,
event: sseResponseEventEnum.error,
data: JSON.stringify({ message: msg })
});
};

View File

@@ -0,0 +1,14 @@
import { POST } from '@fastgpt/service/common/api/plusRequest';
import type {
AuthLinkLimitProps,
AuthShareChatInitProps
} from '@fastgpt/global/support/outLink/api.d';
export function authOutLinkLimit(data: AuthLinkLimitProps) {
return POST('/support/outLink/authLimit', data);
}
export function authShareChatInit(data: AuthShareChatInitProps) {
if (!global.feConfigs?.isPlus) return;
return POST('/support/outLink/authShareChatInit', data);
}

View File

@@ -0,0 +1,8 @@
import { GET } from '@fastgpt/service/common/api/plusRequest';
export const authTeamBalance = async (teamId: string) => {
if (global.systemEnv.pluginBaseUrl) {
return GET('/support/permission/authBalance', { teamId });
}
return true;
};

View File

@@ -0,0 +1,36 @@
import { getDatasetPgData } from '@/service/core/dataset/data/controller';
import { PgDataItemType } from '@fastgpt/global/core/dataset/type';
import { AuthResponseType } from '@fastgpt/global/support/permission/type';
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
import { parseHeaderCert } from '@fastgpt/service/support/permission/controller';
import { AuthModeType } from '@fastgpt/service/support/permission/type';
export async function authDatasetData({
dataId,
...props
}: AuthModeType & {
dataId: string;
}): Promise<
AuthResponseType & {
datasetData: PgDataItemType;
}
> {
const result = await parseHeaderCert(props);
const { tmbId } = result;
// get pg data
const datasetData = await getDatasetPgData({ id: dataId });
const isOwner = String(datasetData.tmbId) === tmbId;
// data has the same permissions as collection
const { canWrite } = await authDatasetCollection({
...props,
collectionId: datasetData.collectionId
});
return {
...result,
datasetData,
isOwner,
canWrite
};
}

View File

@@ -0,0 +1,31 @@
import { authOutLinkLimit } from '@/service/support/outLink/auth';
import { AuthLinkChatProps } from '@fastgpt/global/support/outLink/api.d';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { getUserAndAuthBalance } from './user';
import { authOutLinkValid } from '@fastgpt/service/support/permission/auth/outLink';
export async function authOutLinkChat({
shareId,
ip,
authToken,
question
}: AuthLinkChatProps & {
shareId: string;
}) {
// get outLink
const { shareChat, app } = await authOutLinkValid({ shareId });
const [user] = await Promise.all([
getUserAndAuthBalance({ tmbId: shareChat.tmbId, minBalance: 0 }),
global.feConfigs?.isPlus
? authOutLinkLimit({ outLink: shareChat, ip, authToken, question })
: undefined
]);
return {
authType: AuthUserTypeEnum.token,
responseDetail: shareChat.responseDetail,
user,
app
};
}

View File

@@ -0,0 +1,48 @@
import { AuthResponseType } from '@fastgpt/global/support/permission/type';
import { parseHeaderCert } from '@fastgpt/service/support/permission/controller';
import { AuthModeType } from '@fastgpt/service/support/permission/type';
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
import { UserType } from '@fastgpt/global/support/user/type';
import { getUserDetail } from '@/service/support/user/controller';
export async function getUserAndAuthBalance({
tmbId,
minBalance
}: {
tmbId: string;
minBalance?: number;
}) {
const user = await getUserDetail({ tmbId });
if (!user) {
return Promise.reject(UserErrEnum.unAuthUser);
}
if (minBalance !== undefined && global.feConfigs.isPlus && user.team.balance < minBalance) {
return Promise.reject(UserErrEnum.balanceNotEnough);
}
return user;
}
/* get user */
export async function authUser({
minBalance,
...props
}: AuthModeType & {
minBalance?: number;
}): Promise<
AuthResponseType & {
user: UserType;
}
> {
const { userId, teamId, tmbId } = await parseHeaderCert(props);
return {
userId,
teamId,
tmbId,
user: await getUserAndAuthBalance({ tmbId, minBalance }),
isOwner: true,
canWrite: true
};
}

View File

@@ -0,0 +1,30 @@
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { UserType } from '@fastgpt/global/support/user/type';
import { getTeamInfoByTmbId } from '@fastgpt/service/support/user/team/controller';
export async function getUserDetail({
tmbId,
userId
}: {
tmbId?: string;
userId?: string;
}): Promise<UserType> {
const team = await getTeamInfoByTmbId({ tmbId, userId });
const user = await MongoUser.findById(team.userId);
if (!user) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
return {
_id: user._id,
username: user.username,
avatar: user.avatar,
balance: user.balance,
timezone: user.timezone,
promotionRate: user.promotionRate,
openaiAccount: user.openaiAccount,
team
};
}

View File

@@ -0,0 +1,7 @@
import { POST } from '@fastgpt/service/common/api/plusRequest';
import { SendInformProps } from '@fastgpt/global/support/user/inform/type';
export function sendOneInform(data: SendInformProps) {
if (!global.systemEnv.pluginBaseUrl) return;
return POST('/support/user/inform/create', data);
}

View File

@@ -1,80 +1,41 @@
import { Bill } from '@/service/mongo';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { BillSourceEnum } from '@/constants/user';
import { getModelMap, ModelTypeEnum } from '@/service/core/ai/model';
import { ChatHistoryItemResType } from '@/types/chat';
import { formatPrice } from '@fastgpt/global/common/bill/tools';
import { addLog } from '@/service/utils/tools';
import type { CreateBillType } from '@/types/common/bill';
import { defaultQGModels } from '@/constants/model';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { getAudioSpeechModel, getModelMap, ModelTypeEnum } from '@/service/core/ai/model';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { addLog } from '@fastgpt/service/common/mongo/controller';
import type { ConcatBillProps, CreateBillProps } from '@fastgpt/global/support/wallet/bill/api.d';
import { defaultQGModels } from '@fastgpt/global/core/ai/model';
import { POST } from '@fastgpt/service/common/api/plusRequest';
async function createBill(data: CreateBillType) {
try {
await Promise.all([
MongoUser.findByIdAndUpdate(data.userId, {
$inc: { balance: -data.total }
}),
Bill.create(data)
]);
} catch (error) {
addLog.error(`createBill error`, error);
}
export function createBill(data: CreateBillProps) {
if (!global.systemEnv.pluginBaseUrl) return;
POST('/support/wallet/bill/createBill', data);
}
async function concatBill({
billId,
total,
listIndex,
tokens = 0,
userId
}: {
billId?: string;
total: number;
listIndex?: number;
tokens?: number;
userId: string;
}) {
if (!billId) return;
try {
await Promise.all([
Bill.findOneAndUpdate(
{
_id: billId,
userId
},
{
$inc: {
total,
...(listIndex !== undefined && {
[`list.${listIndex}.amount`]: total,
[`list.${listIndex}.tokenLen`]: tokens
})
}
}
),
MongoUser.findByIdAndUpdate(userId, {
$inc: { balance: -total }
})
]);
} catch (error) {}
export function concatBill(data: ConcatBillProps) {
if (!global.systemEnv.pluginBaseUrl) return;
POST('/support/wallet/bill/concatBill', data);
}
export const pushChatBill = ({
appName,
appId,
userId,
teamId,
tmbId,
source,
response
}: {
appName: string;
appId: string;
userId: string;
teamId: string;
tmbId: string;
source: `${BillSourceEnum}`;
response: ChatHistoryItemResType[];
}) => {
const total = response.reduce((sum, item) => sum + item.price, 0);
createBill({
userId,
teamId,
tmbId,
appName,
appId,
total,
@@ -88,31 +49,35 @@ export const pushChatBill = ({
});
addLog.info(`finish completions`, {
source,
userId,
teamId,
tmbId,
price: formatPrice(total)
});
return { total };
};
export const pushQABill = async ({
userId,
teamId,
tmbId,
totalTokens,
billId
}: {
userId: string;
teamId: string;
tmbId: string;
totalTokens: number;
billId: string;
}) => {
addLog.info('splitData generate success', { totalTokens });
// 获取模型单价格, 都是用 gpt35 拆分
// 获取模型单价格
const unitPrice = global.qaModels?.[0]?.price || 3;
// 计算价格
const total = unitPrice * totalTokens;
concatBill({
billId,
userId,
teamId,
tmbId,
total,
tokens: totalTokens,
listIndex: 1
@@ -123,14 +88,18 @@ export const pushQABill = async ({
export const pushGenerateVectorBill = async ({
billId,
userId,
teamId,
tmbId,
tokenLen,
model
model,
source = BillSourceEnum.fastgpt
}: {
billId?: string;
userId: string;
teamId: string;
tmbId: string;
tokenLen: number;
model: string;
source?: `${BillSourceEnum}`;
}) => {
// 计算价格. 至少为1
const vectorModel =
@@ -142,7 +111,8 @@ export const pushGenerateVectorBill = async ({
// 插入 Bill 记录
if (billId) {
concatBill({
userId,
teamId,
tmbId,
total,
billId,
tokens: tokenLen,
@@ -150,10 +120,11 @@ export const pushGenerateVectorBill = async ({
});
} else {
createBill({
userId,
teamId,
tmbId,
appName: '索引生成',
total,
source: BillSourceEnum.fastgpt,
source,
list: [
{
moduleName: '索引生成',
@@ -167,25 +138,20 @@ export const pushGenerateVectorBill = async ({
return { total };
};
export const countModelPrice = ({
model,
export const pushQuestionGuideBill = ({
tokens,
type
teamId,
tmbId
}: {
model: string;
tokens: number;
type: `${ModelTypeEnum}`;
teamId: string;
tmbId: string;
}) => {
const modelData = getModelMap?.[type]?.(model);
if (!modelData) return 0;
return modelData.price * tokens;
};
export const pushQuestionGuideBill = ({ tokens, userId }: { tokens: number; userId: string }) => {
const qgModel = global.qgModels?.[0] || defaultQGModels[0];
const total = qgModel.price * tokens;
createBill({
userId,
teamId,
tmbId,
appName: '下一步指引',
total,
source: BillSourceEnum.fastgpt,
@@ -199,3 +165,37 @@ export const pushQuestionGuideBill = ({ tokens, userId }: { tokens: number; user
]
});
};
export function pushAudioSpeechBill({
appName = 'wallet.bill.Audio Speech',
model,
textLength,
teamId,
tmbId,
source = BillSourceEnum.fastgpt
}: {
appName?: string;
model: string;
textLength: number;
teamId: string;
tmbId: string;
source: `${BillSourceEnum}`;
}) {
const modelData = getAudioSpeechModel(model);
const total = modelData.price * textLength;
createBill({
teamId,
tmbId,
appName,
total,
source,
list: [
{
moduleName: appName,
amount: total,
model: modelData.name,
tokenLen: textLength
}
]
});
}

View File

@@ -0,0 +1,32 @@
import { ModelTypeEnum, getModelMap } from '@/service/core/ai/model';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
export function authType2BillSource({
authType,
shareId,
source
}: {
authType?: `${AuthUserTypeEnum}`;
shareId?: string;
source?: `${BillSourceEnum}`;
}) {
if (source) return source;
if (shareId) return BillSourceEnum.shareLink;
if (authType === AuthUserTypeEnum.apikey) return BillSourceEnum.api;
return BillSourceEnum.fastgpt;
}
export const countModelPrice = ({
model,
tokens,
type
}: {
model: string;
tokens: number;
type: `${ModelTypeEnum}`;
}) => {
const modelData = getModelMap?.[type]?.(model);
if (!modelData) return 0;
return modelData.price * tokens;
};

View File

@@ -1,49 +0,0 @@
import { App } from '../mongo';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import type { AppSchema } from '@/types/mongoSchema';
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
// 模型使用权校验
export const authApp = async ({
appId,
userId,
authUser = true,
authOwner = true
}: {
appId: string;
userId: string;
authUser?: boolean;
authOwner?: boolean;
}) => {
// 获取 app 数据
const app = await App.findById<AppSchema>(appId);
if (!app) {
return Promise.reject('App is not exists');
}
/*
Access verification
1. authOwner=true or authUser = true , just owner can use
2. authUser = false and share, anyone can use
*/
if (authOwner || authUser) {
if (userId !== String(app.userId)) return Promise.reject(ERROR_ENUM.unAuthModel);
}
return {
app,
showModelDetail: userId === String(app.userId)
};
};
// 知识库操作权限
export const authDataset = async ({ datasetId, userId }: { datasetId: string; userId: string }) => {
const dataset = await MongoDataset.findOne({
_id: datasetId,
userId
});
if (dataset) {
return dataset;
}
return Promise.reject(ERROR_ENUM.unAuthDataset);
};

View File

@@ -1,11 +1,15 @@
import { ChatItemType } from '@/types/chat';
import { Chat, App, ChatItem } from '@/service/mongo';
import { ChatSourceEnum } from '@/constants/chat';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { addLog } from '@fastgpt/service/common/mongo/controller';
type Props = {
chatId: string;
appId: string;
userId: string;
teamId: string;
tmbId: string;
variables?: Record<string, any>;
isOwner: boolean;
source: `${ChatSourceEnum}`;
@@ -16,7 +20,8 @@ type Props = {
export async function saveChat({
chatId,
appId,
userId,
teamId,
tmbId,
variables,
isOwner,
source,
@@ -24,20 +29,22 @@ export async function saveChat({
content
}: Props) {
try {
const chatHistory = await Chat.findOne(
const chatHistory = await MongoChat.findOne(
{
chatId,
userId,
teamId,
tmbId,
appId
},
'_id'
);
const promise: any[] = [
ChatItem.insertMany(
MongoChatItem.insertMany(
content.map((item) => ({
chatId,
userId,
teamId,
tmbId,
appId,
...item
}))
@@ -46,8 +53,8 @@ export async function saveChat({
if (chatHistory) {
promise.push(
Chat.updateOne(
{ chatId, userId, appId },
MongoChat.updateOne(
{ chatId },
{
title: content[0].value.slice(0, 20),
updateTime: new Date()
@@ -56,9 +63,10 @@ export async function saveChat({
);
} else {
promise.push(
Chat.create({
MongoChat.create({
chatId,
userId,
teamId,
tmbId,
appId,
variables,
title: content[0].value.slice(0, 20),
@@ -70,7 +78,7 @@ export async function saveChat({
if (isOwner && source === ChatSourceEnum.online) {
promise.push(
App.findByIdAndUpdate(appId, {
MongoApp.findByIdAndUpdate(appId, {
updateTime: new Date()
})
);
@@ -78,16 +86,6 @@ export async function saveChat({
await Promise.all(promise);
} catch (error) {
Chat.updateOne(
{ chatId, userId },
{
$push: {
content: {
$each: [],
$slice: -10
}
}
}
);
addLog.error(`update chat history error`, error);
}
}

View File

@@ -19,30 +19,3 @@ export const startQueue = (limit?: number) => {
generateVector();
}
};
/* add logger */
export const addLog = {
info: (msg: string, obj?: Record<string, any>) => {
global.logger?.info(msg, { meta: obj });
},
error: (msg: string, error?: any) => {
global.logger?.error(msg, {
meta: {
stack: error?.stack,
...(error?.config && {
config: {
headers: error.config.headers,
url: error.config.url,
data: error.config.data
}
}),
...(error?.response && {
response: {
status: error.response.status,
statusText: error.response.statusText
}
})
}
});
}
};