diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cfb7d3a91..04cb80b4d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -138,6 +138,7 @@ Flowise support different environment variables to configure your instance. You | DATABASE_USER | Database username (When DATABASE_TYPE is not sqlite) | String | | | DATABASE_PASSWORD | Database password (When DATABASE_TYPE is not sqlite) | String | | | DATABASE_NAME | Database name (When DATABASE_TYPE is not sqlite) | String | | +| DATABASE_SSL | Database connection overssl (When DATABASE_TYPE is postgre) | Boolean | false | | SECRETKEY_PATH | Location where encryption key (used to encrypt/decrypt credentials) is saved | String | `your-path/Flowise/packages/server` | | FLOWISE_SECRETKEY_OVERWRITE | Encryption key to be used instead of the key stored in SECRETKEY_PATH | String | diff --git a/LICENSE.md b/LICENSE.md index 0f4afcd11..808000018 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -2,22 +2,6 @@ Version 2.0, January 2004 http://www.apache.org/licenses/ -Flowise is governed by the Apache License 2.0, with additional terms and conditions outlined below: - -Flowise can be used for commercial purposes for "backend-as-a-service" for your applications or as a development platform for enterprises. However, under specific conditions, you must reach out to the project's administrators to secure a commercial license: - -a. Multi-tenant SaaS service: Unless you have explicit written authorization from Flowise, you may not utilize the Flowise source code to operate a multi-tenant SaaS service that closely resembles the Flowise cloud-based services. -b. Logo and copyright information: While using Flowise in commercial application, you are prohibited from removing or altering the LOGO or copyright information displayed in the Flowise console and UI. - -For inquiries regarding licensing matters, please contact hello@flowiseai.com via email. - -Contributors are required to consent to the following terms related to their contributed code: - -a. The project maintainers have the authority to modify the open-source agreement to be more stringent or lenient. -b. Contributed code can be used for commercial purposes, including Flowise's cloud-based services. - -All other rights and restrictions are in accordance with the Apache License 2.0. - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. diff --git a/docker/.env.example b/docker/.env.example index 967a1ab6b..7d4f16995 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -12,6 +12,7 @@ LOG_PATH=/root/.flowise/logs # DATABASE_NAME="flowise" # DATABASE_USER="" # DATABASE_PASSWORD="" +# DATABASE_SSL=true # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 8e0e1af50..926884693 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -16,6 +16,7 @@ services: - DATABASE_NAME=${DATABASE_NAME} - DATABASE_USER=${DATABASE_USER} - DATABASE_PASSWORD=${DATABASE_PASSWORD} + - DATABASE_SSL=${DATABASE_SSL} - APIKEY_PATH=${APIKEY_PATH} - SECRETKEY_PATH=${SECRETKEY_PATH} - FLOWISE_SECRETKEY_OVERWRITE=${FLOWISE_SECRETKEY_OVERWRITE} diff --git a/package.json b/package.json index 81fb36379..5a9bfcbf3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.4.7", + "version": "1.4.9", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index 42686ae0f..cf69022ba 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -9,6 +9,8 @@ import fetch from 'node-fetch' import { flatten, uniqWith, isEqual } from 'lodash' import { zodToJsonSchema } from 'zod-to-json-schema' import { AnalyticHandler } from '../../../src/handler' +import { Moderation, checkInputs, streamResponse } from '../../moderation/Moderation' +import { formatResponse } from '../../outputparsers/OutputParserHelpers' class OpenAIAssistant_Agents implements INode { label: string @@ -24,7 +26,7 @@ class OpenAIAssistant_Agents implements INode { constructor() { this.label = 'OpenAI Assistant' this.name = 'openAIAssistant' - this.version = 2.0 + this.version = 3.0 this.type = 'OpenAIAssistant' this.category = 'Agents' this.icon = 'assistant.svg' @@ -43,6 +45,14 @@ class OpenAIAssistant_Agents implements INode { type: 'Tool', list: true }, + { + label: 'Input Moderation', + description: 'Detect text that could generate harmful output and prevent it from being sent to the language model', + name: 'inputModeration', + type: 'Moderation', + optional: true, + list: true + }, { label: 'Disable File Download', name: 'disableFileDownload', @@ -133,6 +143,20 @@ class OpenAIAssistant_Agents implements INode { const appDataSource = options.appDataSource as DataSource const databaseEntities = options.databaseEntities as IDatabaseEntity const disableFileDownload = nodeData.inputs?.disableFileDownload as boolean + const moderations = nodeData.inputs?.inputModeration as Moderation[] + const isStreaming = options.socketIO && options.socketIOClientId + const socketIO = isStreaming ? options.socketIO : undefined + const socketIOClientId = isStreaming ? options.socketIOClientId : '' + + if (moderations && moderations.length > 0) { + try { + input = await checkInputs(moderations, input) + } catch (e) { + await new Promise((resolve) => setTimeout(resolve, 500)) + streamResponse(isStreaming, e.message, socketIO, socketIOClientId) + return formatResponse(e.message) + } + } let tools = nodeData.inputs?.tools tools = flatten(tools) @@ -249,7 +273,12 @@ class OpenAIAssistant_Agents implements INode { const actions: ICommonObject[] = [] run.required_action.submit_tool_outputs.tool_calls.forEach((item) => { const functionCall = item.function - const args = JSON.parse(functionCall.arguments) + let args = {} + try { + args = JSON.parse(functionCall.arguments) + } catch (e) { + console.error('Error parsing arguments, default to empty object') + } actions.push({ tool: functionCall.name, toolInput: args, @@ -264,31 +293,50 @@ class OpenAIAssistant_Agents implements INode { // Start tool analytics const toolIds = await analyticHandlers.onToolStart(tool.name, actions[i].toolInput, parentIds) + if (options.socketIO && options.socketIOClientId) + options.socketIO.to(options.socketIOClientId).emit('tool', tool.name) - const toolOutput = await tool.call(actions[i].toolInput) - - // End tool analytics - await analyticHandlers.onToolEnd(toolIds, toolOutput) - - submitToolOutputs.push({ - tool_call_id: actions[i].toolCallId, - output: toolOutput - }) - usedTools.push({ - tool: tool.name, - toolInput: actions[i].toolInput, - toolOutput - }) + try { + const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, threadId) + await analyticHandlers.onToolEnd(toolIds, toolOutput) + submitToolOutputs.push({ + tool_call_id: actions[i].toolCallId, + output: toolOutput + }) + usedTools.push({ + tool: tool.name, + toolInput: actions[i].toolInput, + toolOutput + }) + } catch (e) { + await analyticHandlers.onToolEnd(toolIds, e) + console.error('Error executing tool', e) + clearInterval(timeout) + reject( + new Error( + `Error processing thread: ${state}, Thread ID: ${threadId}, Run ID: ${runId}, Tool: ${tool.name}` + ) + ) + break + } } - if (submitToolOutputs.length) { - await openai.beta.threads.runs.submitToolOutputs(threadId, runId, { - tool_outputs: submitToolOutputs - }) - resolve(state) - } else { - await openai.beta.threads.runs.cancel(threadId, runId) - resolve('requires_action_retry') + const newRun = await openai.beta.threads.runs.retrieve(threadId, runId) + const newStatus = newRun?.status + + try { + if (submitToolOutputs.length && newStatus === 'requires_action') { + await openai.beta.threads.runs.submitToolOutputs(threadId, runId, { + tool_outputs: submitToolOutputs + }) + resolve(state) + } else { + await openai.beta.threads.runs.cancel(threadId, runId) + resolve('requires_action_retry') + } + } catch (e) { + clearInterval(timeout) + reject(new Error(`Error submitting tool outputs: ${state}, Thread ID: ${threadId}, Run ID: ${runId}`)) } } } else if (state === 'cancelled' || state === 'expired' || state === 'failed') { diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index c019ca5a9..c0095cee1 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -1,10 +1,17 @@ -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { initializeAgentExecutorWithOptions, AgentExecutor } from 'langchain/agents' -import { getBaseClasses, mapChatHistory } from '../../../src/utils' -import { BaseLanguageModel } from 'langchain/base_language' +import { FlowiseMemory, ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { AgentExecutor as LCAgentExecutor, AgentExecutorInput } from 'langchain/agents' +import { ChainValues, AgentStep, AgentFinish, AgentAction, BaseMessage, FunctionMessage, AIMessage } from 'langchain/schema' +import { OutputParserException } from 'langchain/schema/output_parser' +import { CallbackManagerForChainRun } from 'langchain/callbacks' +import { formatToOpenAIFunction } from 'langchain/tools' +import { ToolInputParsingException, Tool } from '@langchain/core/tools' +import { getBaseClasses } from '../../../src/utils' import { flatten } from 'lodash' -import { BaseChatMemory } from 'langchain/memory' +import { RunnableSequence } from 'langchain/schema/runnable' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' +import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts' +import { ChatOpenAI } from 'langchain/chat_models/openai' +import { OpenAIFunctionsAgentOutputParser } from 'langchain/agents/openai/output_parser' class OpenAIFunctionAgent_Agents implements INode { label: string @@ -16,8 +23,9 @@ class OpenAIFunctionAgent_Agents implements INode { category: string baseClasses: string[] inputs: INodeParams[] + sessionId?: string - constructor() { + constructor(fields: { sessionId?: string }) { this.label = 'OpenAI Function Agent' this.name = 'openAIFunctionAgent' this.version = 3.0 @@ -25,7 +33,7 @@ class OpenAIFunctionAgent_Agents implements INode { this.category = 'Agents' this.icon = 'function.svg' this.description = `An agent that uses Function Calling to pick the tool and args to call` - this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)] + this.baseClasses = [this.type, ...getBaseClasses(LCAgentExecutor)] this.inputs = [ { label: 'Allowed Tools', @@ -52,54 +60,324 @@ class OpenAIFunctionAgent_Agents implements INode { additionalParams: true } ] + this.sessionId = fields?.sessionId } async init(nodeData: INodeData): Promise { - const model = nodeData.inputs?.model as BaseLanguageModel - const memory = nodeData.inputs?.memory as BaseChatMemory - const systemMessage = nodeData.inputs?.systemMessage as string + const memory = nodeData.inputs?.memory as FlowiseMemory - let tools = nodeData.inputs?.tools - tools = flatten(tools) - - const executor = await initializeAgentExecutorWithOptions(tools, model, { - agentType: 'openai-functions', - verbose: process.env.DEBUG === 'true' ? true : false, - agentArgs: { - prefix: systemMessage ?? `You are a helpful AI assistant.` - } - }) + const executor = prepareAgent(nodeData, this.sessionId) if (memory) executor.memory = memory return executor } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { - const executor = nodeData.instance as AgentExecutor - const memory = nodeData.inputs?.memory as BaseChatMemory + const memory = nodeData.inputs?.memory as FlowiseMemory - if (options && options.chatHistory) { - const chatHistoryClassName = memory.chatHistory.constructor.name - // Only replace when its In-Memory - if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { - memory.chatHistory = mapChatHistory(options) - executor.memory = memory - } - } - - ;(executor.memory as any).returnMessages = true // Return true for BaseChatModel + const executor = prepareAgent(nodeData, this.sessionId) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) + let res: ChainValues = {} + if (options.socketIO && options.socketIOClientId) { const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId) - const result = await executor.run(input, [loggerHandler, handler, ...callbacks]) - return result + res = await executor.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] }) } else { - const result = await executor.run(input, [loggerHandler, ...callbacks]) - return result + res = await executor.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] }) } + + await memory.addChatMessages( + [ + { + text: input, + type: 'userMessage' + }, + { + text: res?.output, + type: 'apiMessage' + } + ], + this.sessionId + ) + + return res?.output + } +} + +const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] => + steps.flatMap(({ action, observation }) => { + if ('messageLog' in action && action.messageLog !== undefined) { + const log = action.messageLog as BaseMessage[] + return log.concat(new FunctionMessage(observation, action.tool)) + } else { + return [new AIMessage(action.log)] + } + }) + +const prepareAgent = (nodeData: INodeData, sessionId?: string) => { + const model = nodeData.inputs?.model as ChatOpenAI + const memory = nodeData.inputs?.memory as FlowiseMemory + const systemMessage = nodeData.inputs?.systemMessage as string + let tools = nodeData.inputs?.tools + tools = flatten(tools) + const memoryKey = memory.memoryKey ? memory.memoryKey : 'chat_history' + const inputKey = memory.inputKey ? memory.inputKey : 'input' + + const prompt = ChatPromptTemplate.fromMessages([ + ['ai', systemMessage ? systemMessage : `You are a helpful AI assistant.`], + new MessagesPlaceholder(memoryKey), + ['human', `{${inputKey}}`], + new MessagesPlaceholder('agent_scratchpad') + ]) + + const modelWithFunctions = model.bind({ + functions: [...tools.map((tool: any) => formatToOpenAIFunction(tool))] + }) + + const runnableAgent = RunnableSequence.from([ + { + [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, + agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps), + [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { + const messages = (await memory.getChatMessages(sessionId, true)) as BaseMessage[] + return messages ?? [] + } + }, + prompt, + modelWithFunctions, + new OpenAIFunctionsAgentOutputParser() + ]) + + const executor = AgentExecutor.fromAgentAndTools({ + agent: runnableAgent, + tools, + sessionId + }) + + return executor +} + +type AgentExecutorOutput = ChainValues + +class AgentExecutor extends LCAgentExecutor { + sessionId?: string + + static fromAgentAndTools(fields: AgentExecutorInput & { sessionId?: string }): AgentExecutor { + const newInstance = new AgentExecutor(fields) + if (fields.sessionId) newInstance.sessionId = fields.sessionId + return newInstance + } + + shouldContinueIteration(iterations: number): boolean { + return this.maxIterations === undefined || iterations < this.maxIterations + } + + async _call(inputs: ChainValues, runManager?: CallbackManagerForChainRun): Promise { + const toolsByName = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t])) + + const steps: AgentStep[] = [] + let iterations = 0 + + const getOutput = async (finishStep: AgentFinish): Promise => { + const { returnValues } = finishStep + const additional = await this.agent.prepareForOutput(returnValues, steps) + + if (this.returnIntermediateSteps) { + return { ...returnValues, intermediateSteps: steps, ...additional } + } + await runManager?.handleAgentEnd(finishStep) + return { ...returnValues, ...additional } + } + + while (this.shouldContinueIteration(iterations)) { + let output + try { + output = await this.agent.plan(steps, inputs, runManager?.getChild()) + } catch (e) { + if (e instanceof OutputParserException) { + let observation + let text = e.message + if (this.handleParsingErrors === true) { + if (e.sendToLLM) { + observation = e.observation + text = e.llmOutput ?? '' + } else { + observation = 'Invalid or incomplete response' + } + } else if (typeof this.handleParsingErrors === 'string') { + observation = this.handleParsingErrors + } else if (typeof this.handleParsingErrors === 'function') { + observation = this.handleParsingErrors(e) + } else { + throw e + } + output = { + tool: '_Exception', + toolInput: observation, + log: text + } as AgentAction + } else { + throw e + } + } + // Check if the agent has finished + if ('returnValues' in output) { + return getOutput(output) + } + + let actions: AgentAction[] + if (Array.isArray(output)) { + actions = output as AgentAction[] + } else { + actions = [output as AgentAction] + } + + const newSteps = await Promise.all( + actions.map(async (action) => { + await runManager?.handleAgentAction(action) + const tool = action.tool === '_Exception' ? new ExceptionTool() : toolsByName[action.tool?.toLowerCase()] + let observation + try { + // here we need to override Tool call method to include sessionId as parameter + observation = tool + ? // @ts-ignore + await tool.call(action.toolInput, runManager?.getChild(), undefined, this.sessionId) + : `${action.tool} is not a valid tool, try another one.` + } catch (e) { + if (e instanceof ToolInputParsingException) { + if (this.handleParsingErrors === true) { + observation = 'Invalid or incomplete tool input. Please try again.' + } else if (typeof this.handleParsingErrors === 'string') { + observation = this.handleParsingErrors + } else if (typeof this.handleParsingErrors === 'function') { + observation = this.handleParsingErrors(e) + } else { + throw e + } + observation = await new ExceptionTool().call(observation, runManager?.getChild()) + return { action, observation: observation ?? '' } + } + } + return { action, observation: observation ?? '' } + }) + ) + + steps.push(...newSteps) + + const lastStep = steps[steps.length - 1] + const lastTool = toolsByName[lastStep.action.tool?.toLowerCase()] + + if (lastTool?.returnDirect) { + return getOutput({ + returnValues: { [this.agent.returnValues[0]]: lastStep.observation }, + log: '' + }) + } + + iterations += 1 + } + + const finish = await this.agent.returnStoppedResponse(this.earlyStoppingMethod, steps, inputs) + + return getOutput(finish) + } + + async _takeNextStep( + nameToolMap: Record, + inputs: ChainValues, + intermediateSteps: AgentStep[], + runManager?: CallbackManagerForChainRun + ): Promise { + let output + try { + output = await this.agent.plan(intermediateSteps, inputs, runManager?.getChild()) + } catch (e) { + if (e instanceof OutputParserException) { + let observation + let text = e.message + if (this.handleParsingErrors === true) { + if (e.sendToLLM) { + observation = e.observation + text = e.llmOutput ?? '' + } else { + observation = 'Invalid or incomplete response' + } + } else if (typeof this.handleParsingErrors === 'string') { + observation = this.handleParsingErrors + } else if (typeof this.handleParsingErrors === 'function') { + observation = this.handleParsingErrors(e) + } else { + throw e + } + output = { + tool: '_Exception', + toolInput: observation, + log: text + } as AgentAction + } else { + throw e + } + } + + if ('returnValues' in output) { + return output + } + + let actions: AgentAction[] + if (Array.isArray(output)) { + actions = output as AgentAction[] + } else { + actions = [output as AgentAction] + } + + const result: AgentStep[] = [] + for (const agentAction of actions) { + let observation = '' + if (runManager) { + await runManager?.handleAgentAction(agentAction) + } + if (agentAction.tool in nameToolMap) { + const tool = nameToolMap[agentAction.tool] + try { + // here we need to override Tool call method to include sessionId as parameter + // @ts-ignore + observation = await tool.call(agentAction.toolInput, runManager?.getChild(), undefined, this.sessionId) + } catch (e) { + if (e instanceof ToolInputParsingException) { + if (this.handleParsingErrors === true) { + observation = 'Invalid or incomplete tool input. Please try again.' + } else if (typeof this.handleParsingErrors === 'string') { + observation = this.handleParsingErrors + } else if (typeof this.handleParsingErrors === 'function') { + observation = this.handleParsingErrors(e) + } else { + throw e + } + observation = await new ExceptionTool().call(observation, runManager?.getChild()) + } + } + } else { + observation = `${agentAction.tool} is not a valid tool, try another available tool: ${Object.keys(nameToolMap).join(', ')}` + } + result.push({ + action: agentAction, + observation + }) + } + return result + } +} + +class ExceptionTool extends Tool { + name = '_Exception' + + description = 'Exception tool' + + async _call(query: string) { + return query } } diff --git a/packages/components/nodes/chatmodels/AWSBedrock/aws.svg b/packages/components/nodes/chatmodels/AWSBedrock/aws.svg index 0e630a3bf..d783497e8 100644 --- a/packages/components/nodes/chatmodels/AWSBedrock/aws.svg +++ b/packages/components/nodes/chatmodels/AWSBedrock/aws.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/Azure.svg b/packages/components/nodes/chatmodels/AzureChatOpenAI/Azure.svg index 47ad8c440..7b1508111 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/Azure.svg +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/Azure.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts index 99e151e6c..9b7b724a2 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts @@ -1,7 +1,6 @@ -import { OpenAIBaseInput } from 'langchain/dist/types/openai-types' import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' -import { AzureOpenAIInput, ChatOpenAI } from 'langchain/chat_models/openai' +import { AzureOpenAIInput, ChatOpenAI, OpenAIChatInput } from 'langchain/chat_models/openai' import { BaseCache } from 'langchain/schema' import { BaseLLMParams } from 'langchain/llms/base' @@ -123,7 +122,7 @@ class AzureChatOpenAI_ChatModels implements INode { const azureOpenAIApiDeploymentName = getCredentialParam('azureOpenAIApiDeploymentName', credentialData, nodeData) const azureOpenAIApiVersion = getCredentialParam('azureOpenAIApiVersion', credentialData, nodeData) - const obj: Partial & BaseLLMParams & Partial = { + const obj: Partial & BaseLLMParams & Partial = { temperature: parseFloat(temperature), modelName, azureOpenAIApiKey, diff --git a/packages/components/nodes/chatmodels/Bittensor/Bittensor.ts b/packages/components/nodes/chatmodels/Bittensor/Bittensor.ts index 36b084e64..9563ea43c 100644 --- a/packages/components/nodes/chatmodels/Bittensor/Bittensor.ts +++ b/packages/components/nodes/chatmodels/Bittensor/Bittensor.ts @@ -19,7 +19,7 @@ class Bittensor_ChatModels implements INode { this.name = 'NIBittensorChatModel' this.version = 2.0 this.type = 'BittensorChat' - this.icon = 'logo.png' + this.icon = 'NIBittensor.svg' this.category = 'Chat Models' this.description = 'Wrapper around Bittensor subnet 1 large language models' this.baseClasses = [this.type, ...getBaseClasses(NIBittensorChatModel)] diff --git a/packages/components/nodes/chatmodels/Bittensor/NIBittensor.svg b/packages/components/nodes/chatmodels/Bittensor/NIBittensor.svg new file mode 100644 index 000000000..062cd66bb --- /dev/null +++ b/packages/components/nodes/chatmodels/Bittensor/NIBittensor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/chatmodels/Bittensor/logo.png b/packages/components/nodes/chatmodels/Bittensor/logo.png deleted file mode 100644 index ad51774d5..000000000 Binary files a/packages/components/nodes/chatmodels/Bittensor/logo.png and /dev/null differ diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/Anthropic.svg b/packages/components/nodes/chatmodels/ChatAnthropic/Anthropic.svg new file mode 100644 index 000000000..84bc18ca5 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatAnthropic/Anthropic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts index 358a15d1e..599578f5a 100644 --- a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts +++ b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts @@ -21,7 +21,7 @@ class ChatAnthropic_ChatModels implements INode { this.name = 'chatAnthropic' this.version = 3.0 this.type = 'ChatAnthropic' - this.icon = 'chatAnthropic.png' + this.icon = 'Anthropic.svg' this.category = 'Chat Models' this.description = 'Wrapper around ChatAnthropic large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatAnthropic)] diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/chatAnthropic.png b/packages/components/nodes/chatmodels/ChatAnthropic/chatAnthropic.png deleted file mode 100644 index 42324cb7c..000000000 Binary files a/packages/components/nodes/chatmodels/ChatAnthropic/chatAnthropic.png and /dev/null differ diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts index 7044645f6..546fa224c 100644 --- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts @@ -20,7 +20,7 @@ class GoogleGenerativeAI_ChatModels implements INode { this.name = 'chatGoogleGenerativeAI' this.version = 1.0 this.type = 'ChatGoogleGenerativeAI' - this.icon = 'gemini.png' + this.icon = 'GoogleGemini.svg' this.category = 'Chat Models' this.description = 'Wrapper around Google Gemini large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatGoogleGenerativeAI)] diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/GoogleGemini.svg b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/GoogleGemini.svg new file mode 100644 index 000000000..53b497fa1 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/GoogleGemini.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/gemini.png b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/gemini.png deleted file mode 100644 index 6c0d60f44..000000000 Binary files a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/gemini.png and /dev/null differ diff --git a/packages/components/nodes/chatmodels/ChatGooglePaLM/ChatGooglePaLM.ts b/packages/components/nodes/chatmodels/ChatGooglePaLM/ChatGooglePaLM.ts index 121406c76..ab7a6169b 100644 --- a/packages/components/nodes/chatmodels/ChatGooglePaLM/ChatGooglePaLM.ts +++ b/packages/components/nodes/chatmodels/ChatGooglePaLM/ChatGooglePaLM.ts @@ -20,7 +20,7 @@ class ChatGooglePaLM_ChatModels implements INode { this.name = 'chatGooglePaLM' this.version = 2.0 this.type = 'ChatGooglePaLM' - this.icon = 'Google_PaLM_Logo.svg' + this.icon = 'GooglePaLM.svg' this.category = 'Chat Models' this.description = 'Wrapper around Google MakerSuite PaLM large language models using the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatGooglePaLM)] diff --git a/packages/components/nodes/chatmodels/ChatGooglePaLM/GooglePaLM.svg b/packages/components/nodes/chatmodels/ChatGooglePaLM/GooglePaLM.svg new file mode 100644 index 000000000..ed47326a9 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatGooglePaLM/GooglePaLM.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/chatmodels/ChatGooglePaLM/Google_PaLM_Logo.svg b/packages/components/nodes/chatmodels/ChatGooglePaLM/Google_PaLM_Logo.svg deleted file mode 100644 index 5c345fe1c..000000000 --- a/packages/components/nodes/chatmodels/ChatGooglePaLM/Google_PaLM_Logo.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/components/nodes/chatmodels/ChatGoogleVertexAI/ChatGoogleVertexAI.ts b/packages/components/nodes/chatmodels/ChatGoogleVertexAI/ChatGoogleVertexAI.ts index 6b070bd93..4c961853b 100644 --- a/packages/components/nodes/chatmodels/ChatGoogleVertexAI/ChatGoogleVertexAI.ts +++ b/packages/components/nodes/chatmodels/ChatGoogleVertexAI/ChatGoogleVertexAI.ts @@ -21,7 +21,7 @@ class GoogleVertexAI_ChatModels implements INode { this.name = 'chatGoogleVertexAI' this.version = 2.0 this.type = 'ChatGoogleVertexAI' - this.icon = 'vertexai.svg' + this.icon = 'GoogleVertex.svg' this.category = 'Chat Models' this.description = 'Wrapper around VertexAI large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatGoogleVertexAI)] diff --git a/packages/components/nodes/chatmodels/ChatGoogleVertexAI/GoogleVertex.svg b/packages/components/nodes/chatmodels/ChatGoogleVertexAI/GoogleVertex.svg new file mode 100644 index 000000000..a517740fe --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatGoogleVertexAI/GoogleVertex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/chatmodels/ChatGoogleVertexAI/vertexai.svg b/packages/components/nodes/chatmodels/ChatGoogleVertexAI/vertexai.svg deleted file mode 100644 index 31244412a..000000000 --- a/packages/components/nodes/chatmodels/ChatGoogleVertexAI/vertexai.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts index 153c5d100..dff78193f 100644 --- a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts @@ -20,7 +20,7 @@ class ChatHuggingFace_ChatModels implements INode { this.name = 'chatHuggingFace' this.version = 2.0 this.type = 'ChatHuggingFace' - this.icon = 'huggingface.png' + this.icon = 'HuggingFace.svg' this.category = 'Chat Models' this.description = 'Wrapper around HuggingFace large language models' this.baseClasses = [this.type, 'BaseChatModel', ...getBaseClasses(HuggingFaceInference)] diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/HuggingFace.svg b/packages/components/nodes/chatmodels/ChatHuggingFace/HuggingFace.svg new file mode 100644 index 000000000..58c85d57e --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/HuggingFace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/huggingface.png b/packages/components/nodes/chatmodels/ChatHuggingFace/huggingface.png deleted file mode 100644 index f8f202a46..000000000 Binary files a/packages/components/nodes/chatmodels/ChatHuggingFace/huggingface.png and /dev/null differ diff --git a/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts index 2548dd991..4524db462 100644 --- a/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts +++ b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts @@ -20,7 +20,7 @@ class ChatMistral_ChatModels implements INode { this.name = 'chatMistralAI' this.version = 1.0 this.type = 'ChatMistralAI' - this.icon = 'mistralai.png' + this.icon = 'MistralAI.svg' this.category = 'Chat Models' this.description = 'Wrapper around Mistral large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatMistralAI)] @@ -124,13 +124,13 @@ class ChatMistral_ChatModels implements INode { const safeMode = nodeData.inputs?.safeMode as boolean const randomSeed = nodeData.inputs?.safeMode as string const overrideEndpoint = nodeData.inputs?.overrideEndpoint as string - // Waiting fix from langchain + mistral to enable streaming - https://github.com/mistralai/client-js/issues/18 - + const streaming = nodeData.inputs?.streaming as boolean const cache = nodeData.inputs?.cache as BaseCache const obj: ChatMistralAIInput = { apiKey: apiKey, - modelName: modelName + modelName: modelName, + streaming: streaming ?? true } if (maxOutputTokens) obj.maxTokens = parseInt(maxOutputTokens, 10) diff --git a/packages/components/nodes/chatmodels/ChatMistral/MistralAI.svg b/packages/components/nodes/chatmodels/ChatMistral/MistralAI.svg new file mode 100644 index 000000000..aa84b39c5 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatMistral/MistralAI.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/chatmodels/ChatMistral/mistralai.png b/packages/components/nodes/chatmodels/ChatMistral/mistralai.png deleted file mode 100644 index 1019f495d..000000000 Binary files a/packages/components/nodes/chatmodels/ChatMistral/mistralai.png and /dev/null differ diff --git a/packages/components/nodes/chatmodels/ChatOllama/ChatOllama.ts b/packages/components/nodes/chatmodels/ChatOllama/ChatOllama.ts index 31267743f..d445c7e12 100644 --- a/packages/components/nodes/chatmodels/ChatOllama/ChatOllama.ts +++ b/packages/components/nodes/chatmodels/ChatOllama/ChatOllama.ts @@ -1,8 +1,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { ChatOllama } from 'langchain/chat_models/ollama' +import { ChatOllama, ChatOllamaInput } from 'langchain/chat_models/ollama' import { BaseCache } from 'langchain/schema' -import { OllamaInput } from 'langchain/dist/util/ollama' import { BaseLLMParams } from 'langchain/llms/base' class ChatOllama_ChatModels implements INode { @@ -22,7 +21,7 @@ class ChatOllama_ChatModels implements INode { this.name = 'chatOllama' this.version = 2.0 this.type = 'ChatOllama' - this.icon = 'ollama.png' + this.icon = 'Ollama.svg' this.category = 'Chat Models' this.description = 'Chat completion using open-source LLM on Ollama' this.baseClasses = [this.type, ...getBaseClasses(ChatOllama)] @@ -209,7 +208,7 @@ class ChatOllama_ChatModels implements INode { const cache = nodeData.inputs?.cache as BaseCache - const obj: OllamaInput & BaseLLMParams = { + const obj: ChatOllamaInput & BaseLLMParams = { baseUrl, temperature: parseFloat(temperature), model: modelName diff --git a/packages/components/nodes/chatmodels/ChatOllama/Ollama.svg b/packages/components/nodes/chatmodels/ChatOllama/Ollama.svg new file mode 100644 index 000000000..2dc8df531 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatOllama/Ollama.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/chatmodels/ChatOllama/ollama.png b/packages/components/nodes/chatmodels/ChatOllama/ollama.png deleted file mode 100644 index 8cd2cf1ed..000000000 Binary files a/packages/components/nodes/chatmodels/ChatOllama/ollama.png and /dev/null differ diff --git a/packages/components/nodes/documentloaders/Airtable/Airtable.ts b/packages/components/nodes/documentloaders/Airtable/Airtable.ts index 70d0c674a..9a824ac9a 100644 --- a/packages/components/nodes/documentloaders/Airtable/Airtable.ts +++ b/packages/components/nodes/documentloaders/Airtable/Airtable.ts @@ -20,7 +20,7 @@ class Airtable_DocumentLoaders implements INode { constructor() { this.label = 'Airtable' this.name = 'airtable' - this.version = 1.0 + this.version = 2.0 this.type = 'Document' this.icon = 'airtable.svg' this.category = 'Document Loaders' @@ -55,6 +55,15 @@ class Airtable_DocumentLoaders implements INode { description: 'If your table URL looks like: https://airtable.com/app11RobdGoX0YNsC/tblJdmvbrgizbYICO/viw9UrP77Id0CE4ee, tblJdmvbrgizbYICO is the table id' }, + { + label: 'View Id', + name: 'viewId', + type: 'string', + placeholder: 'viw9UrP77Id0CE4ee', + description: + 'If your view URL looks like: https://airtable.com/app11RobdGoX0YNsC/tblJdmvbrgizbYICO/viw9UrP77Id0CE4ee, viw9UrP77Id0CE4ee is the view id', + optional: true + }, { label: 'Return All', name: 'returnAll', @@ -83,6 +92,7 @@ class Airtable_DocumentLoaders implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const baseId = nodeData.inputs?.baseId as string const tableId = nodeData.inputs?.tableId as string + const viewId = nodeData.inputs?.viewId as string const returnAll = nodeData.inputs?.returnAll as boolean const limit = nodeData.inputs?.limit as string const textSplitter = nodeData.inputs?.textSplitter as TextSplitter @@ -94,6 +104,7 @@ class Airtable_DocumentLoaders implements INode { const airtableOptions: AirtableLoaderParams = { baseId, tableId, + viewId, returnAll, accessToken, limit: limit ? parseInt(limit, 10) : 100 @@ -133,6 +144,7 @@ interface AirtableLoaderParams { baseId: string tableId: string accessToken: string + viewId?: string limit?: number returnAll?: boolean } @@ -153,16 +165,19 @@ class AirtableLoader extends BaseDocumentLoader { public readonly tableId: string + public readonly viewId?: string + public readonly accessToken: string public readonly limit: number public readonly returnAll: boolean - constructor({ baseId, tableId, accessToken, limit = 100, returnAll = false }: AirtableLoaderParams) { + constructor({ baseId, tableId, viewId, accessToken, limit = 100, returnAll = false }: AirtableLoaderParams) { super() this.baseId = baseId this.tableId = tableId + this.viewId = viewId this.accessToken = accessToken this.limit = limit this.returnAll = returnAll @@ -203,7 +218,7 @@ class AirtableLoader extends BaseDocumentLoader { } private async loadLimit(): Promise { - const params = { maxRecords: this.limit } + const params = { maxRecords: this.limit, view: this.viewId } const data = await this.fetchAirtableData(`https://api.airtable.com/v0/${this.baseId}/${this.tableId}`, params) if (data.records.length === 0) { return [] @@ -212,7 +227,7 @@ class AirtableLoader extends BaseDocumentLoader { } private async loadAll(): Promise { - const params: ICommonObject = { pageSize: 100 } + const params: ICommonObject = { pageSize: 100, view: this.viewId } let data: AirtableLoaderResponse let returnPages: AirtableLoaderPage[] = [] diff --git a/packages/components/nodes/documentloaders/Notion/NotionDB.ts b/packages/components/nodes/documentloaders/Notion/NotionDB.ts index 5c171c7f0..4e37ad224 100644 --- a/packages/components/nodes/documentloaders/Notion/NotionDB.ts +++ b/packages/components/nodes/documentloaders/Notion/NotionDB.ts @@ -66,6 +66,10 @@ class NotionDB_DocumentLoaders implements INode { auth: notionIntegrationToken }, id: databaseId, + callerOptions: { + maxConcurrency: 64 // Default value + }, + propertiesAsHeader: true, // Prepends a front matter header of the page properties to the page contents type: 'database' } const loader = new NotionAPILoader(obj) diff --git a/packages/components/nodes/documentloaders/S3File/S3File.ts b/packages/components/nodes/documentloaders/S3File/S3File.ts index 58ffd8af7..eadb4d992 100644 --- a/packages/components/nodes/documentloaders/S3File/S3File.ts +++ b/packages/components/nodes/documentloaders/S3File/S3File.ts @@ -30,7 +30,7 @@ class S3_DocumentLoaders implements INode { constructor() { this.label = 'S3' this.name = 'S3' - this.version = 1.0 + this.version = 2.0 this.type = 'Document' this.icon = 's3.svg' this.category = 'Document Loaders' @@ -113,12 +113,62 @@ class S3_DocumentLoaders implements INode { optional: true }, { - label: 'NarrativeText Only', - name: 'narrativeTextOnly', + label: 'Element Type', + name: 'elementType', description: - 'Only load documents with NarrativeText metadata from Unstructured. See how Unstructured partition data here', - default: true, - type: 'boolean', + 'Unstructured partition document into different types, select the types to return. If not selected, all types will be returned', + type: 'multiOptions', + options: [ + { + label: 'FigureCaption', + name: 'FigureCaption' + }, + { + label: 'NarrativeText', + name: 'NarrativeText' + }, + { + label: 'ListItem', + name: 'ListItem' + }, + { + label: 'Title', + name: 'Title' + }, + { + label: 'Address', + name: 'Address' + }, + { + label: 'Table', + name: 'Table' + }, + { + label: 'PageBreak', + name: 'PageBreak' + }, + { + label: 'Header', + name: 'Header' + }, + { + label: 'Footer', + name: 'Footer' + }, + { + label: 'UncategorizedText', + name: 'UncategorizedText' + }, + { + label: 'Image', + name: 'Image' + }, + { + label: 'Formula', + name: 'Formula' + } + ], + default: [], optional: true, additionalParams: true }, @@ -138,7 +188,7 @@ class S3_DocumentLoaders implements INode { const unstructuredAPIUrl = nodeData.inputs?.unstructuredAPIUrl as string const unstructuredAPIKey = nodeData.inputs?.unstructuredAPIKey as string const metadata = nodeData.inputs?.metadata - const narrativeTextOnly = nodeData.inputs?.narrativeTextOnly as boolean + const elementType = nodeData.inputs?.elementType as string const credentialData = await getCredentialData(nodeData.credential ?? '', options) const accessKeyId = getCredentialParam('awsKey', credentialData, nodeData) @@ -169,6 +219,15 @@ class S3_DocumentLoaders implements INode { } } + let elementTypes: string[] = [] + if (elementType) { + try { + elementTypes = JSON.parse(elementType) + } catch (e) { + elementTypes = [] + } + } + loader.load = async () => { const tempDir = fsDefault.mkdtempSync(path.join(os.tmpdir(), 's3fileloader-')) @@ -235,10 +294,10 @@ class S3_DocumentLoaders implements INode { } } }) - return narrativeTextOnly ? finaldocs.filter((doc) => doc.metadata.category === 'NarrativeText') : finaldocs + return elementTypes.length ? finaldocs.filter((doc) => elementTypes.includes(doc.metadata.category)) : finaldocs } - return narrativeTextOnly ? docs.filter((doc) => doc.metadata.category === 'NarrativeText') : docs + return elementTypes.length ? docs.filter((doc) => elementTypes.includes(doc.metadata.category)) : docs } } module.exports = { nodeClass: S3_DocumentLoaders } diff --git a/packages/components/nodes/embeddings/AWSBedrockEmbedding/aws.svg b/packages/components/nodes/embeddings/AWSBedrockEmbedding/aws.svg index 0e630a3bf..d783497e8 100644 --- a/packages/components/nodes/embeddings/AWSBedrockEmbedding/aws.svg +++ b/packages/components/nodes/embeddings/AWSBedrockEmbedding/aws.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/Azure.svg b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/Azure.svg index 47ad8c440..7b1508111 100644 --- a/packages/components/nodes/embeddings/AzureOpenAIEmbedding/Azure.svg +++ b/packages/components/nodes/embeddings/AzureOpenAIEmbedding/Azure.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/components/nodes/embeddings/CohereEmbedding/Cohere.svg b/packages/components/nodes/embeddings/CohereEmbedding/Cohere.svg new file mode 100644 index 000000000..88bcabe34 --- /dev/null +++ b/packages/components/nodes/embeddings/CohereEmbedding/Cohere.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/embeddings/CohereEmbedding/CohereEmbedding.ts b/packages/components/nodes/embeddings/CohereEmbedding/CohereEmbedding.ts index b42a0357e..92d0fe7db 100644 --- a/packages/components/nodes/embeddings/CohereEmbedding/CohereEmbedding.ts +++ b/packages/components/nodes/embeddings/CohereEmbedding/CohereEmbedding.ts @@ -19,7 +19,7 @@ class CohereEmbedding_Embeddings implements INode { this.name = 'cohereEmbeddings' this.version = 1.0 this.type = 'CohereEmbeddings' - this.icon = 'cohere.png' + this.icon = 'Cohere.svg' this.category = 'Embeddings' this.description = 'Cohere API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(CohereEmbeddings)] diff --git a/packages/components/nodes/embeddings/CohereEmbedding/cohere.png b/packages/components/nodes/embeddings/CohereEmbedding/cohere.png deleted file mode 100644 index 266adeac2..000000000 Binary files a/packages/components/nodes/embeddings/CohereEmbedding/cohere.png and /dev/null differ diff --git a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGemini.svg b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGemini.svg new file mode 100644 index 000000000..53b497fa1 --- /dev/null +++ b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGemini.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts index fa5cff450..ac84fd27e 100644 --- a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts @@ -20,7 +20,7 @@ class GoogleGenerativeAIEmbedding_Embeddings implements INode { this.name = 'googleGenerativeAiEmbeddings' this.version = 1.0 this.type = 'GoogleGenerativeAiEmbeddings' - this.icon = 'gemini.png' + this.icon = 'GoogleGemini.svg' this.category = 'Embeddings' this.description = 'Google Generative API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(GoogleGenerativeAIEmbeddings)] diff --git a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/gemini.png b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/gemini.png deleted file mode 100644 index 6c0d60f44..000000000 Binary files a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/gemini.png and /dev/null differ diff --git a/packages/components/nodes/embeddings/GooglePaLMEmbedding/GooglePaLM.svg b/packages/components/nodes/embeddings/GooglePaLMEmbedding/GooglePaLM.svg new file mode 100644 index 000000000..ed47326a9 --- /dev/null +++ b/packages/components/nodes/embeddings/GooglePaLMEmbedding/GooglePaLM.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/embeddings/GooglePaLMEmbedding/GooglePaLMEmbedding.ts b/packages/components/nodes/embeddings/GooglePaLMEmbedding/GooglePaLMEmbedding.ts index 81507d00e..d003a928f 100644 --- a/packages/components/nodes/embeddings/GooglePaLMEmbedding/GooglePaLMEmbedding.ts +++ b/packages/components/nodes/embeddings/GooglePaLMEmbedding/GooglePaLMEmbedding.ts @@ -19,7 +19,7 @@ class GooglePaLMEmbedding_Embeddings implements INode { this.name = 'googlePaLMEmbeddings' this.version = 1.0 this.type = 'GooglePaLMEmbeddings' - this.icon = 'Google_PaLM_Logo.svg' + this.icon = 'GooglePaLM.svg' this.category = 'Embeddings' this.description = 'Google MakerSuite PaLM API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(GooglePaLMEmbeddings)] diff --git a/packages/components/nodes/embeddings/GooglePaLMEmbedding/Google_PaLM_Logo.svg b/packages/components/nodes/embeddings/GooglePaLMEmbedding/Google_PaLM_Logo.svg deleted file mode 100644 index 5c345fe1c..000000000 --- a/packages/components/nodes/embeddings/GooglePaLMEmbedding/Google_PaLM_Logo.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertex.svg b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertex.svg new file mode 100644 index 000000000..a517740fe --- /dev/null +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts index 7d086e0cd..e60f688dd 100644 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/GoogleVertexAIEmbedding.ts @@ -20,7 +20,7 @@ class GoogleVertexAIEmbedding_Embeddings implements INode { this.name = 'googlevertexaiEmbeddings' this.version = 1.0 this.type = 'GoogleVertexAIEmbeddings' - this.icon = 'vertexai.svg' + this.icon = 'GoogleVertex.svg' this.category = 'Embeddings' this.description = 'Google vertexAI API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(GoogleVertexAIEmbeddings)] diff --git a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg b/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg deleted file mode 100644 index 31244412a..000000000 --- a/packages/components/nodes/embeddings/GoogleVertexAIEmbedding/vertexai.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFace.svg b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFace.svg new file mode 100644 index 000000000..58c85d57e --- /dev/null +++ b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts index 6d75b9559..5768f1d95 100644 --- a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts +++ b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/HuggingFaceInferenceEmbedding.ts @@ -19,7 +19,7 @@ class HuggingFaceInferenceEmbedding_Embeddings implements INode { this.name = 'huggingFaceInferenceEmbeddings' this.version = 1.0 this.type = 'HuggingFaceInferenceEmbeddings' - this.icon = 'huggingface.png' + this.icon = 'HuggingFace.svg' this.category = 'Embeddings' this.description = 'HuggingFace Inference API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(HuggingFaceInferenceEmbeddings)] diff --git a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/huggingface.png b/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/huggingface.png deleted file mode 100644 index f8f202a46..000000000 Binary files a/packages/components/nodes/embeddings/HuggingFaceInferenceEmbedding/huggingface.png and /dev/null differ diff --git a/packages/components/nodes/embeddings/MistralEmbedding/MistralAI.svg b/packages/components/nodes/embeddings/MistralEmbedding/MistralAI.svg new file mode 100644 index 000000000..aa84b39c5 --- /dev/null +++ b/packages/components/nodes/embeddings/MistralEmbedding/MistralAI.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/embeddings/MistralEmbedding/MistralEmbedding.ts b/packages/components/nodes/embeddings/MistralEmbedding/MistralEmbedding.ts index d0a0198c3..9ad63533f 100644 --- a/packages/components/nodes/embeddings/MistralEmbedding/MistralEmbedding.ts +++ b/packages/components/nodes/embeddings/MistralEmbedding/MistralEmbedding.ts @@ -19,7 +19,7 @@ class MistralEmbedding_Embeddings implements INode { this.name = 'mistralAI Embeddings' this.version = 1.0 this.type = 'MistralAIEmbeddings' - this.icon = 'mistralai.png' + this.icon = 'MistralAI.svg' this.category = 'Embeddings' this.description = 'MistralAI API to generate embeddings for a given text' this.baseClasses = [this.type, ...getBaseClasses(MistralAIEmbeddings)] diff --git a/packages/components/nodes/embeddings/MistralEmbedding/mistralai.png b/packages/components/nodes/embeddings/MistralEmbedding/mistralai.png deleted file mode 100644 index 1019f495d..000000000 Binary files a/packages/components/nodes/embeddings/MistralEmbedding/mistralai.png and /dev/null differ diff --git a/packages/components/nodes/embeddings/OllamaEmbedding/Ollama.svg b/packages/components/nodes/embeddings/OllamaEmbedding/Ollama.svg new file mode 100644 index 000000000..2dc8df531 --- /dev/null +++ b/packages/components/nodes/embeddings/OllamaEmbedding/Ollama.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/embeddings/OllamaEmbedding/OllamaEmbedding.ts b/packages/components/nodes/embeddings/OllamaEmbedding/OllamaEmbedding.ts index eb528aff1..8892b03f2 100644 --- a/packages/components/nodes/embeddings/OllamaEmbedding/OllamaEmbedding.ts +++ b/packages/components/nodes/embeddings/OllamaEmbedding/OllamaEmbedding.ts @@ -1,7 +1,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' +import { OllamaInput } from 'langchain/llms/ollama' import { OllamaEmbeddings } from 'langchain/embeddings/ollama' -import { OllamaInput } from 'langchain/dist/util/ollama' class OllamaEmbedding_Embeddings implements INode { label: string @@ -20,7 +20,7 @@ class OllamaEmbedding_Embeddings implements INode { this.name = 'ollamaEmbedding' this.version = 1.0 this.type = 'OllamaEmbeddings' - this.icon = 'ollama.png' + this.icon = 'Ollama.svg' this.category = 'Embeddings' this.description = 'Generate embeddings for a given text using open source model on Ollama' this.baseClasses = [this.type, ...getBaseClasses(OllamaEmbeddings)] diff --git a/packages/components/nodes/embeddings/OllamaEmbedding/ollama.png b/packages/components/nodes/embeddings/OllamaEmbedding/ollama.png deleted file mode 100644 index 8cd2cf1ed..000000000 Binary files a/packages/components/nodes/embeddings/OllamaEmbedding/ollama.png and /dev/null differ diff --git a/packages/components/nodes/llms/AWSBedrock/aws.svg b/packages/components/nodes/llms/AWSBedrock/aws.svg index 0e630a3bf..d783497e8 100644 --- a/packages/components/nodes/llms/AWSBedrock/aws.svg +++ b/packages/components/nodes/llms/AWSBedrock/aws.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/packages/components/nodes/llms/Azure OpenAI/Azure.svg b/packages/components/nodes/llms/Azure OpenAI/Azure.svg index 47ad8c440..7b1508111 100644 --- a/packages/components/nodes/llms/Azure OpenAI/Azure.svg +++ b/packages/components/nodes/llms/Azure OpenAI/Azure.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/components/nodes/llms/Bittensor/Bittensor.ts b/packages/components/nodes/llms/Bittensor/Bittensor.ts index e6cc2bb61..68652828d 100644 --- a/packages/components/nodes/llms/Bittensor/Bittensor.ts +++ b/packages/components/nodes/llms/Bittensor/Bittensor.ts @@ -20,7 +20,7 @@ class Bittensor_LLMs implements INode { this.name = 'NIBittensorLLM' this.version = 2.0 this.type = 'Bittensor' - this.icon = 'logo.png' + this.icon = 'NIBittensor.svg' this.category = 'LLMs' this.description = 'Wrapper around Bittensor subnet 1 large language models' this.baseClasses = [this.type, ...getBaseClasses(NIBittensorLLM)] diff --git a/packages/components/nodes/llms/Bittensor/NIBittensor.svg b/packages/components/nodes/llms/Bittensor/NIBittensor.svg new file mode 100644 index 000000000..062cd66bb --- /dev/null +++ b/packages/components/nodes/llms/Bittensor/NIBittensor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/llms/Bittensor/logo.png b/packages/components/nodes/llms/Bittensor/logo.png deleted file mode 100644 index ad51774d5..000000000 Binary files a/packages/components/nodes/llms/Bittensor/logo.png and /dev/null differ diff --git a/packages/components/nodes/llms/Cohere/Cohere.svg b/packages/components/nodes/llms/Cohere/Cohere.svg new file mode 100644 index 000000000..88bcabe34 --- /dev/null +++ b/packages/components/nodes/llms/Cohere/Cohere.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/llms/Cohere/Cohere.ts b/packages/components/nodes/llms/Cohere/Cohere.ts index 3fde0af00..1760b10db 100644 --- a/packages/components/nodes/llms/Cohere/Cohere.ts +++ b/packages/components/nodes/llms/Cohere/Cohere.ts @@ -20,7 +20,7 @@ class Cohere_LLMs implements INode { this.name = 'cohere' this.version = 2.0 this.type = 'Cohere' - this.icon = 'cohere.png' + this.icon = 'Cohere.svg' this.category = 'LLMs' this.description = 'Wrapper around Cohere large language models' this.baseClasses = [this.type, ...getBaseClasses(Cohere)] diff --git a/packages/components/nodes/llms/Cohere/cohere.png b/packages/components/nodes/llms/Cohere/cohere.png deleted file mode 100644 index 266adeac2..000000000 Binary files a/packages/components/nodes/llms/Cohere/cohere.png and /dev/null differ diff --git a/packages/components/nodes/llms/GooglePaLM/GooglePaLM.svg b/packages/components/nodes/llms/GooglePaLM/GooglePaLM.svg new file mode 100644 index 000000000..ed47326a9 --- /dev/null +++ b/packages/components/nodes/llms/GooglePaLM/GooglePaLM.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/llms/GooglePaLM/GooglePaLM.ts b/packages/components/nodes/llms/GooglePaLM/GooglePaLM.ts index d3212a1cd..d22b70f73 100644 --- a/packages/components/nodes/llms/GooglePaLM/GooglePaLM.ts +++ b/packages/components/nodes/llms/GooglePaLM/GooglePaLM.ts @@ -19,7 +19,7 @@ class GooglePaLM_LLMs implements INode { this.name = 'GooglePaLM' this.version = 2.0 this.type = 'GooglePaLM' - this.icon = 'Google_PaLM_Logo.svg' + this.icon = 'GooglePaLM.svg' this.category = 'LLMs' this.description = 'Wrapper around Google MakerSuite PaLM large language models' this.baseClasses = [this.type, ...getBaseClasses(GooglePaLM)] diff --git a/packages/components/nodes/llms/GooglePaLM/Google_PaLM_Logo.svg b/packages/components/nodes/llms/GooglePaLM/Google_PaLM_Logo.svg deleted file mode 100644 index 5c345fe1c..000000000 --- a/packages/components/nodes/llms/GooglePaLM/Google_PaLM_Logo.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/components/nodes/llms/GoogleVertexAI/GoogleVertex.svg b/packages/components/nodes/llms/GoogleVertexAI/GoogleVertex.svg new file mode 100644 index 000000000..a517740fe --- /dev/null +++ b/packages/components/nodes/llms/GoogleVertexAI/GoogleVertex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/llms/GoogleVertexAI/GoogleVertexAI.ts b/packages/components/nodes/llms/GoogleVertexAI/GoogleVertexAI.ts index 6b6d534ba..f3f807f89 100644 --- a/packages/components/nodes/llms/GoogleVertexAI/GoogleVertexAI.ts +++ b/packages/components/nodes/llms/GoogleVertexAI/GoogleVertexAI.ts @@ -21,7 +21,7 @@ class GoogleVertexAI_LLMs implements INode { this.name = 'googlevertexai' this.version = 2.0 this.type = 'GoogleVertexAI' - this.icon = 'vertexai.svg' + this.icon = 'GoogleVertex.svg' this.category = 'LLMs' this.description = 'Wrapper around GoogleVertexAI large language models' this.baseClasses = [this.type, ...getBaseClasses(GoogleVertexAI)] diff --git a/packages/components/nodes/llms/GoogleVertexAI/vertexai.svg b/packages/components/nodes/llms/GoogleVertexAI/vertexai.svg deleted file mode 100644 index 31244412a..000000000 --- a/packages/components/nodes/llms/GoogleVertexAI/vertexai.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/packages/components/nodes/llms/HuggingFaceInference/HuggingFace.svg b/packages/components/nodes/llms/HuggingFaceInference/HuggingFace.svg new file mode 100644 index 000000000..58c85d57e --- /dev/null +++ b/packages/components/nodes/llms/HuggingFaceInference/HuggingFace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts index 8dcf021bb..17260e9b8 100644 --- a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts +++ b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts @@ -20,7 +20,7 @@ class HuggingFaceInference_LLMs implements INode { this.name = 'huggingFaceInference_LLMs' this.version = 2.0 this.type = 'HuggingFaceInference' - this.icon = 'huggingface.png' + this.icon = 'HuggingFace.svg' this.category = 'LLMs' this.description = 'Wrapper around HuggingFace large language models' this.baseClasses = [this.type, ...getBaseClasses(HuggingFaceInference)] diff --git a/packages/components/nodes/llms/HuggingFaceInference/huggingface.png b/packages/components/nodes/llms/HuggingFaceInference/huggingface.png deleted file mode 100644 index f8f202a46..000000000 Binary files a/packages/components/nodes/llms/HuggingFaceInference/huggingface.png and /dev/null differ diff --git a/packages/components/nodes/llms/Ollama/Ollama.svg b/packages/components/nodes/llms/Ollama/Ollama.svg new file mode 100644 index 000000000..2dc8df531 --- /dev/null +++ b/packages/components/nodes/llms/Ollama/Ollama.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/llms/Ollama/Ollama.ts b/packages/components/nodes/llms/Ollama/Ollama.ts index 348b1883a..385890c9a 100644 --- a/packages/components/nodes/llms/Ollama/Ollama.ts +++ b/packages/components/nodes/llms/Ollama/Ollama.ts @@ -1,8 +1,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { Ollama } from 'langchain/llms/ollama' +import { Ollama, OllamaInput } from 'langchain/llms/ollama' import { BaseCache } from 'langchain/schema' -import { OllamaInput } from 'langchain/dist/util/ollama' import { BaseLLMParams } from 'langchain/llms/base' class Ollama_LLMs implements INode { @@ -22,7 +21,7 @@ class Ollama_LLMs implements INode { this.name = 'ollama' this.version = 2.0 this.type = 'Ollama' - this.icon = 'ollama.png' + this.icon = 'Ollama.svg' this.category = 'LLMs' this.description = 'Wrapper around open source large language models on Ollama' this.baseClasses = [this.type, ...getBaseClasses(Ollama)] diff --git a/packages/components/nodes/llms/Ollama/ollama.png b/packages/components/nodes/llms/Ollama/ollama.png deleted file mode 100644 index 8cd2cf1ed..000000000 Binary files a/packages/components/nodes/llms/Ollama/ollama.png and /dev/null differ diff --git a/packages/components/nodes/memory/BufferMemory/BufferMemory.ts b/packages/components/nodes/memory/BufferMemory/BufferMemory.ts index 7793d96d4..0ad8adec9 100644 --- a/packages/components/nodes/memory/BufferMemory/BufferMemory.ts +++ b/packages/components/nodes/memory/BufferMemory/BufferMemory.ts @@ -1,6 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' -import { BufferMemory } from 'langchain/memory' +import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' +import { BufferMemory, BufferMemoryInput } from 'langchain/memory' +import { BaseMessage } from 'langchain/schema' class BufferMemory_Memory implements INode { label: string @@ -41,7 +42,7 @@ class BufferMemory_Memory implements INode { async init(nodeData: INodeData): Promise { const memoryKey = nodeData.inputs?.memoryKey as string const inputKey = nodeData.inputs?.inputKey as string - return new BufferMemory({ + return new BufferMemoryExtended({ returnMessages: true, memoryKey, inputKey @@ -49,4 +50,41 @@ class BufferMemory_Memory implements INode { } } +class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { + constructor(fields: BufferMemoryInput) { + super(fields) + } + + async getChatMessages(_?: string, returnBaseMessages = false): Promise { + const memoryResult = await this.loadMemoryVariables({}) + const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } + + async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise { + const input = msgArray.find((msg) => msg.type === 'userMessage') + const output = msgArray.find((msg) => msg.type === 'apiMessage') + + const inputValues = { [this.inputKey ?? 'input']: input?.text } + const outputValues = { output: output?.text } + + await this.saveContext(inputValues, outputValues) + } + + async clearChatMessages(): Promise { + await this.clear() + } + + async resumeMessages(messages: IMessage[]): Promise { + // Clear existing chatHistory to avoid duplication + if (messages.length) await this.clear() + + // Insert into chatHistory + for (const msg of messages) { + if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) + else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + } + } +} + module.exports = { nodeClass: BufferMemory_Memory } diff --git a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts index 84e607e54..ca8d0ddfd 100644 --- a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts +++ b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts @@ -1,6 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { FlowiseWindowMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' import { BufferWindowMemory, BufferWindowMemoryInput } from 'langchain/memory' +import { BaseMessage } from 'langchain/schema' class BufferWindowMemory_Memory implements INode { label: string @@ -57,7 +58,44 @@ class BufferWindowMemory_Memory implements INode { k: parseInt(k, 10) } - return new BufferWindowMemory(obj) + return new BufferWindowMemoryExtended(obj) + } +} + +class BufferWindowMemoryExtended extends FlowiseWindowMemory implements MemoryMethods { + constructor(fields: BufferWindowMemoryInput) { + super(fields) + } + + async getChatMessages(_?: string, returnBaseMessages = false): Promise { + const memoryResult = await this.loadMemoryVariables({}) + const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } + + async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise { + const input = msgArray.find((msg) => msg.type === 'userMessage') + const output = msgArray.find((msg) => msg.type === 'apiMessage') + + const inputValues = { [this.inputKey ?? 'input']: input?.text } + const outputValues = { output: output?.text } + + await this.saveContext(inputValues, outputValues) + } + + async clearChatMessages(): Promise { + await this.clear() + } + + async resumeMessages(messages: IMessage[]): Promise { + // Clear existing chatHistory to avoid duplication + if (messages.length) await this.clear() + + // Insert into chatHistory + for (const msg of messages) { + if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) + else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + } } } diff --git a/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts b/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts index 332d73aa9..107ab7db9 100644 --- a/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts +++ b/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts @@ -1,7 +1,8 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { FlowiseSummaryMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' import { ConversationSummaryMemory, ConversationSummaryMemoryInput } from 'langchain/memory' import { BaseLanguageModel } from 'langchain/base_language' +import { BaseMessage } from 'langchain/schema' class ConversationSummaryMemory_Memory implements INode { label: string @@ -56,7 +57,48 @@ class ConversationSummaryMemory_Memory implements INode { inputKey } - return new ConversationSummaryMemory(obj) + return new ConversationSummaryMemoryExtended(obj) + } +} + +class ConversationSummaryMemoryExtended extends FlowiseSummaryMemory implements MemoryMethods { + constructor(fields: ConversationSummaryMemoryInput) { + super(fields) + } + + async getChatMessages(_?: string, returnBaseMessages = false): Promise { + const memoryResult = await this.loadMemoryVariables({}) + const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } + + async addChatMessages(msgArray: { text: string; type: MessageType }[]): Promise { + const input = msgArray.find((msg) => msg.type === 'userMessage') + const output = msgArray.find((msg) => msg.type === 'apiMessage') + + const inputValues = { [this.inputKey ?? 'input']: input?.text } + const outputValues = { output: output?.text } + + await this.saveContext(inputValues, outputValues) + } + + async clearChatMessages(): Promise { + await this.clear() + } + + async resumeMessages(messages: IMessage[]): Promise { + // Clear existing chatHistory to avoid duplication + if (messages.length) await this.clear() + + // Insert into chatHistory + for (const msg of messages) { + if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) + else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + } + + // Replace buffer + const chatMessages = await this.chatHistory.getMessages() + this.buffer = await this.predictNewSummary(chatMessages.slice(-2), this.buffer) } } diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 8ca6cf9e5..872ec0b51 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -1,15 +1,25 @@ import { - ICommonObject, - INode, - INodeData, - INodeParams, + DynamoDBClient, + DynamoDBClientConfig, + GetItemCommand, + GetItemCommandInput, + UpdateItemCommand, + UpdateItemCommandInput, + DeleteItemCommand, + DeleteItemCommandInput, + AttributeValue +} from '@aws-sdk/client-dynamodb' +import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' +import { BufferMemory, BufferMemoryInput } from 'langchain/memory' +import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from 'langchain/schema' +import { + convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam, serializeChatHistory -} from '../../../src' -import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' -import { BufferMemory, BufferMemoryInput } from 'langchain/memory' +} from '../../../src/utils' +import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' class DynamoDb_Memory implements INode { label: string @@ -102,49 +112,203 @@ class DynamoDb_Memory implements INode { const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): Promise => { const tableName = nodeData.inputs?.tableName as string const partitionKey = nodeData.inputs?.partitionKey as string - const sessionId = nodeData.inputs?.sessionId as string const region = nodeData.inputs?.region as string const memoryKey = nodeData.inputs?.memoryKey as string const chatId = options.chatId let isSessionIdUsingChatMessageId = false - if (!sessionId && chatId) isSessionIdUsingChatMessageId = true + let sessionId = '' + + if (!nodeData.inputs?.sessionId && chatId) { + isSessionIdUsingChatMessageId = true + sessionId = chatId + } else { + sessionId = nodeData.inputs?.sessionId + } const credentialData = await getCredentialData(nodeData.credential ?? '', options) const accessKeyId = getCredentialParam('accessKey', credentialData, nodeData) const secretAccessKey = getCredentialParam('secretAccessKey', credentialData, nodeData) + const config: DynamoDBClientConfig = { + region, + credentials: { + accessKeyId, + secretAccessKey + } + } + + const client = new DynamoDBClient(config ?? {}) + const dynamoDb = new DynamoDBChatMessageHistory({ tableName, partitionKey, - sessionId: sessionId ? sessionId : chatId, - config: { - region, - credentials: { - accessKeyId, - secretAccessKey - } - } + sessionId, + config }) const memory = new BufferMemoryExtended({ memoryKey: memoryKey ?? 'chat_history', chatHistory: dynamoDb, - isSessionIdUsingChatMessageId + isSessionIdUsingChatMessageId, + sessionId, + dynamodbClient: client }) return memory } interface BufferMemoryExtendedInput { isSessionIdUsingChatMessageId: boolean + dynamodbClient: DynamoDBClient + sessionId: string } -class BufferMemoryExtended extends BufferMemory { - isSessionIdUsingChatMessageId? = false +interface DynamoDBSerializedChatMessage { + M: { + type: { + S: string + } + text: { + S: string + } + role?: { + S: string + } + } +} - constructor(fields: BufferMemoryInput & Partial) { +class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { + isSessionIdUsingChatMessageId = false + sessionId = '' + dynamodbClient: DynamoDBClient + + constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { super(fields) - this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId + this.sessionId = fields.sessionId + this.dynamodbClient = fields.dynamodbClient + } + + overrideDynamoKey(overrideSessionId = '') { + const existingDynamoKey = (this as any).dynamoKey + const partitionKey = (this as any).partitionKey + + let newDynamoKey: Record = {} + + if (Object.keys(existingDynamoKey).includes(partitionKey)) { + newDynamoKey[partitionKey] = { S: overrideSessionId } + } + + return Object.keys(newDynamoKey).length ? newDynamoKey : existingDynamoKey + } + + async addNewMessage( + messages: StoredMessage[], + client: DynamoDBClient, + tableName = '', + dynamoKey: Record = {}, + messageAttributeName = 'messages' + ) { + const params: UpdateItemCommandInput = { + TableName: tableName, + Key: dynamoKey, + ExpressionAttributeNames: { + '#m': messageAttributeName + }, + ExpressionAttributeValues: { + ':empty_list': { + L: [] + }, + ':m': { + L: messages.map((message) => { + const dynamoSerializedMessage: DynamoDBSerializedChatMessage = { + M: { + type: { + S: message.type + }, + text: { + S: message.data.content + } + } + } + if (message.data.role) { + dynamoSerializedMessage.M.role = { S: message.data.role } + } + return dynamoSerializedMessage + }) + } + }, + UpdateExpression: 'SET #m = list_append(if_not_exists(#m, :empty_list), :m)' + } + + await client.send(new UpdateItemCommand(params)) + } + + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { + if (!this.dynamodbClient) return [] + + const dynamoKey = overrideSessionId ? this.overrideDynamoKey(overrideSessionId) : (this as any).dynamoKey + const tableName = (this as any).tableName + const messageAttributeName = (this as any).messageAttributeName + + const params: GetItemCommandInput = { + TableName: tableName, + Key: dynamoKey + } + + const response = await this.dynamodbClient.send(new GetItemCommand(params)) + const items = response.Item ? response.Item[messageAttributeName]?.L ?? [] : [] + const messages = items + .map((item) => ({ + type: item.M?.type.S, + data: { + role: item.M?.role?.S, + content: item.M?.text.S + } + })) + .filter((x): x is StoredMessage => x.type !== undefined && x.data.content !== undefined) + const baseMessages = messages.map(mapStoredMessageToChatMessage) + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } + + async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise { + if (!this.dynamodbClient) return + + const dynamoKey = overrideSessionId ? this.overrideDynamoKey(overrideSessionId) : (this as any).dynamoKey + const tableName = (this as any).tableName + const messageAttributeName = (this as any).messageAttributeName + + const input = msgArray.find((msg) => msg.type === 'userMessage') + const output = msgArray.find((msg) => msg.type === 'apiMessage') + + if (input) { + const newInputMessage = new HumanMessage(input.text) + const messageToAdd = [newInputMessage].map((msg) => msg.toDict()) + await this.addNewMessage(messageToAdd, this.dynamodbClient, tableName, dynamoKey, messageAttributeName) + } + + if (output) { + const newOutputMessage = new AIMessage(output.text) + const messageToAdd = [newOutputMessage].map((msg) => msg.toDict()) + await this.addNewMessage(messageToAdd, this.dynamodbClient, tableName, dynamoKey, messageAttributeName) + } + } + + async clearChatMessages(overrideSessionId = ''): Promise { + if (!this.dynamodbClient) return + + const dynamoKey = overrideSessionId ? this.overrideDynamoKey(overrideSessionId) : (this as any).dynamoKey + const tableName = (this as any).tableName + + const params: DeleteItemCommandInput = { + TableName: tableName, + Key: dynamoKey + } + await this.dynamodbClient.send(new DeleteItemCommand(params)) + await this.clear() + } + + async resumeMessages(): Promise { + return } } diff --git a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts index b654a5b20..b422921e6 100644 --- a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts +++ b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts @@ -1,17 +1,15 @@ +import { MongoClient, Collection, Document } from 'mongodb' +import { MongoDBChatMessageHistory } from 'langchain/stores/message/mongodb' +import { BufferMemory, BufferMemoryInput } from 'langchain/memory' +import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, BaseMessage } from 'langchain/schema' import { + convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam, - ICommonObject, - INode, - INodeData, - INodeParams, serializeChatHistory -} from '../../../src' -import { MongoDBChatMessageHistory } from 'langchain/stores/message/mongodb' -import { BufferMemory, BufferMemoryInput } from 'langchain/memory' -import { BaseMessage, mapStoredMessageToChatMessage } from 'langchain/schema' -import { MongoClient } from 'mongodb' +} from '../../../src/utils' +import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' class MongoDB_Memory implements INode { label: string @@ -99,23 +97,30 @@ class MongoDB_Memory implements INode { const initializeMongoDB = async (nodeData: INodeData, options: ICommonObject): Promise => { const databaseName = nodeData.inputs?.databaseName as string const collectionName = nodeData.inputs?.collectionName as string - const sessionId = nodeData.inputs?.sessionId as string const memoryKey = nodeData.inputs?.memoryKey as string const chatId = options?.chatId as string let isSessionIdUsingChatMessageId = false - if (!sessionId && chatId) isSessionIdUsingChatMessageId = true + let sessionId = '' + + if (!nodeData.inputs?.sessionId && chatId) { + isSessionIdUsingChatMessageId = true + sessionId = chatId + } else { + sessionId = nodeData.inputs?.sessionId + } const credentialData = await getCredentialData(nodeData.credential ?? '', options) - let mongoDBConnectUrl = getCredentialParam('mongoDBConnectUrl', credentialData, nodeData) + const mongoDBConnectUrl = getCredentialParam('mongoDBConnectUrl', credentialData, nodeData) const client = new MongoClient(mongoDBConnectUrl) await client.connect() + const collection = client.db(databaseName).collection(collectionName) const mongoDBChatMessageHistory = new MongoDBChatMessageHistory({ collection, - sessionId: sessionId ? sessionId : chatId + sessionId }) mongoDBChatMessageHistory.getMessages = async (): Promise => { @@ -144,20 +149,81 @@ const initializeMongoDB = async (nodeData: INodeData, options: ICommonObject): P return new BufferMemoryExtended({ memoryKey: memoryKey ?? 'chat_history', chatHistory: mongoDBChatMessageHistory, - isSessionIdUsingChatMessageId + isSessionIdUsingChatMessageId, + sessionId, + collection }) } interface BufferMemoryExtendedInput { isSessionIdUsingChatMessageId: boolean + collection: Collection + sessionId: string } -class BufferMemoryExtended extends BufferMemory { +class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { + sessionId = '' + collection: Collection isSessionIdUsingChatMessageId? = false - constructor(fields: BufferMemoryInput & Partial) { + constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { super(fields) - this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId + this.sessionId = fields.sessionId + this.collection = fields.collection + } + + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { + if (!this.collection) return [] + + const id = overrideSessionId ?? this.sessionId + const document = await this.collection.findOne({ sessionId: id }) + const messages = document?.messages || [] + const baseMessages = messages.map(mapStoredMessageToChatMessage) + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } + + async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise { + if (!this.collection) return + + const id = overrideSessionId ?? this.sessionId + const input = msgArray.find((msg) => msg.type === 'userMessage') + const output = msgArray.find((msg) => msg.type === 'apiMessage') + + if (input) { + const newInputMessage = new HumanMessage(input.text) + const messageToAdd = [newInputMessage].map((msg) => msg.toDict()) + await this.collection.updateOne( + { sessionId: id }, + { + $push: { messages: { $each: messageToAdd } } + }, + { upsert: true } + ) + } + + if (output) { + const newOutputMessage = new AIMessage(output.text) + const messageToAdd = [newOutputMessage].map((msg) => msg.toDict()) + await this.collection.updateOne( + { sessionId: id }, + { + $push: { messages: { $each: messageToAdd } } + }, + { upsert: true } + ) + } + } + + async clearChatMessages(overrideSessionId = ''): Promise { + if (!this.collection) return + + const id = overrideSessionId ?? this.sessionId + await this.collection.deleteOne({ sessionId: id }) + await this.clear() + } + + async resumeMessages(): Promise { + return } } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index fc4a06dcc..938cc8731 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -1,9 +1,9 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ICommonObject } from '../../../src' -import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory' +import { MotorheadMemory, MotorheadMemoryInput, InputValues, MemoryVariables, OutputValues, getBufferString } from 'langchain/memory' import fetch from 'node-fetch' -import { getBufferString } from 'langchain/memory' +import { BaseMessage } from 'langchain/schema' class MotorMemory_Memory implements INode { label: string @@ -88,19 +88,26 @@ class MotorMemory_Memory implements INode { const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject): Promise => { const memoryKey = nodeData.inputs?.memoryKey as string const baseURL = nodeData.inputs?.baseURL as string - const sessionId = nodeData.inputs?.sessionId as string const chatId = options?.chatId as string let isSessionIdUsingChatMessageId = false - if (!sessionId && chatId) isSessionIdUsingChatMessageId = true + let sessionId = '' + + if (!nodeData.inputs?.sessionId && chatId) { + isSessionIdUsingChatMessageId = true + sessionId = chatId + } else { + sessionId = nodeData.inputs?.sessionId + } const credentialData = await getCredentialData(nodeData.credential ?? '', options) const apiKey = getCredentialParam('apiKey', credentialData, nodeData) const clientId = getCredentialParam('clientId', credentialData, nodeData) - let obj: MotorheadMemoryInput & Partial = { + let obj: MotorheadMemoryInput & MotorheadMemoryExtendedInput = { returnMessages: true, - sessionId: sessionId ? sessionId : chatId, + isSessionIdUsingChatMessageId, + sessionId, memoryKey } @@ -117,8 +124,6 @@ const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject): } } - if (isSessionIdUsingChatMessageId) obj.isSessionIdUsingChatMessageId = true - const motorheadMemory = new MotorheadMemoryExtended(obj) // Get messages from sessionId @@ -131,15 +136,32 @@ interface MotorheadMemoryExtendedInput { isSessionIdUsingChatMessageId: boolean } -class MotorheadMemoryExtended extends MotorheadMemory { +class MotorheadMemoryExtended extends MotorheadMemory implements MemoryMethods { isSessionIdUsingChatMessageId? = false - constructor(fields: MotorheadMemoryInput & Partial) { + constructor(fields: MotorheadMemoryInput & MotorheadMemoryExtendedInput) { super(fields) this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId } - async clear(): Promise { + async loadMemoryVariables(values: InputValues, overrideSessionId = ''): Promise { + if (overrideSessionId) { + this.sessionId = overrideSessionId + } + return super.loadMemoryVariables({ values }) + } + + async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideSessionId = ''): Promise { + if (overrideSessionId) { + this.sessionId = overrideSessionId + } + return super.saveContext(inputValues, outputValues) + } + + async clear(overrideSessionId = ''): Promise { + if (overrideSessionId) { + this.sessionId = overrideSessionId + } try { await this.caller.call(fetch, `${this.url}/sessions/${this.sessionId}/memory`, { //@ts-ignore @@ -155,6 +177,28 @@ class MotorheadMemoryExtended extends MotorheadMemory { await this.chatHistory.clear() await super.clear() } + + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { + const id = overrideSessionId ?? this.sessionId + const memoryVariables = await this.loadMemoryVariables({}, id) + const baseMessages = memoryVariables[this.memoryKey] + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } + + async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise { + const id = overrideSessionId ?? this.sessionId + const input = msgArray.find((msg) => msg.type === 'userMessage') + const output = msgArray.find((msg) => msg.type === 'apiMessage') + const inputValues = { [this.inputKey ?? 'input']: input?.text } + const outputValues = { output: output?.text } + + await this.saveContext(inputValues, outputValues, id) + } + + async clearChatMessages(overrideSessionId = ''): Promise { + const id = overrideSessionId ?? this.sessionId + await this.clear(id) + } } module.exports = { nodeClass: MotorMemory_Memory } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index d6ec9a114..a02df3ea2 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -1,8 +1,14 @@ -import { INode, INodeData, INodeParams, ICommonObject } from '../../../src/Interface' -import { getBaseClasses, getCredentialData, getCredentialParam, serializeChatHistory } from '../../../src/utils' +import { INode, INodeData, INodeParams, ICommonObject, IMessage, MessageType, FlowiseMemory, MemoryMethods } from '../../../src/Interface' +import { + convertBaseMessagetoIMessage, + getBaseClasses, + getCredentialData, + getCredentialParam, + serializeChatHistory +} from '../../../src/utils' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { RedisChatMessageHistory, RedisChatMessageHistoryInput } from 'langchain/stores/message/ioredis' -import { mapStoredMessageToChatMessage, BaseMessage } from 'langchain/schema' +import { mapStoredMessageToChatMessage, BaseMessage, AIMessage, HumanMessage } from 'langchain/schema' import { Redis } from 'ioredis' class RedisBackedChatMemory_Memory implements INode { @@ -94,14 +100,20 @@ class RedisBackedChatMemory_Memory implements INode { } const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Promise => { - const sessionId = nodeData.inputs?.sessionId as string const sessionTTL = nodeData.inputs?.sessionTTL as number const memoryKey = nodeData.inputs?.memoryKey as string const windowSize = nodeData.inputs?.windowSize as number const chatId = options?.chatId as string let isSessionIdUsingChatMessageId = false - if (!sessionId && chatId) isSessionIdUsingChatMessageId = true + let sessionId = '' + + if (!nodeData.inputs?.sessionId && chatId) { + isSessionIdUsingChatMessageId = true + sessionId = chatId + } else { + sessionId = nodeData.inputs?.sessionId + } const credentialData = await getCredentialData(nodeData.credential ?? '', options) const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData) @@ -128,7 +140,7 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom } let obj: RedisChatMessageHistoryInput = { - sessionId: sessionId ? sessionId : chatId, + sessionId, client } @@ -162,21 +174,71 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom const memory = new BufferMemoryExtended({ memoryKey: memoryKey ?? 'chat_history', chatHistory: redisChatMessageHistory, - isSessionIdUsingChatMessageId + isSessionIdUsingChatMessageId, + sessionId, + redisClient: client }) return memory } interface BufferMemoryExtendedInput { isSessionIdUsingChatMessageId: boolean + redisClient: Redis + sessionId: string } -class BufferMemoryExtended extends BufferMemory { +class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { isSessionIdUsingChatMessageId? = false + sessionId = '' + redisClient: Redis - constructor(fields: BufferMemoryInput & Partial) { + constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { super(fields) this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId + this.sessionId = fields.sessionId + this.redisClient = fields.redisClient + } + + async getChatMessages(overrideSessionId = '', returnBaseMessage = false): Promise { + if (!this.redisClient) return [] + + const id = overrideSessionId ?? this.sessionId + const rawStoredMessages = await this.redisClient.lrange(id, 0, -1) + const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message)) + const baseMessages = orderedMessages.map(mapStoredMessageToChatMessage) + return returnBaseMessage ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } + + async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise { + if (!this.redisClient) return + + const id = overrideSessionId ?? this.sessionId + const input = msgArray.find((msg) => msg.type === 'userMessage') + const output = msgArray.find((msg) => msg.type === 'apiMessage') + + if (input) { + const newInputMessage = new HumanMessage(input.text) + const messageToAdd = [newInputMessage].map((msg) => msg.toDict()) + await this.redisClient.lpush(id, JSON.stringify(messageToAdd[0])) + } + + if (output) { + const newOutputMessage = new AIMessage(output.text) + const messageToAdd = [newOutputMessage].map((msg) => msg.toDict()) + await this.redisClient.lpush(id, JSON.stringify(messageToAdd[0])) + } + } + + async clearChatMessages(overrideSessionId = ''): Promise { + if (!this.redisClient) return + + const id = overrideSessionId ?? this.sessionId + await this.redisClient.del(id) + await this.clear() + } + + async resumeMessages(): Promise { + return } } diff --git a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts index 8bca04404..c3f971231 100644 --- a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts @@ -1,8 +1,16 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses, getCredentialData, getCredentialParam, serializeChatHistory } from '../../../src/utils' -import { ICommonObject } from '../../../src' +import { Redis } from '@upstash/redis' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { UpstashRedisChatMessageHistory } from 'langchain/stores/message/upstash_redis' +import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from 'langchain/schema' +import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { + convertBaseMessagetoIMessage, + getBaseClasses, + getCredentialData, + getCredentialParam, + serializeChatHistory +} from '../../../src/utils' +import { ICommonObject } from '../../../src/Interface' class UpstashRedisBackedChatMemory_Memory implements INode { label: string @@ -84,29 +92,39 @@ class UpstashRedisBackedChatMemory_Memory implements INode { const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject): Promise => { const baseURL = nodeData.inputs?.baseURL as string - const sessionId = nodeData.inputs?.sessionId as string const sessionTTL = nodeData.inputs?.sessionTTL as string const chatId = options?.chatId as string let isSessionIdUsingChatMessageId = false - if (!sessionId && chatId) isSessionIdUsingChatMessageId = true + let sessionId = '' + + if (!nodeData.inputs?.sessionId && chatId) { + isSessionIdUsingChatMessageId = true + sessionId = chatId + } else { + sessionId = nodeData.inputs?.sessionId + } const credentialData = await getCredentialData(nodeData.credential ?? '', options) const upstashRestToken = getCredentialParam('upstashRestToken', credentialData, nodeData) + const client = new Redis({ + url: baseURL, + token: upstashRestToken + }) + const redisChatMessageHistory = new UpstashRedisChatMessageHistory({ - sessionId: sessionId ? sessionId : chatId, + sessionId, sessionTTL: sessionTTL ? parseInt(sessionTTL, 10) : undefined, - config: { - url: baseURL, - token: upstashRestToken - } + client }) const memory = new BufferMemoryExtended({ memoryKey: 'chat_history', chatHistory: redisChatMessageHistory, - isSessionIdUsingChatMessageId + isSessionIdUsingChatMessageId, + sessionId, + redisClient: client }) return memory @@ -114,14 +132,63 @@ const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject interface BufferMemoryExtendedInput { isSessionIdUsingChatMessageId: boolean + redisClient: Redis + sessionId: string } -class BufferMemoryExtended extends BufferMemory { +class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { isSessionIdUsingChatMessageId? = false + sessionId = '' + redisClient: Redis - constructor(fields: BufferMemoryInput & Partial) { + constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { super(fields) this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId + this.sessionId = fields.sessionId + this.redisClient = fields.redisClient + } + + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { + if (!this.redisClient) return [] + + const id = overrideSessionId ?? this.sessionId + const rawStoredMessages: StoredMessage[] = await this.redisClient.lrange(id, 0, -1) + const orderedMessages = rawStoredMessages.reverse() + const previousMessages = orderedMessages.filter((x): x is StoredMessage => x.type !== undefined && x.data.content !== undefined) + const baseMessages = previousMessages.map(mapStoredMessageToChatMessage) + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } + + async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise { + if (!this.redisClient) return + + const id = overrideSessionId ?? this.sessionId + const input = msgArray.find((msg) => msg.type === 'userMessage') + const output = msgArray.find((msg) => msg.type === 'apiMessage') + + if (input) { + const newInputMessage = new HumanMessage(input.text) + const messageToAdd = [newInputMessage].map((msg) => msg.toDict()) + await this.redisClient.lpush(id, JSON.stringify(messageToAdd[0])) + } + + if (output) { + const newOutputMessage = new AIMessage(output.text) + const messageToAdd = [newOutputMessage].map((msg) => msg.toDict()) + await this.redisClient.lpush(id, JSON.stringify(messageToAdd[0])) + } + } + + async clearChatMessages(overrideSessionId = ''): Promise { + if (!this.redisClient) return + + const id = overrideSessionId ?? this.sessionId + await this.redisClient.del(id) + await this.clear() + } + + async resumeMessages(): Promise { + return } } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index ea52cb0b3..4dda76df1 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -1,8 +1,9 @@ +import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' -import { getBufferString, InputValues, MemoryVariables, OutputValues } from 'langchain/memory' -import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ICommonObject } from '../../../src' +import { InputValues, MemoryVariables, OutputValues, getBufferString } from 'langchain/memory' +import { BaseMessage } from 'langchain/schema' class ZepMemory_Memory implements INode { label: string @@ -147,7 +148,7 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis const obj: ZepMemoryInput & ZepMemoryExtendedInput = { baseURL, - sessionId: sessionId ? sessionId : chatId, + sessionId, aiPrefix, humanPrefix, returnMessages: true, @@ -166,7 +167,7 @@ interface ZepMemoryExtendedInput { k?: number } -class ZepMemoryExtended extends ZepMemory { +class ZepMemoryExtended extends ZepMemory implements MemoryMethods { isSessionIdUsingChatMessageId? = false lastN?: number @@ -196,6 +197,28 @@ class ZepMemoryExtended extends ZepMemory { } return super.clear() } + + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { + const id = overrideSessionId ?? this.sessionId + const memoryVariables = await this.loadMemoryVariables({}, id) + const baseMessages = memoryVariables[this.memoryKey] + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } + + async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise { + const id = overrideSessionId ?? this.sessionId + const input = msgArray.find((msg) => msg.type === 'userMessage') + const output = msgArray.find((msg) => msg.type === 'apiMessage') + const inputValues = { [this.inputKey ?? 'input']: input?.text } + const outputValues = { output: output?.text } + + await this.saveContext(inputValues, outputValues, id) + } + + async clearChatMessages(overrideSessionId = ''): Promise { + const id = overrideSessionId ?? this.sessionId + await this.clear(id) + } } module.exports = { nodeClass: ZepMemory_Memory } diff --git a/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModeration.ts b/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModeration.ts index d98c48672..ad3cfadd9 100644 --- a/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModeration.ts +++ b/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModeration.ts @@ -2,6 +2,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src' import { Moderation } from '../Moderation' import { SimplePromptModerationRunner } from './SimplePromptModerationRunner' +import { BaseChatModel } from 'langchain/chat_models/base' class SimplePromptModeration implements INode { label: string @@ -17,7 +18,7 @@ class SimplePromptModeration implements INode { constructor() { this.label = 'Simple Prompt Moderation' this.name = 'inputModerationSimple' - this.version = 1.0 + this.version = 2.0 this.type = 'Moderation' this.icon = 'moderation.svg' this.category = 'Moderation' @@ -30,8 +31,14 @@ class SimplePromptModeration implements INode { type: 'string', rows: 4, placeholder: `ignore previous instructions\ndo not follow the directions\nyou must ignore all previous instructions`, - description: 'An array of string literals (enter one per line) that should not appear in the prompt text.', - optional: false + description: 'An array of string literals (enter one per line) that should not appear in the prompt text.' + }, + { + label: 'Chat Model', + name: 'model', + type: 'BaseChatModel', + description: 'Use LLM to detect if the input is similar to those specified in Deny List', + optional: true }, { label: 'Error Message', @@ -46,9 +53,10 @@ class SimplePromptModeration implements INode { async init(nodeData: INodeData): Promise { const denyList = nodeData.inputs?.denyList as string + const model = nodeData.inputs?.model as BaseChatModel const moderationErrorMessage = nodeData.inputs?.moderationErrorMessage as string - return new SimplePromptModerationRunner(denyList, moderationErrorMessage) + return new SimplePromptModerationRunner(denyList, moderationErrorMessage, model) } } diff --git a/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts b/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts index 08f9ed1ed..c9a116432 100644 --- a/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts +++ b/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts @@ -1,23 +1,39 @@ import { Moderation } from '../Moderation' +import { BaseChatModel } from 'langchain/chat_models/base' export class SimplePromptModerationRunner implements Moderation { private readonly denyList: string = '' private readonly moderationErrorMessage: string = '' + private readonly model: BaseChatModel - constructor(denyList: string, moderationErrorMessage: string) { + constructor(denyList: string, moderationErrorMessage: string, model?: BaseChatModel) { this.denyList = denyList if (denyList.indexOf('\n') === -1) { this.denyList += '\n' } this.moderationErrorMessage = moderationErrorMessage + if (model) this.model = model } async checkForViolations(input: string): Promise { - this.denyList.split('\n').forEach((denyListItem) => { - if (denyListItem && denyListItem !== '' && input.toLowerCase().includes(denyListItem.toLowerCase())) { - throw Error(this.moderationErrorMessage) + if (this.model) { + const denyArray = this.denyList.split('\n') + for (const denyStr of denyArray) { + if (!denyStr || denyStr === '') continue + const res = await this.model.invoke( + `Are these two sentences similar to each other? Only return Yes or No.\nFirst sentence: ${input}\nSecond sentence: ${denyStr}` + ) + if (res.content.toString().toLowerCase().includes('yes')) { + throw Error(this.moderationErrorMessage) + } } - }) + } else { + this.denyList.split('\n').forEach((denyListItem) => { + if (denyListItem && denyListItem !== '' && input.toLowerCase().includes(denyListItem.toLowerCase())) { + throw Error(this.moderationErrorMessage) + } + }) + } return Promise.resolve(input) } } diff --git a/packages/components/nodes/prompts/FewShotPromptTemplate/prompt.svg b/packages/components/nodes/prompts/FewShotPromptTemplate/prompt.svg index 1484fcb21..e3a0c868d 100644 --- a/packages/components/nodes/prompts/FewShotPromptTemplate/prompt.svg +++ b/packages/components/nodes/prompts/FewShotPromptTemplate/prompt.svg @@ -1,5 +1,4 @@ - diff --git a/packages/components/nodes/prompts/PromptTemplate/prompt.svg b/packages/components/nodes/prompts/PromptTemplate/prompt.svg index 1484fcb21..e3a0c868d 100644 --- a/packages/components/nodes/prompts/PromptTemplate/prompt.svg +++ b/packages/components/nodes/prompts/PromptTemplate/prompt.svg @@ -1,5 +1,4 @@ - diff --git a/packages/components/nodes/tools/CustomTool/CustomTool.ts b/packages/components/nodes/tools/CustomTool/CustomTool.ts index 541edcf07..6ffcc0e21 100644 --- a/packages/components/nodes/tools/CustomTool/CustomTool.ts +++ b/packages/components/nodes/tools/CustomTool/CustomTool.ts @@ -60,7 +60,7 @@ class CustomTool_Tools implements INode { } } - async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { const selectedToolId = nodeData.inputs?.selectedTool as string const customToolFunc = nodeData.inputs?.customToolFunc as string @@ -80,7 +80,36 @@ class CustomTool_Tools implements INode { code: tool.func } if (customToolFunc) obj.code = customToolFunc - return new DynamicStructuredTool(obj) + + const variables = await appDataSource.getRepository(databaseEntities['Variable']).find() + + // override variables defined in overrideConfig + // nodeData.inputs.variables is an Object, check each property and override the variable + if (nodeData?.inputs?.vars) { + for (const propertyName of Object.getOwnPropertyNames(nodeData.inputs.vars)) { + const foundVar = variables.find((v) => v.name === propertyName) + if (foundVar) { + // even if the variable was defined as runtime, we override it with static value + foundVar.type = 'static' + foundVar.value = nodeData.inputs.vars[propertyName] + } else { + // add it the variables, if not found locally in the db + variables.push({ name: propertyName, type: 'static', value: nodeData.inputs.vars[propertyName] }) + } + } + } + + const flow = { + chatId: options.chatId, // id is uppercase (I) + chatflowId: options.chatflowid, // id is lowercase (i) + input + } + + let dynamicStructuredTool = new DynamicStructuredTool(obj) + dynamicStructuredTool.setVariables(variables) + dynamicStructuredTool.setFlowObject(flow) + + return dynamicStructuredTool } catch (e) { throw new Error(e) } diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts index 2aa06b547..338b0ae9a 100644 --- a/packages/components/nodes/tools/CustomTool/core.ts +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -1,8 +1,18 @@ import { z } from 'zod' -import { CallbackManagerForToolRun } from 'langchain/callbacks' -import { StructuredTool, ToolParams } from 'langchain/tools' import { NodeVM } from 'vm2' import { availableDependencies } from '../../../src/utils' +import { RunnableConfig } from '@langchain/core/runnables' +import { StructuredTool, ToolParams } from '@langchain/core/tools' +import { CallbackManagerForToolRun, Callbacks, CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager' + +class ToolInputParsingException extends Error { + output?: string + + constructor(message: string, output?: string) { + super(message) + this.output = output + } +} export interface BaseDynamicToolInput extends ToolParams { name: string @@ -32,6 +42,8 @@ export class DynamicStructuredTool< func: DynamicStructuredToolInput['func'] schema: T + private variables: any[] + private flowObj: any constructor(fields: DynamicStructuredToolInput) { super(fields) @@ -43,7 +55,47 @@ export class DynamicStructuredTool< this.schema = fields.schema } - protected async _call(arg: z.output): Promise { + async call(arg: z.output, configArg?: RunnableConfig | Callbacks, tags?: string[], overrideSessionId?: string): Promise { + const config = parseCallbackConfigArg(configArg) + if (config.runName === undefined) { + config.runName = this.name + } + let parsed + try { + parsed = await this.schema.parseAsync(arg) + } catch (e) { + throw new ToolInputParsingException(`Received tool input did not match expected schema`, JSON.stringify(arg)) + } + const callbackManager_ = await CallbackManager.configure( + config.callbacks, + this.callbacks, + config.tags || tags, + this.tags, + config.metadata, + this.metadata, + { verbose: this.verbose } + ) + const runManager = await callbackManager_?.handleToolStart( + this.toJSON(), + typeof parsed === 'string' ? parsed : JSON.stringify(parsed), + undefined, + undefined, + undefined, + undefined, + config.runName + ) + let result + try { + result = await this._call(parsed, runManager, overrideSessionId) + } catch (e) { + await runManager?.handleToolError(e) + throw e + } + await runManager?.handleToolEnd(result) + return result + } + + protected async _call(arg: z.output, _?: CallbackManagerForToolRun, overrideSessionId?: string): Promise { let sandbox: any = {} if (typeof arg === 'object' && Object.keys(arg).length) { for (const item in arg) { @@ -51,6 +103,32 @@ export class DynamicStructuredTool< } } + // inject variables + let vars = {} + if (this.variables) { + for (const item of this.variables) { + let value = item.value + + // read from .env file + if (item.type === 'runtime') { + value = process.env[item.name] + } + + Object.defineProperty(vars, item.name, { + enumerable: true, + configurable: true, + writable: true, + value: value + }) + } + } + sandbox['$vars'] = vars + + // inject flow properties + if (this.flowObj) { + sandbox['$flow'] = { ...this.flowObj, sessionId: overrideSessionId } + } + const defaultAllowBuiltInDep = [ 'assert', 'buffer', @@ -87,4 +165,12 @@ export class DynamicStructuredTool< return response } + + setVariables(variables: any[]) { + this.variables = variables + } + + setFlowObject(flow: any) { + this.flowObj = flow + } } diff --git a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts index b358b24b3..37511e476 100644 --- a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts +++ b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts @@ -65,7 +65,7 @@ class CustomFunction_Utilities implements INode { inputVars = typeof functionInputVariablesRaw === 'object' ? functionInputVariablesRaw : JSON.parse(functionInputVariablesRaw) } catch (exception) { - throw new Error("Invalid JSON in the PromptTemplate's promptValues: " + exception) + throw new Error('Invalid JSON in the Custom Function Input Variables: ' + exception) } } diff --git a/packages/components/nodes/utilities/IfElseFunction/ifelsefunction.svg b/packages/components/nodes/utilities/IfElseFunction/ifelsefunction.svg index f5dd5979f..f4ccc78be 100644 --- a/packages/components/nodes/utilities/IfElseFunction/ifelsefunction.svg +++ b/packages/components/nodes/utilities/IfElseFunction/ifelsefunction.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/components/nodes/vectorstores/Postgres/Postgres.ts b/packages/components/nodes/vectorstores/Postgres/Postgres.ts index ac4b80c3c..4e8bae32b 100644 --- a/packages/components/nodes/vectorstores/Postgres/Postgres.ts +++ b/packages/components/nodes/vectorstores/Postgres/Postgres.ts @@ -24,7 +24,7 @@ class Postgres_VectorStores implements INode { constructor() { this.label = 'Postgres' this.name = 'postgres' - this.version = 1.0 + this.version = 2.0 this.type = 'Postgres' this.icon = 'postgres.svg' this.category = 'Vector Stores' @@ -60,6 +60,13 @@ class Postgres_VectorStores implements INode { name: 'database', type: 'string' }, + { + label: 'SSL Connection', + name: 'sslConnection', + type: 'boolean', + default: false, + optional: false + }, { label: 'Port', name: 'port', @@ -117,6 +124,7 @@ class Postgres_VectorStores implements INode { const docs = nodeData.inputs?.document as Document[] const embeddings = nodeData.inputs?.embeddings as Embeddings const additionalConfig = nodeData.inputs?.additionalConfig as string + const sslConnection = nodeData.inputs?.sslConnection as boolean let additionalConfiguration = {} if (additionalConfig) { @@ -134,7 +142,8 @@ class Postgres_VectorStores implements INode { port: nodeData.inputs?.port as number, username: user, password: password, - database: nodeData.inputs?.database as string + database: nodeData.inputs?.database as string, + ssl: sslConnection } const args = { diff --git a/packages/components/nodes/vectorstores/Postgres/Postgres_Exisiting.ts b/packages/components/nodes/vectorstores/Postgres/Postgres_Exisiting.ts index 99794a0de..3fa8a1078 100644 --- a/packages/components/nodes/vectorstores/Postgres/Postgres_Exisiting.ts +++ b/packages/components/nodes/vectorstores/Postgres/Postgres_Exisiting.ts @@ -23,7 +23,7 @@ class Postgres_Existing_VectorStores implements INode { constructor() { this.label = 'Postgres Load Existing Index' this.name = 'postgresExistingIndex' - this.version = 1.0 + this.version = 2.0 this.type = 'Postgres' this.icon = 'postgres.svg' this.category = 'Vector Stores' @@ -52,6 +52,13 @@ class Postgres_Existing_VectorStores implements INode { name: 'database', type: 'string' }, + { + label: 'SSL Connection', + name: 'sslConnection', + type: 'boolean', + default: false, + optional: false + }, { label: 'Port', name: 'port', @@ -109,6 +116,7 @@ class Postgres_Existing_VectorStores implements INode { const output = nodeData.outputs?.output as string const topK = nodeData.inputs?.topK as string const k = topK ? parseFloat(topK) : 4 + const sslConnection = nodeData.inputs?.sslConnection as boolean let additionalConfiguration = {} if (additionalConfig) { @@ -126,7 +134,8 @@ class Postgres_Existing_VectorStores implements INode { port: nodeData.inputs?.port as number, username: user, password: password, - database: nodeData.inputs?.database as string + database: nodeData.inputs?.database as string, + ssl: sslConnection } const args = { diff --git a/packages/components/nodes/vectorstores/Postgres/Postgres_Upsert.ts b/packages/components/nodes/vectorstores/Postgres/Postgres_Upsert.ts index f706cbe82..d26a642dd 100644 --- a/packages/components/nodes/vectorstores/Postgres/Postgres_Upsert.ts +++ b/packages/components/nodes/vectorstores/Postgres/Postgres_Upsert.ts @@ -24,7 +24,7 @@ class PostgresUpsert_VectorStores implements INode { constructor() { this.label = 'Postgres Upsert Document' this.name = 'postgresUpsert' - this.version = 1.0 + this.version = 2.0 this.type = 'Postgres' this.icon = 'postgres.svg' this.category = 'Vector Stores' @@ -59,6 +59,13 @@ class PostgresUpsert_VectorStores implements INode { name: 'database', type: 'string' }, + { + label: 'SSL Connection', + name: 'sslConnection', + type: 'boolean', + default: false, + optional: false + }, { label: 'Port', name: 'port', @@ -117,6 +124,7 @@ class PostgresUpsert_VectorStores implements INode { const output = nodeData.outputs?.output as string const topK = nodeData.inputs?.topK as string const k = topK ? parseFloat(topK) : 4 + const sslConnection = nodeData.inputs?.sslConnection as boolean let additionalConfiguration = {} if (additionalConfig) { @@ -134,7 +142,8 @@ class PostgresUpsert_VectorStores implements INode { port: nodeData.inputs?.port as number, username: user, password: password, - database: nodeData.inputs?.database as string + database: nodeData.inputs?.database as string, + ssl: sslConnection } const args = { diff --git a/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts b/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts index 6413f8bf8..e07b728a5 100644 --- a/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts +++ b/packages/components/nodes/vectorstores/Qdrant/Qdrant.ts @@ -149,9 +149,12 @@ class Qdrant_VectorStores implements INode { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const qdrantApiKey = getCredentialParam('qdrantApiKey', credentialData, nodeData) + const port = Qdrant_VectorStores.determinePortByUrl(qdrantServerUrl) + const client = new QdrantClient({ url: qdrantServerUrl, - apiKey: qdrantApiKey + apiKey: qdrantApiKey, + port: port }) const flattenDocs = docs && docs.length ? flatten(docs) : [] @@ -198,9 +201,12 @@ class Qdrant_VectorStores implements INode { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const qdrantApiKey = getCredentialParam('qdrantApiKey', credentialData, nodeData) + const port = Qdrant_VectorStores.determinePortByUrl(qdrantServerUrl) + const client = new QdrantClient({ url: qdrantServerUrl, - apiKey: qdrantApiKey + apiKey: qdrantApiKey, + port: port }) const dbConfig: QdrantLibArgs = { @@ -242,6 +248,28 @@ class Qdrant_VectorStores implements INode { } return vectorStore } + + /** + * Determine the port number from the given URL. + * + * The problem is when not doing this the qdrant-client.js will fall back on 6663 when you enter a port 443 and 80. + * See: https://stackoverflow.com/questions/59104197/nodejs-new-url-urlhttps-myurl-com80-lists-the-port-as-empty + * @param qdrantServerUrl the url to get the port from + */ + static determinePortByUrl(qdrantServerUrl: string): number { + const parsedUrl = new URL(qdrantServerUrl) + + let port = parsedUrl.port ? parseInt(parsedUrl.port) : 6663 + + if (parsedUrl.protocol === 'https:' && parsedUrl.port === '') { + port = 443 + } + if (parsedUrl.protocol === 'http:' && parsedUrl.port === '') { + port = 80 + } + + return port + } } module.exports = { nodeClass: Qdrant_VectorStores } diff --git a/packages/components/package.json b/packages/components/package.json index 9cb0bf1e9..a2565430b 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.4.9", + "version": "1.5.0", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", @@ -26,8 +26,8 @@ "@gomomento/sdk-core": "^1.51.1", "@google-ai/generativelanguage": "^0.2.1", "@huggingface/inference": "^2.6.1", - "@langchain/google-genai": "^0.0.3", - "@langchain/mistralai": "^0.0.3", + "@langchain/google-genai": "^0.0.6", + "@langchain/mistralai": "^0.0.6", "@notionhq/client": "^2.2.8", "@opensearch-project/opensearch": "^1.2.0", "@pinecone-database/pinecone": "^1.1.1", @@ -52,10 +52,10 @@ "html-to-text": "^9.0.5", "husky": "^8.0.3", "ioredis": "^5.3.2", - "langchain": "^0.0.196", - "langfuse": "^1.2.0", - "langfuse-langchain": "^1.0.31", - "langsmith": "^0.0.49", + "langchain": "^0.0.214", + "langfuse": "2.0.2", + "langfuse-langchain": "2.0.2", + "langsmith": "0.0.53", "linkifyjs": "^4.1.1", "llmonitor": "^0.5.5", "mammoth": "^1.5.1", diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index 6752f9440..2a625ff6a 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -73,6 +73,7 @@ export interface INodeParams { additionalParams?: boolean loadMethod?: string hidden?: boolean + variables?: ICommonObject[] } export interface INodeExecutionData { @@ -195,3 +196,37 @@ export class VectorStoreRetriever { this.vectorStore = fields.vectorStore } } + +/** + * Implement abstract classes and interface for memory + */ +import { BaseMessage } from 'langchain/schema' +import { BufferMemory, BufferWindowMemory, ConversationSummaryMemory } from 'langchain/memory' + +export interface MemoryMethods { + getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise + clearChatMessages(overrideSessionId?: string): Promise + resumeMessages?(messages: IMessage[]): Promise +} + +export abstract class FlowiseMemory extends BufferMemory implements MemoryMethods { + abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise + abstract clearChatMessages(overrideSessionId?: string): Promise + abstract resumeMessages(messages: IMessage[]): Promise +} + +export abstract class FlowiseWindowMemory extends BufferWindowMemory implements MemoryMethods { + abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise + abstract clearChatMessages(overrideSessionId?: string): Promise + abstract resumeMessages(messages: IMessage[]): Promise +} + +export abstract class FlowiseSummaryMemory extends ConversationSummaryMemory implements MemoryMethods { + abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise + abstract clearChatMessages(overrideSessionId?: string): Promise + abstract resumeMessages(messages: IMessage[]): Promise +} diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts index 29aff3e2f..1eb05a517 100644 --- a/packages/components/src/handler.ts +++ b/packages/components/src/handler.ts @@ -538,7 +538,7 @@ export class AnalyticHandler { if (trace) { const generation = trace.generation({ name, - prompt: input + input: input }) this.handlers['langFuse'].generation = { [generation.id]: generation } returnIds['langFuse'].generation = generation.id @@ -583,7 +583,7 @@ export class AnalyticHandler { const generation: LangfuseGenerationClient | undefined = this.handlers['langFuse'].generation[returnIds['langFuse'].generation] if (generation) { generation.end({ - completion: output + output: output }) } } @@ -618,7 +618,7 @@ export class AnalyticHandler { const generation: LangfuseGenerationClient | undefined = this.handlers['langFuse'].generation[returnIds['langFuse'].generation] if (generation) { generation.end({ - completion: error + output: error }) } } diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 239b13ca8..22fa6f4a9 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -8,7 +8,7 @@ import { DataSource } from 'typeorm' import { ICommonObject, IDatabaseEntity, IMessage, INodeData } from './Interface' import { AES, enc } from 'crypto-js' import { ChatMessageHistory } from 'langchain/memory' -import { AIMessage, HumanMessage } from 'langchain/schema' +import { AIMessage, HumanMessage, BaseMessage } from 'langchain/schema' export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}} export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank @@ -436,7 +436,8 @@ const getEncryptionKeyFilePath = (): string => { path.join(__dirname, '..', '..', '..', '..', 'encryption.key'), path.join(__dirname, '..', '..', '..', '..', 'server', 'encryption.key'), path.join(__dirname, '..', '..', '..', '..', '..', 'encryption.key'), - path.join(__dirname, '..', '..', '..', '..', '..', 'server', 'encryption.key') + path.join(__dirname, '..', '..', '..', '..', '..', 'server', 'encryption.key'), + path.join(getUserHome(), '.flowise', 'encryption.key') ] for (const checkPath of checkPaths) { if (fs.existsSync(checkPath)) { @@ -446,7 +447,7 @@ const getEncryptionKeyFilePath = (): string => { return '' } -const getEncryptionKeyPath = (): string => { +export const getEncryptionKeyPath = (): string => { return process.env.SECRETKEY_PATH ? path.join(process.env.SECRETKEY_PATH, 'encryption.key') : getEncryptionKeyFilePath() } @@ -644,3 +645,31 @@ export const convertSchemaToZod = (schema: string | object): ICommonObject => { throw new Error(e) } } + +/** + * Convert BaseMessage to IMessage + * @param {BaseMessage[]} messages + * @returns {IMessage[]} + */ +export const convertBaseMessagetoIMessage = (messages: BaseMessage[]): IMessage[] => { + const formatmessages: IMessage[] = [] + for (const m of messages) { + if (m._getType() === 'human') { + formatmessages.push({ + message: m.content as string, + type: 'userMessage' + }) + } else if (m._getType() === 'ai') { + formatmessages.push({ + message: m.content as string, + type: 'apiMessage' + }) + } else if (m._getType() === 'system') { + formatmessages.push({ + message: m.content as string, + type: 'apiMessage' + }) + } + } + return formatmessages +} diff --git a/packages/server/.env.example b/packages/server/.env.example index 0ad11f3f7..6e746a4df 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -12,6 +12,7 @@ PORT=3000 # DATABASE_NAME="flowise" # DATABASE_USER="" # DATABASE_PASSWORD="" +# DATABASE_SSL=true # FLOWISE_USERNAME=user # FLOWISE_PASSWORD=1234 diff --git a/packages/server/marketplaces/chatflows/OpenAI Assistant.json b/packages/server/marketplaces/chatflows/OpenAI Assistant.json index ba4c61343..e9311c978 100644 --- a/packages/server/marketplaces/chatflows/OpenAI Assistant.json +++ b/packages/server/marketplaces/chatflows/OpenAI Assistant.json @@ -14,7 +14,7 @@ "data": { "id": "openAIAssistant_0", "label": "OpenAI Assistant", - "version": 2, + "version": 3, "name": "openAIAssistant", "type": "OpenAIAssistant", "baseClasses": ["OpenAIAssistant"], @@ -45,6 +45,15 @@ "type": "Tool", "list": true, "id": "openAIAssistant_0-input-tools-Tool" + }, + { + "label": "Input Moderation", + "description": "Detect text that could generate harmful output and prevent it from being sent to the language model", + "name": "inputModeration", + "type": "Moderation", + "optional": true, + "list": true, + "id": "openAIAssistant_0-input-inputModeration-Moderation" } ], "inputs": { diff --git a/packages/server/package.json b/packages/server/package.json index 581dfafe1..f1c0b7f79 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.4.7", + "version": "1.4.9", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", diff --git a/packages/server/src/DataSource.ts b/packages/server/src/DataSource.ts index 9265e55f4..762315ac9 100644 --- a/packages/server/src/DataSource.ts +++ b/packages/server/src/DataSource.ts @@ -46,6 +46,7 @@ export const init = async (): Promise => { username: process.env.DATABASE_USER, password: process.env.DATABASE_PASSWORD, database: process.env.DATABASE_NAME, + ssl: process.env.DATABASE_SSL === 'true', synchronize: false, migrationsRun: false, entities: Object.values(entities), diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 80257c9cb..942fe490c 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -69,6 +69,15 @@ export interface ICredential { createdDate: Date } +export interface IVariable { + id: string + name: string + value: string + type: string + updatedDate: Date + createdDate: Date +} + export interface IComponentNodes { [key: string]: INode } diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index cd8742649..d4e8cfdbf 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -35,6 +35,7 @@ export default class Start extends Command { DATABASE_NAME: Flags.string(), DATABASE_USER: Flags.string(), DATABASE_PASSWORD: Flags.string(), + DATABASE_SSL: Flags.string(), LANGCHAIN_TRACING_V2: Flags.string(), LANGCHAIN_ENDPOINT: Flags.string(), LANGCHAIN_API_KEY: Flags.string(), @@ -104,6 +105,7 @@ export default class Start extends Command { if (flags.DATABASE_NAME) process.env.DATABASE_NAME = flags.DATABASE_NAME if (flags.DATABASE_USER) process.env.DATABASE_USER = flags.DATABASE_USER if (flags.DATABASE_PASSWORD) process.env.DATABASE_PASSWORD = flags.DATABASE_PASSWORD + if (flags.DATABASE_SSL) process.env.DATABASE_SSL = flags.DATABASE_SSL // Langsmith tracing if (flags.LANGCHAIN_TRACING_V2) process.env.LANGCHAIN_TRACING_V2 = flags.LANGCHAIN_TRACING_V2 diff --git a/packages/server/src/database/entities/Variable.ts b/packages/server/src/database/entities/Variable.ts new file mode 100644 index 000000000..6af7a2378 --- /dev/null +++ b/packages/server/src/database/entities/Variable.ts @@ -0,0 +1,24 @@ +/* eslint-disable */ +import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm' +import { IVariable } from '../../Interface' + +@Entity() +export class Variable implements IVariable { + @PrimaryGeneratedColumn('uuid') + id: string + + @Column() + name: string + + @Column({ nullable: true, type: 'text' }) + value: string + + @Column({ default: 'string', type: 'text' }) + type: string + + @CreateDateColumn() + createdDate: Date + + @UpdateDateColumn() + updatedDate: Date +} diff --git a/packages/server/src/database/entities/index.ts b/packages/server/src/database/entities/index.ts index 58447a1f5..af5c559f5 100644 --- a/packages/server/src/database/entities/index.ts +++ b/packages/server/src/database/entities/index.ts @@ -3,11 +3,13 @@ import { ChatMessage } from './ChatMessage' import { Credential } from './Credential' import { Tool } from './Tool' import { Assistant } from './Assistant' +import { Variable } from './Variable' export const entities = { ChatFlow, ChatMessage, Credential, Tool, - Assistant + Assistant, + Variable } diff --git a/packages/server/src/database/migrations/mysql/1702200925471-AddVariableEntity.ts b/packages/server/src/database/migrations/mysql/1702200925471-AddVariableEntity.ts new file mode 100644 index 000000000..a6c818874 --- /dev/null +++ b/packages/server/src/database/migrations/mysql/1702200925471-AddVariableEntity.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddVariableEntity1699325775451 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE IF NOT EXISTS \`variable\` ( + \`id\` varchar(36) NOT NULL, + \`name\` varchar(255) NOT NULL, + \`value\` text NOT NULL, + \`type\` varchar(255) DEFAULT NULL, + \`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + \`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY (\`id\`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE variable`) + } +} diff --git a/packages/server/src/database/migrations/mysql/index.ts b/packages/server/src/database/migrations/mysql/index.ts index f5adff64f..ad5f103dc 100644 --- a/packages/server/src/database/migrations/mysql/index.ts +++ b/packages/server/src/database/migrations/mysql/index.ts @@ -11,6 +11,7 @@ import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedT import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow' import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' import { AddFileUploadsToChatMessage1701788586491 } from './1701788586491-AddFileUploadsToChatMessage' +import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity' export const mysqlMigrations = [ Init1693840429259, @@ -25,5 +26,6 @@ export const mysqlMigrations = [ AddUsedToolsToChatMessage1699481607341, AddCategoryToChatFlow1699900910291, AddFileAnnotationsToChatMessage1700271021237, - AddFileUploadsToChatMessage1701788586491 + AddFileUploadsToChatMessage1701788586491, + AddVariableEntity1699325775451 ] diff --git a/packages/server/src/database/migrations/postgres/1702200925471-AddVariableEntity.ts b/packages/server/src/database/migrations/postgres/1702200925471-AddVariableEntity.ts new file mode 100644 index 000000000..c6d3902f7 --- /dev/null +++ b/packages/server/src/database/migrations/postgres/1702200925471-AddVariableEntity.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddVariableEntity1699325775451 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE IF NOT EXISTS variable ( + id uuid NOT NULL DEFAULT uuid_generate_v4(), + "name" varchar NOT NULL, + "value" text NOT NULL, + "type" text NULL, + "createdDate" timestamp NOT NULL DEFAULT now(), + "updatedDate" timestamp NOT NULL DEFAULT now(), + CONSTRAINT "PK_98419043dd704f54-9830ab78f8" PRIMARY KEY (id) + );` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE variable`) + } +} diff --git a/packages/server/src/database/migrations/postgres/index.ts b/packages/server/src/database/migrations/postgres/index.ts index f80335a03..984bac663 100644 --- a/packages/server/src/database/migrations/postgres/index.ts +++ b/packages/server/src/database/migrations/postgres/index.ts @@ -11,6 +11,7 @@ import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedT import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow' import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' import { AddFileUploadsToChatMessage1701788586491 } from './1701788586491-AddFileUploadsToChatMessage' +import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity' export const postgresMigrations = [ Init1693891895163, @@ -25,5 +26,6 @@ export const postgresMigrations = [ AddUsedToolsToChatMessage1699481607341, AddCategoryToChatFlow1699900910291, AddFileAnnotationsToChatMessage1700271021237, - AddFileUploadsToChatMessage1701788586491 + AddFileUploadsToChatMessage1701788586491, + AddVariableEntity1699325775451 ] diff --git a/packages/server/src/database/migrations/sqlite/1702200925471-AddVariableEntity.ts b/packages/server/src/database/migrations/sqlite/1702200925471-AddVariableEntity.ts new file mode 100644 index 000000000..63ec709fa --- /dev/null +++ b/packages/server/src/database/migrations/sqlite/1702200925471-AddVariableEntity.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddVariableEntity1699325775451 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE IF NOT EXISTS "variable" ("id" varchar PRIMARY KEY NOT NULL, "name" text NOT NULL, "value" text NOT NULL, "type" varchar, "createdDate" datetime NOT NULL DEFAULT (datetime('now')), "updatedDate" datetime NOT NULL DEFAULT (datetime('now')));` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE variable`) + } +} diff --git a/packages/server/src/database/migrations/sqlite/index.ts b/packages/server/src/database/migrations/sqlite/index.ts index bae0cec88..19b122d8d 100644 --- a/packages/server/src/database/migrations/sqlite/index.ts +++ b/packages/server/src/database/migrations/sqlite/index.ts @@ -11,6 +11,7 @@ import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedT import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow' import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' import { AddFileUploadsToChatMessage1701788586491 } from './1701788586491-AddFileUploadsToChatMessage' +import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity' export const sqliteMigrations = [ Init1693835579790, @@ -25,5 +26,6 @@ export const sqliteMigrations = [ AddUsedToolsToChatMessage1699481607341, AddCategoryToChatFlow1699900910291, AddFileAnnotationsToChatMessage1700271021237, - AddFileUploadsToChatMessage1701788586491 + AddFileUploadsToChatMessage1701788586491, + AddVariableEntity1699325775451 ] diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 4af094c60..58f520469 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -65,6 +65,7 @@ import { sanitizeMiddleware } from './utils/XSS' import axios from 'axios' import { Client } from 'langchainhub' import { parsePrompt } from './utils/hub' +import { Variable } from './database/entities/Variable' export class App { app: express.Application @@ -1227,6 +1228,47 @@ export class App { return res.json(templates) }) + // ---------------------------------------- + // Variables + // ---------------------------------------- + this.app.get('/api/v1/variables', async (req: Request, res: Response) => { + const variables = await getDataSource().getRepository(Variable).find() + return res.json(variables) + }) + + // Create new variable + this.app.post('/api/v1/variables', async (req: Request, res: Response) => { + const body = req.body + const newVariable = new Variable() + Object.assign(newVariable, body) + const variable = this.AppDataSource.getRepository(Variable).create(newVariable) + const results = await this.AppDataSource.getRepository(Variable).save(variable) + return res.json(results) + }) + + // Update variable + this.app.put('/api/v1/variables/:id', async (req: Request, res: Response) => { + const variable = await this.AppDataSource.getRepository(Variable).findOneBy({ + id: req.params.id + }) + + if (!variable) return res.status(404).send(`Variable ${req.params.id} not found`) + + const body = req.body + const updateVariable = new Variable() + Object.assign(updateVariable, body) + this.AppDataSource.getRepository(Variable).merge(variable, updateVariable) + const result = await this.AppDataSource.getRepository(Variable).save(variable) + + return res.json(result) + }) + + // Delete variable via id + this.app.delete('/api/v1/variables/:id', async (req: Request, res: Response) => { + const results = await this.AppDataSource.getRepository(Variable).delete({ id: req.params.id }) + return res.json(results) + }) + // ---------------------------------------- // API Keys // ---------------------------------------- @@ -1658,9 +1700,9 @@ export class App { if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { if (endingNodeData.type !== 'OpenAIMultiModalChain') { - return res.status(500).send(`Ending node must be either a Chain or Agent`) + return res.status(500).send(`Ending node must be either a Chain or Agent`) + } } - } if ( endingNodeData.outputs && @@ -1749,10 +1791,6 @@ export class App { this.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig) } - const nodeInstanceFilePath = this.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string - const nodeModule = await import(nodeInstanceFilePath) - const nodeInstance = new nodeModule.nodeClass() - logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`) let sessionId = undefined @@ -1766,6 +1804,10 @@ export class App { chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger) } + const nodeInstanceFilePath = this.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const nodeInstance = new nodeModule.nodeClass({ sessionId }) + let result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { uploads: incomingInput.uploads, diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index eec813d4f..9c2d1d79d 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -23,6 +23,7 @@ import { convertChatHistoryToText, getInputVariables, handleEscapeCharacters, + getEncryptionKeyPath, ICommonObject, IDatabaseEntity, IMessage @@ -37,6 +38,7 @@ import { Tool } from '../database/entities/Tool' import { Assistant } from '../database/entities/Assistant' import { DataSource } from 'typeorm' import { CachePool } from '../CachePool' +import { Variable } from '../database/entities/Variable' const QUESTION_VAR_PREFIX = 'question' const CHAT_HISTORY_VAR_PREFIX = 'chat_history' @@ -47,7 +49,8 @@ export const databaseEntities: IDatabaseEntity = { ChatMessage: ChatMessage, Tool: Tool, Credential: Credential, - Assistant: Assistant + Assistant: Assistant, + Variable: Variable } /** @@ -558,7 +561,11 @@ export const getVariableValue = ( variablePaths.forEach((path) => { const variableValue = variableDict[path] // Replace all occurrence - returnVal = returnVal.split(path).join(variableValue) + if (typeof variableValue === 'object') { + returnVal = returnVal.split(path).join(JSON.stringify(variableValue).replace(/"/g, '\\"')) + } else { + returnVal = returnVal.split(path).join(variableValue) + } }) return returnVal } @@ -815,7 +822,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component */ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNodeData: INodeData) => { const streamAvailableLLMs = { - 'Chat Models': ['azureChatOpenAI', 'chatOpenAI', 'chatAnthropic', 'chatOllama', 'awsChatBedrock'], + 'Chat Models': ['azureChatOpenAI', 'chatOpenAI', 'chatAnthropic', 'chatOllama', 'awsChatBedrock', 'chatMistralAI'], LLMs: ['azureOpenAI', 'openAI', 'ollama'] } @@ -852,16 +859,6 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod return isChatOrLLMsExist && isValidChainOrAgent && !isOutputParserExist } -/** - * Returns the path of encryption key - * @returns {string} - */ -export const getEncryptionKeyPath = (): string => { - return process.env.SECRETKEY_PATH - ? path.join(process.env.SECRETKEY_PATH, 'encryption.key') - : path.join(__dirname, '..', '..', 'encryption.key') -} - /** * Generate an encryption key * @returns {string} @@ -882,7 +879,10 @@ export const getEncryptionKey = async (): Promise => { return await fs.promises.readFile(getEncryptionKeyPath(), 'utf8') } catch (error) { const encryptKey = generateEncryptKey() - await fs.promises.writeFile(getEncryptionKeyPath(), encryptKey) + const defaultLocation = process.env.SECRETKEY_PATH + ? path.join(process.env.SECRETKEY_PATH, 'encryption.key') + : path.join(getUserHome(), '.flowise', 'encryption.key') + await fs.promises.writeFile(defaultLocation, encryptKey) return encryptKey } } diff --git a/packages/ui/package.json b/packages/ui/package.json index 2ab0befba..c5549b23c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.4.5", + "version": "1.4.6", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { diff --git a/packages/ui/src/api/variables.js b/packages/ui/src/api/variables.js new file mode 100644 index 000000000..944b83198 --- /dev/null +++ b/packages/ui/src/api/variables.js @@ -0,0 +1,16 @@ +import client from './client' + +const getAllVariables = () => client.get('/variables') + +const createVariable = (body) => client.post(`/variables`, body) + +const updateVariable = (id, body) => client.put(`/variables/${id}`, body) + +const deleteVariable = (id) => client.delete(`/variables/${id}`) + +export default { + getAllVariables, + createVariable, + updateVariable, + deleteVariable +} diff --git a/packages/ui/src/assets/images/variables_empty.svg b/packages/ui/src/assets/images/variables_empty.svg new file mode 100644 index 000000000..eb461e39f --- /dev/null +++ b/packages/ui/src/assets/images/variables_empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/menu-items/dashboard.js b/packages/ui/src/menu-items/dashboard.js index 8bf5b3924..793bc290c 100644 --- a/packages/ui/src/menu-items/dashboard.js +++ b/packages/ui/src/menu-items/dashboard.js @@ -1,8 +1,8 @@ // assets -import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot } from '@tabler/icons' +import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot, IconVariable } from '@tabler/icons' // constant -const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot } +const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot, IconVariable } // ==============================|| DASHBOARD MENU ITEMS ||============================== // @@ -51,6 +51,14 @@ const dashboard = { icon: icons.IconLock, breadcrumbs: true }, + { + id: 'variables', + title: 'Variables', + type: 'item', + url: '/variables', + icon: icons.IconVariable, + breadcrumbs: true + }, { id: 'apikey', title: 'API Keys', diff --git a/packages/ui/src/routes/MainRoutes.js b/packages/ui/src/routes/MainRoutes.js index bce0de137..08dd721dd 100644 --- a/packages/ui/src/routes/MainRoutes.js +++ b/packages/ui/src/routes/MainRoutes.js @@ -22,6 +22,9 @@ const Assistants = Loadable(lazy(() => import('views/assistants'))) // credentials routing const Credentials = Loadable(lazy(() => import('views/credentials'))) +// variables routing +const Variables = Loadable(lazy(() => import('views/variables'))) + // ==============================|| MAIN ROUTING ||============================== // const MainRoutes = { @@ -55,6 +58,10 @@ const MainRoutes = { { path: '/credentials', element: + }, + { + path: '/variables', + element: } ] } diff --git a/packages/ui/src/ui-component/dialog/ExpandTextDialog.js b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js index 0ef70e29e..f4fdb9f9e 100644 --- a/packages/ui/src/ui-component/dialog/ExpandTextDialog.js +++ b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js @@ -67,7 +67,11 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { useEffect(() => { if (executeCustomFunctionNodeApi.data) { - setCodeExecutedResult(executeCustomFunctionNodeApi.data) + if (typeof executeCustomFunctionNodeApi.data === 'object') { + setCodeExecutedResult(JSON.stringify(executeCustomFunctionNodeApi.data, null, 2)) + } else { + setCodeExecutedResult(executeCustomFunctionNodeApi.data) + } } }, [executeCustomFunctionNodeApi.data]) diff --git a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js index 35b4ead78..8d89efc91 100644 --- a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js +++ b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.js @@ -92,24 +92,27 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { const getAvailablePromptsApi = useApi(promptApi.getAvailablePrompts) useEffect(() => { - if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) - else dispatch({ type: HIDE_CANVAS_DIALOG }) + if (show) { + dispatch({ type: SHOW_CANVAS_DIALOG }) + } else dispatch({ type: HIDE_CANVAS_DIALOG }) return () => dispatch({ type: HIDE_CANVAS_DIALOG }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [show, dispatch]) useEffect(() => { - if (promptType) { + if (promptType && show) { + setLoading(true) getAvailablePromptsApi.request({ tags: promptType === 'template' ? 'StringPromptTemplate&' : 'ChatPromptTemplate&' }) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [promptType]) + }, [promptType, show]) useEffect(() => { if (getAvailablePromptsApi.data && getAvailablePromptsApi.data.repos) { setAvailablePrompNameList(getAvailablePromptsApi.data.repos) if (getAvailablePromptsApi.data.repos?.length) handleListItemClick(0, getAvailablePromptsApi.data.repos) + setLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -174,6 +177,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { const [selectedPrompt, setSelectedPrompt] = useState({}) const [accordionExpanded, setAccordionExpanded] = useState(['prompt']) + const [loading, setLoading] = useState(false) const handleAccordionChange = (accordionName) => (event, isExpanded) => { const accordians = [...accordionExpanded] @@ -209,6 +213,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { language.forEach((item) => { tags += `tags=${item.name}&` }) + setLoading(true) getAvailablePromptsApi.request({ tags: tags }) } @@ -379,7 +384,15 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { - {availablePrompNameList && availablePrompNameList.length == 0 && ( + {loading && ( + + + promptEmptySVG + +
Please wait....loading Prompts
+ + )} + {!loading && availablePrompNameList && availablePrompNameList.length === 0 && ( promptEmptySVG @@ -387,7 +400,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => {
No Available Prompts
)} - {availablePrompNameList && availablePrompNameList.length > 0 && ( + {!loading && availablePrompNameList && availablePrompNameList.length > 0 && ( diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 33e997362..617d1066c 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -369,7 +369,12 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA {inputParam?.acceptVariable && ( <> {dialogProps.type !== 'TEMPLATE' && ( + ) + } + }) + onConfirm(createResp.data.id) + } + } catch (err) { + const errorData = typeof err === 'string' ? err : err.response?.data || `${err.response?.status}: ${err.response?.statusText}` + enqueueSnackbar({ + message: `Failed to add new Variable: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + + const saveVariable = async () => { + try { + const saveObj = { + name: variableName, + value: variableValue, + type: variableType + } + + const saveResp = await variablesApi.updateVariable(variable.id, saveObj) + if (saveResp.data) { + enqueueSnackbar({ + message: 'Variable saved', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm(saveResp.data.id) + } + } catch (error) { + const errorData = error.response?.data || `${error.response?.status}: ${error.response?.statusText}` + enqueueSnackbar({ + message: `Failed to save Variable: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + onCancel() + } + } + + const component = show ? ( + + +
+
+ +
+ {dialogProps.type === 'ADD' ? 'Add Variable' : 'Edit Variable'} +
+
+ + +
+ + Variable Name * + + +
+
+ setVariableName(e.target.value)} + value={variableName ?? ''} + /> +
+ +
+ + Type * + +
+
+ setVariableType(newValue)} + value={variableType ?? 'choose an option'} + /> +
+ {variableType === 'static' && ( + +
+ + Value * + +
+
+ setVariableValue(e.target.value)} + value={variableValue ?? ''} + /> +
+ )} +
+ + (dialogType === 'ADD' ? addNewVariable() : saveVariable())} + > + {dialogProps.confirmButtonName} + + + +
+ ) : null + + return createPortal(component, portalElement) +} + +AddEditVariableDialog.propTypes = { + show: PropTypes.bool, + dialogProps: PropTypes.object, + onCancel: PropTypes.func, + onConfirm: PropTypes.func +} + +export default AddEditVariableDialog diff --git a/packages/ui/src/views/variables/HowToUseVariablesDialog.js b/packages/ui/src/views/variables/HowToUseVariablesDialog.js new file mode 100644 index 000000000..f328f226c --- /dev/null +++ b/packages/ui/src/views/variables/HowToUseVariablesDialog.js @@ -0,0 +1,72 @@ +import { createPortal } from 'react-dom' +import PropTypes from 'prop-types' +import { Dialog, DialogContent, DialogTitle } from '@mui/material' +import { CodeEditor } from 'ui-component/editor/CodeEditor' + +const overrideConfig = `{ + overrideConfig: { + vars: { + var1: 'abc' + } + } +}` + +const HowToUseVariablesDialog = ({ show, onCancel }) => { + const portalElement = document.getElementById('portal') + + const component = show ? ( + + + How To Use Variables + + +

Variables can be used in Custom Tool Function with the $ prefix.

+ `} + height={'50px'} + theme={'dark'} + lang={'js'} + basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }} + /> +

+ If variable type is Static, the value will be retrieved as it is. If variable type is Runtime, the value will be + retrieved from .env file. +

+

+ You can also override variable values in API overrideConfig using vars: +

+ +

+ Read more from{' '} + + docs + +

+
+
+ ) : null + + return createPortal(component, portalElement) +} + +HowToUseVariablesDialog.propTypes = { + show: PropTypes.bool, + onCancel: PropTypes.func +} + +export default HowToUseVariablesDialog diff --git a/packages/ui/src/views/variables/index.js b/packages/ui/src/views/variables/index.js new file mode 100644 index 000000000..9d0b2e3fe --- /dev/null +++ b/packages/ui/src/views/variables/index.js @@ -0,0 +1,314 @@ +import { useEffect, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions' +import moment from 'moment' + +// material-ui +import { + Button, + Box, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + IconButton, + Toolbar, + TextField, + InputAdornment, + ButtonGroup, + Chip +} from '@mui/material' +import { useTheme } from '@mui/material/styles' + +// project imports +import MainCard from 'ui-component/cards/MainCard' +import { StyledButton } from 'ui-component/button/StyledButton' +import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' + +// API +import variablesApi from 'api/variables' + +// Hooks +import useApi from 'hooks/useApi' +import useConfirm from 'hooks/useConfirm' + +// utils +import useNotifier from 'utils/useNotifier' + +// Icons +import { IconTrash, IconEdit, IconX, IconPlus, IconSearch, IconVariable } from '@tabler/icons' +import VariablesEmptySVG from 'assets/images/variables_empty.svg' + +// const +import AddEditVariableDialog from './AddEditVariableDialog' +import HowToUseVariablesDialog from './HowToUseVariablesDialog' + +// ==============================|| Credentials ||============================== // + +const Variables = () => { + const theme = useTheme() + const customization = useSelector((state) => state.customization) + + const dispatch = useDispatch() + useNotifier() + + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [showVariableDialog, setShowVariableDialog] = useState(false) + const [variableDialogProps, setVariableDialogProps] = useState({}) + const [variables, setVariables] = useState([]) + const [showHowToDialog, setShowHowToDialog] = useState(false) + + const { confirm } = useConfirm() + + const getAllVariables = useApi(variablesApi.getAllVariables) + + const [search, setSearch] = useState('') + const onSearchChange = (event) => { + setSearch(event.target.value) + } + function filterVariables(data) { + return data.name.toLowerCase().indexOf(search.toLowerCase()) > -1 + } + + const addNew = () => { + const dialogProp = { + type: 'ADD', + cancelButtonName: 'Cancel', + confirmButtonName: 'Add', + data: {} + } + setVariableDialogProps(dialogProp) + setShowVariableDialog(true) + } + + const edit = (variable) => { + const dialogProp = { + type: 'EDIT', + cancelButtonName: 'Cancel', + confirmButtonName: 'Save', + data: variable + } + setVariableDialogProps(dialogProp) + setShowVariableDialog(true) + } + + const deleteVariable = async (variable) => { + const confirmPayload = { + title: `Delete`, + description: `Delete variable ${variable.name}?`, + confirmButtonName: 'Delete', + cancelButtonName: 'Cancel' + } + const isConfirmed = await confirm(confirmPayload) + + if (isConfirmed) { + try { + const deleteResp = await variablesApi.deleteVariable(variable.id) + if (deleteResp.data) { + enqueueSnackbar({ + message: 'Variable deleted', + options: { + key: new Date().getTime() + Math.random(), + variant: 'success', + action: (key) => ( + + ) + } + }) + onConfirm() + } + } catch (error) { + const errorData = error.response?.data || `${error.response?.status}: ${error.response?.statusText}` + enqueueSnackbar({ + message: `Failed to delete Variable: ${errorData}`, + options: { + key: new Date().getTime() + Math.random(), + variant: 'error', + persist: true, + action: (key) => ( + + ) + } + }) + } + } + } + + const onConfirm = () => { + setShowVariableDialog(false) + getAllVariables.request() + } + + useEffect(() => { + getAllVariables.request() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (getAllVariables.data) { + setVariables(getAllVariables.data) + } + }, [getAllVariables.data]) + + return ( + <> + + + + +

Variables 

+ + + + ) + }} + /> + + + + + } + > + Add Variable + + + +
+
+
+ {variables.length === 0 && ( + + + VariablesEmptySVG + +
No Variables Yet
+
+ )} + {variables.length > 0 && ( + + + + + Name + Value + Type + Last Updated + Created + + + + + + {variables.filter(filterVariables).map((variable, index) => ( + + +
+
+ +
+ {variable.name} +
+
+ {variable.value} + + + + {moment(variable.updatedDate).format('DD-MMM-YY')} + {moment(variable.createdDate).format('DD-MMM-YY')} + + edit(variable)}> + + + + + deleteVariable(variable)}> + + + +
+ ))} +
+
+
+ )} +
+ setShowVariableDialog(false)} + onConfirm={onConfirm} + > + setShowHowToDialog(false)}> + + + ) +} + +export default Variables