UNPKG

dtamind-components

Version:

DTAmindai Components

1,046 lines 91.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const messages_1 = require("@langchain/core/messages"); const prompt_1 = require("../prompt"); const agents_1 = require("../../../src/agents"); const lodash_1 = require("lodash"); const zod_to_json_schema_1 = __importDefault(require("zod-to-json-schema")); const error_1 = require("../../../src/error"); const utils_1 = require("../utils"); const utils_2 = require("../../../src/utils"); const storageUtils_1 = require("../../../src/storageUtils"); const node_fetch_1 = __importDefault(require("node-fetch")); class Agent_Agentflow { constructor() { //@ts-ignore this.loadMethods = { async listModels(_, options) { const componentNodes = options.componentNodes; const returnOptions = []; for (const nodeName in componentNodes) { const componentNode = componentNodes[nodeName]; if (componentNode.category === 'Chat Models') { if (componentNode.tags?.includes('LlamaIndex')) { continue; } returnOptions.push({ label: componentNode.label, name: nodeName, imageSrc: componentNode.icon }); } } return returnOptions; }, async listEmbeddings(_, options) { const componentNodes = options.componentNodes; const returnOptions = []; for (const nodeName in componentNodes) { const componentNode = componentNodes[nodeName]; if (componentNode.category === 'Embeddings') { if (componentNode.tags?.includes('LlamaIndex')) { continue; } returnOptions.push({ label: componentNode.label, name: nodeName, imageSrc: componentNode.icon }); } } return returnOptions; }, async listTools(_, options) { const componentNodes = options.componentNodes; const removeTools = ['chainTool', 'retrieverTool', 'webBrowser']; const returnOptions = []; for (const nodeName in componentNodes) { const componentNode = componentNodes[nodeName]; if (componentNode.category === 'Tools' || componentNode.category === 'Tools (MCP)') { if (componentNode.tags?.includes('LlamaIndex')) { continue; } if (removeTools.includes(nodeName)) { continue; } returnOptions.push({ label: componentNode.label, name: nodeName, imageSrc: componentNode.icon }); } } return returnOptions; }, async listRuntimeStateKeys(_, options) { const previousNodes = options.previousNodes; const startAgentflowNode = previousNodes.find((node) => node.name === 'startAgentflow'); const state = startAgentflowNode?.inputs?.startState; return state.map((item) => ({ label: item.key, name: item.key })); }, async listStores(_, options) { const returnData = []; const appDataSource = options.appDataSource; const databaseEntities = options.databaseEntities; if (appDataSource === undefined || !appDataSource) { return returnData; } const searchOptions = options.searchOptions || {}; const stores = await appDataSource.getRepository(databaseEntities['DocumentStore']).findBy(searchOptions); for (const store of stores) { if (store.status === 'UPSERTED') { const obj = { name: `${store.id}:${store.name}`, label: store.name, description: store.description }; returnData.push(obj); } } return returnData; }, async listVectorStores(_, options) { const componentNodes = options.componentNodes; const returnOptions = []; for (const nodeName in componentNodes) { const componentNode = componentNodes[nodeName]; if (componentNode.category === 'Vector Stores') { if (componentNode.tags?.includes('LlamaIndex')) { continue; } returnOptions.push({ label: componentNode.label, name: nodeName, imageSrc: componentNode.icon }); } } return returnOptions; } }; this.label = 'Agent'; this.name = 'agentAgentflow'; this.version = 2.0; this.type = 'Agent'; this.category = 'Agent Flows'; this.description = 'Dynamically choose and utilize tools during runtime, enabling multi-step reasoning'; this.color = '#4DD0E1'; this.baseClasses = [this.type]; this.inputs = [ { label: 'Model', name: 'agentModel', type: 'asyncOptions', loadMethod: 'listModels', loadConfig: true }, { label: 'Messages', name: 'agentMessages', type: 'array', optional: true, acceptVariable: true, array: [ { label: 'Role', name: 'role', type: 'options', options: [ { label: 'System', name: 'system' }, { label: 'Assistant', name: 'assistant' }, { label: 'Developer', name: 'developer' }, { label: 'User', name: 'user' } ] }, { label: 'Content', name: 'content', type: 'string', acceptVariable: true, generateInstruction: true, rows: 4 } ] }, { label: 'OpenAI Built-in Tools', name: 'agentToolsBuiltInOpenAI', type: 'multiOptions', optional: true, options: [ { label: 'Web Search', name: 'web_search_preview', description: 'Search the web for the latest information' }, { label: 'Code Interpreter', name: 'code_interpreter', description: 'Write and run Python code in a sandboxed environment' }, { label: 'Image Generation', name: 'image_generation', description: 'Generate images based on a text prompt' } ], show: { agentModel: 'chatOpenAI' } }, { label: 'Tools', name: 'agentTools', type: 'array', optional: true, array: [ { label: 'Tool', name: 'agentSelectedTool', type: 'asyncOptions', loadMethod: 'listTools', loadConfig: true }, { label: 'Require Human Input', name: 'agentSelectedToolRequiresHumanInput', type: 'boolean', optional: true } ] }, { label: 'Knowledge (Document Stores)', name: 'agentKnowledgeDocumentStores', type: 'array', description: 'Give your agent context about different document sources. Document stores must be upserted in advance.', array: [ { label: 'Document Store', name: 'documentStore', type: 'asyncOptions', loadMethod: 'listStores' }, { label: 'Describe Knowledge', name: 'docStoreDescription', type: 'string', generateDocStoreDescription: true, placeholder: 'Describe what the knowledge base is about, this is useful for the AI to know when and how to search for correct information', rows: 4 }, { label: 'Return Source Documents', name: 'returnSourceDocuments', type: 'boolean', optional: true } ], optional: true }, { label: 'Knowledge (Vector Embeddings)', name: 'agentKnowledgeVSEmbeddings', type: 'array', description: 'Give your agent context about different document sources from existing vector stores and embeddings', array: [ { label: 'Vector Store', name: 'vectorStore', type: 'asyncOptions', loadMethod: 'listVectorStores', loadConfig: true }, { label: 'Embedding Model', name: 'embeddingModel', type: 'asyncOptions', loadMethod: 'listEmbeddings', loadConfig: true }, { label: 'Knowledge Name', name: 'knowledgeName', type: 'string', placeholder: 'A short name for the knowledge base, this is useful for the AI to know when and how to search for correct information' }, { label: 'Describe Knowledge', name: 'knowledgeDescription', type: 'string', placeholder: 'Describe what the knowledge base is about, this is useful for the AI to know when and how to search for correct information', rows: 4 }, { label: 'Return Source Documents', name: 'returnSourceDocuments', type: 'boolean', optional: true } ], optional: true }, { label: 'Enable Memory', name: 'agentEnableMemory', type: 'boolean', description: 'Enable memory for the conversation thread', default: true, optional: true }, { label: 'Memory Type', name: 'agentMemoryType', type: 'options', options: [ { label: 'All Messages', name: 'allMessages', description: 'Retrieve all messages from the conversation' }, { label: 'Window Size', name: 'windowSize', description: 'Uses a fixed window size to surface the last N messages' }, { label: 'Conversation Summary', name: 'conversationSummary', description: 'Summarizes the whole conversation' }, { label: 'Conversation Summary Buffer', name: 'conversationSummaryBuffer', description: 'Summarize conversations once token limit is reached. Default to 2000' } ], optional: true, default: 'allMessages', show: { agentEnableMemory: true } }, { label: 'Window Size', name: 'agentMemoryWindowSize', type: 'number', default: '20', description: 'Uses a fixed window size to surface the last N messages', show: { agentMemoryType: 'windowSize' } }, { label: 'Max Token Limit', name: 'agentMemoryMaxTokenLimit', type: 'number', default: '2000', description: 'Summarize conversations once token limit is reached. Default to 2000', show: { agentMemoryType: 'conversationSummaryBuffer' } }, { label: 'Input Message', name: 'agentUserMessage', type: 'string', description: 'Add an input message as user message at the end of the conversation', rows: 4, optional: true, acceptVariable: true, show: { agentEnableMemory: true } }, { label: 'Return Response As', name: 'agentReturnResponseAs', type: 'options', options: [ { label: 'User Message', name: 'userMessage' }, { label: 'Assistant Message', name: 'assistantMessage' } ], default: 'userMessage' }, { label: 'Update Flow State', name: 'agentUpdateState', description: 'Update runtime state during the execution of the workflow', type: 'array', optional: true, acceptVariable: true, array: [ { label: 'Key', name: 'key', type: 'asyncOptions', loadMethod: 'listRuntimeStateKeys', freeSolo: true }, { label: 'Value', name: 'value', type: 'string', acceptVariable: true, acceptNodeOutputAsVariable: true } ] } ]; } async run(nodeData, input, options) { let llmIds; let analyticHandlers = options.analyticHandlers; try { const abortController = options.abortController; // Extract input parameters const model = nodeData.inputs?.agentModel; const modelConfig = nodeData.inputs?.agentModelConfig; if (!model) { throw new Error('Model is required'); } // Extract tools const tools = nodeData.inputs?.agentTools; const toolsInstance = []; for (const tool of tools) { const toolConfig = tool.agentSelectedToolConfig; const nodeInstanceFilePath = options.componentNodes[tool.agentSelectedTool].filePath; const nodeModule = await Promise.resolve(`${nodeInstanceFilePath}`).then(s => __importStar(require(s))); const newToolNodeInstance = new nodeModule.nodeClass(); const newNodeData = { ...nodeData, credential: toolConfig['FLOWISE_CREDENTIAL_ID'], inputs: { ...nodeData.inputs, ...toolConfig } }; const toolInstance = await newToolNodeInstance.init(newNodeData, '', options); // toolInstance might returns a list of tools like MCP tools if (Array.isArray(toolInstance)) { for (const subTool of toolInstance) { const subToolInstance = subTool; subToolInstance.agentSelectedTool = tool.agentSelectedTool; if (tool.agentSelectedToolRequiresHumanInput) { ; subToolInstance.requiresHumanInput = true; } toolsInstance.push(subToolInstance); } } else { if (tool.agentSelectedToolRequiresHumanInput) { toolInstance.requiresHumanInput = true; } toolsInstance.push(toolInstance); } } const availableTools = toolsInstance.map((tool, index) => { const originalTool = tools[index]; let agentSelectedTool = tool?.agentSelectedTool; if (!agentSelectedTool) { agentSelectedTool = originalTool?.agentSelectedTool; } const componentNode = options.componentNodes[agentSelectedTool]; const jsonSchema = (0, zod_to_json_schema_1.default)(tool.schema); if (jsonSchema.$schema) { delete jsonSchema.$schema; } return { name: tool.name, description: tool.description, schema: jsonSchema, toolNode: { label: componentNode?.label || tool.name, name: componentNode?.name || tool.name } }; }); // Extract knowledge const knowledgeBases = nodeData.inputs?.agentKnowledgeDocumentStores; if (knowledgeBases && knowledgeBases.length > 0) { for (const knowledgeBase of knowledgeBases) { const nodeInstanceFilePath = options.componentNodes['retrieverTool'].filePath; const nodeModule = await Promise.resolve(`${nodeInstanceFilePath}`).then(s => __importStar(require(s))); const newRetrieverToolNodeInstance = new nodeModule.nodeClass(); const [storeId, storeName] = knowledgeBase.documentStore.split(':'); const docStoreVectorInstanceFilePath = options.componentNodes['documentStoreVS'].filePath; const docStoreVectorModule = await Promise.resolve(`${docStoreVectorInstanceFilePath}`).then(s => __importStar(require(s))); const newDocStoreVectorInstance = new docStoreVectorModule.nodeClass(); const docStoreVectorInstance = await newDocStoreVectorInstance.init({ ...nodeData, inputs: { ...nodeData.inputs, selectedStore: storeId }, outputs: { output: 'retriever' } }, '', options); const newRetrieverToolNodeData = { ...nodeData, inputs: { ...nodeData.inputs, name: storeName .toLowerCase() .replace(/ /g, '_') .replace(/[^a-z0-9_-]/g, ''), description: knowledgeBase.docStoreDescription, retriever: docStoreVectorInstance, returnSourceDocuments: knowledgeBase.returnSourceDocuments } }; const retrieverToolInstance = await newRetrieverToolNodeInstance.init(newRetrieverToolNodeData, '', options); toolsInstance.push(retrieverToolInstance); const jsonSchema = (0, zod_to_json_schema_1.default)(retrieverToolInstance.schema); if (jsonSchema.$schema) { delete jsonSchema.$schema; } const componentNode = options.componentNodes['retrieverTool']; availableTools.push({ name: storeName .toLowerCase() .replace(/ /g, '_') .replace(/[^a-z0-9_-]/g, ''), description: knowledgeBase.docStoreDescription, schema: jsonSchema, toolNode: { label: componentNode?.label || retrieverToolInstance.name, name: componentNode?.name || retrieverToolInstance.name } }); } } const knowledgeBasesForVSEmbeddings = nodeData.inputs?.agentKnowledgeVSEmbeddings; if (knowledgeBasesForVSEmbeddings && knowledgeBasesForVSEmbeddings.length > 0) { for (const knowledgeBase of knowledgeBasesForVSEmbeddings) { const nodeInstanceFilePath = options.componentNodes['retrieverTool'].filePath; const nodeModule = await Promise.resolve(`${nodeInstanceFilePath}`).then(s => __importStar(require(s))); const newRetrieverToolNodeInstance = new nodeModule.nodeClass(); const selectedEmbeddingModel = knowledgeBase.embeddingModel; const selectedEmbeddingModelConfig = knowledgeBase.embeddingModelConfig; const embeddingInstanceFilePath = options.componentNodes[selectedEmbeddingModel].filePath; const embeddingModule = await Promise.resolve(`${embeddingInstanceFilePath}`).then(s => __importStar(require(s))); const newEmbeddingInstance = new embeddingModule.nodeClass(); const newEmbeddingNodeData = { ...nodeData, credential: selectedEmbeddingModelConfig['FLOWISE_CREDENTIAL_ID'], inputs: { ...nodeData.inputs, ...selectedEmbeddingModelConfig } }; const embeddingInstance = await newEmbeddingInstance.init(newEmbeddingNodeData, '', options); const selectedVectorStore = knowledgeBase.vectorStore; const selectedVectorStoreConfig = knowledgeBase.vectorStoreConfig; const vectorStoreInstanceFilePath = options.componentNodes[selectedVectorStore].filePath; const vectorStoreModule = await Promise.resolve(`${vectorStoreInstanceFilePath}`).then(s => __importStar(require(s))); const newVectorStoreInstance = new vectorStoreModule.nodeClass(); const newVSNodeData = { ...nodeData, credential: selectedVectorStoreConfig['FLOWISE_CREDENTIAL_ID'], inputs: { ...nodeData.inputs, ...selectedVectorStoreConfig, embeddings: embeddingInstance }, outputs: { output: 'retriever' } }; const vectorStoreInstance = await newVectorStoreInstance.init(newVSNodeData, '', options); const knowledgeName = knowledgeBase.knowledgeName || ''; const newRetrieverToolNodeData = { ...nodeData, inputs: { ...nodeData.inputs, name: knowledgeName .toLowerCase() .replace(/ /g, '_') .replace(/[^a-z0-9_-]/g, ''), description: knowledgeBase.knowledgeDescription, retriever: vectorStoreInstance, returnSourceDocuments: knowledgeBase.returnSourceDocuments } }; const retrieverToolInstance = await newRetrieverToolNodeInstance.init(newRetrieverToolNodeData, '', options); toolsInstance.push(retrieverToolInstance); const jsonSchema = (0, zod_to_json_schema_1.default)(retrieverToolInstance.schema); if (jsonSchema.$schema) { delete jsonSchema.$schema; } const componentNode = options.componentNodes['retrieverTool']; availableTools.push({ name: knowledgeName .toLowerCase() .replace(/ /g, '_') .replace(/[^a-z0-9_-]/g, ''), description: knowledgeBase.knowledgeDescription, schema: jsonSchema, toolNode: { label: componentNode?.label || retrieverToolInstance.name, name: componentNode?.name || retrieverToolInstance.name } }); } } // Extract memory and configuration options const enableMemory = nodeData.inputs?.agentEnableMemory; const memoryType = nodeData.inputs?.agentMemoryType; const userMessage = nodeData.inputs?.agentUserMessage; const _agentUpdateState = nodeData.inputs?.agentUpdateState; const agentMessages = nodeData.inputs?.agentMessages ?? []; // Extract runtime state and history const state = options.agentflowRuntime?.state; const pastChatHistory = options.pastChatHistory ?? []; const runtimeChatHistory = options.agentflowRuntime?.chatHistory ?? []; const prependedChatHistory = options.prependedChatHistory; const chatId = options.chatId; // Initialize the LLM model instance const nodeInstanceFilePath = options.componentNodes[model].filePath; const nodeModule = await Promise.resolve(`${nodeInstanceFilePath}`).then(s => __importStar(require(s))); const newLLMNodeInstance = new nodeModule.nodeClass(); const newNodeData = { ...nodeData, credential: modelConfig['FLOWISE_CREDENTIAL_ID'], inputs: { ...nodeData.inputs, ...modelConfig } }; const llmWithoutToolsBind = (await newLLMNodeInstance.init(newNodeData, '', options)); let llmNodeInstance = llmWithoutToolsBind; const agentToolsBuiltInOpenAI = (0, utils_2.convertMultiOptionsToStringArray)(nodeData.inputs?.agentToolsBuiltInOpenAI); if (agentToolsBuiltInOpenAI && agentToolsBuiltInOpenAI.length > 0) { for (const tool of agentToolsBuiltInOpenAI) { const builtInTool = { type: tool }; if (tool === 'code_interpreter') { builtInTool.container = { type: 'auto' }; } ; toolsInstance.push(builtInTool); availableTools.push({ name: tool, toolNode: { label: tool, name: tool } }); } } if (llmNodeInstance && toolsInstance.length > 0) { if (llmNodeInstance.bindTools === undefined) { throw new Error(`Agent needs to have a function calling capable models.`); } // @ts-ignore llmNodeInstance = llmNodeInstance.bindTools(toolsInstance); } // Prepare messages array const messages = []; // Use to store messages with image file references as we do not want to store the base64 data into database let runtimeImageMessagesWithFileRef = []; // Use to keep track of past messages with image file references let pastImageMessagesWithFileRef = []; // Prepend history ONLY if it is the first node if (prependedChatHistory.length > 0 && !runtimeChatHistory.length) { for (const msg of prependedChatHistory) { const role = msg.role === 'apiMessage' ? 'assistant' : 'user'; const content = msg.content ?? ''; messages.push({ role, content }); } } for (const msg of agentMessages) { const role = msg.role; const content = msg.content; if (role && content) { messages.push({ role, content }); } } // Handle memory management if enabled if (enableMemory) { await this.handleMemory({ messages, memoryType, pastChatHistory, runtimeChatHistory, llmNodeInstance, nodeData, userMessage, input, abortController, options, modelConfig, runtimeImageMessagesWithFileRef, pastImageMessagesWithFileRef }); } else if (!runtimeChatHistory.length) { /* * If this is the first node: * - Add images to messages if exist * - Add user message if it does not exist in the agentMessages array */ if (options.uploads) { const imageContents = await (0, utils_1.getUniqueImageMessages)(options, messages, modelConfig); if (imageContents) { const { imageMessageWithBase64, imageMessageWithFileRef } = imageContents; messages.push(imageMessageWithBase64); runtimeImageMessagesWithFileRef.push(imageMessageWithFileRef); } } if (input && typeof input === 'string' && !agentMessages.some((msg) => msg.role === 'user')) { messages.push({ role: 'user', content: input }); } } delete nodeData.inputs?.agentMessages; // Initialize response and determine if streaming is possible let response = new messages_1.AIMessageChunk(''); const isLastNode = options.isLastNode; const isStreamable = isLastNode && options.sseStreamer !== undefined && modelConfig?.streaming !== false; // Start analytics if (analyticHandlers && options.parentTraceIds) { const llmLabel = options?.componentNodes?.[model]?.label || model; llmIds = await analyticHandlers.onLLMStart(llmLabel, messages, options.parentTraceIds); } // Track execution time const startTime = Date.now(); // Get initial response from LLM const sseStreamer = options.sseStreamer; // Handle tool calls with support for recursion let usedTools = []; let sourceDocuments = []; let artifacts = []; let fileAnnotations = []; let additionalTokens = 0; let isWaitingForHumanInput = false; // Store the current messages length to track which messages are added during tool calls const messagesBeforeToolCalls = [...messages]; let _toolCallMessages = []; // Check if this is hummanInput for tool calls const _humanInput = nodeData.inputs?.humanInput; const humanInput = typeof _humanInput === 'string' ? JSON.parse(_humanInput) : _humanInput; const humanInputAction = options.humanInputAction; const iterationContext = options.iterationContext; if (humanInput) { if (humanInput.type !== 'proceed' && humanInput.type !== 'reject') { throw new Error(`Invalid human input type. Expected 'proceed' or 'reject', but got '${humanInput.type}'`); } const result = await this.handleResumedToolCalls({ humanInput, humanInputAction, messages, toolsInstance, sseStreamer, chatId, input, options, abortController, llmWithoutToolsBind, isStreamable, isLastNode, iterationContext }); response = result.response; usedTools = result.usedTools; sourceDocuments = result.sourceDocuments; artifacts = result.artifacts; additionalTokens = result.totalTokens; isWaitingForHumanInput = result.isWaitingForHumanInput || false; // Calculate which messages were added during tool calls _toolCallMessages = messages.slice(messagesBeforeToolCalls.length); // Stream additional data if this is the last node if (isLastNode && sseStreamer) { if (usedTools.length > 0) { sseStreamer.streamUsedToolsEvent(chatId, (0, lodash_1.flatten)(usedTools)); } if (sourceDocuments.length > 0) { sseStreamer.streamSourceDocumentsEvent(chatId, (0, lodash_1.flatten)(sourceDocuments)); } if (artifacts.length > 0) { sseStreamer.streamArtifactsEvent(chatId, (0, lodash_1.flatten)(artifacts)); } } } else { if (isStreamable) { response = await this.handleStreamingResponse(sseStreamer, llmNodeInstance, messages, chatId, abortController); } else { response = await llmNodeInstance.invoke(messages, { signal: abortController?.signal }); } } // Address built in tools (after artifacts are processed) const builtInUsedTools = await this.extractBuiltInUsedTools(response, []); if (!humanInput && response.tool_calls && response.tool_calls.length > 0) { const result = await this.handleToolCalls({ response, messages, toolsInstance, sseStreamer, chatId, input, options, abortController, llmNodeInstance, isStreamable, isLastNode, iterationContext }); response = result.response; usedTools = result.usedTools; sourceDocuments = result.sourceDocuments; artifacts = result.artifacts; additionalTokens = result.totalTokens; isWaitingForHumanInput = result.isWaitingForHumanInput || false; // Calculate which messages were added during tool calls _toolCallMessages = messages.slice(messagesBeforeToolCalls.length); // Stream additional data if this is the last node if (isLastNode && sseStreamer) { if (usedTools.length > 0) { sseStreamer.streamUsedToolsEvent(chatId, (0, lodash_1.flatten)(usedTools)); } if (sourceDocuments.length > 0) { sseStreamer.streamSourceDocumentsEvent(chatId, (0, lodash_1.flatten)(sourceDocuments)); } if (artifacts.length > 0) { sseStreamer.streamArtifactsEvent(chatId, (0, lodash_1.flatten)(artifacts)); } } } else if (!humanInput && !isStreamable && isLastNode && sseStreamer) { // Stream whole response back to UI if not streaming and no tool calls let finalResponse = ''; if (response.content && Array.isArray(response.content)) { finalResponse = response.content.map((item) => item.text).join('\n'); } else if (response.content && typeof response.content === 'string') { finalResponse = response.content; } else { finalResponse = JSON.stringify(response, null, 2); } sseStreamer.streamTokenEvent(chatId, finalResponse); } // Calculate execution time const endTime = Date.now(); const timeDelta = endTime - startTime; // Update flow state if needed let newState = { ...state }; if (_agentUpdateState && Array.isArray(_agentUpdateState) && _agentUpdateState.length > 0) { newState = (0, utils_1.updateFlowState)(state, _agentUpdateState); } // Clean up empty inputs for (const key in nodeData.inputs) { if (nodeData.inputs[key] === '') { delete nodeData.inputs[key]; } } // Prepare final response and output object let finalResponse = ''; if (response.content && Array.isArray(response.content)) { finalResponse = response.content.map((item) => item.text).join('\n'); } else if (response.content && typeof response.content === 'string') { finalResponse = response.content; } else { finalResponse = JSON.stringify(response, null, 2); } // Address built in tools const additionalBuiltInUsedTools = await this.extractBuiltInUsedTools(response, builtInUsedTools); if (additionalBuiltInUsedTools.length > 0) { usedTools = [...new Set([...usedTools, ...additionalBuiltInUsedTools])]; // Stream used tools if this is the last node if (isLastNode && sseStreamer) { sseStreamer.streamUsedToolsEvent(chatId, (0, lodash_1.flatten)(usedTools)); } } // Extract artifacts from annotations in response metadata if (response.response_metadata) { const { artifacts: extractedArtifacts, fileAnnotations: extractedFileAnnotations } = await this.extractArtifactsFromResponse(response.response_metadata, newNodeData, options); if (extractedArtifacts.length > 0) { artifacts = [...artifacts, ...extractedArtifacts]; // Stream artifacts if this is the last node if (isLastNode && sseStreamer) { sseStreamer.streamArtifactsEvent(chatId, extractedArtifacts); } } if (extractedFileAnnotations.length > 0) { fileAnnotations = [...fileAnnotations, ...extractedFileAnnotations]; // Stream file annotations if this is the last node if (isLastNode && sseStreamer) { sseStreamer.streamFileAnnotationsEvent(chatId, fileAnnotations); } } } // Replace sandbox links with proper download URLs. Example: [Download the script](sandbox:/mnt/data/dummy_bar_graph.py) if (finalResponse.includes('sandbox:/')) { finalResponse = await this.processSandboxLinks(finalResponse, options.baseURL, options.chatflowid, chatId); } const output = this.prepareOutputObject(response, availableTools, finalResponse, startTime, endTime, timeDelta, usedTools, sourceDocuments, artifacts, additionalTokens, isWaitingForHumanInput, fileAnnotations); // End analytics tracking if (analyticHandlers && llmIds) { await analyticHandlers.onLLMEnd(llmIds, finalResponse); } // Send additional streaming events if needed if (isStreamable) { this.sendStreamingEvents(options, chatId, response); } // Stream file annotations if any were extracted if (fileAnnotations.length > 0 && isLastNode && sseStreamer) { sseStreamer.streamFileAnnotationsEvent(chatId, fileAnnotations); } // Process template variables in state if (newState && Object.keys(newState).length > 0) { for (const key in newState) { if (newState[key].toString().includes('{{ output }}')) { newState[key] = newState[key].replaceAll('{{ output }}', finalResponse); } } } // Replace the actual messages array with one that includes the file references for images instead of base64 data const messagesWithFileReferences = (0, utils_1.replaceBase64ImagesWithFileReferences)(messages, runtimeImageMessagesWithFileRef, pastImageMessagesWithFileRef); // Only add to runtime chat history if this is the first node const inputMessages = []; if (!runtimeChatHistory.length) { if (runtimeImageMessagesWithFileRef.length) { inputMessages.push(...runtimeImageMessagesWithFileRef); } if (input && typeof input === 'string') { if (!enableMemory) { if (!agentMessages.some((msg) => msg.role === 'user')) { inputMessages.push({ role: 'user', content: input }); } else { agentMessages.map((msg) => { if (msg.role === 'user') { inputMessages.push({ role: 'user', content: msg.content }); } }); } } else { inputMessages.push({ role: 'user', content: input }); } } } const returnResponseAs = nodeData.inputs?.agentReturnResponseAs; let returnRole = 'user'; if (returnResponseAs === 'assistantMessage') { returnRole = 'assistant'; } // Prepare and return the final output return { id: nodeData.id, name: this.name, input: { messages: messagesWithFileReferences, ...nodeData.inputs }, output, state: newState, chatHistory: [ ...inputMessages, // Add the messages that were specifically added during tool calls, this enable other nodes to see the full tool call history, temporaraily disabled // ...toolCallMessages, // End with the final assistant response { role: returnRole, content: finalResponse, name: nodeData?.label ? nodeData?.label.toLowerCase().replace(/\s/g, '_').trim() : nodeData?.id, ...(((artifacts && artifacts.length > 0) || (fileAnnotations && fileAnnotations.length > 0) || (usedTools && usedTools.length > 0)) && { additional_kwargs: { ...(artifacts && artifacts.length > 0 && { artifacts }), ...(fileAnnotations && fileAnnotations.length > 0 && { fileAnnotations }), ...(usedTools && usedTools.length > 0 && { usedTools }) } }) } ] }; } catch (error) { if (options.analyticHandlers && llmIds) { await options.analyticHandlers.onLLMError(llmIds, error instanceof Error ? error.message : String(error)); } if (error instanceof Error && error.message === 'Aborted') { throw error; } throw new Error(`Error in Agent node: ${error instanceof Error ? error.message : String(error)}`); } } /** * Extracts built-in used tools from response metadata and processes image generation results */ async extractBuiltInUsedTools(response, builtInUsedTools = []) { if (!response.response_metadata) { return builtInUsedTools; } const { output, tools } = response.response_metadata; if (!output || !Array.isArray(output) || output.length === 0 || !tools || !Array.isArray(tools) || tools.length === 0) { return builtInUsedTools; } for (const outputItem of output) { if (outputItem.type && outputItem.type.endsWith('_call')) { let toolInput = outputItem.action ?? outputItem.code; let toolOutput = outputItem.status === 'completed' ? 'Success' : outputItem.status; // Handle image generation calls specially if (outputItem.type === 'image_generation_call') { // Create input summary for image generation toolInput = { prompt: outputItem.revised_prompt || 'Image generation request', siz