diff --git a/packages/components/nodes/sequentialagents/Agent/Agent.ts b/packages/components/nodes/sequentialagents/Agent/Agent.ts index 3f2edb983fe..ad626207429 100644 --- a/packages/components/nodes/sequentialagents/Agent/Agent.ts +++ b/packages/components/nodes/sequentialagents/Agent/Agent.ts @@ -205,7 +205,7 @@ class Agent_SeqAgents implements INode { constructor() { this.label = 'Agent' this.name = 'seqAgent' - this.version = 4.0 + this.version = 4.1 this.type = 'Agent' this.icon = 'seqAgent.png' this.category = 'Sequential Agents' @@ -291,9 +291,11 @@ class Agent_SeqAgents implements INode { optional: true }, { - label: 'Start | Agent | Condition | LLM | Tool Node', + label: 'Sequential Node', name: 'sequentialNode', - type: 'Start | Agent | Condition | LLMNode | ToolNode', + type: 'Start | Agent | Condition | LLMNode | ToolNode | CustomFunction | ExecuteFlow', + description: + 'Can be connected to one of the following nodes: Start, Agent, Condition, LLM Node, Tool Node, Custom Function, Execute Flow', list: true }, { diff --git a/packages/components/nodes/sequentialagents/Condition/Condition.ts b/packages/components/nodes/sequentialagents/Condition/Condition.ts index bb402805b22..1455c17b1a7 100644 --- a/packages/components/nodes/sequentialagents/Condition/Condition.ts +++ b/packages/components/nodes/sequentialagents/Condition/Condition.ts @@ -96,7 +96,7 @@ class Condition_SeqAgents implements INode { constructor() { this.label = 'Condition' this.name = 'seqCondition' - this.version = 2.0 + this.version = 2.1 this.type = 'Condition' this.icon = 'condition.svg' this.category = 'Sequential Agents' @@ -112,9 +112,11 @@ class Condition_SeqAgents implements INode { placeholder: 'If X, then Y' }, { - label: 'Start | Agent | LLM | Tool Node', + label: 'Sequential Node', name: 'sequentialNode', - type: 'Start | Agent | LLMNode | ToolNode', + type: 'Start | Agent | LLMNode | ToolNode | CustomFunction | ExecuteFlow', + description: + 'Can be connected to one of the following nodes: Start, Agent, LLM Node, Tool Node, Custom Function, Execute Flow', list: true }, { diff --git a/packages/components/nodes/sequentialagents/ConditionAgent/ConditionAgent.ts b/packages/components/nodes/sequentialagents/ConditionAgent/ConditionAgent.ts index 2e6a39385e9..ae56efe129b 100644 --- a/packages/components/nodes/sequentialagents/ConditionAgent/ConditionAgent.ts +++ b/packages/components/nodes/sequentialagents/ConditionAgent/ConditionAgent.ts @@ -151,7 +151,7 @@ class ConditionAgent_SeqAgents implements INode { constructor() { this.label = 'Condition Agent' this.name = 'seqConditionAgent' - this.version = 3.0 + this.version = 3.1 this.type = 'ConditionAgent' this.icon = 'condition.svg' this.category = 'Sequential Agents' @@ -166,9 +166,11 @@ class ConditionAgent_SeqAgents implements INode { placeholder: 'Condition Agent' }, { - label: 'Start | Agent | LLM | Tool Node', + label: 'Sequential Node', name: 'sequentialNode', - type: 'Start | Agent | LLMNode | ToolNode', + type: 'Start | Agent | LLMNode | ToolNode | CustomFunction | ExecuteFlow', + description: + 'Can be connected to one of the following nodes: Start, Agent, LLM Node, Tool Node, Custom Function, Execute Flow', list: true }, { diff --git a/packages/components/nodes/sequentialagents/CustomFunction/CustomFunction.ts b/packages/components/nodes/sequentialagents/CustomFunction/CustomFunction.ts new file mode 100644 index 00000000000..b7d831e17d0 --- /dev/null +++ b/packages/components/nodes/sequentialagents/CustomFunction/CustomFunction.ts @@ -0,0 +1,257 @@ +import { NodeVM } from '@flowiseai/nodevm' +import { DataSource } from 'typeorm' +import { availableDependencies, defaultAllowBuiltInDep, getVars, handleEscapeCharacters, prepareSandboxVars } from '../../../src/utils' +import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeParams, ISeqAgentNode, ISeqAgentsState } from '../../../src/Interface' +import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages' +import { customGet } from '../commonUtils' + +const howToUseCode = ` +1. Must return a string value at the end of function. + +2. You can get default flow config, including the current "state": + - \`$flow.sessionId\` + - \`$flow.chatId\` + - \`$flow.chatflowId\` + - \`$flow.input\` + - \`$flow.state\` + +3. You can get custom variables: \`$vars.\` + +` + +class CustomFunction_SeqAgents implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Custom JS Function' + this.name = 'seqCustomFunction' + this.version = 1.0 + this.type = 'CustomFunction' + this.icon = 'customfunction.svg' + this.category = 'Sequential Agents' + this.description = `Execute custom javascript function` + this.baseClasses = [this.type] + this.inputs = [ + { + label: 'Input Variables', + name: 'functionInputVariables', + description: 'Input variables can be used in the function with prefix $. For example: $var', + type: 'json', + optional: true, + acceptVariable: true, + list: true + }, + { + label: 'Sequential Node', + name: 'sequentialNode', + type: 'Start | Agent | Condition | LLMNode | ToolNode | CustomFunction | ExecuteFlow', + description: + 'Can be connected to one of the following nodes: Start, Agent, Condition, LLM Node, Tool Node, Custom Function, Execute Flow', + list: true + }, + { + label: 'Function Name', + name: 'functionName', + type: 'string', + placeholder: 'My Function' + }, + { + label: 'Javascript Function', + name: 'javascriptFunction', + type: 'code', + hint: { + label: 'How to use', + value: howToUseCode + } + }, + { + label: 'Return Value As', + name: 'returnValueAs', + type: 'options', + options: [ + { label: 'AI Message', name: 'aiMessage' }, + { label: 'Human Message', name: 'humanMessage' }, + { + label: 'State Object', + name: 'stateObj', + description: "Return as state object, ex: { foo: bar }. This will update the custom state 'foo' to 'bar'" + } + ], + default: 'aiMessage' + } + ] + } + + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const functionName = nodeData.inputs?.functionName as string + const javascriptFunction = nodeData.inputs?.javascriptFunction as string + const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + const sequentialNodes = nodeData.inputs?.sequentialNode as ISeqAgentNode[] + const returnValueAs = nodeData.inputs?.returnValueAs as string + + if (!sequentialNodes || !sequentialNodes.length) throw new Error('Custom function must have a predecessor!') + + const executeFunc = async (state: ISeqAgentsState) => { + const variables = await getVars(appDataSource, databaseEntities, nodeData) + const flow = { + chatflowId: options.chatflowid, + sessionId: options.sessionId, + chatId: options.chatId, + input, + state + } + + let inputVars: ICommonObject = {} + if (functionInputVariablesRaw) { + try { + inputVars = + typeof functionInputVariablesRaw === 'object' ? functionInputVariablesRaw : JSON.parse(functionInputVariablesRaw) + } catch (exception) { + throw new Error('Invalid JSON in the Custom Function Input Variables: ' + exception) + } + } + + // Some values might be a stringified JSON, parse it + for (const key in inputVars) { + let value = inputVars[key] + if (typeof value === 'string') { + value = handleEscapeCharacters(value, true) + if (value.startsWith('{') && value.endsWith('}')) { + try { + value = JSON.parse(value) + const nodeId = value.id || '' + if (nodeId) { + const messages = state.messages as unknown as BaseMessage[] + const content = messages.find((msg) => msg.additional_kwargs?.nodeId === nodeId)?.content + if (content) { + value = content + } + } + } catch (e) { + // ignore + } + } + + if (value.startsWith('$flow.')) { + const variableValue = customGet(flow, value.replace('$flow.', '')) + if (variableValue) { + value = variableValue + } + } else if (value.startsWith('$vars')) { + value = customGet(flow, value.replace('$', '')) + } + inputVars[key] = value + } + } + + let sandbox: any = { + $input: input, + util: undefined, + Symbol: undefined, + child_process: undefined, + fs: undefined, + process: undefined + } + sandbox['$vars'] = prepareSandboxVars(variables) + sandbox['$flow'] = flow + + if (Object.keys(inputVars).length) { + for (const item in inputVars) { + sandbox[`$${item}`] = inputVars[item] + } + } + + const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP + ? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(',')) + : defaultAllowBuiltInDep + const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : [] + const deps = availableDependencies.concat(externalDeps) + + const nodeVMOptions = { + console: 'inherit', + sandbox, + require: { + external: { modules: deps }, + builtin: builtinDeps + }, + eval: false, + wasm: false, + timeout: 10000 + } as any + + const vm = new NodeVM(nodeVMOptions) + try { + const response = await vm.run(`module.exports = async function() {${javascriptFunction}}()`, __dirname) + + if (returnValueAs === 'stateObj') { + if (typeof response !== 'object') { + throw new Error('Custom function must return an object!') + } + return { + ...state, + ...response + } + } + + if (typeof response !== 'string') { + throw new Error('Custom function must return a string!') + } + + if (returnValueAs === 'humanMessage') { + return { + messages: [ + new HumanMessage({ + content: response, + additional_kwargs: { + nodeId: nodeData.id + } + }) + ] + } + } + + return { + messages: [ + new AIMessage({ + content: response, + additional_kwargs: { + nodeId: nodeData.id + } + }) + ] + } + } catch (e) { + throw new Error(e) + } + } + + const startLLM = sequentialNodes[0].startLLM + + const returnOutput: ISeqAgentNode = { + id: nodeData.id, + node: executeFunc, + name: functionName.toLowerCase().replace(/\s/g, '_').trim(), + label: functionName, + type: 'utilities', + output: 'CustomFunction', + llm: startLLM, + startLLM, + multiModalMessageContent: sequentialNodes[0]?.multiModalMessageContent, + predecessorAgents: sequentialNodes + } + + return returnOutput + } +} + +module.exports = { nodeClass: CustomFunction_SeqAgents } diff --git a/packages/components/nodes/sequentialagents/CustomFunction/customfunction.svg b/packages/components/nodes/sequentialagents/CustomFunction/customfunction.svg new file mode 100644 index 00000000000..506f3248cb5 --- /dev/null +++ b/packages/components/nodes/sequentialagents/CustomFunction/customfunction.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/components/nodes/sequentialagents/End/End.ts b/packages/components/nodes/sequentialagents/End/End.ts index 8b22338610d..7705195665b 100644 --- a/packages/components/nodes/sequentialagents/End/End.ts +++ b/packages/components/nodes/sequentialagents/End/End.ts @@ -18,7 +18,7 @@ class End_SeqAgents implements INode { constructor() { this.label = 'End' this.name = 'seqEnd' - this.version = 2.0 + this.version = 2.1 this.type = 'End' this.icon = 'end.svg' this.category = 'Sequential Agents' @@ -27,9 +27,11 @@ class End_SeqAgents implements INode { this.documentation = 'https://docs.flowiseai.com/using-flowise/agentflows/sequential-agents#id-10.-end-node' this.inputs = [ { - label: 'Agent | Condition | LLM | Tool Node', + label: 'Sequential Node', name: 'sequentialNode', - type: 'Agent | Condition | LLMNode | ToolNode' + type: 'Agent | Condition | LLMNode | ToolNode | CustomFunction | ExecuteFlow', + description: + 'Can be connected to one of the following nodes: Agent, Condition, LLM Node, Tool Node, Custom Function, Execute Flow' } ] this.hideOutput = true diff --git a/packages/components/nodes/sequentialagents/ExecuteFlow/ExecuteFlow.ts b/packages/components/nodes/sequentialagents/ExecuteFlow/ExecuteFlow.ts new file mode 100644 index 00000000000..479ebfbb48a --- /dev/null +++ b/packages/components/nodes/sequentialagents/ExecuteFlow/ExecuteFlow.ts @@ -0,0 +1,339 @@ +import { NodeVM } from '@flowiseai/nodevm' +import { DataSource } from 'typeorm' +import { + availableDependencies, + defaultAllowBuiltInDep, + getCredentialData, + getCredentialParam, + getVars, + prepareSandboxVars +} from '../../../src/utils' +import { + ICommonObject, + IDatabaseEntity, + INode, + INodeData, + INodeOptionsValue, + INodeParams, + ISeqAgentNode, + ISeqAgentsState +} from '../../../src/Interface' +import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages' +import { v4 as uuidv4 } from 'uuid' + +class ExecuteFlow_SeqAgents implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'Execute Flow' + this.name = 'seqExecuteFlow' + this.version = 1.0 + this.type = 'ExecuteFlow' + this.icon = 'executeflow.svg' + this.category = 'Sequential Agents' + this.description = `Execute chatflow/agentflow and return final response` + this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['chatflowApi'], + optional: true + } + this.inputs = [ + { + label: 'Sequential Node', + name: 'sequentialNode', + type: 'Start | Agent | Condition | LLMNode | ToolNode | CustomFunction | ExecuteFlow', + description: + 'Can be connected to one of the following nodes: Start, Agent, Condition, LLM Node, Tool Node, Custom Function, Execute Flow', + list: true + }, + { + label: 'Name', + name: 'seqExecuteFlowName', + type: 'string' + }, + { + label: 'Select Flow', + name: 'selectedFlow', + type: 'asyncOptions', + loadMethod: 'listFlows' + }, + { + label: 'Input', + name: 'seqExecuteFlowInput', + type: 'options', + description: 'Select one of the following or enter custom input', + freeSolo: true, + loadPreviousNodes: true, + options: [ + { + label: '{{ question }}', + name: 'userQuestion', + description: 'Use the user question from the chat as input.' + } + ] + }, + { + label: 'Override Config', + name: 'overrideConfig', + description: 'Override the config passed to the flow.', + type: 'json', + optional: true, + additionalParams: true + }, + { + label: 'Base URL', + name: 'baseURL', + type: 'string', + description: + 'Base URL to Flowise. By default, it is the URL of the incoming request. Useful when you need to execute flow through an alternative route.', + placeholder: 'http://localhost:3000', + optional: true, + additionalParams: true + }, + { + label: 'Start new session per message', + name: 'startNewSession', + type: 'boolean', + description: + 'Whether to continue the session or start a new one with each interaction. Useful for flows with memory if you want to avoid it.', + default: false, + optional: true, + additionalParams: true + }, + { + label: 'Return Value As', + name: 'returnValueAs', + type: 'options', + options: [ + { label: 'AI Message', name: 'aiMessage' }, + { label: 'Human Message', name: 'humanMessage' }, + { + label: 'State Object', + name: 'stateObj', + description: "Return as state object, ex: { foo: bar }. This will update the custom state 'foo' to 'bar'" + } + ], + default: 'aiMessage' + } + ] + } + + //@ts-ignore + loadMethods = { + async listFlows(_: INodeData, options: ICommonObject): Promise { + const returnData: INodeOptionsValue[] = [] + + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + if (appDataSource === undefined || !appDataSource) { + return returnData + } + + const chatflows = await appDataSource.getRepository(databaseEntities['ChatFlow']).find() + + for (let i = 0; i < chatflows.length; i += 1) { + const data = { + label: chatflows[i].name, + name: chatflows[i].id + } as INodeOptionsValue + returnData.push(data) + } + return returnData + } + } + + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const selectedFlowId = nodeData.inputs?.selectedFlow as string + const _seqExecuteFlowName = nodeData.inputs?.seqExecuteFlowName as string + if (!_seqExecuteFlowName) throw new Error('Execute Flow node name is required!') + const seqExecuteFlowName = _seqExecuteFlowName.toLowerCase().replace(/\s/g, '_').trim() + const startNewSession = nodeData.inputs?.startNewSession as boolean + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + const sequentialNodes = nodeData.inputs?.sequentialNode as ISeqAgentNode[] + const seqExecuteFlowInput = nodeData.inputs?.seqExecuteFlowInput as string + const overrideConfig = + typeof nodeData.inputs?.overrideConfig === 'string' && + nodeData.inputs.overrideConfig.startsWith('{') && + nodeData.inputs.overrideConfig.endsWith('}') + ? JSON.parse(nodeData.inputs.overrideConfig) + : nodeData.inputs?.overrideConfig + + if (!sequentialNodes || !sequentialNodes.length) throw new Error('Execute Flow must have a predecessor!') + + const baseURL = (nodeData.inputs?.baseURL as string) || (options.baseURL as string) + const returnValueAs = nodeData.inputs?.returnValueAs as string + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const chatflowApiKey = getCredentialParam('chatflowApiKey', credentialData, nodeData) + + if (selectedFlowId === options.chatflowid) throw new Error('Cannot call the same agentflow!') + + let headers = {} + if (chatflowApiKey) headers = { Authorization: `Bearer ${chatflowApiKey}` } + + const chatflowId = options.chatflowid + const sessionId = options.sessionId + const chatId = options.chatId + + const executeFunc = async (state: ISeqAgentsState) => { + const variables = await getVars(appDataSource, databaseEntities, nodeData) + + let flowInput = '' + if (seqExecuteFlowInput === 'userQuestion') { + flowInput = input + } else if (seqExecuteFlowInput && seqExecuteFlowInput.startsWith('{{') && seqExecuteFlowInput.endsWith('}}')) { + const nodeId = seqExecuteFlowInput.replace('{{', '').replace('}}', '').replace('$', '').trim() + const messageOutputs = ((state.messages as unknown as BaseMessage[]) ?? []).filter( + (message) => message.additional_kwargs && message.additional_kwargs?.nodeId === nodeId + ) + const messageOutput = messageOutputs[messageOutputs.length - 1] + + if (messageOutput) { + flowInput = JSON.stringify(messageOutput.content) + } + } + + const flow = { + chatflowId, + sessionId, + chatId, + input: flowInput, + state + } + + const body = { + question: flowInput, + chatId: startNewSession ? uuidv4() : chatId, + overrideConfig: { + sessionId: startNewSession ? uuidv4() : sessionId, + ...(overrideConfig ?? {}) + } + } + + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...headers + }, + body: JSON.stringify(body) + } + + let sandbox: ICommonObject = { + $input: flowInput, + $callOptions: options, + $callBody: body, + util: undefined, + Symbol: undefined, + child_process: undefined, + fs: undefined, + process: undefined + } + sandbox['$vars'] = prepareSandboxVars(variables) + sandbox['$flow'] = flow + + const code = ` + const fetch = require('node-fetch'); + const url = "${baseURL}/api/v1/prediction/${selectedFlowId}"; + + const body = $callBody; + + const options = $callOptions; + + try { + const response = await fetch(url, options); + const resp = await response.json(); + return resp.text; + } catch (error) { + console.error(error); + return ''; + } +` + + const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP + ? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(',')) + : defaultAllowBuiltInDep + const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : [] + const deps = availableDependencies.concat(externalDeps) + + const nodeVMOptions = { + console: 'inherit', + sandbox, + require: { + external: { modules: deps }, + builtin: builtinDeps + }, + eval: false, + wasm: false, + timeout: 10000 + } as any + + const vm = new NodeVM(nodeVMOptions) + try { + let response = await vm.run(`module.exports = async function() {${code}}()`, __dirname) + + if (typeof response === 'object') { + response = JSON.stringify(response) + } + + if (returnValueAs === 'humanMessage') { + return { + messages: [ + new HumanMessage({ + content: response, + additional_kwargs: { + nodeId: nodeData.id + } + }) + ] + } + } + + return { + messages: [ + new AIMessage({ + content: response, + additional_kwargs: { + nodeId: nodeData.id + } + }) + ] + } + } catch (e) { + throw new Error(e) + } + } + + const startLLM = sequentialNodes[0].startLLM + + const returnOutput: ISeqAgentNode = { + id: nodeData.id, + node: executeFunc, + name: seqExecuteFlowName, + label: _seqExecuteFlowName, + type: 'utilities', + output: 'ExecuteFlow', + llm: startLLM, + startLLM, + multiModalMessageContent: sequentialNodes[0]?.multiModalMessageContent, + predecessorAgents: sequentialNodes + } + + return returnOutput + } +} + +module.exports = { nodeClass: ExecuteFlow_SeqAgents } diff --git a/packages/components/nodes/sequentialagents/ExecuteFlow/executeflow.svg b/packages/components/nodes/sequentialagents/ExecuteFlow/executeflow.svg new file mode 100644 index 00000000000..f97d2511457 --- /dev/null +++ b/packages/components/nodes/sequentialagents/ExecuteFlow/executeflow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/sequentialagents/LLMNode/LLMNode.ts b/packages/components/nodes/sequentialagents/LLMNode/LLMNode.ts index 45216b4b5fd..182f1a41bf4 100644 --- a/packages/components/nodes/sequentialagents/LLMNode/LLMNode.ts +++ b/packages/components/nodes/sequentialagents/LLMNode/LLMNode.ts @@ -182,7 +182,7 @@ class LLMNode_SeqAgents implements INode { constructor() { this.label = 'LLM Node' this.name = 'seqLLMNode' - this.version = 4.0 + this.version = 4.1 this.type = 'LLMNode' this.icon = 'llmNode.svg' this.category = 'Sequential Agents' @@ -261,9 +261,11 @@ class LLMNode_SeqAgents implements INode { additionalParams: true }, { - label: 'Start | Agent | Condition | LLM | Tool Node', + label: 'Sequential Node', name: 'sequentialNode', - type: 'Start | Agent | Condition | LLMNode | ToolNode', + type: 'Start | Agent | Condition | LLMNode | ToolNode | CustomFunction | ExecuteFlow', + description: + 'Can be connected to one of the following nodes: Start, Agent, Condition, LLM, Tool Node, Custom Function, Execute Flow', list: true }, { diff --git a/packages/components/nodes/sequentialagents/Loop/Loop.ts b/packages/components/nodes/sequentialagents/Loop/Loop.ts index 357c0883b89..ba0a012c620 100644 --- a/packages/components/nodes/sequentialagents/Loop/Loop.ts +++ b/packages/components/nodes/sequentialagents/Loop/Loop.ts @@ -17,7 +17,7 @@ class Loop_SeqAgents implements INode { constructor() { this.label = 'Loop' this.name = 'seqLoop' - this.version = 2.0 + this.version = 2.1 this.type = 'Loop' this.icon = 'loop.svg' this.category = 'Sequential Agents' @@ -26,9 +26,11 @@ class Loop_SeqAgents implements INode { this.documentation = 'https://docs.flowiseai.com/using-flowise/agentflows/sequential-agents#id-9.-loop-node' this.inputs = [ { - label: 'Agent | Condition | LLM | Tool Node', + label: 'Sequential Node', name: 'sequentialNode', - type: 'Agent | Condition | LLMNode | ToolNode', + type: 'Agent | Condition | LLMNode | ToolNode | CustomFunction | ExecuteFlow', + description: + 'Can be connected to one of the following nodes: Agent, Condition, LLM Node, Tool Node, Custom Function, Execute Flow', list: true }, { diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index 42e3273cafe..c507106487a 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -94,6 +94,8 @@ export interface INodeParams { tabIdentifier?: string tabs?: Array refresh?: boolean + freeSolo?: boolean + loadPreviousNodes?: boolean } export interface INodeExecutionData { @@ -183,7 +185,7 @@ export interface IMultiAgentNode { checkpointMemory?: any } -type SeqAgentType = 'agent' | 'condition' | 'end' | 'start' | 'tool' | 'state' | 'llm' +type SeqAgentType = 'agent' | 'condition' | 'end' | 'start' | 'tool' | 'state' | 'llm' | 'utilities' export type ConversationHistorySelection = 'user_question' | 'last_message' | 'all_messages' | 'empty' export interface ISeqAgentNode { diff --git a/packages/server/src/utils/buildAgentGraph.ts b/packages/server/src/utils/buildAgentGraph.ts index f3bec805a39..10e412d4d53 100644 --- a/packages/server/src/utils/buildAgentGraph.ts +++ b/packages/server/src/utils/buildAgentGraph.ts @@ -904,8 +904,8 @@ const compileSeqAgentsGraph = async (params: SeqAgentsGraphParams) => { const agentNode = reactFlowNodes.find((node) => node.id === agentNodeId) if (!agentNode) continue - const eligibleSeqNodes = ['seqAgent', 'seqEnd', 'seqLoop', 'seqToolNode', 'seqLLMNode'] - const nodesToAdd = ['seqAgent', 'seqToolNode', 'seqLLMNode'] + const eligibleSeqNodes = ['seqAgent', 'seqEnd', 'seqLoop', 'seqToolNode', 'seqLLMNode', 'seqCustomFunction', 'seqExecuteFlow'] + const nodesToAdd = ['seqAgent', 'seqToolNode', 'seqLLMNode', 'seqCustomFunction', 'seqExecuteFlow'] if (eligibleSeqNodes.includes(agentNode.data.name)) { try { diff --git a/packages/ui/src/ui-component/dropdown/AsyncDropdown.jsx b/packages/ui/src/ui-component/dropdown/AsyncDropdown.jsx index 6427f8bc986..4469fdf9915 100644 --- a/packages/ui/src/ui-component/dropdown/AsyncDropdown.jsx +++ b/packages/ui/src/ui-component/dropdown/AsyncDropdown.jsx @@ -58,6 +58,7 @@ export const AsyncDropdown = ({ onCreateNew, credentialNames = [], disabled = false, + freeSolo = false, disableClearable = false }) => { const customization = useSelector((state) => state.customization) @@ -114,6 +115,7 @@ export const AsyncDropdown = ({ <> { +export const Dropdown = ({ name, value, loading, options, onSelect, disabled = false, freeSolo = false, disableClearable = false }) => { const customization = useSelector((state) => state.customization) const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value) const getDefaultOptionValue = () => '' @@ -29,6 +29,7 @@ export const Dropdown = ({ name, value, loading, options, onSelect, disabled = f { + const preLoadOptions = [] + if (inputParam.loadPreviousNodes) { + const nodes = getAvailableNodesForVariable( + reactFlowInstance?.getNodes() || [], + reactFlowInstance?.getEdges() || [], + data.id, + inputParam.id + ) + for (const node of nodes) { + preLoadOptions.push({ + name: `{{ ${node.data.id} }}`, + label: `{{ ${node.data.id} }}`, + description: `Output from ${node.data.id}` + }) + } + } + return [...preLoadOptions, ...inputParam.options] + } + const getTabValue = (inputParam) => { return inputParam.tabs.findIndex((item) => item.name === data.inputs[`${inputParam.tabIdentifier}_${data.id}`]) >= 0 ? inputParam.tabs.findIndex((item) => item.name === data.inputs[`${inputParam.tabIdentifier}_${data.id}`]) @@ -515,6 +535,20 @@ const NodeInputHandler = ({ {inputParam.description && }
+ {inputParam.hint && !isAdditionalParams && ( + onInputHintDialogClicked(inputParam.hint)} + > + + + )} {inputParam.hint && isAdditionalParams && (