From 24cf32f8b0c88de0c46fe690943961abf2fe46c3 Mon Sep 17 00:00:00 2001 From: archer <545436317@qq.com> Date: Tue, 9 Sep 2025 20:24:43 +0800 Subject: [PATCH] perf: sub agent output --- .../core/workflow/dispatch/ai/agent/index.ts | 39 ++- .../dispatch/ai/agent/sub/app/index.ts | 280 ++++++++++-------- .../service/core/workflow/dispatch/utils.ts | 12 + .../core/chat/ChatContainer/ChatBox/index.tsx | 1 + .../detail/Edit/component/ConfigToolModal.tsx | 7 +- 5 files changed, 199 insertions(+), 140 deletions(-) diff --git a/packages/service/core/workflow/dispatch/ai/agent/index.ts b/packages/service/core/workflow/dispatch/ai/agent/index.ts index b1efa2a8d5..e8ec2a45cb 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/index.ts @@ -9,7 +9,7 @@ import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; import { getLLMModel } from '../../../../ai/model'; -import { getNodeErrResponse, getHistories } from '../../utils'; +import { getNodeErrResponse, getHistories, getWorkflowChildResponseWrite } from '../../utils'; import { runAgentCall } from '../../../../ai/llm/agentCall'; import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type'; import { type ChatItemType } from '@fastgpt/global/core/chat/type'; @@ -48,7 +48,7 @@ import { getMasterAgentDefaultPrompt } from './constants'; import { addFilePrompt2Input, getFileInputPrompt } from './sub/file/utils'; import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type'; import { dispatchFileRead } from './sub/file'; -import { dispatchApp } from './sub/app'; +import { dispatchApp, dispatchPlugin } from './sub/app'; export type DispatchAgentModuleProps = ModuleDispatchProps<{ [NodeInputKeyEnum.history]?: ChatItemType[]; @@ -252,6 +252,10 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise handleToolResponse: async ({ call, messages }) => { const toolId = call.function.name; + const childWorkflowStreamResponse = getWorkflowChildResponseWrite({ + id: call.id, + fn: workflowStreamResponse + }); const { response, @@ -276,8 +280,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise stream, onStreaming({ text }) { //TODO: 需要一个新的 plan sse event - workflowStreamResponse?.({ - id: call.id, + childWorkflowStreamResponse?.({ event: SseResponseEventEnum.toolResponse, data: { tool: { @@ -312,8 +315,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise systemPrompt, task, onStreaming({ text }) { - workflowStreamResponse?.({ - id: call.id, + childWorkflowStreamResponse?.({ event: SseResponseEventEnum.toolResponse, data: { tool: { @@ -419,18 +421,32 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise runningUserInfo, runningAppInfo, variables, - workflowStreamResponse + workflowStreamResponse: childWorkflowStreamResponse }); return { response, usages, isEnd: false }; - } else if (node.flowNodeType === FlowNodeTypeEnum.appModule) { - const { response, usages } = await dispatchApp({ + } else if ( + node.flowNodeType === FlowNodeTypeEnum.appModule || + node.flowNodeType === FlowNodeTypeEnum.pluginModule + ) { + const fn = + node.flowNodeType === FlowNodeTypeEnum.appModule ? dispatchApp : dispatchPlugin; + console.log(requestParams, 22); + const { response, usages } = await fn({ ...props, - node + node, + // stream: false, + workflowStreamResponse: undefined, + callParams: { + appId: node.pluginId, + version: node.version, + ...requestParams + } }); + return { response, usages, @@ -455,8 +471,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise // Push stream response if (streamResponse) { - workflowStreamResponse?.({ - id: call.id, + childWorkflowStreamResponse?.({ event: SseResponseEventEnum.toolResponse, data: { tool: { diff --git a/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts index 2522a9112c..eef1c67bfd 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts @@ -1,17 +1,11 @@ import type { DispatchSubAppResponse } from '../../type'; import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; -import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import type { ChatItemType } from '@fastgpt/global/core/chat/type'; -import { - filterSystemVariables, - getHistories -} from '../../../../../../../core/workflow/dispatch/utils'; +import { filterSystemVariables } from '../../../../../../../core/workflow/dispatch/utils'; import { authAppByTmbId } from '../../../../../../../support/permission/app/auth'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { getAppVersionById } from '../../../../../../../core/app/version/controller'; import { getRunningUserInfoByTmbId } from '../../../../../../../support/user/team/utils'; import { runWorkflow } from '../../../../../../../core/workflow/dispatch'; -import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { getWorkflowEntryNodeIds, rewriteNodeOutputByHistories, @@ -19,145 +13,181 @@ import { storeNodes2RuntimeNodes, textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; -import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/global/core/chat/adapt'; +import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt'; import { getUserChatInfoAndAuthTeamPoints } from '../../../../../../../support/permission/auth/team'; -import { parseUrlToFileType } from '@fastgpt/global/common/file/tools'; +import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; +import { getChildAppRuntimeById } from '../../../../../../app/plugin/controller'; +import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; +import { getPluginRunUserQuery } from '@fastgpt/global/core/workflow/utils'; +import { getPluginInputsFromStoreNodes } from '@fastgpt/global/core/app/plugin/utils'; -export type appConfigType = { - variables: Record; +type Props = ModuleDispatchProps<{}> & { + callParams: { + appId?: string; + version?: string; + [key: string]: any; + }; }; -type Props = ModuleDispatchProps<{ - [NodeInputKeyEnum.userChatInput]: string; - [NodeInputKeyEnum.history]?: ChatItemType[] | number; - [NodeInputKeyEnum.fileUrlList]?: string[]; - [NodeInputKeyEnum.forbidStream]?: boolean; - [NodeInputKeyEnum.fileUrlList]?: string[]; -}>; - export const dispatchApp = async (props: Props): Promise => { const { runningAppInfo, - histories, - query, - lastInteractive, - node: { pluginId: appId, version }, workflowStreamResponse, - params, variables, - stream - } = props; - const { - system_forbid_stream = false, - userChatInput, - history, - fileUrlList, - ...childrenAppVariables - } = params; - - const { files } = chatValue2RuntimePrompt(query); - - const userInputFiles = (() => { - if (fileUrlList) { - return fileUrlList.map((url) => parseUrlToFileType(url)).filter(Boolean); - } - // Adapt version 4.8.13 upgrade - return files; - })(); - - if (!userChatInput && !userInputFiles) { - return Promise.reject(new Error('Input is empty')); - } - if (!appId) { - return Promise.reject(new Error('pluginId is empty')); - } - - try { - // Auth the app by tmbId(Not the user, but the workflow user) - const { app: appData } = await authAppByTmbId({ - appId: appId, - tmbId: runningAppInfo.tmbId, - per: ReadPermissionVal - }); - const { nodes, edges, chatConfig } = await getAppVersionById({ + callParams: { appId, - versionId: version, - app: appData - }); - - const childStreamResponse = system_forbid_stream ? false : stream; - // Auto line - if (childStreamResponse) { - workflowStreamResponse?.({ - event: SseResponseEventEnum.answer, - data: textAdaptGptResponse({ - text: '\n' - }) - }); + version, + userChatInput, + system_forbid_stream, + history, + fileUrlList, + ...data } + } = props; - const chatHistories = getHistories(history, histories); + if (!appId) { + return Promise.reject(new Error('AppId is empty')); + } - // Rewrite children app variables - const systemVariables = filterSystemVariables(variables); - const { externalProvider } = await getUserChatInfoAndAuthTeamPoints(appData.tmbId); - const childrenRunVariables = { - ...systemVariables, - ...childrenAppVariables, - histories: chatHistories, - appId: String(appData._id), - ...(externalProvider ? externalProvider.externalWorkflowVariables : {}) + // Auth the app by tmbId(Not the user, but the workflow user) + const { app: appData } = await authAppByTmbId({ + appId, + tmbId: runningAppInfo.tmbId, + per: ReadPermissionVal + }); + const { nodes, edges, chatConfig } = await getAppVersionById({ + appId, + versionId: version, + app: appData + }); + + // Rewrite children app variables + const systemVariables = filterSystemVariables(variables); + const { externalProvider } = await getUserChatInfoAndAuthTeamPoints(appData.tmbId); + const childrenRunVariables = { + ...systemVariables, + histories: [], + appId: String(appData._id), + ...data, + ...(externalProvider ? externalProvider.externalWorkflowVariables : {}) + }; + + const runtimeNodes = rewriteNodeOutputByHistories( + storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes)) + ); + const runtimeEdges = storeEdges2RuntimeEdges(edges); + + const { assistantResponses, flowUsages } = await runWorkflow({ + ...props, + runningAppInfo: { + id: String(appData._id), + teamId: String(appData.teamId), + tmbId: String(appData.tmbId), + isChildApp: true + }, + runningUserInfo: await getRunningUserInfoByTmbId(appData.tmbId), + runtimeNodes, + runtimeEdges, + histories: [], + variables: childrenRunVariables, + query: [ + { + type: ChatItemValueTypeEnum.text, + text: { + content: userChatInput + } + } + ], + chatConfig + }); + + const { text } = chatValue2RuntimePrompt(assistantResponses); + + return { + response: text, + usages: flowUsages + }; +}; + +export const dispatchPlugin = async (props: Props): Promise => { + const { + runningAppInfo, + callParams: { appId, version, system_forbid_stream, ...data } + } = props; + + if (!appId) { + return Promise.reject(new Error('AppId is empty')); + } + + // Auth the app by tmbId(Not the user, but the workflow user) + const { + app: { tmbId } + } = await authAppByTmbId({ + appId, + tmbId: runningAppInfo.tmbId, + per: ReadPermissionVal + }); + const plugin = await getChildAppRuntimeById({ id: appId, versionId: version }); + + const outputFilterMap = + plugin.nodes + .find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginOutput) + ?.inputs.reduce>((acc, cur) => { + acc[cur.key] = cur.isToolOutput === false ? false : true; + return acc; + }, {}) ?? {}; + const runtimeNodes = storeNodes2RuntimeNodes( + plugin.nodes, + getWorkflowEntryNodeIds(plugin.nodes) + ).map((node) => { + // Update plugin input value + if (node.flowNodeType === FlowNodeTypeEnum.pluginInput) { + return { + ...node, + showStatus: false, + inputs: node.inputs.map((input) => ({ + ...input, + value: data[input.key] ?? input.value + })) + }; + } + return { + ...node, + showStatus: false }; + }); - const childrenInteractive = - lastInteractive?.type === 'childrenInteractive' - ? lastInteractive.params.childrenResponse - : undefined; - const runtimeNodes = rewriteNodeOutputByHistories( - storeNodes2RuntimeNodes( - nodes, - getWorkflowEntryNodeIds(nodes, childrenInteractive || undefined) - ), - childrenInteractive - ); + const { externalProvider } = await getUserChatInfoAndAuthTeamPoints(tmbId); + const runtimeVariables = { + ...filterSystemVariables(props.variables), + appId: String(plugin.id), + ...(externalProvider ? externalProvider.externalWorkflowVariables : {}) + }; - const runtimeEdges = storeEdges2RuntimeEdges(edges, childrenInteractive); - const theQuery = childrenInteractive - ? query - : runtimePrompt2ChatsValue({ files: userInputFiles, text: userChatInput }); - - const { assistantResponses } = await runWorkflow({ + const { flowResponses, flowUsages, assistantResponses, runTimes, system_memories } = + await runWorkflow({ ...props, - lastInteractive: childrenInteractive, - // Rewrite stream mode - ...(system_forbid_stream - ? { - stream: false, - workflowStreamResponse: undefined - } - : {}), runningAppInfo: { - id: String(appData._id), - teamId: String(appData.teamId), - tmbId: String(appData.tmbId), + id: String(plugin.id), + // 如果系统插件有 teamId 和 tmbId,则使用系统插件的 teamId 和 tmbId(管理员指定了插件作为系统插件) + teamId: plugin.teamId || runningAppInfo.teamId, + tmbId: plugin.tmbId || runningAppInfo.tmbId, isChildApp: true }, - runningUserInfo: await getRunningUserInfoByTmbId(appData.tmbId), + variables: runtimeVariables, + query: getPluginRunUserQuery({ + pluginInputs: getPluginInputsFromStoreNodes(plugin.nodes), + variables: runtimeVariables + }).value, + chatConfig: {}, runtimeNodes, - runtimeEdges, - histories: chatHistories, - variables: childrenRunVariables, - query: theQuery, - chatConfig + runtimeEdges: storeEdges2RuntimeEdges(plugin.edges) }); + const output = flowResponses.find((item) => item.moduleType === FlowNodeTypeEnum.pluginOutput); + const response = output?.pluginOutput ? JSON.stringify(output?.pluginOutput) : 'No output'; - const { text } = chatValue2RuntimePrompt(assistantResponses); - - return { - response: text, - usages: [] - }; - } catch (error) { - return Promise.reject('dispatch app failed'); - } + return { + response, + usages: flowUsages + }; }; diff --git a/packages/service/core/workflow/dispatch/utils.ts b/packages/service/core/workflow/dispatch/utils.ts index b1629fc4d7..409b8317dd 100644 --- a/packages/service/core/workflow/dispatch/utils.ts +++ b/packages/service/core/workflow/dispatch/utils.ts @@ -70,6 +70,18 @@ export const getWorkflowResponseWrite = ({ }; return fn; }; +export const getWorkflowChildResponseWrite = ({ + id, + fn +}: { + id: string; + fn?: WorkflowResponseType; +}): WorkflowResponseType | undefined => { + if (!fn) return; + return (e: Parameters[0]) => { + return fn({ ...e, id }); + }; +}; export const filterToolNodeIdByEdges = ({ nodeId, diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx index e3c86e118b..f3bbc6ce11 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx @@ -324,6 +324,7 @@ const ChatBox = ({ } } else if (event === SseResponseEventEnum.toolCall && tool) { const val: AIChatItemValueItemType = { + id: responseValueId, type: ChatItemValueTypeEnum.tool, tools: [tool] }; diff --git a/projects/app/src/pageComponents/app/detail/Edit/component/ConfigToolModal.tsx b/projects/app/src/pageComponents/app/detail/Edit/component/ConfigToolModal.tsx index 37e0ec944f..2e78731f66 100644 --- a/projects/app/src/pageComponents/app/detail/Edit/component/ConfigToolModal.tsx +++ b/projects/app/src/pageComponents/app/detail/Edit/component/ConfigToolModal.tsx @@ -20,6 +20,7 @@ import SecretInputModal, { } from '@/pageComponents/app/tool/SecretInputModal'; import { SystemToolSecretInputTypeMap } from '@fastgpt/global/core/app/tool/systemTool/constants'; import { useBoolean } from 'ahooks'; +import { useSafeTranslation } from '@fastgpt/web/hooks/useSafeTranslation'; const ConfigToolModal = ({ configTool, @@ -30,7 +31,7 @@ const ConfigToolModal = ({ onCloseConfigTool: () => void; onAddTool: (tool: AppFormEditFormType['selectedTools'][number]) => void; }) => { - const { t } = useTranslation(); + const { t } = useSafeTranslation(); const [isOpenSecretModal, { setTrue: setTrueSecretModal, setFalse: setFalseSecretModal }] = useBoolean(false); @@ -86,8 +87,8 @@ const ConfigToolModal = ({ {input.required && *} - {input.label} - {input.description && } + {t(input.label)} + {input.description && } {input.key === NodeInputKeyEnum.systemInputConfig && input.inputList ? (