System plugin (#2091)

* System template (#2082)

* feat: system plugin (#2024)

* add plugin cost & change plugin avatar (#2030)

* add plugin cost & change plugin avatar

* add author

* feat: duckduckgo plugin

* duckduck search

* perf: templates select system plugin

* perf: system plugin avatar

* feat: duckduck plugins

* doc

* perf: plugin classify

* perf: icon avatar component

* perf: system template avatar

---------

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>

* feat: system plugin search

* perf: plugin packages important

* perf: source avatar

* nextconfig

* perf: i18n

* perf: default model

* perf: system plugin author

---------

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-07-19 14:15:01 +08:00
committed by GitHub
parent 1eedb9caba
commit cf7145ab54
165 changed files with 2643 additions and 557 deletions

View File

@@ -9,7 +9,7 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
import { cloneDeep } from 'lodash';
import { MongoApp } from '../schema';
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
import { getCommunityPlugins } from '@fastgpt/plugins/register';
import { getSystemPluginTemplates } from '../../../../plugins/register';
/*
plugin id rule:
@@ -28,7 +28,7 @@ export async function splitCombinePluginId(id: string) {
};
}
const [source, pluginId] = id.split('-') as [`${PluginSourceEnum}`, string];
const [source, pluginId] = id.split('-') as [PluginSourceEnum, string];
if (!source || !pluginId) return Promise.reject('pluginId not found');
return { source, pluginId: id };
@@ -39,14 +39,6 @@ const getPluginTemplateById = async (
): Promise<SystemPluginTemplateItemType & { teamId?: string }> => {
const { source, pluginId } = await splitCombinePluginId(id);
if (source === PluginSourceEnum.community) {
const item = [...global.communityPlugins, ...getCommunityPlugins()].find(
(plugin) => plugin.id === pluginId
);
if (!item) return Promise.reject('plugin not found');
return cloneDeep(item);
}
if (source === PluginSourceEnum.personal) {
const item = await MongoApp.findById(id).lean();
if (!item) return Promise.reject('plugin not found');
@@ -68,8 +60,14 @@ const getPluginTemplateById = async (
originCost: 0,
currentCost: 0
};
} else {
const item = [...global.communityPlugins, ...(await getSystemPluginTemplates())].find(
(plugin) => plugin.id === pluginId
);
if (!item) return Promise.reject('plugin not found');
return cloneDeep(item);
}
return Promise.reject('plugin not found');
};
/* format plugin modules to plugin preview module */
@@ -98,10 +96,12 @@ export async function getPluginRuntimeById(id: string): Promise<PluginRuntimeTyp
const plugin = await getPluginTemplateById(id);
return {
id: plugin.id,
teamId: plugin.teamId,
name: plugin.name,
avatar: plugin.avatar,
showStatus: plugin.showStatus,
currentCost: plugin.currentCost,
nodes: plugin.workflow.nodes,
edges: plugin.workflow.edges
};

View File

@@ -0,0 +1,35 @@
import { connectionMongo, getMongoModel } from '../../../common/mongo/index';
const { Schema } = connectionMongo;
import type { SystemPluginConfigSchemaType } from './type';
export const collectionName = 'app_system_plugins';
const SystemPluginSchema = new Schema({
pluginId: {
type: String,
required: true
},
isActive: {
type: Boolean,
required: true
},
inputConfig: {
type: Array,
default: []
},
originCost: {
type: Number,
default: 0
},
currentCost: {
type: Number,
default: 0
}
});
SystemPluginSchema.index({ pluginId: 1 });
export const MongoSystemPluginSchema = getMongoModel<SystemPluginConfigSchemaType>(
collectionName,
SystemPluginSchema
);

View File

@@ -0,0 +1,10 @@
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
export type SystemPluginConfigSchemaType = {
pluginId: string;
originCost: number; // n points/one time
currentCost: number;
isActive: boolean;
inputConfig: SystemPluginTemplateItemType['inputConfig'];
};

View File

@@ -0,0 +1,21 @@
import { PluginRuntimeType } from '@fastgpt/global/core/workflow/runtime/type';
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
import { splitCombinePluginId } from './controller';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
/*
1. Commercial plugin: n points per times
2. Other plugin: sum of children points
*/
export const computedPluginUsage = async (
plugin: PluginRuntimeType,
childrenUsage: ChatNodeUsageType[]
) => {
const { source } = await splitCombinePluginId(plugin.id);
if (source === PluginSourceEnum.commercial) {
return plugin.currentCost ?? 0;
}
return childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0);
};

View File

@@ -67,7 +67,7 @@ const DatasetSchema = new Schema({
agentModel: {
type: String,
required: true,
default: 'gpt-3.5-turbo'
default: 'gpt-4o-mini'
},
intro: {
type: String,

View File

@@ -161,70 +161,86 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
stream,
messages: requestMessages
};
const response = await ai.chat.completions.create(requestBody, {
headers: {
Accept: 'application/json, text/plain, */*'
}
});
try {
const response = await ai.chat.completions.create(requestBody, {
headers: {
Accept: 'application/json, text/plain, */*'
}
});
const { answerText } = await (async () => {
if (res && stream) {
// sse response
const { answer } = await streamResponse({
res,
detail,
stream: response,
requestBody
});
const { answerText } = await (async () => {
if (res && stream) {
// sse response
const { answer } = await streamResponse({
res,
detail,
stream: response
});
return {
answerText: answer
};
} else {
const unStreamResponse = response as ChatCompletion;
const answer = unStreamResponse.choices?.[0]?.message?.content || '';
if (!answer) {
throw new Error('LLM model response empty');
}
return {
answerText: answer
};
}
})();
return {
answerText: answer
};
} else {
const unStreamResponse = response as ChatCompletion;
const answer = unStreamResponse.choices?.[0]?.message?.content || '';
const completeMessages = filterMessages.concat({
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: answerText
});
const chatCompleteMessages = GPTMessages2Chats(completeMessages);
return {
answerText: answer
};
}
})();
const tokens = await countMessagesTokens(chatCompleteMessages);
const { totalPoints, modelName } = formatModelChars2Points({
model,
tokens,
modelType: ModelTypeEnum.llm
});
const completeMessages = filterMessages.concat({
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: answerText
});
const chatCompleteMessages = GPTMessages2Chats(completeMessages);
return {
answerText,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
const tokens = await countMessagesTokens(chatCompleteMessages);
const { totalPoints, modelName } = formatModelChars2Points({
model,
tokens,
query: `${userChatInput}`,
maxToken: max_tokens,
historyPreview: getHistoryPreview(chatCompleteMessages),
contextTotalLen: completeMessages.length
},
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{
moduleName: name,
modelType: ModelTypeEnum.llm
});
return {
answerText,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
tokens
}
],
[DispatchNodeResponseKeyEnum.toolResponses]: answerText,
history: chatCompleteMessages
};
tokens,
query: `${userChatInput}`,
maxToken: max_tokens,
historyPreview: getHistoryPreview(chatCompleteMessages),
contextTotalLen: completeMessages.length
},
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{
moduleName: name,
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
tokens
}
],
[DispatchNodeResponseKeyEnum.toolResponses]: answerText,
history: chatCompleteMessages
};
} catch (error) {
addLog.warn(`LLM response error`, {
baseUrl: user.openaiAccount?.baseUrl,
requestBody
});
if (user.openaiAccount?.baseUrl) {
return Promise.reject(`您的 OpenAI key 出错了: ${JSON.stringify(requestBody)}`);
}
return Promise.reject(error);
}
};
async function filterQuote({
@@ -334,13 +350,11 @@ async function getMaxTokens({
async function streamResponse({
res,
detail,
stream,
requestBody
stream
}: {
res: NextApiResponse;
detail: boolean;
stream: StreamChatType;
requestBody: Record<string, any>;
}) {
const write = responseWriteController({
res,
@@ -364,10 +378,5 @@ async function streamResponse({
});
}
if (!answer) {
addLog.info(`LLM model response empty`, requestBody);
return Promise.reject('core.chat.Chat API is error or undefined');
}
return { answer };
}

View File

@@ -2,7 +2,7 @@ import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/
import { dispatchWorkFlow } from '../index';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { getPluginRuntimeById, splitCombinePluginId } from '../../../app/plugin/controller';
import { getPluginRuntimeById } from '../../../app/plugin/controller';
import {
getDefaultEntryNodeIds,
initWorkflowEdgeStatus,
@@ -10,9 +10,9 @@ import {
} from '@fastgpt/global/core/workflow/runtime/utils';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { updateToolInputValue } from '../agent/runTool/utils';
import { authAppByTmbId } from '../../../../support/permission/app/auth';
import { authPluginByTmbId } from '../../../../support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import { computedPluginUsage } from '../../../app/plugin/utils';
type RunPluginProps = ModuleDispatchProps<{
[key: string]: any;
@@ -33,14 +33,12 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
}
// auth plugin
const { source } = await splitCombinePluginId(pluginId);
if (source === PluginSourceEnum.personal) {
await authAppByTmbId({
appId: pluginId,
tmbId: workflowApp.tmbId,
per: ReadPermissionVal
});
}
await authPluginByTmbId({
appId: pluginId,
tmbId: workflowApp.tmbId,
per: ReadPermissionVal
});
const plugin = await getPluginRuntimeById(pluginId);
// concat dynamic inputs
@@ -78,12 +76,15 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
output.moduleLogo = plugin.avatar;
}
const isError = !!output?.pluginOutput?.error;
const usagePoints = isError ? 0 : await computedPluginUsage(plugin, flowUsages);
return {
assistantResponses,
// responseData, // debug
[DispatchNodeResponseKeyEnum.nodeResponse]: {
moduleLogo: plugin.avatar,
totalPoints: flowResponses.reduce((sum, item) => sum + (item.totalPoints || 0), 0),
totalPoints: usagePoints,
pluginOutput: output?.pluginOutput,
pluginDetail:
mode === 'test' && plugin.teamId === teamId
@@ -96,8 +97,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{
moduleName: plugin.name,
totalPoints: flowUsages.reduce((sum, item) => sum + (item.totalPoints || 0), 0),
model: plugin.name,
totalPoints: usagePoints,
tokens: 0
}
],

View File

@@ -16,7 +16,7 @@ import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/ty
import { getErrText } from '@fastgpt/global/common/error/utils';
import { responseWrite } from '../../../../common/response';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { getCommunityCb } from '@fastgpt/plugins/register';
import { getSystemPluginCb } from '../../../../../plugins/register';
type PropsArrType = {
key: string;
@@ -121,9 +121,9 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
try {
const { formatResponse, rawResponse } = await (async () => {
const communityPluginCb = await getCommunityCb();
if (communityPluginCb[httpReqUrl]) {
const pluginResult = await communityPluginCb[httpReqUrl](requestBody);
const systemPluginCb = await getSystemPluginCb();
if (systemPluginCb[httpReqUrl]) {
const pluginResult = await systemPluginCb[httpReqUrl](requestBody);
return {
formatResponse: pluginResult,
rawResponse: pluginResult