From b75e807f249daf134cc0d325d36e44b57ce2bec7 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Tue, 31 Dec 2024 10:58:52 +0800 Subject: [PATCH] fix: tool choice run same tool will error (#3502) --- .../dispatch/agent/runTool/toolChoice.ts | 143 +++++++++++------- .../service/core/workflow/dispatch/index.ts | 7 +- 2 files changed, 90 insertions(+), 60 deletions(-) diff --git a/packages/service/core/workflow/dispatch/agent/runTool/toolChoice.ts b/packages/service/core/workflow/dispatch/agent/runTool/toolChoice.ts index ae8a1331bfe..2c20b790be5 100644 --- a/packages/service/core/workflow/dispatch/agent/runTool/toolChoice.ts +++ b/packages/service/core/workflow/dispatch/agent/runTool/toolChoice.ts @@ -27,9 +27,10 @@ import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants'; import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; +import { getErrText } from '@fastgpt/global/common/error/utils'; type ToolRunResponseType = { - toolRunResponse: DispatchFlowResponse; + toolRunResponse?: DispatchFlowResponse; toolMsgParams: ChatCompletionToolMessageParam; }[]; @@ -344,59 +345,87 @@ export const runToolWithToolChoice = async ( return Promise.reject(getEmptyResponseTip()); } - // Run the selected tool by LLM. - const toolsRunResponse = ( - await Promise.all( - toolCalls.map(async (tool) => { - const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name); + /* Run the selected tool by LLM. + Since only reference parameters are passed, if the same tool is run in parallel, it will get the same run parameters + */ + const toolsRunResponse: ToolRunResponseType = []; + for await (const tool of toolCalls) { + try { + const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name); + + if (!toolNode) continue; + + const startParams = (() => { + try { + return json5.parse(tool.function.arguments); + } catch (error) { + return {}; + } + })(); - if (!toolNode) return; + initToolNodes(runtimeNodes, [toolNode.nodeId], startParams); + const toolRunResponse = await dispatchWorkFlow({ + ...workflowProps, + isToolCall: true + }); - const startParams = (() => { - try { - return json5.parse(tool.function.arguments); - } catch (error) { - return {}; - } - })(); + const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses); - initToolNodes(runtimeNodes, [toolNode.nodeId], startParams); - const toolRunResponse = await dispatchWorkFlow({ - ...workflowProps, - isToolCall: true - }); + const toolMsgParams: ChatCompletionToolMessageParam = { + tool_call_id: tool.id, + role: ChatCompletionRequestMessageRoleEnum.Tool, + name: tool.function.name, + content: stringToolResponse + }; - const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses); + workflowStreamResponse?.({ + event: SseResponseEventEnum.toolResponse, + data: { + tool: { + id: tool.id, + toolName: '', + toolAvatar: '', + params: '', + response: sliceStrStartEnd(stringToolResponse, 5000, 5000) + } + } + }); - const toolMsgParams: ChatCompletionToolMessageParam = { + toolsRunResponse.push({ + toolRunResponse, + toolMsgParams + }); + } catch (error) { + const err = getErrText(error); + workflowStreamResponse?.({ + event: SseResponseEventEnum.toolResponse, + data: { + tool: { + id: tool.id, + toolName: '', + toolAvatar: '', + params: '', + response: sliceStrStartEnd(err, 5000, 5000) + } + } + }); + + toolsRunResponse.push({ + toolRunResponse: undefined, + toolMsgParams: { tool_call_id: tool.id, role: ChatCompletionRequestMessageRoleEnum.Tool, name: tool.function.name, - content: stringToolResponse - }; - - workflowStreamResponse?.({ - event: SseResponseEventEnum.toolResponse, - data: { - tool: { - id: tool.id, - toolName: '', - toolAvatar: '', - params: '', - response: sliceStrStartEnd(stringToolResponse, 5000, 5000) - } - } - }); - - return { - toolRunResponse, - toolMsgParams - }; - }) - ) - ).filter(Boolean) as ToolRunResponseType; + content: sliceStrStartEnd(err, 5000, 5000) + } + }); + } + } - const flatToolsResponseData = toolsRunResponse.map((item) => item.toolRunResponse).flat(); + const flatToolsResponseData = toolsRunResponse + .map((item) => item.toolRunResponse) + .flat() + .filter(Boolean) as DispatchFlowResponse[]; // concat tool responses const dispatchFlowResponse = response ? response.dispatchFlowResponse.concat(flatToolsResponseData) @@ -434,22 +463,22 @@ export const runToolWithToolChoice = async ( const outputTokens = await countGptMessagesTokens(assistantToolMsgParams); /* - ... - user - assistant: tool data - tool: tool response - */ + ... + user + assistant: tool data + tool: tool response + */ const completeMessages = [ ...concatToolMessages, ...toolsRunResponse.map((item) => item?.toolMsgParams) ]; /* - Get tool node assistant response - history assistant - current tool assistant - tool child assistant - */ + Get tool node assistant response + history assistant + current tool assistant + tool child assistant + */ const toolNodeAssistant = GPTMessages2Chats([ ...assistantToolMsgParams, ...toolsRunResponse.map((item) => item?.toolMsgParams) @@ -478,12 +507,12 @@ export const runToolWithToolChoice = async ( ); // Check interactive response(Only 1 interaction is reserved) const workflowInteractiveResponseItem = toolsRunResponse.find( - (item) => item.toolRunResponse.workflowInteractiveResponse + (item) => item.toolRunResponse?.workflowInteractiveResponse ); if (hasStopSignal || workflowInteractiveResponseItem) { // Get interactive tool data const workflowInteractiveResponse = - workflowInteractiveResponseItem?.toolRunResponse.workflowInteractiveResponse; + workflowInteractiveResponseItem?.toolRunResponse?.workflowInteractiveResponse; // Flashback traverses completeMessages, intercepting messages that know the first user const firstUserIndex = completeMessages.findLastIndex((item) => item.role === 'user'); diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index e47c57e3307..c482c38ae70 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -72,6 +72,7 @@ import { dispatchLoopEnd } from './loop/runLoopEnd'; import { dispatchLoopStart } from './loop/runLoopStart'; import { dispatchFormInput } from './interactive/formInput'; import { dispatchToolParams } from './agent/runTool/toolParams'; +import { getErrText } from '@fastgpt/global/common/error/utils'; const callbackMap: Record = { [FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart, @@ -231,9 +232,7 @@ export async function dispatchWorkFlow(data: Props): Promise item.source === node.nodeId); const skipHandleIds = targetEdges.map((item) => item.sourceHandle); + toolRunResponse = getErrText(error); + // Skip all edges and return error return { [DispatchNodeResponseKeyEnum.nodeResponse]: {