diff --git a/packages/components/nodes/agentflow/Agent/Agent.ts b/packages/components/nodes/agentflow/Agent/Agent.ts index 500b8e3d9..38af93505 100644 --- a/packages/components/nodes/agentflow/Agent/Agent.ts +++ b/packages/components/nodes/agentflow/Agent/Agent.ts @@ -1676,7 +1676,12 @@ class Agent_Agentflow implements INode { const sseStreamer: IServerSideEventStreamer = options.sseStreamer as IServerSideEventStreamer if (response.tool_calls) { - sseStreamer.streamCalledToolsEvent(chatId, response.tool_calls) + const formattedToolCalls = response.tool_calls.map((toolCall: any) => ({ + tool: toolCall.name || 'tool', + toolInput: toolCall.args, + toolOutput: '' + })) + sseStreamer.streamCalledToolsEvent(chatId, flatten(formattedToolCalls)) } if (response.usage_metadata) { @@ -1736,7 +1741,12 @@ class Agent_Agentflow implements INode { // Stream tool calls if available if (sseStreamer) { - sseStreamer.streamCalledToolsEvent(chatId, JSON.stringify(response.tool_calls)) + const formattedToolCalls = response.tool_calls.map((toolCall: any) => ({ + tool: toolCall.name || 'tool', + toolInput: toolCall.args, + toolOutput: '' + })) + sseStreamer.streamCalledToolsEvent(chatId, flatten(formattedToolCalls)) } // Remove tool calls with no id @@ -2045,7 +2055,12 @@ class Agent_Agentflow implements INode { // Stream tool calls if available if (sseStreamer) { - sseStreamer.streamCalledToolsEvent(chatId, JSON.stringify(response.tool_calls)) + const formattedToolCalls = response.tool_calls.map((toolCall: any) => ({ + tool: toolCall.name || 'tool', + toolInput: toolCall.args, + toolOutput: '' + })) + sseStreamer.streamCalledToolsEvent(chatId, flatten(formattedToolCalls)) } // Remove tool calls with no id diff --git a/packages/components/nodes/agentflow/LLM/LLM.ts b/packages/components/nodes/agentflow/LLM/LLM.ts index 8ad1d2aae..9b434f268 100644 --- a/packages/components/nodes/agentflow/LLM/LLM.ts +++ b/packages/components/nodes/agentflow/LLM/LLM.ts @@ -13,6 +13,7 @@ import { updateFlowState } from '../utils' import { processTemplateVariables } from '../../../src/utils' +import { flatten } from 'lodash' class LLM_Agentflow implements INode { label: string @@ -892,7 +893,12 @@ class LLM_Agentflow implements INode { const sseStreamer: IServerSideEventStreamer = options.sseStreamer as IServerSideEventStreamer if (response.tool_calls) { - sseStreamer.streamCalledToolsEvent(chatId, response.tool_calls) + const formattedToolCalls = response.tool_calls.map((toolCall: any) => ({ + tool: toolCall.name || 'tool', + toolInput: toolCall.args, + toolOutput: '' + })) + sseStreamer.streamCalledToolsEvent(chatId, flatten(formattedToolCalls)) } if (response.usage_metadata) { diff --git a/packages/ui/src/views/chatmessage/ChatMessage.jsx b/packages/ui/src/views/chatmessage/ChatMessage.jsx index d0211f78c..08ca5f27c 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.jsx +++ b/packages/ui/src/views/chatmessage/ChatMessage.jsx @@ -687,11 +687,57 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP setMessages((prevMessages) => { let allMessages = [...cloneDeep(prevMessages)] if (allMessages[allMessages.length - 1].type === 'userMessage') return allMessages + + // When usedTools are received, check if there are matching calledTools to replace + const lastMessage = allMessages[allMessages.length - 1] + if (lastMessage.calledTools && lastMessage.calledTools.length > 0) { + // Replace calledTools with usedTools for matching tool names + const updatedCalledTools = lastMessage.calledTools.map((calledTool) => { + const matchingUsedTool = usedTools.find((usedTool) => usedTool.tool === calledTool.tool) + return matchingUsedTool || calledTool + }) + + // Remove calledTools that have been replaced by usedTools + const remainingCalledTools = updatedCalledTools.filter( + (calledTool) => !usedTools.some((usedTool) => usedTool.tool === calledTool.tool) + ) + + allMessages[allMessages.length - 1].calledTools = remainingCalledTools.length > 0 ? remainingCalledTools : undefined + } + allMessages[allMessages.length - 1].usedTools = usedTools return allMessages }) } + const updateLastMessageCalledTools = (calledTools) => { + setMessages((prevMessages) => { + let allMessages = [...cloneDeep(prevMessages)] + if (allMessages[allMessages.length - 1].type === 'userMessage') return allMessages + allMessages[allMessages.length - 1].calledTools = calledTools + return allMessages + }) + } + + const cleanupCalledTools = () => { + setMessages((prevMessages) => { + let allMessages = [...cloneDeep(prevMessages)] + if (allMessages[allMessages.length - 1].type === 'userMessage') return allMessages + + // Remove any remaining calledTools when the stream ends + const lastMessage = allMessages[allMessages.length - 1] + if (lastMessage && lastMessage.calledTools && lastMessage.calledTools.length > 0) { + // Only remove if there are still calledTools and no matching usedTools + const hasUsedTools = lastMessage.usedTools && lastMessage.usedTools.length > 0 + if (!hasUsedTools) { + allMessages[allMessages.length - 1].calledTools = undefined + } + } + + return allMessages + }) + } + const updateLastMessageFileAnnotations = (fileAnnotations) => { setMessages((prevMessages) => { let allMessages = [...cloneDeep(prevMessages)] @@ -710,6 +756,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP if (lastAgentReasoning && lastAgentReasoning.length > 0) { allMessages[allMessages.length - 1].agentReasoning = lastAgentReasoning.filter((reasoning) => !reasoning.nextAgent) } + allMessages[allMessages.length - 1].calledTools = undefined return allMessages }) setTimeout(() => { @@ -1038,6 +1085,9 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP case 'usedTools': updateLastMessageUsedTools(payload.data) break + case 'calledTools': + updateLastMessageCalledTools(payload.data) + break case 'fileAnnotations': updateLastMessageFileAnnotations(payload.data) break @@ -1085,12 +1135,14 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP handleTTSAbort(payload.data) break case 'end': + cleanupCalledTools() setLocalStorageChatflow(chatflowid, chatId) closeResponse() break } }, async onclose() { + cleanupCalledTools() closeResponse() }, async onerror(err) { @@ -1102,6 +1154,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP } const closeResponse = () => { + cleanupCalledTools() setLoading(false) setUserInput('') setUploadedFiles([]) @@ -1202,6 +1255,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP } if (message.sourceDocuments) obj.sourceDocuments = message.sourceDocuments if (message.usedTools) obj.usedTools = message.usedTools + if (message.calledTools) obj.calledTools = message.calledTools if (message.fileAnnotations) obj.fileAnnotations = message.fileAnnotations if (message.agentReasoning) obj.agentReasoning = message.agentReasoning if (message.action) obj.action = message.action @@ -2423,6 +2477,42 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP sessionId={chatId} /> )} + {message.calledTools && ( +