mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-01 03:48:24 +00:00
v4.6 -1 (#459)
This commit is contained in:
@@ -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);
|
15
projects/app/src/service/common/censor/index.ts
Normal file
15
projects/app/src/service/common/censor/index.ts
Normal 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('');
|
||||
});
|
@@ -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';
|
||||
|
@@ -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',
|
||||
|
70
projects/app/src/service/core/ai/vector.ts
Normal file
70
projects/app/src/service/core/ai/vector.ts
Normal 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);
|
||||
}
|
168
projects/app/src/service/core/dataset/data/controller.ts
Normal file
168
projects/app/src/service/core/dataset/data/controller.ts
Normal 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("','")}')`]
|
||||
});
|
||||
}
|
@@ -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
|
||||
};
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
}
|
||||
};
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
@@ -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);
|
@@ -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);
|
@@ -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 });
|
||||
|
||||
|
@@ -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 });
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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)
|
||||
};
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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<{
|
||||
|
@@ -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';
|
||||
|
@@ -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 {
|
||||
|
@@ -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([
|
||||
|
@@ -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';
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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 })
|
||||
});
|
||||
};
|
14
projects/app/src/service/support/outLink/auth.ts
Normal file
14
projects/app/src/service/support/outLink/auth.ts
Normal 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);
|
||||
}
|
8
projects/app/src/service/support/permission/auth/bill.ts
Normal file
8
projects/app/src/service/support/permission/auth/bill.ts
Normal 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;
|
||||
};
|
36
projects/app/src/service/support/permission/auth/dataset.ts
Normal file
36
projects/app/src/service/support/permission/auth/dataset.ts
Normal 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
|
||||
};
|
||||
}
|
31
projects/app/src/service/support/permission/auth/outLink.ts
Normal file
31
projects/app/src/service/support/permission/auth/outLink.ts
Normal 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
|
||||
};
|
||||
}
|
48
projects/app/src/service/support/permission/auth/user.ts
Normal file
48
projects/app/src/service/support/permission/auth/user.ts
Normal 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
|
||||
};
|
||||
}
|
30
projects/app/src/service/support/user/controller.ts
Normal file
30
projects/app/src/service/support/user/controller.ts
Normal 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
|
||||
};
|
||||
}
|
7
projects/app/src/service/support/user/inform/api.ts
Normal file
7
projects/app/src/service/support/user/inform/api.ts
Normal 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);
|
||||
}
|
@@ -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
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
32
projects/app/src/service/support/wallet/bill/utils.ts
Normal file
32
projects/app/src/service/support/wallet/bill/utils.ts
Normal 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;
|
||||
};
|
@@ -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);
|
||||
};
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user