V4.8.17 feature (#3493)
* split tokens into input and output (#3477) * split tokens into input and output * query extension & tool call & question guide * fix * perf: input and output tokens * perf: tool call if else * perf: remove code * fix: extract usage count * fix: qa usage count --------- Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
parent
da2831b948
commit
50bf7f9a3b
@ -28,6 +28,11 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4817' \
|
|||||||
|
|
||||||
会将用户绑定的 OpenAI 账号移动到团队中。
|
会将用户绑定的 OpenAI 账号移动到团队中。
|
||||||
|
|
||||||
|
|
||||||
|
## 调整 completions 接口返回值
|
||||||
|
|
||||||
|
/api/v1/chat/completions 接口返回值调整,对话节点、工具节点等使用到模型的节点,将不再返回 `tokens` 字段,改为返回 `inputTokens` 和 `outputTokens` 字段,分别表示输入和输出的 Token 数量。
|
||||||
|
|
||||||
## 完整更新内容
|
## 完整更新内容
|
||||||
|
|
||||||
1. 新增 - 简易模式工具调用支持数组类型插件。
|
1. 新增 - 简易模式工具调用支持数组类型插件。
|
||||||
@ -36,10 +41,12 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4817' \
|
|||||||
4. 新增 - 商业版支持后台配置模板市场。
|
4. 新增 - 商业版支持后台配置模板市场。
|
||||||
5. 新增 - 商业版支持后台配置自定义工作流变量,用于与业务系统鉴权打通。
|
5. 新增 - 商业版支持后台配置自定义工作流变量,用于与业务系统鉴权打通。
|
||||||
6. 新增 - 搜索测试接口支持问题优化。
|
6. 新增 - 搜索测试接口支持问题优化。
|
||||||
7. 优化 - Markdown 大小测试,超出 20 万字符不使用 Markdown 组件,避免崩溃。
|
7. 新增 - 工作流中 Input Token 和 Output Token 分开记录展示。并修复部分请求未记录输出 Token 计费问题。
|
||||||
8. 优化 - 知识库搜索参数,滑动条支持输入模式,可以更精准的控制。
|
8. 优化 - Markdown 大小测试,超出 20 万字符不使用 Markdown 组件,避免崩溃。
|
||||||
9. 优化 - 可用模型展示
|
9. 优化 - 知识库搜索参数,滑动条支持输入模式,可以更精准的控制。
|
||||||
10. 优化 - Mongo 查询语句,增加 virtual 字段。
|
10. 优化 - 可用模型展示UI。
|
||||||
11. 修复 - 文件返回接口缺少 Content-Length 头,导致通过非同源文件上传时,阿里 vision 模型无法识别图片。
|
11. 优化 - Mongo 查询语句,增加 virtual 字段。
|
||||||
12. 修复 - 去除判断器两端字符串隐藏换行符,避免判断器失效。
|
12. 修复 - 文件返回接口缺少 Content-Length 头,导致通过非同源文件上传时,阿里 vision 模型无法识别图片。
|
||||||
13. 修复 - 变量更新节点,手动输入更新内容时候,非字符串类型数据类型无法自动转化。
|
13. 修复 - 去除判断器两端字符串隐藏换行符,避免判断器失效。
|
||||||
|
14. 修复 - 变量更新节点,手动输入更新内容时候,非字符串类型数据类型无法自动转化。
|
||||||
|
15. 修复 - 豆包模型无法工具调用。
|
||||||
23
packages/global/core/ai/model.d.ts
vendored
23
packages/global/core/ai/model.d.ts
vendored
@ -1,6 +1,13 @@
|
|||||||
import type { ModelProviderIdType } from './provider';
|
import type { ModelProviderIdType } from './provider';
|
||||||
|
|
||||||
export type LLMModelItemType = {
|
type PriceType = {
|
||||||
|
charsPointsPrice?: number; // 1k chars=n points; 60s=n points;
|
||||||
|
|
||||||
|
// If inputPrice is set, the input-output charging scheme is adopted
|
||||||
|
inputPrice?: number; // 1k tokens=n points
|
||||||
|
outputPrice?: number; // 1k tokens=n points
|
||||||
|
};
|
||||||
|
export type LLMModelItemType = PriceType & {
|
||||||
provider: ModelProviderIdType;
|
provider: ModelProviderIdType;
|
||||||
model: string;
|
model: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -10,8 +17,6 @@ export type LLMModelItemType = {
|
|||||||
quoteMaxToken: number;
|
quoteMaxToken: number;
|
||||||
maxTemperature: number;
|
maxTemperature: number;
|
||||||
|
|
||||||
charsPointsPrice: number; // 1k chars=n points
|
|
||||||
|
|
||||||
censor?: boolean;
|
censor?: boolean;
|
||||||
vision?: boolean;
|
vision?: boolean;
|
||||||
|
|
||||||
@ -33,13 +38,12 @@ export type LLMModelItemType = {
|
|||||||
fieldMap?: Record<string, string>;
|
fieldMap?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type VectorModelItemType = {
|
export type VectorModelItemType = PriceType & {
|
||||||
provider: ModelProviderIdType;
|
provider: ModelProviderIdType;
|
||||||
model: string; // model name
|
model: string; // model name
|
||||||
name: string; // show name
|
name: string; // show name
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
defaultToken: number; // split text default token
|
defaultToken: number; // split text default token
|
||||||
charsPointsPrice: number; // 1k tokens=n points
|
|
||||||
maxToken: number; // model max token
|
maxToken: number; // model max token
|
||||||
weight: number; // training weight
|
weight: number; // training weight
|
||||||
hidden?: boolean; // Disallow creation
|
hidden?: boolean; // Disallow creation
|
||||||
@ -48,25 +52,22 @@ export type VectorModelItemType = {
|
|||||||
queryConfig?: Record<string, any>; // Custom parameters for query
|
queryConfig?: Record<string, any>; // Custom parameters for query
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReRankModelItemType = {
|
export type ReRankModelItemType = PriceType & {
|
||||||
model: string;
|
model: string;
|
||||||
name: string;
|
name: string;
|
||||||
charsPointsPrice: number;
|
|
||||||
requestUrl: string;
|
requestUrl: string;
|
||||||
requestAuth: string;
|
requestAuth: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AudioSpeechModelType = {
|
export type AudioSpeechModelType = PriceType & {
|
||||||
provider: ModelProviderIdType;
|
provider: ModelProviderIdType;
|
||||||
model: string;
|
model: string;
|
||||||
name: string;
|
name: string;
|
||||||
charsPointsPrice: number;
|
|
||||||
voices: { label: string; value: string; bufferId: string }[];
|
voices: { label: string; value: string; bufferId: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type STTModelType = {
|
export type STTModelType = PriceType & {
|
||||||
provider: ModelProviderIdType;
|
provider: ModelProviderIdType;
|
||||||
model: string;
|
model: string;
|
||||||
name: string;
|
name: string;
|
||||||
charsPointsPrice: number; // 60s = n points
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -107,7 +107,9 @@ export type DispatchNodeResponseType = {
|
|||||||
mergeSignId?: string;
|
mergeSignId?: string;
|
||||||
|
|
||||||
// bill
|
// bill
|
||||||
tokens?: number;
|
tokens?: number; // deprecated
|
||||||
|
inputTokens?: number;
|
||||||
|
outputTokens?: number;
|
||||||
model?: string;
|
model?: string;
|
||||||
contextTotalLen?: number;
|
contextTotalLen?: number;
|
||||||
totalPoints?: number;
|
totalPoints?: number;
|
||||||
@ -157,6 +159,8 @@ export type DispatchNodeResponseType = {
|
|||||||
|
|
||||||
// tool
|
// tool
|
||||||
toolCallTokens?: number;
|
toolCallTokens?: number;
|
||||||
|
toolCallInputTokens?: number;
|
||||||
|
toolCallOutputTokens?: number;
|
||||||
toolDetail?: ChatHistoryItemResType[];
|
toolDetail?: ChatHistoryItemResType[];
|
||||||
toolStop?: boolean;
|
toolStop?: boolean;
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,8 @@ export type BillSchemaType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ChatNodeUsageType = {
|
export type ChatNodeUsageType = {
|
||||||
tokens?: number;
|
inputTokens?: number;
|
||||||
|
outputTokens?: number;
|
||||||
totalPoints: number;
|
totalPoints: number;
|
||||||
moduleName: string;
|
moduleName: string;
|
||||||
model?: string;
|
model?: string;
|
||||||
|
|||||||
@ -2,9 +2,13 @@ import { CreateUsageProps } from './api';
|
|||||||
import { UsageSourceEnum } from './constants';
|
import { UsageSourceEnum } from './constants';
|
||||||
|
|
||||||
export type UsageListItemCountType = {
|
export type UsageListItemCountType = {
|
||||||
tokens?: number;
|
inputTokens?: number;
|
||||||
|
outputTokens?: number;
|
||||||
charsLength?: number;
|
charsLength?: number;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
tokens?: number;
|
||||||
};
|
};
|
||||||
export type UsageListItemType = UsageListItemCountType & {
|
export type UsageListItemType = UsageListItemCountType & {
|
||||||
moduleName: string;
|
moduleName: string;
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"version": "488",
|
"version": "488",
|
||||||
"name": "飞书 webhook",
|
"name": "飞书 webhook",
|
||||||
"avatar": "/appMarketTemplates/plugin-feishu/avatar.svg",
|
"avatar": "core/app/templates/plugin-feishu",
|
||||||
"intro": "向飞书机器人发起 webhook 请求。",
|
"intro": "向飞书机器人发起 webhook 请求。",
|
||||||
"courseUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5",
|
"courseUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5",
|
||||||
"showStatus": false,
|
"showStatus": false,
|
||||||
|
|||||||
@ -15,6 +15,9 @@ export const initFastGPTConfig = (config?: FastGPTConfigFileType) => {
|
|||||||
global.subPlans = config.subPlans;
|
global.subPlans = config.subPlans;
|
||||||
|
|
||||||
global.llmModels = config.llmModels;
|
global.llmModels = config.llmModels;
|
||||||
|
global.llmModelPriceType = global.llmModels.some((item) => typeof item.inputPrice === 'number')
|
||||||
|
? 'IO'
|
||||||
|
: 'Tokens';
|
||||||
global.vectorModels = config.vectorModels;
|
global.vectorModels = config.vectorModels;
|
||||||
global.audioSpeechModels = config.audioSpeechModels;
|
global.audioSpeechModels = config.audioSpeechModels;
|
||||||
global.whisperModel = config.whisperModel;
|
global.whisperModel = config.whisperModel;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
|
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
|
||||||
import { createChatCompletion } from '../config';
|
import { createChatCompletion } from '../config';
|
||||||
import { countGptMessagesTokens } from '../../../common/string/tiktoken/index';
|
import { countGptMessagesTokens, countPromptTokens } from '../../../common/string/tiktoken/index';
|
||||||
import { loadRequestMessages } from '../../chat/utils';
|
import { loadRequestMessages } from '../../chat/utils';
|
||||||
import { llmCompletionsBodyFormat } from '../utils';
|
import { llmCompletionsBodyFormat } from '../utils';
|
||||||
import {
|
import {
|
||||||
@ -20,7 +20,8 @@ export async function createQuestionGuide({
|
|||||||
customPrompt?: string;
|
customPrompt?: string;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
result: string[];
|
result: string[];
|
||||||
tokens: number;
|
inputTokens: number;
|
||||||
|
outputTokens: number;
|
||||||
}> {
|
}> {
|
||||||
const concatMessages: ChatCompletionMessageParam[] = [
|
const concatMessages: ChatCompletionMessageParam[] = [
|
||||||
...messages,
|
...messages,
|
||||||
@ -29,6 +30,10 @@ export async function createQuestionGuide({
|
|||||||
content: `${customPrompt || PROMPT_QUESTION_GUIDE}\n${PROMPT_QUESTION_GUIDE_FOOTER}`
|
content: `${customPrompt || PROMPT_QUESTION_GUIDE}\n${PROMPT_QUESTION_GUIDE_FOOTER}`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
const requestMessages = await loadRequestMessages({
|
||||||
|
messages: concatMessages,
|
||||||
|
useVision: false
|
||||||
|
});
|
||||||
|
|
||||||
const { response: data } = await createChatCompletion({
|
const { response: data } = await createChatCompletion({
|
||||||
body: llmCompletionsBodyFormat(
|
body: llmCompletionsBodyFormat(
|
||||||
@ -36,10 +41,7 @@ export async function createQuestionGuide({
|
|||||||
model,
|
model,
|
||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
max_tokens: 200,
|
max_tokens: 200,
|
||||||
messages: await loadRequestMessages({
|
messages: requestMessages,
|
||||||
messages: concatMessages,
|
|
||||||
useVision: false
|
|
||||||
}),
|
|
||||||
stream: false
|
stream: false
|
||||||
},
|
},
|
||||||
model
|
model
|
||||||
@ -51,13 +53,15 @@ export async function createQuestionGuide({
|
|||||||
const start = answer.indexOf('[');
|
const start = answer.indexOf('[');
|
||||||
const end = answer.lastIndexOf(']');
|
const end = answer.lastIndexOf(']');
|
||||||
|
|
||||||
const tokens = await countGptMessagesTokens(concatMessages);
|
const inputTokens = await countGptMessagesTokens(requestMessages);
|
||||||
|
const outputTokens = await countPromptTokens(answer);
|
||||||
|
|
||||||
if (start === -1 || end === -1) {
|
if (start === -1 || end === -1) {
|
||||||
addLog.warn('Create question guide error', { answer });
|
addLog.warn('Create question guide error', { answer });
|
||||||
return {
|
return {
|
||||||
result: [],
|
result: [],
|
||||||
tokens: 0
|
inputTokens: 0,
|
||||||
|
outputTokens: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,14 +73,16 @@ export async function createQuestionGuide({
|
|||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
result: json5.parse(jsonStr),
|
result: json5.parse(jsonStr),
|
||||||
tokens
|
inputTokens,
|
||||||
|
outputTokens
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: [],
|
result: [],
|
||||||
tokens: 0
|
inputTokens: 0,
|
||||||
|
outputTokens: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||||
import { createChatCompletion } from '../config';
|
import { createChatCompletion } from '../config';
|
||||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||||
import { countGptMessagesTokens } from '../../../common/string/tiktoken/index';
|
import { countGptMessagesTokens, countPromptTokens } from '../../../common/string/tiktoken/index';
|
||||||
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
||||||
import { getLLMModel } from '../model';
|
import { getLLMModel } from '../model';
|
||||||
import { llmCompletionsBodyFormat } from '../utils';
|
import { llmCompletionsBodyFormat } from '../utils';
|
||||||
@ -121,7 +121,8 @@ export const queryExtension = async ({
|
|||||||
rawQuery: string;
|
rawQuery: string;
|
||||||
extensionQueries: string[];
|
extensionQueries: string[];
|
||||||
model: string;
|
model: string;
|
||||||
tokens: number;
|
inputTokens: number;
|
||||||
|
outputTokens: number;
|
||||||
}> => {
|
}> => {
|
||||||
const systemFewShot = chatBg
|
const systemFewShot = chatBg
|
||||||
? `Q: 对话背景。
|
? `Q: 对话背景。
|
||||||
@ -166,7 +167,8 @@ A: ${chatBg}
|
|||||||
rawQuery: query,
|
rawQuery: query,
|
||||||
extensionQueries: [],
|
extensionQueries: [],
|
||||||
model,
|
model,
|
||||||
tokens: 0
|
inputTokens: 0,
|
||||||
|
outputTokens: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +183,8 @@ A: ${chatBg}
|
|||||||
rawQuery: query,
|
rawQuery: query,
|
||||||
extensionQueries: Array.isArray(queries) ? queries : [],
|
extensionQueries: Array.isArray(queries) ? queries : [],
|
||||||
model,
|
model,
|
||||||
tokens: await countGptMessagesTokens(messages)
|
inputTokens: await countGptMessagesTokens(messages),
|
||||||
|
outputTokens: await countPromptTokens(answer)
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addLog.error(`Query extension error`, error);
|
addLog.error(`Query extension error`, error);
|
||||||
@ -189,7 +192,8 @@ A: ${chatBg}
|
|||||||
rawQuery: query,
|
rawQuery: query,
|
||||||
extensionQueries: [],
|
extensionQueries: [],
|
||||||
model,
|
model,
|
||||||
tokens: 0
|
inputTokens: 0,
|
||||||
|
outputTokens: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export const getLLMModel = (model?: string) => {
|
|||||||
global.llmModels[0]
|
global.llmModels[0]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDatasetModel = (model?: string) => {
|
export const getDatasetModel = (model?: string) => {
|
||||||
return (
|
return (
|
||||||
global.llmModels
|
global.llmModels
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||||
import { countMessagesTokens } from '../../../../common/string/tiktoken/index';
|
import {
|
||||||
|
countGptMessagesTokens,
|
||||||
|
countPromptTokens
|
||||||
|
} from '../../../../common/string/tiktoken/index';
|
||||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
import { createChatCompletion } from '../../../ai/config';
|
import { createChatCompletion } from '../../../ai/config';
|
||||||
@ -49,7 +52,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
|||||||
|
|
||||||
const chatHistories = getHistories(history, histories);
|
const chatHistories = getHistories(history, histories);
|
||||||
|
|
||||||
const { arg, tokens } = await completions({
|
const { arg, inputTokens, outputTokens } = await completions({
|
||||||
...props,
|
...props,
|
||||||
histories: chatHistories,
|
histories: chatHistories,
|
||||||
cqModel
|
cqModel
|
||||||
@ -59,7 +62,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
|||||||
|
|
||||||
const { totalPoints, modelName } = formatModelChars2Points({
|
const { totalPoints, modelName } = formatModelChars2Points({
|
||||||
model: cqModel.model,
|
model: cqModel.model,
|
||||||
tokens,
|
inputTokens: inputTokens,
|
||||||
|
outputTokens: outputTokens,
|
||||||
modelType: ModelTypeEnum.llm
|
modelType: ModelTypeEnum.llm
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -72,7 +76,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
|||||||
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
||||||
model: modelName,
|
model: modelName,
|
||||||
query: userChatInput,
|
query: userChatInput,
|
||||||
tokens,
|
inputTokens: inputTokens,
|
||||||
|
outputTokens: outputTokens,
|
||||||
cqList: agents,
|
cqList: agents,
|
||||||
cqResult: result.value,
|
cqResult: result.value,
|
||||||
contextTotalLen: chatHistories.length + 2
|
contextTotalLen: chatHistories.length + 2
|
||||||
@ -82,7 +87,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
|||||||
moduleName: name,
|
moduleName: name,
|
||||||
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens
|
inputTokens: inputTokens,
|
||||||
|
outputTokens: outputTokens
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -148,7 +154,8 @@ const completions = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tokens: await countMessagesTokens(messages),
|
inputTokens: await countGptMessagesTokens(requestMessages),
|
||||||
|
outputTokens: await countPromptTokens(answer),
|
||||||
arg: { type: id }
|
arg: { type: id }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,8 @@ import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../chat/
|
|||||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||||
import {
|
import {
|
||||||
countMessagesTokens,
|
countMessagesTokens,
|
||||||
countGptMessagesTokens
|
countGptMessagesTokens,
|
||||||
|
countPromptTokens
|
||||||
} from '../../../../common/string/tiktoken/index';
|
} from '../../../../common/string/tiktoken/index';
|
||||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
import { createChatCompletion } from '../../../ai/config';
|
import { createChatCompletion } from '../../../ai/config';
|
||||||
@ -59,7 +60,7 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
|||||||
const extractModel = getLLMModel(model);
|
const extractModel = getLLMModel(model);
|
||||||
const chatHistories = getHistories(history, histories);
|
const chatHistories = getHistories(history, histories);
|
||||||
|
|
||||||
const { arg, tokens } = await (async () => {
|
const { arg, inputTokens, outputTokens } = await (async () => {
|
||||||
if (extractModel.toolChoice) {
|
if (extractModel.toolChoice) {
|
||||||
return toolChoice({
|
return toolChoice({
|
||||||
...props,
|
...props,
|
||||||
@ -114,7 +115,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
|||||||
|
|
||||||
const { totalPoints, modelName } = formatModelChars2Points({
|
const { totalPoints, modelName } = formatModelChars2Points({
|
||||||
model: extractModel.model,
|
model: extractModel.model,
|
||||||
tokens,
|
inputTokens: inputTokens,
|
||||||
|
outputTokens: outputTokens,
|
||||||
modelType: ModelTypeEnum.llm
|
modelType: ModelTypeEnum.llm
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -126,7 +128,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
|||||||
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
||||||
model: modelName,
|
model: modelName,
|
||||||
query: content,
|
query: content,
|
||||||
tokens,
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
extractDescription: description,
|
extractDescription: description,
|
||||||
extractResult: arg,
|
extractResult: arg,
|
||||||
contextTotalLen: chatHistories.length + 2
|
contextTotalLen: chatHistories.length + 2
|
||||||
@ -136,7 +139,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
|||||||
moduleName: name,
|
moduleName: name,
|
||||||
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens
|
inputTokens,
|
||||||
|
outputTokens
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -249,15 +253,18 @@ const toolChoice = async (props: ActionProps) => {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const completeMessages: ChatCompletionMessageParam[] = [
|
const AIMessages: ChatCompletionMessageParam[] = [
|
||||||
...filterMessages,
|
|
||||||
{
|
{
|
||||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||||
tool_calls: response.choices?.[0]?.message?.tool_calls
|
tool_calls: response.choices?.[0]?.message?.tool_calls
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const inputTokens = await countGptMessagesTokens(filterMessages, tools);
|
||||||
|
const outputTokens = await countGptMessagesTokens(AIMessages);
|
||||||
return {
|
return {
|
||||||
tokens: await countGptMessagesTokens(completeMessages, tools),
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
arg
|
arg
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -286,17 +293,21 @@ const functionCall = async (props: ActionProps) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || '');
|
const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || '');
|
||||||
const completeMessages: ChatCompletionMessageParam[] = [
|
|
||||||
...filterMessages,
|
const AIMessages: ChatCompletionMessageParam[] = [
|
||||||
{
|
{
|
||||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||||
function_call: response.choices?.[0]?.message?.function_call
|
function_call: response.choices?.[0]?.message?.function_call
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const inputTokens = await countGptMessagesTokens(filterMessages, undefined, functions);
|
||||||
|
const outputTokens = await countGptMessagesTokens(AIMessages);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
arg,
|
arg,
|
||||||
tokens: await countGptMessagesTokens(completeMessages, undefined, functions)
|
inputTokens,
|
||||||
|
outputTokens
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(response.choices?.[0]?.message);
|
console.log(response.choices?.[0]?.message);
|
||||||
@ -305,7 +316,8 @@ const functionCall = async (props: ActionProps) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
arg: {},
|
arg: {},
|
||||||
tokens: 0
|
inputTokens: 0,
|
||||||
|
outputTokens: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -370,7 +382,8 @@ Human: ${content}`
|
|||||||
if (!jsonStr) {
|
if (!jsonStr) {
|
||||||
return {
|
return {
|
||||||
rawResponse: answer,
|
rawResponse: answer,
|
||||||
tokens: await countMessagesTokens(messages),
|
inputTokens: await countMessagesTokens(messages),
|
||||||
|
outputTokens: await countPromptTokens(answer),
|
||||||
arg: {}
|
arg: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -378,7 +391,8 @@ Human: ${content}`
|
|||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
rawResponse: answer,
|
rawResponse: answer,
|
||||||
tokens: await countMessagesTokens(messages),
|
inputTokens: await countMessagesTokens(messages),
|
||||||
|
outputTokens: await countPromptTokens(answer),
|
||||||
arg: json5.parse(jsonStr) as Record<string, any>
|
arg: json5.parse(jsonStr) as Record<string, any>
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -386,7 +400,8 @@ Human: ${content}`
|
|||||||
console.log(error);
|
console.log(error);
|
||||||
return {
|
return {
|
||||||
rawResponse: answer,
|
rawResponse: answer,
|
||||||
tokens: await countMessagesTokens(messages),
|
inputTokens: await countMessagesTokens(messages),
|
||||||
|
outputTokens: await countPromptTokens(answer),
|
||||||
arg: {}
|
arg: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,7 +109,8 @@ export const runToolWithFunctionCall = async (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dispatchFlowResponse: [toolRunResponse],
|
dispatchFlowResponse: [toolRunResponse],
|
||||||
toolNodeTokens: 0,
|
toolNodeInputTokens: 0,
|
||||||
|
toolNodeOutputTokens: 0,
|
||||||
completeMessages: requestMessages,
|
completeMessages: requestMessages,
|
||||||
assistantResponses: toolRunResponse.assistantResponses,
|
assistantResponses: toolRunResponse.assistantResponses,
|
||||||
runTimes: toolRunResponse.runTimes,
|
runTimes: toolRunResponse.runTimes,
|
||||||
@ -126,7 +127,8 @@ export const runToolWithFunctionCall = async (
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispatchFlowResponse: [toolRunResponse],
|
dispatchFlowResponse: [toolRunResponse],
|
||||||
toolNodeTokens: 0,
|
toolNodeInputTokens: 0,
|
||||||
|
toolNodeOutputTokens: 0,
|
||||||
assistantResponses: toolRunResponse.assistantResponses,
|
assistantResponses: toolRunResponse.assistantResponses,
|
||||||
runTimes: toolRunResponse.runTimes
|
runTimes: toolRunResponse.runTimes
|
||||||
}
|
}
|
||||||
@ -340,7 +342,9 @@ export const runToolWithFunctionCall = async (
|
|||||||
assistantToolMsgParams
|
assistantToolMsgParams
|
||||||
] as ChatCompletionMessageParam[];
|
] as ChatCompletionMessageParam[];
|
||||||
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
|
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
|
||||||
const tokens = await countGptMessagesTokens(concatToolMessages, undefined, functions);
|
// const tokens = await countGptMessagesTokens(concatToolMessages, undefined, functions);
|
||||||
|
const inputTokens = await countGptMessagesTokens(requestMessages, undefined, functions);
|
||||||
|
const outputTokens = await countGptMessagesTokens([assistantToolMsgParams]);
|
||||||
/*
|
/*
|
||||||
...
|
...
|
||||||
user
|
user
|
||||||
@ -375,7 +379,12 @@ export const runToolWithFunctionCall = async (
|
|||||||
const runTimes =
|
const runTimes =
|
||||||
(response?.runTimes || 0) +
|
(response?.runTimes || 0) +
|
||||||
flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0);
|
flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0);
|
||||||
const toolNodeTokens = response?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens;
|
const toolNodeInputTokens = response?.toolNodeInputTokens
|
||||||
|
? response.toolNodeInputTokens + inputTokens
|
||||||
|
: inputTokens;
|
||||||
|
const toolNodeOutputTokens = response?.toolNodeOutputTokens
|
||||||
|
? response.toolNodeOutputTokens + outputTokens
|
||||||
|
: outputTokens;
|
||||||
|
|
||||||
// Check stop signal
|
// Check stop signal
|
||||||
const hasStopSignal = flatToolsResponseData.some(
|
const hasStopSignal = flatToolsResponseData.some(
|
||||||
@ -408,7 +417,8 @@ export const runToolWithFunctionCall = async (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dispatchFlowResponse,
|
dispatchFlowResponse,
|
||||||
toolNodeTokens,
|
toolNodeInputTokens,
|
||||||
|
toolNodeOutputTokens,
|
||||||
completeMessages,
|
completeMessages,
|
||||||
assistantResponses: toolNodeAssistants,
|
assistantResponses: toolNodeAssistants,
|
||||||
runTimes,
|
runTimes,
|
||||||
@ -423,7 +433,8 @@ export const runToolWithFunctionCall = async (
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispatchFlowResponse,
|
dispatchFlowResponse,
|
||||||
toolNodeTokens,
|
toolNodeInputTokens,
|
||||||
|
toolNodeOutputTokens,
|
||||||
assistantResponses: toolNodeAssistants,
|
assistantResponses: toolNodeAssistants,
|
||||||
runTimes
|
runTimes
|
||||||
}
|
}
|
||||||
@ -435,7 +446,8 @@ export const runToolWithFunctionCall = async (
|
|||||||
content: answer
|
content: answer
|
||||||
};
|
};
|
||||||
const completeMessages = filterMessages.concat(gptAssistantResponse);
|
const completeMessages = filterMessages.concat(gptAssistantResponse);
|
||||||
const tokens = await countGptMessagesTokens(completeMessages, undefined, functions);
|
const inputTokens = await countGptMessagesTokens(requestMessages, undefined, functions);
|
||||||
|
const outputTokens = await countGptMessagesTokens([gptAssistantResponse]);
|
||||||
// console.log(tokens, 'response token');
|
// console.log(tokens, 'response token');
|
||||||
|
|
||||||
// concat tool assistant
|
// concat tool assistant
|
||||||
@ -443,7 +455,12 @@ export const runToolWithFunctionCall = async (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dispatchFlowResponse: response?.dispatchFlowResponse || [],
|
dispatchFlowResponse: response?.dispatchFlowResponse || [],
|
||||||
toolNodeTokens: response?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens,
|
toolNodeInputTokens: response?.toolNodeInputTokens
|
||||||
|
? response.toolNodeInputTokens + inputTokens
|
||||||
|
: inputTokens,
|
||||||
|
toolNodeOutputTokens: response?.toolNodeOutputTokens
|
||||||
|
? response.toolNodeOutputTokens + outputTokens
|
||||||
|
: outputTokens,
|
||||||
completeMessages,
|
completeMessages,
|
||||||
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
|
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
|
||||||
runTimes: (response?.runTimes || 0) + 1
|
runTimes: (response?.runTimes || 0) + 1
|
||||||
|
|||||||
@ -165,6 +165,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
toolWorkflowInteractiveResponse,
|
toolWorkflowInteractiveResponse,
|
||||||
dispatchFlowResponse, // tool flow response
|
dispatchFlowResponse, // tool flow response
|
||||||
toolNodeTokens,
|
toolNodeTokens,
|
||||||
|
toolNodeInputTokens,
|
||||||
|
toolNodeOutputTokens,
|
||||||
completeMessages = [], // The actual message sent to AI(just save text)
|
completeMessages = [], // The actual message sent to AI(just save text)
|
||||||
assistantResponses = [], // FastGPT system store assistant.value response
|
assistantResponses = [], // FastGPT system store assistant.value response
|
||||||
runTimes
|
runTimes
|
||||||
@ -225,7 +227,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
|
|
||||||
const { totalPoints, modelName } = formatModelChars2Points({
|
const { totalPoints, modelName } = formatModelChars2Points({
|
||||||
model,
|
model,
|
||||||
tokens: toolNodeTokens,
|
inputTokens: toolNodeInputTokens,
|
||||||
|
outputTokens: toolNodeOutputTokens,
|
||||||
modelType: ModelTypeEnum.llm
|
modelType: ModelTypeEnum.llm
|
||||||
});
|
});
|
||||||
const toolAIUsage = externalProvider.openaiAccount?.key ? 0 : totalPoints;
|
const toolAIUsage = externalProvider.openaiAccount?.key ? 0 : totalPoints;
|
||||||
@ -255,6 +258,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
// 展示的积分消耗
|
// 展示的积分消耗
|
||||||
totalPoints: totalPointsUsage,
|
totalPoints: totalPointsUsage,
|
||||||
toolCallTokens: toolNodeTokens,
|
toolCallTokens: toolNodeTokens,
|
||||||
|
toolCallInputTokens: toolNodeInputTokens,
|
||||||
|
toolCallOutputTokens: toolNodeOutputTokens,
|
||||||
childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0),
|
childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0),
|
||||||
model: modelName,
|
model: modelName,
|
||||||
query: userChatInput,
|
query: userChatInput,
|
||||||
@ -270,9 +275,10 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
// 工具调用本身的积分消耗
|
// 工具调用本身的积分消耗
|
||||||
{
|
{
|
||||||
moduleName: name,
|
moduleName: name,
|
||||||
totalPoints: toolAIUsage,
|
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens: toolNodeTokens
|
totalPoints: toolAIUsage,
|
||||||
|
inputTokens: toolNodeInputTokens,
|
||||||
|
outputTokens: toolNodeOutputTokens
|
||||||
},
|
},
|
||||||
// 工具的消耗
|
// 工具的消耗
|
||||||
...flatUsages
|
...flatUsages
|
||||||
|
|||||||
@ -115,7 +115,8 @@ export const runToolWithPromptCall = async (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dispatchFlowResponse: [toolRunResponse],
|
dispatchFlowResponse: [toolRunResponse],
|
||||||
toolNodeTokens: 0,
|
toolNodeInputTokens: 0,
|
||||||
|
toolNodeOutputTokens: 0,
|
||||||
completeMessages: concatMessages,
|
completeMessages: concatMessages,
|
||||||
assistantResponses: toolRunResponse.assistantResponses,
|
assistantResponses: toolRunResponse.assistantResponses,
|
||||||
runTimes: toolRunResponse.runTimes,
|
runTimes: toolRunResponse.runTimes,
|
||||||
@ -131,7 +132,8 @@ export const runToolWithPromptCall = async (
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispatchFlowResponse: [toolRunResponse],
|
dispatchFlowResponse: [toolRunResponse],
|
||||||
toolNodeTokens: 0,
|
toolNodeInputTokens: 0,
|
||||||
|
toolNodeOutputTokens: 0,
|
||||||
assistantResponses: toolRunResponse.assistantResponses,
|
assistantResponses: toolRunResponse.assistantResponses,
|
||||||
runTimes: toolRunResponse.runTimes
|
runTimes: toolRunResponse.runTimes
|
||||||
}
|
}
|
||||||
@ -286,15 +288,20 @@ export const runToolWithPromptCall = async (
|
|||||||
content: replaceAnswer
|
content: replaceAnswer
|
||||||
};
|
};
|
||||||
const completeMessages = filterMessages.concat(gptAssistantResponse);
|
const completeMessages = filterMessages.concat(gptAssistantResponse);
|
||||||
const tokens = await countGptMessagesTokens(completeMessages, undefined);
|
const inputTokens = await countGptMessagesTokens(requestMessages);
|
||||||
// console.log(tokens, 'response token');
|
const outputTokens = await countGptMessagesTokens([gptAssistantResponse]);
|
||||||
|
|
||||||
// concat tool assistant
|
// concat tool assistant
|
||||||
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
|
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dispatchFlowResponse: response?.dispatchFlowResponse || [],
|
dispatchFlowResponse: response?.dispatchFlowResponse || [],
|
||||||
toolNodeTokens: response?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens,
|
toolNodeInputTokens: response?.toolNodeInputTokens
|
||||||
|
? response.toolNodeInputTokens + inputTokens
|
||||||
|
: inputTokens,
|
||||||
|
toolNodeOutputTokens: response?.toolNodeOutputTokens
|
||||||
|
? response.toolNodeOutputTokens + outputTokens
|
||||||
|
: outputTokens,
|
||||||
completeMessages,
|
completeMessages,
|
||||||
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
|
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
|
||||||
runTimes: (response?.runTimes || 0) + 1
|
runTimes: (response?.runTimes || 0) + 1
|
||||||
@ -366,17 +373,9 @@ export const runToolWithPromptCall = async (
|
|||||||
function_call: toolJson
|
function_call: toolJson
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
...
|
|
||||||
user
|
|
||||||
assistant: tool data
|
|
||||||
*/
|
|
||||||
const concatToolMessages = [
|
|
||||||
...requestMessages,
|
|
||||||
assistantToolMsgParams
|
|
||||||
] as ChatCompletionMessageParam[];
|
|
||||||
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
|
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
|
||||||
const tokens = await countGptMessagesTokens(concatToolMessages, undefined);
|
const inputTokens = await countGptMessagesTokens(requestMessages);
|
||||||
|
const outputTokens = await countGptMessagesTokens([assistantToolMsgParams]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
...
|
...
|
||||||
@ -437,7 +436,12 @@ ANSWER: `;
|
|||||||
}
|
}
|
||||||
|
|
||||||
const runTimes = (response?.runTimes || 0) + toolsRunResponse.toolResponse.runTimes;
|
const runTimes = (response?.runTimes || 0) + toolsRunResponse.toolResponse.runTimes;
|
||||||
const toolNodeTokens = response?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens;
|
const toolNodeInputTokens = response?.toolNodeInputTokens
|
||||||
|
? response.toolNodeInputTokens + inputTokens
|
||||||
|
: inputTokens;
|
||||||
|
const toolNodeOutputTokens = response?.toolNodeOutputTokens
|
||||||
|
? response.toolNodeOutputTokens + outputTokens
|
||||||
|
: outputTokens;
|
||||||
|
|
||||||
// Check stop signal
|
// Check stop signal
|
||||||
const hasStopSignal = toolsRunResponse.toolResponse.flowResponses.some((item) => !!item.toolStop);
|
const hasStopSignal = toolsRunResponse.toolResponse.flowResponses.some((item) => !!item.toolStop);
|
||||||
@ -460,7 +464,8 @@ ANSWER: `;
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dispatchFlowResponse,
|
dispatchFlowResponse,
|
||||||
toolNodeTokens,
|
toolNodeInputTokens,
|
||||||
|
toolNodeOutputTokens,
|
||||||
completeMessages: filterMessages,
|
completeMessages: filterMessages,
|
||||||
assistantResponses: toolNodeAssistants,
|
assistantResponses: toolNodeAssistants,
|
||||||
runTimes,
|
runTimes,
|
||||||
@ -475,7 +480,8 @@ ANSWER: `;
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispatchFlowResponse,
|
dispatchFlowResponse,
|
||||||
toolNodeTokens,
|
toolNodeInputTokens,
|
||||||
|
toolNodeOutputTokens,
|
||||||
assistantResponses: toolNodeAssistants,
|
assistantResponses: toolNodeAssistants,
|
||||||
runTimes
|
runTimes
|
||||||
}
|
}
|
||||||
|
|||||||
@ -158,7 +158,8 @@ export const runToolWithToolChoice = async (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dispatchFlowResponse: [toolRunResponse],
|
dispatchFlowResponse: [toolRunResponse],
|
||||||
toolNodeTokens: 0,
|
toolNodeInputTokens: 0,
|
||||||
|
toolNodeOutputTokens: 0,
|
||||||
completeMessages: requestMessages,
|
completeMessages: requestMessages,
|
||||||
assistantResponses: toolRunResponse.assistantResponses,
|
assistantResponses: toolRunResponse.assistantResponses,
|
||||||
runTimes: toolRunResponse.runTimes,
|
runTimes: toolRunResponse.runTimes,
|
||||||
@ -176,7 +177,8 @@ export const runToolWithToolChoice = async (
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispatchFlowResponse: [toolRunResponse],
|
dispatchFlowResponse: [toolRunResponse],
|
||||||
toolNodeTokens: 0,
|
toolNodeInputTokens: 0,
|
||||||
|
toolNodeOutputTokens: 0,
|
||||||
assistantResponses: toolRunResponse.assistantResponses,
|
assistantResponses: toolRunResponse.assistantResponses,
|
||||||
runTimes: toolRunResponse.runTimes
|
runTimes: toolRunResponse.runTimes
|
||||||
}
|
}
|
||||||
@ -428,7 +430,9 @@ export const runToolWithToolChoice = async (
|
|||||||
] as ChatCompletionMessageParam[];
|
] as ChatCompletionMessageParam[];
|
||||||
|
|
||||||
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
|
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
|
||||||
const tokens = await countGptMessagesTokens(concatToolMessages, tools);
|
const inputTokens = await countGptMessagesTokens(requestMessages, tools);
|
||||||
|
const outputTokens = await countGptMessagesTokens(assistantToolMsgParams);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
...
|
...
|
||||||
user
|
user
|
||||||
@ -463,7 +467,10 @@ export const runToolWithToolChoice = async (
|
|||||||
const runTimes =
|
const runTimes =
|
||||||
(response?.runTimes || 0) +
|
(response?.runTimes || 0) +
|
||||||
flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0);
|
flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0);
|
||||||
const toolNodeTokens = response ? response.toolNodeTokens + tokens : tokens;
|
const toolNodeInputTokens = response ? response.toolNodeInputTokens + inputTokens : inputTokens;
|
||||||
|
const toolNodeOutputTokens = response
|
||||||
|
? response.toolNodeOutputTokens + outputTokens
|
||||||
|
: outputTokens;
|
||||||
|
|
||||||
// Check stop signal
|
// Check stop signal
|
||||||
const hasStopSignal = flatToolsResponseData.some(
|
const hasStopSignal = flatToolsResponseData.some(
|
||||||
@ -496,7 +503,8 @@ export const runToolWithToolChoice = async (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dispatchFlowResponse,
|
dispatchFlowResponse,
|
||||||
toolNodeTokens,
|
toolNodeInputTokens,
|
||||||
|
toolNodeOutputTokens,
|
||||||
completeMessages,
|
completeMessages,
|
||||||
assistantResponses: toolNodeAssistants,
|
assistantResponses: toolNodeAssistants,
|
||||||
runTimes,
|
runTimes,
|
||||||
@ -512,7 +520,8 @@ export const runToolWithToolChoice = async (
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dispatchFlowResponse,
|
dispatchFlowResponse,
|
||||||
toolNodeTokens,
|
toolNodeInputTokens,
|
||||||
|
toolNodeOutputTokens,
|
||||||
assistantResponses: toolNodeAssistants,
|
assistantResponses: toolNodeAssistants,
|
||||||
runTimes
|
runTimes
|
||||||
}
|
}
|
||||||
@ -524,14 +533,17 @@ export const runToolWithToolChoice = async (
|
|||||||
content: answer
|
content: answer
|
||||||
};
|
};
|
||||||
const completeMessages = filterMessages.concat(gptAssistantResponse);
|
const completeMessages = filterMessages.concat(gptAssistantResponse);
|
||||||
const tokens = await countGptMessagesTokens(completeMessages, tools);
|
const inputTokens = await countGptMessagesTokens(requestMessages, tools);
|
||||||
|
const outputTokens = await countGptMessagesTokens([gptAssistantResponse]);
|
||||||
|
|
||||||
// concat tool assistant
|
// concat tool assistant
|
||||||
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
|
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dispatchFlowResponse: response?.dispatchFlowResponse || [],
|
dispatchFlowResponse: response?.dispatchFlowResponse || [],
|
||||||
toolNodeTokens: response ? response.toolNodeTokens + tokens : tokens,
|
toolNodeInputTokens: response ? response.toolNodeInputTokens + inputTokens : inputTokens,
|
||||||
|
toolNodeOutputTokens: response ? response.toolNodeOutputTokens + outputTokens : outputTokens,
|
||||||
|
|
||||||
completeMessages,
|
completeMessages,
|
||||||
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
|
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
|
||||||
runTimes: (response?.runTimes || 0) + 1
|
runTimes: (response?.runTimes || 0) + 1
|
||||||
@ -578,7 +590,8 @@ async function streamResponse({
|
|||||||
text: content
|
text: content
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
} else if (responseChoice?.tool_calls?.[0]) {
|
}
|
||||||
|
if (responseChoice?.tool_calls?.[0]) {
|
||||||
const toolCall: ChatCompletionMessageToolCall = responseChoice.tool_calls[0];
|
const toolCall: ChatCompletionMessageToolCall = responseChoice.tool_calls[0];
|
||||||
// In a stream response, only one tool is returned at a time. If have id, description is executing a tool
|
// In a stream response, only one tool is returned at a time. If have id, description is executing a tool
|
||||||
if (toolCall.id || callingTool) {
|
if (toolCall.id || callingTool) {
|
||||||
|
|||||||
@ -31,7 +31,9 @@ export type DispatchToolModuleProps = ModuleDispatchProps<{
|
|||||||
|
|
||||||
export type RunToolResponse = {
|
export type RunToolResponse = {
|
||||||
dispatchFlowResponse: DispatchFlowResponse[];
|
dispatchFlowResponse: DispatchFlowResponse[];
|
||||||
toolNodeTokens: number;
|
toolNodeTokens?: number; // deprecated
|
||||||
|
toolNodeInputTokens: number;
|
||||||
|
toolNodeOutputTokens: number;
|
||||||
completeMessages?: ChatCompletionMessageParam[];
|
completeMessages?: ChatCompletionMessageParam[];
|
||||||
assistantResponses?: AIChatItemValueItemType[];
|
assistantResponses?: AIChatItemValueItemType[];
|
||||||
toolWorkflowInteractiveResponse?: WorkflowInteractiveResponseType;
|
toolWorkflowInteractiveResponse?: WorkflowInteractiveResponseType;
|
||||||
|
|||||||
@ -5,13 +5,17 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
|||||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||||
import { createChatCompletion } from '../../../ai/config';
|
import { createChatCompletion } from '../../../ai/config';
|
||||||
import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d';
|
import type {
|
||||||
|
ChatCompletion,
|
||||||
|
ChatCompletionMessageParam,
|
||||||
|
StreamChatType
|
||||||
|
} from '@fastgpt/global/core/ai/type.d';
|
||||||
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
|
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
|
||||||
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||||
import { postTextCensor } from '../../../../common/api/requestPlusApi';
|
import { postTextCensor } from '../../../../common/api/requestPlusApi';
|
||||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
||||||
import type { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
import type { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||||
import { countMessagesTokens } from '../../../../common/string/tiktoken/index';
|
import { countGptMessagesTokens } from '../../../../common/string/tiktoken/index';
|
||||||
import {
|
import {
|
||||||
chats2GPTMessages,
|
chats2GPTMessages,
|
||||||
chatValue2RuntimePrompt,
|
chatValue2RuntimePrompt,
|
||||||
@ -214,16 +218,23 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
|||||||
return Promise.reject(getEmptyResponseTip());
|
return Promise.reject(getEmptyResponseTip());
|
||||||
}
|
}
|
||||||
|
|
||||||
const completeMessages = requestMessages.concat({
|
const AIMessages: ChatCompletionMessageParam[] = [
|
||||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
{
|
||||||
content: answerText
|
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||||
});
|
content: answerText
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const completeMessages = [...requestMessages, ...AIMessages];
|
||||||
const chatCompleteMessages = GPTMessages2Chats(completeMessages);
|
const chatCompleteMessages = GPTMessages2Chats(completeMessages);
|
||||||
|
|
||||||
const tokens = await countMessagesTokens(chatCompleteMessages);
|
const inputTokens = await countGptMessagesTokens(requestMessages);
|
||||||
|
const outputTokens = await countGptMessagesTokens(AIMessages);
|
||||||
|
|
||||||
const { totalPoints, modelName } = formatModelChars2Points({
|
const { totalPoints, modelName } = formatModelChars2Points({
|
||||||
model,
|
model,
|
||||||
tokens,
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
modelType: ModelTypeEnum.llm
|
modelType: ModelTypeEnum.llm
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -232,7 +243,9 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
|||||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||||
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens,
|
tokens: inputTokens + outputTokens,
|
||||||
|
inputTokens: inputTokens,
|
||||||
|
outputTokens: outputTokens,
|
||||||
query: `${userChatInput}`,
|
query: `${userChatInput}`,
|
||||||
maxToken: max_tokens,
|
maxToken: max_tokens,
|
||||||
historyPreview: getHistoryPreview(
|
historyPreview: getHistoryPreview(
|
||||||
@ -247,7 +260,8 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
|||||||
moduleName: name,
|
moduleName: name,
|
||||||
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens
|
inputTokens: inputTokens,
|
||||||
|
outputTokens: outputTokens
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[DispatchNodeResponseKeyEnum.toolResponses]: answerText,
|
[DispatchNodeResponseKeyEnum.toolResponses]: answerText,
|
||||||
|
|||||||
@ -120,14 +120,14 @@ export async function dispatchDatasetSearch(
|
|||||||
// vector
|
// vector
|
||||||
const { totalPoints, modelName } = formatModelChars2Points({
|
const { totalPoints, modelName } = formatModelChars2Points({
|
||||||
model: vectorModel.model,
|
model: vectorModel.model,
|
||||||
tokens,
|
inputTokens: tokens,
|
||||||
modelType: ModelTypeEnum.vector
|
modelType: ModelTypeEnum.vector
|
||||||
});
|
});
|
||||||
const responseData: DispatchNodeResponseType & { totalPoints: number } = {
|
const responseData: DispatchNodeResponseType & { totalPoints: number } = {
|
||||||
totalPoints,
|
totalPoints,
|
||||||
query: concatQueries.join('\n'),
|
query: concatQueries.join('\n'),
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens,
|
inputTokens: tokens,
|
||||||
similarity: usingSimilarityFilter ? similarity : undefined,
|
similarity: usingSimilarityFilter ? similarity : undefined,
|
||||||
limit,
|
limit,
|
||||||
searchMode,
|
searchMode,
|
||||||
@ -139,19 +139,21 @@ export async function dispatchDatasetSearch(
|
|||||||
totalPoints,
|
totalPoints,
|
||||||
moduleName: node.name,
|
moduleName: node.name,
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens
|
inputTokens: tokens
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
if (aiExtensionResult) {
|
if (aiExtensionResult) {
|
||||||
const { totalPoints, modelName } = formatModelChars2Points({
|
const { totalPoints, modelName } = formatModelChars2Points({
|
||||||
model: aiExtensionResult.model,
|
model: aiExtensionResult.model,
|
||||||
tokens: aiExtensionResult.tokens,
|
inputTokens: aiExtensionResult.inputTokens,
|
||||||
|
outputTokens: aiExtensionResult.outputTokens,
|
||||||
modelType: ModelTypeEnum.llm
|
modelType: ModelTypeEnum.llm
|
||||||
});
|
});
|
||||||
|
|
||||||
responseData.totalPoints += totalPoints;
|
responseData.totalPoints += totalPoints;
|
||||||
responseData.tokens = aiExtensionResult.tokens;
|
responseData.inputTokens = aiExtensionResult.inputTokens;
|
||||||
|
responseData.outputTokens = aiExtensionResult.outputTokens;
|
||||||
responseData.extensionModel = modelName;
|
responseData.extensionModel = modelName;
|
||||||
responseData.extensionResult =
|
responseData.extensionResult =
|
||||||
aiExtensionResult.extensionQueries?.join('\n') ||
|
aiExtensionResult.extensionQueries?.join('\n') ||
|
||||||
@ -161,7 +163,8 @@ export async function dispatchDatasetSearch(
|
|||||||
totalPoints,
|
totalPoints,
|
||||||
moduleName: 'core.module.template.Query extension',
|
moduleName: 'core.module.template.Query extension',
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens: aiExtensionResult.tokens
|
inputTokens: aiExtensionResult.inputTokens,
|
||||||
|
outputTokens: aiExtensionResult.outputTokens
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -130,8 +130,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
|
|||||||
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
|
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
|
||||||
{
|
{
|
||||||
moduleName: plugin.name,
|
moduleName: plugin.name,
|
||||||
totalPoints: usagePoints,
|
totalPoints: usagePoints
|
||||||
tokens: 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[DispatchNodeResponseKeyEnum.toolResponses]: output?.pluginOutput
|
[DispatchNodeResponseKeyEnum.toolResponses]: output?.pluginOutput
|
||||||
|
|||||||
@ -153,8 +153,7 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
|
|||||||
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
|
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
|
||||||
{
|
{
|
||||||
moduleName: appData.name,
|
moduleName: appData.name,
|
||||||
totalPoints: usagePoints,
|
totalPoints: usagePoints
|
||||||
tokens: 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[DispatchNodeResponseKeyEnum.toolResponses]: text,
|
[DispatchNodeResponseKeyEnum.toolResponses]: text,
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export const dispatchQueryExtension = async ({
|
|||||||
const queryExtensionModel = getLLMModel(model);
|
const queryExtensionModel = getLLMModel(model);
|
||||||
const chatHistories = getHistories(history, histories);
|
const chatHistories = getHistories(history, histories);
|
||||||
|
|
||||||
const { extensionQueries, tokens } = await queryExtension({
|
const { extensionQueries, inputTokens, outputTokens } = await queryExtension({
|
||||||
chatBg: systemPrompt,
|
chatBg: systemPrompt,
|
||||||
query: userChatInput,
|
query: userChatInput,
|
||||||
histories: chatHistories,
|
histories: chatHistories,
|
||||||
@ -42,7 +42,8 @@ export const dispatchQueryExtension = async ({
|
|||||||
|
|
||||||
const { totalPoints, modelName } = formatModelChars2Points({
|
const { totalPoints, modelName } = formatModelChars2Points({
|
||||||
model: queryExtensionModel.model,
|
model: queryExtensionModel.model,
|
||||||
tokens,
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
modelType: ModelTypeEnum.llm
|
modelType: ModelTypeEnum.llm
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -59,7 +60,8 @@ export const dispatchQueryExtension = async ({
|
|||||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||||
totalPoints,
|
totalPoints,
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens,
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
query: userChatInput,
|
query: userChatInput,
|
||||||
textOutput: JSON.stringify(filterSameQueries)
|
textOutput: JSON.stringify(filterSameQueries)
|
||||||
},
|
},
|
||||||
@ -68,7 +70,8 @@ export const dispatchQueryExtension = async ({
|
|||||||
moduleName: node.name,
|
moduleName: node.name,
|
||||||
totalPoints,
|
totalPoints,
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens
|
inputTokens,
|
||||||
|
outputTokens
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[NodeOutputKeyEnum.text]: JSON.stringify(filterSameQueries)
|
[NodeOutputKeyEnum.text]: JSON.stringify(filterSameQueries)
|
||||||
|
|||||||
@ -31,20 +31,23 @@ export const createTrainingUsage = async ({
|
|||||||
{
|
{
|
||||||
moduleName: 'support.wallet.moduleName.index',
|
moduleName: 'support.wallet.moduleName.index',
|
||||||
model: vectorModel,
|
model: vectorModel,
|
||||||
tokens: 0,
|
amount: 0,
|
||||||
amount: 0
|
inputTokens: 0,
|
||||||
|
outputTokens: 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
moduleName: 'support.wallet.moduleName.qa',
|
moduleName: 'support.wallet.moduleName.qa',
|
||||||
model: agentModel,
|
model: agentModel,
|
||||||
tokens: 0,
|
amount: 0,
|
||||||
amount: 0
|
inputTokens: 0,
|
||||||
|
outputTokens: 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
moduleName: 'core.dataset.training.Auto mode',
|
moduleName: 'core.dataset.training.Auto mode',
|
||||||
model: agentModel,
|
model: agentModel,
|
||||||
tokens: 0,
|
amount: 0,
|
||||||
amount: 0
|
inputTokens: 0,
|
||||||
|
outputTokens: 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { connectionMongo, getMongoModel, type Model } from '../../../common/mongo';
|
import { connectionMongo, getMongoModel, type Model } from '../../../common/mongo';
|
||||||
const { Schema, model, models } = connectionMongo;
|
const { Schema } = connectionMongo;
|
||||||
import { UsageSchemaType } from '@fastgpt/global/support/wallet/usage/type';
|
import { UsageSchemaType } from '@fastgpt/global/support/wallet/usage/type';
|
||||||
import { UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
|
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||||
import {
|
import {
|
||||||
TeamCollectionName,
|
TeamCollectionName,
|
||||||
TeamMemberCollectionName
|
TeamMemberCollectionName
|
||||||
@ -22,7 +22,7 @@ const UsageSchema = new Schema({
|
|||||||
},
|
},
|
||||||
source: {
|
source: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: Object.keys(UsageSourceMap),
|
enum: Object.values(UsageSourceEnum),
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
appName: {
|
appName: {
|
||||||
@ -65,7 +65,7 @@ try {
|
|||||||
// timer task. clear dead team
|
// timer task. clear dead team
|
||||||
// UsageSchema.index({ teamId: 1, time: -1 }, { background: true });
|
// UsageSchema.index({ teamId: 1, time: -1 }, { background: true });
|
||||||
|
|
||||||
UsageSchema.index({ time: 1 }, { background: true, expireAfterSeconds: 720 * 24 * 60 * 60 });
|
UsageSchema.index({ time: 1 }, { background: true, expireAfterSeconds: 360 * 24 * 60 * 60 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,20 @@
|
|||||||
|
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||||
import { ModelTypeEnum, getModelMap } from '../../../core/ai/model';
|
import { ModelTypeEnum, getModelMap } from '../../../core/ai/model';
|
||||||
|
|
||||||
export const formatModelChars2Points = ({
|
export const formatModelChars2Points = ({
|
||||||
model,
|
model,
|
||||||
tokens = 0,
|
inputTokens = 0,
|
||||||
|
outputTokens = 0,
|
||||||
modelType,
|
modelType,
|
||||||
multiple = 1000
|
multiple = 1000
|
||||||
}: {
|
}: {
|
||||||
model: string;
|
model: string;
|
||||||
tokens: number;
|
inputTokens?: number;
|
||||||
|
outputTokens?: number;
|
||||||
modelType: `${ModelTypeEnum}`;
|
modelType: `${ModelTypeEnum}`;
|
||||||
multiple?: number;
|
multiple?: number;
|
||||||
}) => {
|
}) => {
|
||||||
const modelData = getModelMap?.[modelType]?.(model);
|
const modelData = getModelMap?.[modelType]?.(model) as LLMModelItemType;
|
||||||
if (!modelData) {
|
if (!modelData) {
|
||||||
return {
|
return {
|
||||||
totalPoints: 0,
|
totalPoints: 0,
|
||||||
@ -19,7 +22,12 @@ export const formatModelChars2Points = ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalPoints = (modelData.charsPointsPrice || 0) * (tokens / multiple);
|
const isIOPriceType = typeof modelData.inputPrice === 'number';
|
||||||
|
|
||||||
|
const totalPoints = isIOPriceType
|
||||||
|
? (modelData.inputPrice || 0) * (inputTokens / multiple) +
|
||||||
|
(modelData.outputPrice || 0) * (outputTokens / multiple)
|
||||||
|
: (modelData.charsPointsPrice || 0) * ((inputTokens + outputTokens) / multiple);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modelName: modelData.name,
|
modelName: modelData.name,
|
||||||
|
|||||||
1
packages/service/type.d.ts
vendored
1
packages/service/type.d.ts
vendored
@ -18,6 +18,7 @@ declare global {
|
|||||||
var subPlans: SubPlanType | undefined;
|
var subPlans: SubPlanType | undefined;
|
||||||
|
|
||||||
var llmModels: LLMModelItemType[];
|
var llmModels: LLMModelItemType[];
|
||||||
|
var llmModelPriceType: 'IO' | 'Tokens';
|
||||||
var vectorModels: VectorModelItemType[];
|
var vectorModels: VectorModelItemType[];
|
||||||
var audioSpeechModels: AudioSpeechModelType[];
|
var audioSpeechModels: AudioSpeechModelType[];
|
||||||
var whisperModel: STTModelType;
|
var whisperModel: STTModelType;
|
||||||
|
|||||||
@ -6,12 +6,14 @@
|
|||||||
"details": "Details",
|
"details": "Details",
|
||||||
"duration_seconds": "Duration (seconds)",
|
"duration_seconds": "Duration (seconds)",
|
||||||
"generation_time": "Generation time",
|
"generation_time": "Generation time",
|
||||||
|
"input_token_length": "input tokens",
|
||||||
"member": "member",
|
"member": "member",
|
||||||
"member_name": "Member name",
|
"member_name": "Member name",
|
||||||
"module_name": "module name",
|
"module_name": "module name",
|
||||||
"month": "moon",
|
"month": "moon",
|
||||||
"no_usage_records": "No usage record yet",
|
"no_usage_records": "No usage record yet",
|
||||||
"order_number": "Order number",
|
"order_number": "Order number",
|
||||||
|
"output_token_length": "output tokens",
|
||||||
"project_name": "Project name",
|
"project_name": "Project name",
|
||||||
"source": "source",
|
"source": "source",
|
||||||
"text_length": "text length",
|
"text_length": "text length",
|
||||||
@ -20,4 +22,4 @@
|
|||||||
"total_points_consumed": "AI points consumption",
|
"total_points_consumed": "AI points consumption",
|
||||||
"usage_detail": "Usage details",
|
"usage_detail": "Usage details",
|
||||||
"user_type": "type"
|
"user_type": "type"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -447,6 +447,8 @@
|
|||||||
"core.chat.response.Extension model": "Question Optimization Model",
|
"core.chat.response.Extension model": "Question Optimization Model",
|
||||||
"core.chat.response.Read complete response": "View Details",
|
"core.chat.response.Read complete response": "View Details",
|
||||||
"core.chat.response.Read complete response tips": "Click to View Detailed Process",
|
"core.chat.response.Read complete response tips": "Click to View Detailed Process",
|
||||||
|
"core.chat.response.Tool call input tokens": "Tool Call Input Tokens Consumption",
|
||||||
|
"core.chat.response.Tool call output tokens": "Tool Call Output Tokens Consumption",
|
||||||
"core.chat.response.Tool call tokens": "Tool Call Tokens Consumption",
|
"core.chat.response.Tool call tokens": "Tool Call Tokens Consumption",
|
||||||
"core.chat.response.context total length": "Total Context Length",
|
"core.chat.response.context total length": "Total Context Length",
|
||||||
"core.chat.response.loop_input": "Loop Input Array",
|
"core.chat.response.loop_input": "Loop Input Array",
|
||||||
@ -460,10 +462,12 @@
|
|||||||
"core.chat.response.module historyPreview": "History Preview (Only Partial Content Displayed)",
|
"core.chat.response.module historyPreview": "History Preview (Only Partial Content Displayed)",
|
||||||
"core.chat.response.module http result": "Response Body",
|
"core.chat.response.module http result": "Response Body",
|
||||||
"core.chat.response.module if else Result": "Condition Result",
|
"core.chat.response.module if else Result": "Condition Result",
|
||||||
|
"core.chat.response.module input tokens": "input tokens",
|
||||||
"core.chat.response.module limit": "Single Search Limit",
|
"core.chat.response.module limit": "Single Search Limit",
|
||||||
"core.chat.response.module maxToken": "Max Response Tokens",
|
"core.chat.response.module maxToken": "Max Response Tokens",
|
||||||
"core.chat.response.module model": "Model",
|
"core.chat.response.module model": "Model",
|
||||||
"core.chat.response.module name": "Model Name",
|
"core.chat.response.module name": "Model Name",
|
||||||
|
"core.chat.response.module output tokens": "output tokens",
|
||||||
"core.chat.response.module query": "Question/Search Term",
|
"core.chat.response.module query": "Question/Search Term",
|
||||||
"core.chat.response.module quoteList": "Quote Content",
|
"core.chat.response.module quoteList": "Quote Content",
|
||||||
"core.chat.response.module similarity": "Similarity",
|
"core.chat.response.module similarity": "Similarity",
|
||||||
@ -1043,6 +1047,8 @@
|
|||||||
"support.user.team.Team Tags Async Success": "Sync Completed",
|
"support.user.team.Team Tags Async Success": "Sync Completed",
|
||||||
"support.user.team.member": "Member",
|
"support.user.team.member": "Member",
|
||||||
"support.wallet.Ai point every thousand tokens": "{{points}} Points/1K Tokens",
|
"support.wallet.Ai point every thousand tokens": "{{points}} Points/1K Tokens",
|
||||||
|
"support.wallet.Ai point every thousand tokens_input": "Input:{{points}} points/1K tokens",
|
||||||
|
"support.wallet.Ai point every thousand tokens_output": "Output:{{points}} points/1K tokens",
|
||||||
"support.wallet.Amount": "Amount",
|
"support.wallet.Amount": "Amount",
|
||||||
"support.wallet.Buy": "Buy",
|
"support.wallet.Buy": "Buy",
|
||||||
"support.wallet.Not sufficient": "Insufficient AI Points, Please Upgrade Your Package or Purchase Additional AI Points to Continue Using.",
|
"support.wallet.Not sufficient": "Insufficient AI Points, Please Upgrade Your Package or Purchase Additional AI Points to Continue Using.",
|
||||||
|
|||||||
@ -1,23 +1,25 @@
|
|||||||
{
|
{
|
||||||
"usage_detail": "使用详情",
|
|
||||||
"order_number": "订单号",
|
|
||||||
"generation_time": "生成时间",
|
|
||||||
"month": "月",
|
|
||||||
"app_name": "应用名",
|
|
||||||
"source": "来源",
|
|
||||||
"total_points_consumed": "AI 积分消耗",
|
|
||||||
"billing_module": "扣费模块",
|
|
||||||
"module_name": "模块名",
|
|
||||||
"ai_model": "AI 模型",
|
"ai_model": "AI 模型",
|
||||||
"token_length": "token 长度",
|
|
||||||
"text_length": "文本长度",
|
|
||||||
"duration_seconds": "时长(秒)",
|
|
||||||
"all": "所有",
|
"all": "所有",
|
||||||
|
"app_name": "应用名",
|
||||||
|
"billing_module": "扣费模块",
|
||||||
|
"details": "详情",
|
||||||
|
"duration_seconds": "时长(秒)",
|
||||||
|
"generation_time": "生成时间",
|
||||||
|
"input_token_length": "输入 tokens",
|
||||||
"member": "成员",
|
"member": "成员",
|
||||||
"member_name": "成员名",
|
"member_name": "成员名",
|
||||||
"user_type": "类型",
|
"module_name": "模块名",
|
||||||
|
"month": "月",
|
||||||
|
"no_usage_records": "暂无使用记录",
|
||||||
|
"order_number": "订单号",
|
||||||
|
"output_token_length": "输出 tokens",
|
||||||
"project_name": "项目名",
|
"project_name": "项目名",
|
||||||
|
"source": "来源",
|
||||||
|
"text_length": "文本长度",
|
||||||
|
"token_length": "token 长度",
|
||||||
"total_points": "AI 积分消耗",
|
"total_points": "AI 积分消耗",
|
||||||
"details": "详情",
|
"total_points_consumed": "AI 积分消耗",
|
||||||
"no_usage_records": "暂无使用记录"
|
"usage_detail": "使用详情",
|
||||||
}
|
"user_type": "类型"
|
||||||
|
}
|
||||||
|
|||||||
@ -450,6 +450,8 @@
|
|||||||
"core.chat.response.Extension model": "问题优化模型",
|
"core.chat.response.Extension model": "问题优化模型",
|
||||||
"core.chat.response.Read complete response": "查看详情",
|
"core.chat.response.Read complete response": "查看详情",
|
||||||
"core.chat.response.Read complete response tips": "点击查看详细流程",
|
"core.chat.response.Read complete response tips": "点击查看详细流程",
|
||||||
|
"core.chat.response.Tool call input tokens": "工具调用输入 Tokens",
|
||||||
|
"core.chat.response.Tool call output tokens": "工具调用输出 Tokens",
|
||||||
"core.chat.response.Tool call tokens": "工具调用 tokens 消耗",
|
"core.chat.response.Tool call tokens": "工具调用 tokens 消耗",
|
||||||
"core.chat.response.context total length": "上下文总长度",
|
"core.chat.response.context total length": "上下文总长度",
|
||||||
"core.chat.response.loop_input": "输入数组",
|
"core.chat.response.loop_input": "输入数组",
|
||||||
@ -463,10 +465,12 @@
|
|||||||
"core.chat.response.module historyPreview": "记录预览(仅展示部分内容)",
|
"core.chat.response.module historyPreview": "记录预览(仅展示部分内容)",
|
||||||
"core.chat.response.module http result": "响应体",
|
"core.chat.response.module http result": "响应体",
|
||||||
"core.chat.response.module if else Result": "判断器结果",
|
"core.chat.response.module if else Result": "判断器结果",
|
||||||
|
"core.chat.response.module input tokens": "输入 Tokens",
|
||||||
"core.chat.response.module limit": "单次搜索上限",
|
"core.chat.response.module limit": "单次搜索上限",
|
||||||
"core.chat.response.module maxToken": "最大响应 tokens",
|
"core.chat.response.module maxToken": "最大响应 tokens",
|
||||||
"core.chat.response.module model": "模型",
|
"core.chat.response.module model": "模型",
|
||||||
"core.chat.response.module name": "模型名",
|
"core.chat.response.module name": "模型名",
|
||||||
|
"core.chat.response.module output tokens": "输出 Tokens",
|
||||||
"core.chat.response.module query": "问题/检索词",
|
"core.chat.response.module query": "问题/检索词",
|
||||||
"core.chat.response.module quoteList": "引用内容",
|
"core.chat.response.module quoteList": "引用内容",
|
||||||
"core.chat.response.module similarity": "相似度",
|
"core.chat.response.module similarity": "相似度",
|
||||||
@ -1046,6 +1050,8 @@
|
|||||||
"support.user.team.Team Tags Async Success": "同步完成",
|
"support.user.team.Team Tags Async Success": "同步完成",
|
||||||
"support.user.team.member": "成员",
|
"support.user.team.member": "成员",
|
||||||
"support.wallet.Ai point every thousand tokens": "{{points}} 积分/1K tokens",
|
"support.wallet.Ai point every thousand tokens": "{{points}} 积分/1K tokens",
|
||||||
|
"support.wallet.Ai point every thousand tokens_input": "输入:{{points}} 积分/1K tokens",
|
||||||
|
"support.wallet.Ai point every thousand tokens_output": "输出:{{points}} 积分/1K tokens",
|
||||||
"support.wallet.Amount": "金额",
|
"support.wallet.Amount": "金额",
|
||||||
"support.wallet.Buy": "购买",
|
"support.wallet.Buy": "购买",
|
||||||
"support.wallet.Not sufficient": "您的 AI 积分不足,请先升级套餐或购买额外 AI 积分后继续使用。",
|
"support.wallet.Not sufficient": "您的 AI 积分不足,请先升级套餐或购买额外 AI 积分后继续使用。",
|
||||||
|
|||||||
@ -6,12 +6,14 @@
|
|||||||
"details": "詳情",
|
"details": "詳情",
|
||||||
"duration_seconds": "時長(秒)",
|
"duration_seconds": "時長(秒)",
|
||||||
"generation_time": "生成時間",
|
"generation_time": "生成時間",
|
||||||
|
"input_token_length": "輸入 tokens",
|
||||||
"member": "成員",
|
"member": "成員",
|
||||||
"member_name": "成員名",
|
"member_name": "成員名",
|
||||||
"module_name": "模組名",
|
"module_name": "模組名",
|
||||||
"month": "月",
|
"month": "月",
|
||||||
"no_usage_records": "暫無使用紀錄",
|
"no_usage_records": "暫無使用紀錄",
|
||||||
"order_number": "訂單編號",
|
"order_number": "訂單編號",
|
||||||
|
"output_token_length": "輸出 tokens",
|
||||||
"project_name": "專案名",
|
"project_name": "專案名",
|
||||||
"source": "來源",
|
"source": "來源",
|
||||||
"text_length": "文字長度",
|
"text_length": "文字長度",
|
||||||
@ -20,4 +22,4 @@
|
|||||||
"total_points_consumed": "AI 積分消耗",
|
"total_points_consumed": "AI 積分消耗",
|
||||||
"usage_detail": "使用詳情",
|
"usage_detail": "使用詳情",
|
||||||
"user_type": "類型"
|
"user_type": "類型"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -447,6 +447,8 @@
|
|||||||
"core.chat.response.Extension model": "問題最佳化模型",
|
"core.chat.response.Extension model": "問題最佳化模型",
|
||||||
"core.chat.response.Read complete response": "檢視詳細資料",
|
"core.chat.response.Read complete response": "檢視詳細資料",
|
||||||
"core.chat.response.Read complete response tips": "點選檢視詳細流程",
|
"core.chat.response.Read complete response tips": "點選檢視詳細流程",
|
||||||
|
"core.chat.response.Tool call input tokens": "工具呼叫輸入 Token 消耗",
|
||||||
|
"core.chat.response.Tool call output tokens": "工具呼叫輸出 Token 消耗",
|
||||||
"core.chat.response.Tool call tokens": "工具呼叫 Token 消耗",
|
"core.chat.response.Tool call tokens": "工具呼叫 Token 消耗",
|
||||||
"core.chat.response.context total length": "上下文總長度",
|
"core.chat.response.context total length": "上下文總長度",
|
||||||
"core.chat.response.loop_input": "輸入陣列",
|
"core.chat.response.loop_input": "輸入陣列",
|
||||||
@ -460,10 +462,12 @@
|
|||||||
"core.chat.response.module historyPreview": "記錄預覽(僅顯示部分內容)",
|
"core.chat.response.module historyPreview": "記錄預覽(僅顯示部分內容)",
|
||||||
"core.chat.response.module http result": "回應內容",
|
"core.chat.response.module http result": "回應內容",
|
||||||
"core.chat.response.module if else Result": "條件判斷結果",
|
"core.chat.response.module if else Result": "條件判斷結果",
|
||||||
|
"core.chat.response.module input tokens": "輸入 tokens",
|
||||||
"core.chat.response.module limit": "單次搜尋上限",
|
"core.chat.response.module limit": "單次搜尋上限",
|
||||||
"core.chat.response.module maxToken": "最大回應 Token 數",
|
"core.chat.response.module maxToken": "最大回應 Token 數",
|
||||||
"core.chat.response.module model": "模型",
|
"core.chat.response.module model": "模型",
|
||||||
"core.chat.response.module name": "模型名稱",
|
"core.chat.response.module name": "模型名稱",
|
||||||
|
"core.chat.response.module output tokens": "輸出 tokens",
|
||||||
"core.chat.response.module query": "問題/搜尋詞",
|
"core.chat.response.module query": "問題/搜尋詞",
|
||||||
"core.chat.response.module quoteList": "引用內容",
|
"core.chat.response.module quoteList": "引用內容",
|
||||||
"core.chat.response.module similarity": "相似度",
|
"core.chat.response.module similarity": "相似度",
|
||||||
@ -1043,6 +1047,8 @@
|
|||||||
"support.user.team.Team Tags Async Success": "同步完成",
|
"support.user.team.Team Tags Async Success": "同步完成",
|
||||||
"support.user.team.member": "成員",
|
"support.user.team.member": "成員",
|
||||||
"support.wallet.Ai point every thousand tokens": "{{points}} 點數/1K tokens",
|
"support.wallet.Ai point every thousand tokens": "{{points}} 點數/1K tokens",
|
||||||
|
"support.wallet.Ai point every thousand tokens_input": "輸入:{{points}} 积分/1K tokens",
|
||||||
|
"support.wallet.Ai point every thousand tokens_output": "輸出:{{points}} 积分/1K tokens",
|
||||||
"support.wallet.Amount": "金額",
|
"support.wallet.Amount": "金額",
|
||||||
"support.wallet.Buy": "購買",
|
"support.wallet.Buy": "購買",
|
||||||
"support.wallet.Not sufficient": "您的 AI 點數不足,請先升級方案或購買額外 AI 點數後繼續使用。",
|
"support.wallet.Not sufficient": "您的 AI 點數不足,請先升級方案或購買額外 AI 點數後繼續使用。",
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import {
|
|||||||
Thead,
|
Thead,
|
||||||
Tr,
|
Tr,
|
||||||
Table,
|
Table,
|
||||||
useDisclosure,
|
|
||||||
FlexProps
|
FlexProps
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
@ -175,10 +174,28 @@ const AIChatSettingsModal = ({
|
|||||||
<Tbody>
|
<Tbody>
|
||||||
<Tr color={'myGray.900'}>
|
<Tr color={'myGray.900'}>
|
||||||
<Td pt={0} pb={2}>
|
<Td pt={0} pb={2}>
|
||||||
{t('common:support.wallet.Ai point every thousand tokens', {
|
{typeof selectedModel?.inputPrice === 'number' ? (
|
||||||
points: selectedModel?.charsPointsPrice || 0
|
<>
|
||||||
})}
|
<Box>
|
||||||
|
{t('common:support.wallet.Ai point every thousand tokens_input', {
|
||||||
|
points: selectedModel?.inputPrice || 0
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
{t('common:support.wallet.Ai point every thousand tokens_output', {
|
||||||
|
points: selectedModel?.outputPrice || 0
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{t('common:support.wallet.Ai point every thousand tokens', {
|
||||||
|
points: selectedModel?.charsPointsPrice || 0
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Td>
|
</Td>
|
||||||
|
|
||||||
<Td pt={0} pb={2}>
|
<Td pt={0} pb={2}>
|
||||||
{Math.round((selectedModel?.maxContext || 4096) / 1000)}K
|
{Math.round((selectedModel?.maxContext || 4096) / 1000)}K
|
||||||
</Td>
|
</Td>
|
||||||
|
|||||||
@ -60,14 +60,32 @@ const ModelTable = () => {
|
|||||||
const formatLLMModelList = llmModelList.map((item) => ({
|
const formatLLMModelList = llmModelList.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
typeLabel: t('common:model.type.chat'),
|
typeLabel: t('common:model.type.chat'),
|
||||||
priceLabel: (
|
priceLabel:
|
||||||
<Flex color={'myGray.700'}>
|
typeof item.inputPrice === 'number' ? (
|
||||||
<Box fontWeight={'bold'} color={'myGray.900'} mr={0.5}>
|
<Box>
|
||||||
{item.charsPointsPrice}
|
<Flex>
|
||||||
|
{`${t('common:common.Input')}:`}
|
||||||
|
<Box fontWeight={'bold'} color={'myGray.900'} mr={0.5} ml={2}>
|
||||||
|
{item.inputPrice || 0}
|
||||||
|
</Box>
|
||||||
|
{`${t('common:support.wallet.subscription.point')} / 1K Tokens`}
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
{`${t('common:common.Output')}:`}
|
||||||
|
<Box fontWeight={'bold'} color={'myGray.900'} mr={0.5} ml={2}>
|
||||||
|
{item.outputPrice || 0}
|
||||||
|
</Box>
|
||||||
|
{`${t('common:support.wallet.subscription.point')} / 1K Tokens`}
|
||||||
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
{`${t('common:support.wallet.subscription.point')} / 1K Tokens`}
|
) : (
|
||||||
</Flex>
|
<Flex color={'myGray.700'}>
|
||||||
),
|
<Box fontWeight={'bold'} color={'myGray.900'} mr={0.5}>
|
||||||
|
{item.charsPointsPrice}
|
||||||
|
</Box>
|
||||||
|
{`${t('common:support.wallet.subscription.point')} / 1K Tokens`}
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
tagColor: 'blue'
|
tagColor: 'blue'
|
||||||
}));
|
}));
|
||||||
const formatVectorModelList = vectorModelList.map((item) => ({
|
const formatVectorModelList = vectorModelList.map((item) => ({
|
||||||
@ -149,13 +167,13 @@ const ModelTable = () => {
|
|||||||
|
|
||||||
return filterList;
|
return filterList;
|
||||||
}, [
|
}, [
|
||||||
provider,
|
|
||||||
modelType,
|
|
||||||
llmModelList,
|
llmModelList,
|
||||||
vectorModelList,
|
vectorModelList,
|
||||||
audioSpeechModelList,
|
audioSpeechModelList,
|
||||||
whisperModel,
|
whisperModel,
|
||||||
t,
|
t,
|
||||||
|
modelType,
|
||||||
|
provider,
|
||||||
search
|
search
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -155,10 +155,26 @@ export const WholeResponseContent = ({
|
|||||||
label={t('common:core.chat.response.module tokens')}
|
label={t('common:core.chat.response.module tokens')}
|
||||||
value={`${activeModule?.tokens}`}
|
value={`${activeModule?.tokens}`}
|
||||||
/>
|
/>
|
||||||
|
<Row
|
||||||
|
label={t('common:core.chat.response.module input tokens')}
|
||||||
|
value={`${activeModule?.inputTokens}`}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={t('common:core.chat.response.module output tokens')}
|
||||||
|
value={`${activeModule?.outputTokens}`}
|
||||||
|
/>
|
||||||
<Row
|
<Row
|
||||||
label={t('common:core.chat.response.Tool call tokens')}
|
label={t('common:core.chat.response.Tool call tokens')}
|
||||||
value={`${activeModule?.toolCallTokens}`}
|
value={`${activeModule?.toolCallTokens}`}
|
||||||
/>
|
/>
|
||||||
|
<Row
|
||||||
|
label={t('common:core.chat.response.Tool call input tokens')}
|
||||||
|
value={`${activeModule?.toolCallInputTokens}`}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={t('common:core.chat.response.Tool call output tokens')}
|
||||||
|
value={`${activeModule?.toolCallOutputTokens}`}
|
||||||
|
/>
|
||||||
|
|
||||||
<Row label={t('common:core.chat.response.module query')} value={activeModule?.query} />
|
<Row label={t('common:core.chat.response.module query')} value={activeModule?.query} />
|
||||||
<Row
|
<Row
|
||||||
|
|||||||
@ -26,37 +26,48 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
|
|||||||
[usage.list]
|
[usage.list]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { hasModel, hasToken, hasCharsLen, hasDuration } = useMemo(() => {
|
const { hasModel, hasToken, hasInputToken, hasOutputToken, hasCharsLen, hasDuration } =
|
||||||
let hasModel = false;
|
useMemo(() => {
|
||||||
let hasToken = false;
|
let hasModel = false;
|
||||||
let hasCharsLen = false;
|
let hasToken = false;
|
||||||
let hasDuration = false;
|
let hasInputToken = false;
|
||||||
let hasDataLen = false;
|
let hasOutputToken = false;
|
||||||
|
let hasCharsLen = false;
|
||||||
|
let hasDuration = false;
|
||||||
|
let hasDataLen = false;
|
||||||
|
|
||||||
usage.list.forEach((item) => {
|
usage.list.forEach((item) => {
|
||||||
if (item.model !== undefined) {
|
if (item.model !== undefined) {
|
||||||
hasModel = true;
|
hasModel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof item.tokens === 'number') {
|
if (typeof item.tokens === 'number') {
|
||||||
hasToken = true;
|
hasToken = true;
|
||||||
}
|
}
|
||||||
if (typeof item.charsLength === 'number') {
|
if (typeof item.inputTokens === 'number') {
|
||||||
hasCharsLen = true;
|
hasInputToken = true;
|
||||||
}
|
}
|
||||||
if (typeof item.duration === 'number') {
|
if (typeof item.outputTokens === 'number') {
|
||||||
hasDuration = true;
|
hasOutputToken = true;
|
||||||
}
|
}
|
||||||
});
|
if (typeof item.charsLength === 'number') {
|
||||||
|
hasCharsLen = true;
|
||||||
|
}
|
||||||
|
if (typeof item.duration === 'number') {
|
||||||
|
hasDuration = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasModel,
|
hasModel,
|
||||||
hasToken,
|
hasToken,
|
||||||
hasCharsLen,
|
hasInputToken,
|
||||||
hasDuration,
|
hasOutputToken,
|
||||||
hasDataLen
|
hasCharsLen,
|
||||||
};
|
hasDuration,
|
||||||
}, [usage.list]);
|
hasDataLen
|
||||||
|
};
|
||||||
|
}, [usage.list]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MyModal
|
<MyModal
|
||||||
@ -98,6 +109,8 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
|
|||||||
<Th>{t('account_usage:module_name')}</Th>
|
<Th>{t('account_usage:module_name')}</Th>
|
||||||
{hasModel && <Th>{t('account_usage:ai_model')}</Th>}
|
{hasModel && <Th>{t('account_usage:ai_model')}</Th>}
|
||||||
{hasToken && <Th>{t('account_usage:token_length')}</Th>}
|
{hasToken && <Th>{t('account_usage:token_length')}</Th>}
|
||||||
|
{hasInputToken && <Th>{t('account_usage:input_token_length')}</Th>}
|
||||||
|
{hasOutputToken && <Th>{t('account_usage:output_token_length')}</Th>}
|
||||||
{hasCharsLen && <Th>{t('account_usage:text_length')}</Th>}
|
{hasCharsLen && <Th>{t('account_usage:text_length')}</Th>}
|
||||||
{hasDuration && <Th>{t('account_usage:duration_seconds')}</Th>}
|
{hasDuration && <Th>{t('account_usage:duration_seconds')}</Th>}
|
||||||
<Th>{t('account_usage:total_points_consumed')}</Th>
|
<Th>{t('account_usage:total_points_consumed')}</Th>
|
||||||
@ -109,6 +122,8 @@ const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () =>
|
|||||||
<Td>{t(item.moduleName as any)}</Td>
|
<Td>{t(item.moduleName as any)}</Td>
|
||||||
{hasModel && <Td>{item.model ?? '-'}</Td>}
|
{hasModel && <Td>{item.model ?? '-'}</Td>}
|
||||||
{hasToken && <Td>{item.tokens ?? '-'}</Td>}
|
{hasToken && <Td>{item.tokens ?? '-'}</Td>}
|
||||||
|
{hasInputToken && <Td>{item.inputTokens ?? '-'}</Td>}
|
||||||
|
{hasOutputToken && <Td>{item.outputTokens ?? '-'}</Td>}
|
||||||
{hasCharsLen && <Td>{item.charsLength ?? '-'}</Td>}
|
{hasCharsLen && <Td>{item.charsLength ?? '-'}</Td>}
|
||||||
{hasDuration && <Td>{item.duration ?? '-'}</Td>}
|
{hasDuration && <Td>{item.duration ?? '-'}</Td>}
|
||||||
<Td>{formatNumber(item.amount)}</Td>
|
<Td>{formatNumber(item.amount)}</Td>
|
||||||
|
|||||||
@ -37,7 +37,7 @@ async function handler(
|
|||||||
|
|
||||||
const qgModel = global.llmModels[0];
|
const qgModel = global.llmModels[0];
|
||||||
|
|
||||||
const { result, tokens } = await createQuestionGuide({
|
const { result, inputTokens, outputTokens } = await createQuestionGuide({
|
||||||
messages,
|
messages,
|
||||||
model: qgModel.model
|
model: qgModel.model
|
||||||
});
|
});
|
||||||
@ -47,7 +47,8 @@ async function handler(
|
|||||||
});
|
});
|
||||||
|
|
||||||
pushQuestionGuideUsage({
|
pushQuestionGuideUsage({
|
||||||
tokens,
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
teamId,
|
teamId,
|
||||||
tmbId
|
tmbId
|
||||||
});
|
});
|
||||||
|
|||||||
@ -52,14 +52,15 @@ async function handler(req: ApiRequestProps<CreateQuestionGuideParams>, res: Nex
|
|||||||
|
|
||||||
const qgModel = questionGuide?.model || global.llmModels[0].model;
|
const qgModel = questionGuide?.model || global.llmModels[0].model;
|
||||||
|
|
||||||
const { result, tokens } = await createQuestionGuide({
|
const { result, inputTokens, outputTokens } = await createQuestionGuide({
|
||||||
messages,
|
messages,
|
||||||
model: qgModel,
|
model: qgModel,
|
||||||
customPrompt: questionGuide?.customPrompt
|
customPrompt: questionGuide?.customPrompt
|
||||||
});
|
});
|
||||||
|
|
||||||
pushQuestionGuideUsage({
|
pushQuestionGuideUsage({
|
||||||
tokens,
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
teamId,
|
teamId,
|
||||||
tmbId
|
tmbId
|
||||||
});
|
});
|
||||||
|
|||||||
@ -89,7 +89,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
pushGenerateVectorUsage({
|
pushGenerateVectorUsage({
|
||||||
teamId,
|
teamId,
|
||||||
tmbId,
|
tmbId,
|
||||||
tokens,
|
inputTokens: tokens,
|
||||||
model: vectorModelData.model
|
model: vectorModelData.model
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ async function handler(req: ApiRequestProps<UpdateDatasetDataProps>) {
|
|||||||
pushGenerateVectorUsage({
|
pushGenerateVectorUsage({
|
||||||
teamId,
|
teamId,
|
||||||
tmbId,
|
tmbId,
|
||||||
tokens,
|
inputTokens: tokens,
|
||||||
model: vectorModel
|
model: vectorModel
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -74,14 +74,15 @@ async function handler(req: NextApiRequest) {
|
|||||||
const { totalPoints } = pushGenerateVectorUsage({
|
const { totalPoints } = pushGenerateVectorUsage({
|
||||||
teamId,
|
teamId,
|
||||||
tmbId,
|
tmbId,
|
||||||
tokens,
|
inputTokens: tokens,
|
||||||
model: dataset.vectorModel,
|
model: dataset.vectorModel,
|
||||||
source: apikey ? UsageSourceEnum.api : UsageSourceEnum.fastgpt,
|
source: apikey ? UsageSourceEnum.api : UsageSourceEnum.fastgpt,
|
||||||
|
|
||||||
...(aiExtensionResult &&
|
...(aiExtensionResult &&
|
||||||
extensionModel && {
|
extensionModel && {
|
||||||
extensionModel: extensionModel.name,
|
extensionModel: extensionModel.name,
|
||||||
extensionTokens: aiExtensionResult.tokens
|
extensionInputTokens: aiExtensionResult.inputTokens,
|
||||||
|
extensionOutputTokens: aiExtensionResult.outputTokens
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
if (apikey) {
|
if (apikey) {
|
||||||
|
|||||||
@ -57,7 +57,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
const { totalPoints } = pushGenerateVectorUsage({
|
const { totalPoints } = pushGenerateVectorUsage({
|
||||||
teamId,
|
teamId,
|
||||||
tmbId,
|
tmbId,
|
||||||
tokens,
|
inputTokens: tokens,
|
||||||
model,
|
model,
|
||||||
billId,
|
billId,
|
||||||
source: getUsageSourceByAuthType({ authType })
|
source: getUsageSourceByAuthType({ authType })
|
||||||
|
|||||||
@ -196,7 +196,7 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
|
|||||||
chunkSize: vectorModel?.defaultToken ? vectorModel?.defaultToken * 2 : 1024,
|
chunkSize: vectorModel?.defaultToken ? vectorModel?.defaultToken * 2 : 1024,
|
||||||
showChunkInput: false,
|
showChunkInput: false,
|
||||||
showPromptInput: false,
|
showPromptInput: false,
|
||||||
charsPointsPrice: agentModel.charsPointsPrice,
|
charsPointsPrice: agentModel.charsPointsPrice || 0,
|
||||||
priceTip: t('dataset:import.Auto mode Estimated Price Tips', {
|
priceTip: t('dataset:import.Auto mode Estimated Price Tips', {
|
||||||
price: agentModel.charsPointsPrice
|
price: agentModel.charsPointsPrice
|
||||||
}),
|
}),
|
||||||
@ -211,7 +211,7 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
|
|||||||
chunkSize: embeddingChunkSize,
|
chunkSize: embeddingChunkSize,
|
||||||
showChunkInput: true,
|
showChunkInput: true,
|
||||||
showPromptInput: false,
|
showPromptInput: false,
|
||||||
charsPointsPrice: vectorModel.charsPointsPrice,
|
charsPointsPrice: vectorModel.charsPointsPrice || 0,
|
||||||
priceTip: t('dataset:import.Embedding Estimated Price Tips', {
|
priceTip: t('dataset:import.Embedding Estimated Price Tips', {
|
||||||
price: vectorModel.charsPointsPrice
|
price: vectorModel.charsPointsPrice
|
||||||
}),
|
}),
|
||||||
@ -226,7 +226,7 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
|
|||||||
chunkSize: qaChunkSize,
|
chunkSize: qaChunkSize,
|
||||||
showChunkInput: true,
|
showChunkInput: true,
|
||||||
showPromptInput: true,
|
showPromptInput: true,
|
||||||
charsPointsPrice: agentModel.charsPointsPrice,
|
charsPointsPrice: agentModel.charsPointsPrice || 0,
|
||||||
priceTip: t('dataset:import.Auto mode Estimated Price Tips', {
|
priceTip: t('dataset:import.Auto mode Estimated Price Tips', {
|
||||||
price: agentModel.charsPointsPrice
|
price: agentModel.charsPointsPrice
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -12,7 +12,10 @@ import { getLLMModel } from '@fastgpt/service/core/ai/model';
|
|||||||
import { checkTeamAiPointsAndLock } from './utils';
|
import { checkTeamAiPointsAndLock } from './utils';
|
||||||
import { checkInvalidChunkAndLock } from '@fastgpt/service/core/dataset/training/utils';
|
import { checkInvalidChunkAndLock } from '@fastgpt/service/core/dataset/training/utils';
|
||||||
import { addMinutes } from 'date-fns';
|
import { addMinutes } from 'date-fns';
|
||||||
import { countGptMessagesTokens } from '@fastgpt/service/common/string/tiktoken/index';
|
import {
|
||||||
|
countGptMessagesTokens,
|
||||||
|
countPromptTokens
|
||||||
|
} from '@fastgpt/service/common/string/tiktoken/index';
|
||||||
import { pushDataListToTrainingQueueByCollectionId } from '@fastgpt/service/core/dataset/training/controller';
|
import { pushDataListToTrainingQueueByCollectionId } from '@fastgpt/service/core/dataset/training/controller';
|
||||||
import { loadRequestMessages } from '@fastgpt/service/core/chat/utils';
|
import { loadRequestMessages } from '@fastgpt/service/core/chat/utils';
|
||||||
import { llmCompletionsBodyFormat } from '@fastgpt/service/core/ai/utils';
|
import { llmCompletionsBodyFormat } from '@fastgpt/service/core/ai/utils';
|
||||||
@ -153,7 +156,8 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
|
|||||||
pushQAUsage({
|
pushQAUsage({
|
||||||
teamId: data.teamId,
|
teamId: data.teamId,
|
||||||
tmbId: data.tmbId,
|
tmbId: data.tmbId,
|
||||||
tokens: await countGptMessagesTokens(messages),
|
inputTokens: await countGptMessagesTokens(messages),
|
||||||
|
outputTokens: await countPromptTokens(answer),
|
||||||
billId: data.billId,
|
billId: data.billId,
|
||||||
model: modelData.model
|
model: modelData.model
|
||||||
});
|
});
|
||||||
|
|||||||
@ -111,7 +111,7 @@ export async function generateVector(): Promise<any> {
|
|||||||
pushGenerateVectorUsage({
|
pushGenerateVectorUsage({
|
||||||
teamId: data.teamId,
|
teamId: data.teamId,
|
||||||
tmbId: data.tmbId,
|
tmbId: data.tmbId,
|
||||||
tokens,
|
inputTokens: tokens,
|
||||||
model: data.model,
|
model: data.model,
|
||||||
billId: data.billId
|
billId: data.billId
|
||||||
});
|
});
|
||||||
|
|||||||
@ -37,7 +37,8 @@ export const pushChatUsage = ({
|
|||||||
moduleName: item.moduleName,
|
moduleName: item.moduleName,
|
||||||
amount: item.totalPoints || 0,
|
amount: item.totalPoints || 0,
|
||||||
model: item.model,
|
model: item.model,
|
||||||
tokens: item.tokens
|
inputTokens: item.inputTokens,
|
||||||
|
outputTokens: item.outputTokens
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
addLog.info(`finish completions`, {
|
addLog.info(`finish completions`, {
|
||||||
@ -52,20 +53,23 @@ export const pushQAUsage = async ({
|
|||||||
teamId,
|
teamId,
|
||||||
tmbId,
|
tmbId,
|
||||||
model,
|
model,
|
||||||
tokens,
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
billId
|
billId
|
||||||
}: {
|
}: {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
tmbId: string;
|
tmbId: string;
|
||||||
model: string;
|
model: string;
|
||||||
tokens: number;
|
inputTokens: number;
|
||||||
|
outputTokens: number;
|
||||||
billId: string;
|
billId: string;
|
||||||
}) => {
|
}) => {
|
||||||
// 计算价格
|
// 计算价格
|
||||||
const { totalPoints } = formatModelChars2Points({
|
const { totalPoints } = formatModelChars2Points({
|
||||||
model,
|
model,
|
||||||
modelType: ModelTypeEnum.llm,
|
modelType: ModelTypeEnum.llm,
|
||||||
tokens
|
inputTokens,
|
||||||
|
outputTokens
|
||||||
});
|
});
|
||||||
|
|
||||||
concatUsage({
|
concatUsage({
|
||||||
@ -73,7 +77,8 @@ export const pushQAUsage = async ({
|
|||||||
teamId,
|
teamId,
|
||||||
tmbId,
|
tmbId,
|
||||||
totalPoints,
|
totalPoints,
|
||||||
tokens,
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
listIndex: 1
|
listIndex: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -84,30 +89,32 @@ export const pushGenerateVectorUsage = ({
|
|||||||
billId,
|
billId,
|
||||||
teamId,
|
teamId,
|
||||||
tmbId,
|
tmbId,
|
||||||
tokens,
|
inputTokens,
|
||||||
model,
|
model,
|
||||||
source = UsageSourceEnum.fastgpt,
|
source = UsageSourceEnum.fastgpt,
|
||||||
extensionModel,
|
extensionModel,
|
||||||
extensionTokens
|
extensionInputTokens,
|
||||||
|
extensionOutputTokens
|
||||||
}: {
|
}: {
|
||||||
billId?: string;
|
billId?: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
tmbId: string;
|
tmbId: string;
|
||||||
tokens: number;
|
inputTokens: number;
|
||||||
model: string;
|
model: string;
|
||||||
source?: UsageSourceEnum;
|
source?: UsageSourceEnum;
|
||||||
|
|
||||||
extensionModel?: string;
|
extensionModel?: string;
|
||||||
extensionTokens?: number;
|
extensionInputTokens?: number;
|
||||||
|
extensionOutputTokens?: number;
|
||||||
}) => {
|
}) => {
|
||||||
const { totalPoints: totalVector, modelName: vectorModelName } = formatModelChars2Points({
|
const { totalPoints: totalVector, modelName: vectorModelName } = formatModelChars2Points({
|
||||||
modelType: ModelTypeEnum.vector,
|
modelType: ModelTypeEnum.vector,
|
||||||
model,
|
model,
|
||||||
tokens
|
inputTokens
|
||||||
});
|
});
|
||||||
|
|
||||||
const { extensionTotalPoints, extensionModelName } = (() => {
|
const { extensionTotalPoints, extensionModelName } = (() => {
|
||||||
if (!extensionModel || !extensionTokens)
|
if (!extensionModel || !extensionInputTokens)
|
||||||
return {
|
return {
|
||||||
extensionTotalPoints: 0,
|
extensionTotalPoints: 0,
|
||||||
extensionModelName: ''
|
extensionModelName: ''
|
||||||
@ -115,7 +122,8 @@ export const pushGenerateVectorUsage = ({
|
|||||||
const { totalPoints, modelName } = formatModelChars2Points({
|
const { totalPoints, modelName } = formatModelChars2Points({
|
||||||
modelType: ModelTypeEnum.llm,
|
modelType: ModelTypeEnum.llm,
|
||||||
model: extensionModel,
|
model: extensionModel,
|
||||||
tokens: extensionTokens
|
inputTokens: extensionInputTokens,
|
||||||
|
outputTokens: extensionOutputTokens
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
extensionTotalPoints: totalPoints,
|
extensionTotalPoints: totalPoints,
|
||||||
@ -132,7 +140,7 @@ export const pushGenerateVectorUsage = ({
|
|||||||
tmbId,
|
tmbId,
|
||||||
totalPoints,
|
totalPoints,
|
||||||
billId,
|
billId,
|
||||||
tokens,
|
inputTokens,
|
||||||
listIndex: 0
|
listIndex: 0
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -147,7 +155,7 @@ export const pushGenerateVectorUsage = ({
|
|||||||
moduleName: 'support.wallet.moduleName.index',
|
moduleName: 'support.wallet.moduleName.index',
|
||||||
amount: totalVector,
|
amount: totalVector,
|
||||||
model: vectorModelName,
|
model: vectorModelName,
|
||||||
tokens
|
inputTokens
|
||||||
},
|
},
|
||||||
...(extensionModel !== undefined
|
...(extensionModel !== undefined
|
||||||
? [
|
? [
|
||||||
@ -155,7 +163,8 @@ export const pushGenerateVectorUsage = ({
|
|||||||
moduleName: 'core.module.template.Query extension',
|
moduleName: 'core.module.template.Query extension',
|
||||||
amount: extensionTotalPoints,
|
amount: extensionTotalPoints,
|
||||||
model: extensionModelName,
|
model: extensionModelName,
|
||||||
tokens: extensionTokens
|
inputTokens: extensionInputTokens,
|
||||||
|
outputTokens: extensionOutputTokens
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [])
|
: [])
|
||||||
@ -166,17 +175,20 @@ export const pushGenerateVectorUsage = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const pushQuestionGuideUsage = ({
|
export const pushQuestionGuideUsage = ({
|
||||||
tokens,
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
teamId,
|
teamId,
|
||||||
tmbId
|
tmbId
|
||||||
}: {
|
}: {
|
||||||
tokens: number;
|
inputTokens: number;
|
||||||
|
outputTokens: number;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
tmbId: string;
|
tmbId: string;
|
||||||
}) => {
|
}) => {
|
||||||
const qgModel = global.llmModels[0];
|
const qgModel = global.llmModels[0];
|
||||||
const { totalPoints, modelName } = formatModelChars2Points({
|
const { totalPoints, modelName } = formatModelChars2Points({
|
||||||
tokens,
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
model: qgModel.model,
|
model: qgModel.model,
|
||||||
modelType: ModelTypeEnum.llm
|
modelType: ModelTypeEnum.llm
|
||||||
});
|
});
|
||||||
@ -192,7 +204,8 @@ export const pushQuestionGuideUsage = ({
|
|||||||
moduleName: 'core.app.Question Guide',
|
moduleName: 'core.app.Question Guide',
|
||||||
amount: totalPoints,
|
amount: totalPoints,
|
||||||
model: modelName,
|
model: modelName,
|
||||||
tokens
|
inputTokens,
|
||||||
|
outputTokens
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@ -215,7 +228,7 @@ export function pushAudioSpeechUsage({
|
|||||||
}) {
|
}) {
|
||||||
const { totalPoints, modelName } = formatModelChars2Points({
|
const { totalPoints, modelName } = formatModelChars2Points({
|
||||||
model,
|
model,
|
||||||
tokens: charsLength,
|
inputTokens: charsLength,
|
||||||
modelType: ModelTypeEnum.audioSpeech
|
modelType: ModelTypeEnum.audioSpeech
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -251,7 +264,7 @@ export function pushWhisperUsage({
|
|||||||
|
|
||||||
const { totalPoints, modelName } = formatModelChars2Points({
|
const { totalPoints, modelName } = formatModelChars2Points({
|
||||||
model: whisperModel.model,
|
model: whisperModel.model,
|
||||||
tokens: duration,
|
inputTokens: duration,
|
||||||
modelType: ModelTypeEnum.whisper,
|
modelType: ModelTypeEnum.whisper,
|
||||||
multiple: 60
|
multiple: 60
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user