mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-03 13:38:00 +00:00
monorepo packages (#344)
This commit is contained in:
172
projects/app/src/service/common/bill/push.ts
Normal file
172
projects/app/src/service/common/bill/push.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { Bill, User, OutLink } from '@/service/mongo';
|
||||
import { BillSourceEnum } from '@/constants/user';
|
||||
import { getModel } from '@/service/utils/data';
|
||||
import { ChatHistoryItemResType } from '@/types/chat';
|
||||
import { formatPrice } from '@fastgpt/common/bill/index';
|
||||
import { addLog } from '@/service/utils/tools';
|
||||
import type { CreateBillType } from '@/types/common/bill';
|
||||
|
||||
async function createBill(data: CreateBillType) {
|
||||
try {
|
||||
await Promise.all([
|
||||
User.findByIdAndUpdate(data.userId, {
|
||||
$inc: { balance: -data.total }
|
||||
}),
|
||||
Bill.create(data)
|
||||
]);
|
||||
} catch (error) {
|
||||
addLog.error(`createBill error`, error);
|
||||
}
|
||||
}
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
),
|
||||
User.findByIdAndUpdate(userId, {
|
||||
$inc: { balance: -total }
|
||||
})
|
||||
]);
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
export const pushChatBill = ({
|
||||
appName,
|
||||
appId,
|
||||
userId,
|
||||
source,
|
||||
response
|
||||
}: {
|
||||
appName: string;
|
||||
appId: string;
|
||||
userId: string;
|
||||
source: `${BillSourceEnum}`;
|
||||
response: ChatHistoryItemResType[];
|
||||
}) => {
|
||||
const total = response.reduce((sum, item) => sum + item.price, 0);
|
||||
|
||||
createBill({
|
||||
userId,
|
||||
appName,
|
||||
appId,
|
||||
total,
|
||||
source,
|
||||
list: response.map((item) => ({
|
||||
moduleName: item.moduleName,
|
||||
amount: item.price || 0,
|
||||
model: item.model,
|
||||
tokenLen: item.tokens
|
||||
}))
|
||||
});
|
||||
addLog.info(`finish completions`, {
|
||||
source,
|
||||
userId,
|
||||
price: formatPrice(total)
|
||||
});
|
||||
return { total };
|
||||
};
|
||||
|
||||
export const pushQABill = async ({
|
||||
userId,
|
||||
totalTokens,
|
||||
billId
|
||||
}: {
|
||||
userId: string;
|
||||
totalTokens: number;
|
||||
billId: string;
|
||||
}) => {
|
||||
addLog.info('splitData generate success', { totalTokens });
|
||||
|
||||
// 获取模型单价格, 都是用 gpt35 拆分
|
||||
const unitPrice = global.qaModel.price || 3;
|
||||
// 计算价格
|
||||
const total = unitPrice * totalTokens;
|
||||
|
||||
concatBill({
|
||||
billId,
|
||||
userId,
|
||||
total,
|
||||
tokens: totalTokens,
|
||||
listIndex: 1
|
||||
});
|
||||
|
||||
return { total };
|
||||
};
|
||||
|
||||
export const pushGenerateVectorBill = async ({
|
||||
billId,
|
||||
userId,
|
||||
tokenLen,
|
||||
model
|
||||
}: {
|
||||
billId?: string;
|
||||
userId: string;
|
||||
tokenLen: number;
|
||||
model: string;
|
||||
}) => {
|
||||
// 计算价格. 至少为1
|
||||
const vectorModel =
|
||||
global.vectorModels.find((item) => item.model === model) || global.vectorModels[0];
|
||||
const unitPrice = vectorModel.price || 0.2;
|
||||
let total = unitPrice * tokenLen;
|
||||
total = total > 1 ? total : 1;
|
||||
|
||||
// 插入 Bill 记录
|
||||
if (billId) {
|
||||
concatBill({
|
||||
userId,
|
||||
total,
|
||||
billId,
|
||||
tokens: tokenLen,
|
||||
listIndex: 0
|
||||
});
|
||||
} else {
|
||||
createBill({
|
||||
userId,
|
||||
appName: '索引生成',
|
||||
total,
|
||||
source: BillSourceEnum.fastgpt,
|
||||
list: [
|
||||
{
|
||||
moduleName: '索引生成',
|
||||
amount: total,
|
||||
model: vectorModel.model,
|
||||
tokenLen
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
return { total };
|
||||
};
|
||||
|
||||
export const countModelPrice = ({ model, tokens }: { model: string; tokens: number }) => {
|
||||
const modelData = getModel(model);
|
||||
if (!modelData) return 0;
|
||||
return modelData.price * tokens;
|
||||
};
|
46
projects/app/src/service/common/bill/schema.ts
Normal file
46
projects/app/src/service/common/bill/schema.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Schema, model, models, Model } from 'mongoose';
|
||||
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);
|
24
projects/app/src/service/common/ipLimit/schema.ts
Normal file
24
projects/app/src/service/common/ipLimit/schema.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Schema, model, models, Model } from 'mongoose';
|
||||
import type { IpLimitSchemaType } from '@/types/common/ipLimit';
|
||||
|
||||
const IpLimitSchema = new Schema({
|
||||
eventId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
ip: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
account: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
lastMinute: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
}
|
||||
});
|
||||
|
||||
export const IpLimit: Model<IpLimitSchemaType> =
|
||||
models['ip_limit'] || model('ip_limit', IpLimitSchema);
|
39
projects/app/src/service/common/stream.ts
Normal file
39
projects/app/src/service/common/stream.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
|
||||
export function responseWriteController({
|
||||
res,
|
||||
readStream
|
||||
}: {
|
||||
res: NextApiResponse;
|
||||
readStream: any;
|
||||
}) {
|
||||
res.on('drain', () => {
|
||||
readStream.resume();
|
||||
});
|
||||
|
||||
return (text: string) => {
|
||||
const writeResult = res.write(text);
|
||||
if (!writeResult) {
|
||||
readStream.pause();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function responseWrite({
|
||||
res,
|
||||
write,
|
||||
event,
|
||||
data
|
||||
}: {
|
||||
res?: NextApiResponse;
|
||||
write?: (text: string) => void;
|
||||
event?: string;
|
||||
data: string;
|
||||
}) {
|
||||
const Write = write || res?.write;
|
||||
|
||||
if (!Write) return;
|
||||
|
||||
event && Write(`event: ${event}\n`);
|
||||
Write(`data: ${data}\n\n`);
|
||||
}
|
68
projects/app/src/service/common/tiktoken.ts
Normal file
68
projects/app/src/service/common/tiktoken.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { ChatRoleEnum } from '@/constants/chat';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { countMessagesTokens, countPromptTokens } from '@/utils/common/tiktoken';
|
||||
import { adaptRole_Chat2Message } from '@/utils/common/adapt/message';
|
||||
|
||||
export type ChatCompletionResponseType = {
|
||||
streamResponse: any;
|
||||
responseMessages: ChatItemType[];
|
||||
responseText: string;
|
||||
totalTokens: number;
|
||||
};
|
||||
export type StreamResponseType = {
|
||||
chatResponse: any;
|
||||
messages: ChatItemType[];
|
||||
res: NextApiResponse;
|
||||
model: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
/* slice chat context by tokens */
|
||||
export function ChatContextFilter({
|
||||
messages = [],
|
||||
maxTokens
|
||||
}: {
|
||||
messages: ChatItemType[];
|
||||
maxTokens: number;
|
||||
}) {
|
||||
if (!Array.isArray(messages)) {
|
||||
return [];
|
||||
}
|
||||
const rawTextLen = messages.reduce((sum, item) => sum + item.value.length, 0);
|
||||
|
||||
// If the text length is less than half of the maximum token, no calculation is required
|
||||
if (rawTextLen < maxTokens * 0.5) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
// filter startWith system prompt
|
||||
const chatStartIndex = messages.findIndex((item) => item.obj !== ChatRoleEnum.System);
|
||||
const systemPrompts: ChatItemType[] = messages.slice(0, chatStartIndex);
|
||||
const chatPrompts: ChatItemType[] = messages.slice(chatStartIndex);
|
||||
|
||||
// reduce token of systemPrompt
|
||||
maxTokens -= countMessagesTokens({
|
||||
messages: systemPrompts
|
||||
});
|
||||
|
||||
// 根据 tokens 截断内容
|
||||
const chats: ChatItemType[] = [];
|
||||
|
||||
// 从后往前截取对话内容
|
||||
for (let i = chatPrompts.length - 1; i >= 0; i--) {
|
||||
const item = chatPrompts[i];
|
||||
chats.unshift(item);
|
||||
|
||||
const tokens = countPromptTokens(item.value, adaptRole_Chat2Message(item.obj));
|
||||
maxTokens -= tokens;
|
||||
|
||||
/* 整体 tokens 超出范围, system必须保留 */
|
||||
if (maxTokens <= 0) {
|
||||
chats.shift();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [...systemPrompts, ...chats];
|
||||
}
|
Reference in New Issue
Block a user