fix: tool id (#6544)

* fix: tool id

* fix: test

* fix: ts

* add test
This commit is contained in:
Archer
2026-03-11 23:15:17 +08:00
committed by GitHub
parent 960c8898cf
commit 38f6f9dd9f
30 changed files with 431 additions and 237 deletions
@@ -20,6 +20,7 @@ import { getAppVersionById } from '../../../../../../app/version/controller';
import { MCPClient } from '../../../../../../app/mcp';
import { runHTTPTool } from '../../../../../../app/http';
import { getS3ChatSource } from '../../../../../../../common/s3/sources/chat';
import { parseToolId } from '../../../../child/runTool';
type SystemInputConfigType = {
type: SystemToolSecretInputTypeEnum;
@@ -175,8 +176,7 @@ export const dispatchTool = async ({
]
};
} else if (toolConfig?.mcpTool?.toolId) {
const { pluginId } = splitCombineToolId(toolConfig.mcpTool.toolId);
const [parentId, toolSetName, toolName] = pluginId.split('/');
const { parentId, toolName } = parseToolId(toolConfig.mcpTool.toolId);
const tool = await getAppVersionById({
appId: parentId,
versionId: version
@@ -203,8 +203,7 @@ export const dispatchTool = async ({
usages: []
};
} else if (toolConfig?.httpTool?.toolId) {
const { pluginId } = splitCombineToolId(toolConfig.httpTool.toolId);
const [parentId, toolSetName, toolName] = pluginId.split('/');
const { parentId, toolName } = parseToolId(toolConfig.httpTool.toolId);
if (!parentId || !toolName) {
return Promise.reject(`Invalid HTTP tool id: ${toolConfig.httpTool.toolId}`);
}
@@ -25,7 +25,7 @@ import type { SubAppInitType } from '../type';
import { getToolConfigStatus } from '@fastgpt/global/core/app/formEdit/utils';
import { getLogger, LogCategories } from '../../../../../../../common/logger';
export const agentSkillToToolRuntime = async ({
export const getAgentRuntimeTools = async ({
tools,
tmbId,
lang
@@ -195,18 +195,15 @@ export const agentSkillToToolRuntime = async ({
if (!app) return [];
const toolList = await getMCPChildren(app);
const toolSetId = mcpToolsetVal.toolId ?? toolNode.pluginId;
const toolSetId = mcpToolsetVal.toolId || toolNode.pluginId;
const children = toolList.map((tool, index) => {
const newToolNode = getMCPToolRuntimeNode({
toolSetId,
toolsetName: toolNode.name,
nodeId: `${toolSetId}${index}`,
avatar: toolNode.avatar,
tool: {
...tool,
name: `${toolNode.name}/${tool.name}`
}
tool
});
return newToolNode;
});
@@ -232,15 +229,12 @@ export const agentSkillToToolRuntime = async ({
} else if (httpToolsetVal) {
const children = httpToolsetVal.toolList.map((tool: HttpToolConfigType, index) => {
const newToolNode = getHTTPToolRuntimeNode({
tool: {
...tool,
name: `${toolNode.name}/${tool.name}`
},
tool,
nodeId: `${pluginId}${index}`,
avatar: toolNode.avatar,
toolSetId: pluginId
toolSetId: pluginId,
toolsetName: toolNode.name
});
return newToolNode;
});
@@ -2,7 +2,7 @@ import type { localeType } from '@fastgpt/global/common/i18n/type';
import type { SkillToolType } from '@fastgpt/global/core/ai/skill/type';
import type { ChatCompletionTool } from '@fastgpt/global/core/ai/type';
import type { SubAppRuntimeType } from './type';
import { agentSkillToToolRuntime } from './sub/tool/utils';
import { getAgentRuntimeTools } from './sub/tool/utils';
import { readFileTool } from './sub/file/utils';
import { PlanAgentTool } from './sub/plan/constants';
import { datasetSearchTool } from './sub/dataset/utils';
@@ -43,7 +43,7 @@ export const getSubapps = async ({
}
/* System tool */
const formatTools = await agentSkillToToolRuntime({
const formatTools = await getAgentRuntimeTools({
tools,
tmbId,
lang
@@ -19,7 +19,6 @@ import { getSystemToolById } from '../../../app/tool/controller';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { pushTrack } from '../../../../common/middle/tracks/utils';
import { getNodeErrResponse } from '../utils';
import { splitCombineToolId } from '@fastgpt/global/core/app/tool/utils';
import { getAppVersionById } from '../../../../core/app/version/controller';
import { runHTTPTool } from '../../../app/http';
import { getS3ChatSource } from '../../../../common/s3/sources/chat';
@@ -207,8 +206,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
};
} else if (toolConfig?.mcpTool?.toolId) {
// pluginId: toolSetAppId/toolsetName/toolName
const { pluginId } = splitCombineToolId(toolConfig.mcpTool.toolId);
const [parentId, toolSetName, toolName] = pluginId.split('/');
const { parentId, toolName } = parseToolId(toolConfig.mcpTool.toolId);
const tool = await getAppVersionById({
appId: parentId,
versionId: version
@@ -241,8 +239,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
[DispatchNodeResponseKeyEnum.toolResponses]: result
};
} else if (toolConfig?.httpTool?.toolId) {
const { pluginId } = splitCombineToolId(toolConfig.httpTool.toolId);
const [parentId, toolSetName, toolName] = pluginId.split('/');
const { parentId, toolName } = parseToolId(toolConfig.httpTool.toolId);
const toolset = await getAppVersionById({
appId: parentId,
versionId: version
@@ -347,3 +344,16 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
});
}
};
export const parseToolId = (id: string) => {
const formatId = id.split('-').slice(1).join('-');
const [parentId, toolsetNameOrToolName, legacyToolName] = formatId.split('/');
if (legacyToolName) {
// 旧版格式: source-appId/toolsetName/toolName
return { parentId, toolName: legacyToolName };
}
// 新版格式: source-appId/toolName
return { parentId, toolName: toolsetNameOrToolName };
};
@@ -14,18 +14,22 @@ import {
storeNodes2RuntimeNodes
} from '@fastgpt/global/core/workflow/runtime/utils';
import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { authPluginByTmbId } from '../../../../support/permission/app/auth';
import { authWorkflowToolByTmbId } from '../../../../support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { computedAppToolUsage } from '../../../app/tool/runtime/utils';
import { filterSystemVariables, getNodeErrResponse } from '../utils';
import { serverGetWorkflowToolRunUserQuery } from '../../../app/tool/workflowTool/utils';
import type { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { getChildAppRuntimeById } from '../../../app/tool/controller';
import {
type NodeInputKeyEnum,
type NodeOutputKeyEnum
} from '@fastgpt/global/core/workflow/constants';
import { runWorkflow } from '../index';
import { getUserChatInfo } from '../../../../support/user/team/utils';
import { dispatchRunTool } from '../child/runTool';
import type { AppToolRuntimeType } from '@fastgpt/global/core/app/tool/type';
import { anyValueDecrypt } from '../../../../common/secret/utils';
import { getAppVersionById } from '../../../app/version/controller';
import { parseI18nString } from '@fastgpt/global/common/i18n/utils';
type RunPluginProps = ModuleDispatchProps<{
[NodeInputKeyEnum.forbidStream]?: boolean;
@@ -51,7 +55,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
return getNodeErrResponse({ error: 'pluginId can not find' });
}
let plugin: AppToolRuntimeType | undefined;
let workflowTool: AppToolRuntimeType | undefined;
try {
// Adapt <= 4.10 system tool
@@ -70,33 +74,55 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
});
}
if (source !== AppToolSourceEnum.commercial && source !== AppToolSourceEnum.personal) {
return getNodeErrResponse({ error: 'pluginId can not find' });
}
/*
1. Team app
2. Admin selected system tool
*/
const { files } = chatValue2RuntimePrompt(query);
// auth plugin
const pluginData = await authPluginByTmbId({
appId: pluginId,
// auth workflowTool
const toolData = await authWorkflowToolByTmbId({
appId: formatPluginId,
tmbId: runningAppInfo.tmbId,
per: ReadPermissionVal
});
plugin = await getChildAppRuntimeById({ id: pluginId, versionId: version });
const toolVersion = await getAppVersionById({
appId: toolData._id,
versionId: version,
app: toolData
});
workflowTool = {
id: String(toolData._id),
teamId: toolData.teamId,
tmbId: toolData.tmbId,
name: parseI18nString(toolData.name, props.lang),
avatar: toolData.avatar || '',
showStatus: true,
currentCost: 0,
systemKeyCost: 0,
nodes: toolVersion.nodes,
edges: toolVersion.edges,
hasTokenFee: false
};
const outputFilterMap =
plugin.nodes
workflowTool.nodes
.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginOutput)
?.inputs.reduce<Record<string, boolean>>((acc, cur) => {
acc[cur.key] = cur.isToolOutput === false ? false : true;
return acc;
}, {}) ?? {};
const runtimeNodes = storeNodes2RuntimeNodes(
plugin.nodes,
getWorkflowEntryNodeIds(plugin.nodes)
workflowTool.nodes,
getWorkflowEntryNodeIds(workflowTool.nodes)
).map((node) => {
// Update plugin input value
// Update workflowTool input value
if (node.flowNodeType === FlowNodeTypeEnum.pluginInput) {
return {
...node,
@@ -129,7 +155,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
const { externalProvider } = await getUserChatInfo(runningAppInfo.tmbId);
const runtimeVariables = {
...filterSystemVariables(props.variables),
appId: String(plugin.id),
appId: String(workflowTool.id),
...(externalProvider ? externalProvider.externalWorkflowVariables : {})
};
const {
@@ -150,27 +176,27 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
}
: {}),
runningAppInfo: {
id: String(plugin.id),
name: plugin.name,
id: String(workflowTool.id),
name: workflowTool.name,
// 如果系统插件有 teamId 和 tmbId,则使用系统插件的 teamId 和 tmbId(管理员指定了插件作为系统插件)
teamId: plugin.teamId || runningAppInfo.teamId,
tmbId: plugin.tmbId || runningAppInfo.tmbId,
teamId: workflowTool.teamId || runningAppInfo.teamId,
tmbId: workflowTool.tmbId || runningAppInfo.tmbId,
isChildApp: true
},
variables: runtimeVariables,
query: serverGetWorkflowToolRunUserQuery({
pluginInputs: getWorkflowToolInputsFromStoreNodes(plugin.nodes),
pluginInputs: getWorkflowToolInputsFromStoreNodes(workflowTool.nodes),
variables: runtimeVariables,
files
}).value,
chatConfig: {},
runtimeNodes,
runtimeEdges: storeEdges2RuntimeEdges(plugin.edges)
runtimeEdges: storeEdges2RuntimeEdges(workflowTool.edges)
});
const output = flowResponses.find((item) => item.moduleType === FlowNodeTypeEnum.pluginOutput);
const usagePoints = await computedAppToolUsage({
plugin,
plugin: workflowTool,
childrenUsage: flowUsages,
error: !!output?.pluginOutput?.error
});
@@ -182,20 +208,17 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
// responseData, // debug
[DispatchNodeResponseKeyEnum.runTimes]: runTimes,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
moduleLogo: plugin.avatar,
moduleLogo: workflowTool.avatar,
totalPoints: usagePoints,
toolInput: data,
pluginOutput: output?.pluginOutput,
pluginDetail: pluginData?.permission?.hasWritePer // Not system plugin
? flowResponses.filter((item) => {
const filterArr = [FlowNodeTypeEnum.pluginOutput];
return !filterArr.includes(item.moduleType as any);
})
pluginDetail: toolData?.permission?.hasWritePer // Not system workflowTool
? flowResponses
: undefined
},
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{
moduleName: plugin.name,
moduleName: workflowTool.name,
totalPoints: usagePoints
}
],
@@ -212,7 +235,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
} catch (error) {
return getNodeErrResponse({
error,
[DispatchNodeResponseKeyEnum.nodeResponse]: { moduleLogo: plugin?.avatar }
[DispatchNodeResponseKeyEnum.nodeResponse]: { moduleLogo: workflowTool?.avatar }
});
}
};
@@ -332,34 +332,29 @@ export const rewriteRuntimeWorkFlow = async ({
if (!app) continue;
const toolList = await getMCPChildren(app);
// mcpToolsetVal.toolId-旧版 MCP
const toolSetId = mcpToolsetVal.toolId ?? toolSetNode.pluginId;
// mcpToolsetVal.toolId: 旧版 MCP
const toolSetId = mcpToolsetVal.toolId || toolSetNode.pluginId;
toolList.forEach((tool, index) => {
const newToolNode = getMCPToolRuntimeNode({
nodeId: `${toolSetNode.nodeId}${index}`,
toolSetId,
toolsetName: toolSetNode.name,
avatar: toolSetNode.avatar,
tool: {
...tool,
name: `${toolSetNode.name}/${tool.name}`
}
tool
});
nodes.push(newToolNode);
pushEdges(newToolNode.nodeId);
});
} else if (httpToolsetVal) {
httpToolsetVal.toolList.forEach((tool: HttpToolConfigType, index: number) => {
const newToolNode = getHTTPToolRuntimeNode({
tool: {
...tool,
name: `${toolSetNode.name}/${tool.name}`
},
tool,
nodeId: `${toolSetNode.nodeId}${index}`,
avatar: toolSetNode.avatar,
toolSetId: toolSetNode.pluginId!
toolSetId: toolSetNode.pluginId!,
toolsetName: toolSetNode.name
});
nodes.push(newToolNode);
pushEdges(newToolNode.nodeId);
});