From 292b90ccb10b298b3519f3631e48d0d989e66806 Mon Sep 17 00:00:00 2001 From: Avin Date: Thu, 23 Nov 2023 11:37:49 +0530 Subject: [PATCH 01/16] refactor: move api key utilities to seperate file --- packages/server/src/index.ts | 8 +- packages/server/src/utils/apiKey.ts | 147 +++++++++++++++++++++++++ packages/server/src/utils/index.ts | 164 ++-------------------------- 3 files changed, 159 insertions(+), 160 deletions(-) create mode 100644 packages/server/src/utils/apiKey.ts diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 2f7d31e2..7e10c817 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -31,18 +31,11 @@ import { constructGraphs, resolveVariables, isStartNodeDependOnInput, - getAPIKeys, - addAPIKey, - updateAPIKey, - deleteAPIKey, - compareKeys, mapMimeTypeToInputField, findAvailableConfigs, isSameOverrideConfig, - replaceAllAPIKeys, isFlowValidForStream, databaseEntities, - getApiKey, transformToCredentialEntity, decryptCredentialData, clearAllSessionMemory, @@ -64,6 +57,7 @@ import { ChatflowPool } from './ChatflowPool' import { CachePool } from './CachePool' import { ICommonObject, INodeOptionsValue } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' +import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, replaceAllAPIKeys, updateAPIKey } from './utils/apiKey' export class App { app: express.Application diff --git a/packages/server/src/utils/apiKey.ts b/packages/server/src/utils/apiKey.ts new file mode 100644 index 00000000..08a9ecd3 --- /dev/null +++ b/packages/server/src/utils/apiKey.ts @@ -0,0 +1,147 @@ +import { randomBytes, scryptSync, timingSafeEqual } from 'crypto' +import { ICommonObject } from 'flowise-components' +import moment from 'moment' +import fs from 'fs' +import path from 'path' +import logger from './logger' + +/** + * Returns the api key path + * @returns {string} + */ +export const getAPIKeyPath = (): string => { + return process.env.APIKEY_PATH ? path.join(process.env.APIKEY_PATH, 'api.json') : path.join(__dirname, '..', '..', 'api.json') +} + +/** + * Generate the api key + * @returns {string} + */ +export const generateAPIKey = (): string => { + const buffer = randomBytes(32) + return buffer.toString('base64') +} + +/** + * Generate the secret key + * @param {string} apiKey + * @returns {string} + */ +export const generateSecretHash = (apiKey: string): string => { + const salt = randomBytes(8).toString('hex') + const buffer = scryptSync(apiKey, salt, 64) as Buffer + return `${buffer.toString('hex')}.${salt}` +} + +/** + * Verify valid keys + * @param {string} storedKey + * @param {string} suppliedKey + * @returns {boolean} + */ +export const compareKeys = (storedKey: string, suppliedKey: string): boolean => { + const [hashedPassword, salt] = storedKey.split('.') + const buffer = scryptSync(suppliedKey, salt, 64) as Buffer + return timingSafeEqual(Buffer.from(hashedPassword, 'hex'), buffer) +} + +/** + * Get API keys + * @returns {Promise} + */ +export const getAPIKeys = async (): Promise => { + try { + const content = await fs.promises.readFile(getAPIKeyPath(), 'utf8') + return JSON.parse(content) + } catch (error) { + const keyName = 'DefaultKey' + const apiKey = generateAPIKey() + const apiSecret = generateSecretHash(apiKey) + const content = [ + { + keyName, + apiKey, + apiSecret, + createdAt: moment().format('DD-MMM-YY'), + id: randomBytes(16).toString('hex') + } + ] + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') + return content + } +} + +/** + * Add new API key + * @param {string} keyName + * @returns {Promise} + */ +export const addAPIKey = async (keyName: string): Promise => { + const existingAPIKeys = await getAPIKeys() + const apiKey = generateAPIKey() + const apiSecret = generateSecretHash(apiKey) + const content = [ + ...existingAPIKeys, + { + keyName, + apiKey, + apiSecret, + createdAt: moment().format('DD-MMM-YY'), + id: randomBytes(16).toString('hex') + } + ] + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') + return content +} + +/** + * Get API Key details + * @param {string} apiKey + * @returns {Promise} + */ +export const getApiKey = async (apiKey: string) => { + const existingAPIKeys = await getAPIKeys() + const keyIndex = existingAPIKeys.findIndex((key) => key.apiKey === apiKey) + if (keyIndex < 0) return undefined + return existingAPIKeys[keyIndex] +} + +/** + * Update existing API key + * @param {string} keyIdToUpdate + * @param {string} newKeyName + * @returns {Promise} + */ +export const updateAPIKey = async (keyIdToUpdate: string, newKeyName: string): Promise => { + const existingAPIKeys = await getAPIKeys() + const keyIndex = existingAPIKeys.findIndex((key) => key.id === keyIdToUpdate) + if (keyIndex < 0) return [] + existingAPIKeys[keyIndex].keyName = newKeyName + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(existingAPIKeys), 'utf8') + return existingAPIKeys +} + +/** + * Delete API key + * @param {string} keyIdToDelete + * @returns {Promise} + */ +export const deleteAPIKey = async (keyIdToDelete: string): Promise => { + const existingAPIKeys = await getAPIKeys() + const result = existingAPIKeys.filter((key) => key.id !== keyIdToDelete) + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8') + return result +} + +/** + * Replace all api keys + * @param {ICommonObject[]} content + * @returns {Promise} + */ +export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise => { + try { + await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') + } catch (error) { + logger.error(error) + } +} diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 0c6f2362..bc36c78f 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -1,33 +1,32 @@ import path from 'path' import fs from 'fs' -import moment from 'moment' import logger from './logger' import { + IComponentCredentials, IComponentNodes, + ICredentialDataDecrypted, + ICredentialReqBody, IDepthQueue, IExploredNode, + INodeData, INodeDependencies, INodeDirectedGraph, INodeQueue, + IOverrideConfig, IReactFlowEdge, IReactFlowNode, - IVariableDict, - INodeData, - IOverrideConfig, - ICredentialDataDecrypted, - IComponentCredentials, - ICredentialReqBody + IVariableDict } from '../Interface' import { cloneDeep, get, isEqual } from 'lodash' import { - ICommonObject, + convertChatHistoryToText, getInputVariables, - IDatabaseEntity, handleEscapeCharacters, - IMessage, - convertChatHistoryToText + ICommonObject, + IDatabaseEntity, + IMessage } from 'flowise-components' -import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' +import { randomBytes } from 'crypto' import { AES, enc } from 'crypto-js' import { ChatFlow } from '../database/entities/ChatFlow' @@ -574,147 +573,6 @@ export const isSameOverrideConfig = ( return false } -/** - * Returns the api key path - * @returns {string} - */ -export const getAPIKeyPath = (): string => { - return process.env.APIKEY_PATH ? path.join(process.env.APIKEY_PATH, 'api.json') : path.join(__dirname, '..', '..', 'api.json') -} - -/** - * Generate the api key - * @returns {string} - */ -export const generateAPIKey = (): string => { - const buffer = randomBytes(32) - return buffer.toString('base64') -} - -/** - * Generate the secret key - * @param {string} apiKey - * @returns {string} - */ -export const generateSecretHash = (apiKey: string): string => { - const salt = randomBytes(8).toString('hex') - const buffer = scryptSync(apiKey, salt, 64) as Buffer - return `${buffer.toString('hex')}.${salt}` -} - -/** - * Verify valid keys - * @param {string} storedKey - * @param {string} suppliedKey - * @returns {boolean} - */ -export const compareKeys = (storedKey: string, suppliedKey: string): boolean => { - const [hashedPassword, salt] = storedKey.split('.') - const buffer = scryptSync(suppliedKey, salt, 64) as Buffer - return timingSafeEqual(Buffer.from(hashedPassword, 'hex'), buffer) -} - -/** - * Get API keys - * @returns {Promise} - */ -export const getAPIKeys = async (): Promise => { - try { - const content = await fs.promises.readFile(getAPIKeyPath(), 'utf8') - return JSON.parse(content) - } catch (error) { - const keyName = 'DefaultKey' - const apiKey = generateAPIKey() - const apiSecret = generateSecretHash(apiKey) - const content = [ - { - keyName, - apiKey, - apiSecret, - createdAt: moment().format('DD-MMM-YY'), - id: randomBytes(16).toString('hex') - } - ] - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') - return content - } -} - -/** - * Add new API key - * @param {string} keyName - * @returns {Promise} - */ -export const addAPIKey = async (keyName: string): Promise => { - const existingAPIKeys = await getAPIKeys() - const apiKey = generateAPIKey() - const apiSecret = generateSecretHash(apiKey) - const content = [ - ...existingAPIKeys, - { - keyName, - apiKey, - apiSecret, - createdAt: moment().format('DD-MMM-YY'), - id: randomBytes(16).toString('hex') - } - ] - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') - return content -} - -/** - * Get API Key details - * @param {string} apiKey - * @returns {Promise} - */ -export const getApiKey = async (apiKey: string) => { - const existingAPIKeys = await getAPIKeys() - const keyIndex = existingAPIKeys.findIndex((key) => key.apiKey === apiKey) - if (keyIndex < 0) return undefined - return existingAPIKeys[keyIndex] -} - -/** - * Update existing API key - * @param {string} keyIdToUpdate - * @param {string} newKeyName - * @returns {Promise} - */ -export const updateAPIKey = async (keyIdToUpdate: string, newKeyName: string): Promise => { - const existingAPIKeys = await getAPIKeys() - const keyIndex = existingAPIKeys.findIndex((key) => key.id === keyIdToUpdate) - if (keyIndex < 0) return [] - existingAPIKeys[keyIndex].keyName = newKeyName - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(existingAPIKeys), 'utf8') - return existingAPIKeys -} - -/** - * Delete API key - * @param {string} keyIdToDelete - * @returns {Promise} - */ -export const deleteAPIKey = async (keyIdToDelete: string): Promise => { - const existingAPIKeys = await getAPIKeys() - const result = existingAPIKeys.filter((key) => key.id !== keyIdToDelete) - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8') - return result -} - -/** - * Replace all api keys - * @param {ICommonObject[]} content - * @returns {Promise} - */ -export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise => { - try { - await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8') - } catch (error) { - logger.error(error) - } -} - /** * Map MimeType to InputField * @param {string} mimeType From 3536c2afec7775189016534dfedc53371d2d5e76 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 27 Nov 2023 15:34:28 +0000 Subject: [PATCH 02/16] fix memory nodes --- .../credentials/RedisCacheApi.credential.ts | 2 +- .../RedisCacheUrlApi.credential.ts | 4 +- .../ConversationalAgent.ts | 14 +- .../ConversationalRetrievalAgent.ts | 2 + .../OpenAIFunctionAgent.ts | 2 + .../ConversationChain/ConversationChain.ts | 4 +- .../nodes/memory/DynamoDb/DynamoDb.ts | 3 +- .../memory/MongoDBMemory/MongoDBMemory.ts | 3 +- .../RedisBackedChatMemory.ts | 2 +- .../UpstashRedisBackedChatMemory.ts | 1 + .../OpenAIModeration/OpenAIModeration.ts | 2 +- .../OpenAIModeration/openai-moderation.png | Bin 47939 -> 0 bytes .../moderation/OpenAIModeration/openai.png | Bin 0 -> 3991 bytes packages/components/package.json | 2 +- .../chatflows/API Agent OpenAI.json | 40 ++-- .../marketplaces/chatflows/API Agent.json | 52 ++--- .../chatflows/Conversational Agent.json | 36 ++-- .../Conversational Retrieval Agent.json | 4 +- .../chatflows/Multiple VectorDB.json | 198 +++++++++--------- .../marketplaces/chatflows/OpenAI Agent.json | 36 ++-- .../marketplaces/chatflows/WebBrowser.json | 188 ++++++++--------- 21 files changed, 301 insertions(+), 294 deletions(-) delete mode 100644 packages/components/nodes/moderation/OpenAIModeration/openai-moderation.png create mode 100644 packages/components/nodes/moderation/OpenAIModeration/openai.png diff --git a/packages/components/credentials/RedisCacheApi.credential.ts b/packages/components/credentials/RedisCacheApi.credential.ts index e09a94e7..4d1a2498 100644 --- a/packages/components/credentials/RedisCacheApi.credential.ts +++ b/packages/components/credentials/RedisCacheApi.credential.ts @@ -8,7 +8,7 @@ class RedisCacheApi implements INodeCredential { inputs: INodeParams[] constructor() { - this.label = 'Redis Cache API' + this.label = 'Redis API' this.name = 'redisCacheApi' this.version = 1.0 this.inputs = [ diff --git a/packages/components/credentials/RedisCacheUrlApi.credential.ts b/packages/components/credentials/RedisCacheUrlApi.credential.ts index fc2e2eb2..e016d78f 100644 --- a/packages/components/credentials/RedisCacheUrlApi.credential.ts +++ b/packages/components/credentials/RedisCacheUrlApi.credential.ts @@ -8,7 +8,7 @@ class RedisCacheUrlApi implements INodeCredential { inputs: INodeParams[] constructor() { - this.label = 'Redis Cache URL' + this.label = 'Redis URL' this.name = 'redisCacheUrlApi' this.version = 1.0 this.inputs = [ @@ -16,7 +16,7 @@ class RedisCacheUrlApi implements INodeCredential { label: 'Redis URL', name: 'redisUrl', type: 'string', - default: '127.0.0.1' + default: 'redis://localhost:6379' } ] } diff --git a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts index 00f825d4..8a2329b5 100644 --- a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts +++ b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts @@ -3,7 +3,7 @@ import { initializeAgentExecutorWithOptions, AgentExecutor, InitializeAgentExecu import { Tool } from 'langchain/tools' import { BaseChatMemory } from 'langchain/memory' import { getBaseClasses, mapChatHistory } from '../../../src/utils' -import { BaseLanguageModel } from 'langchain/base_language' +import { BaseChatModel } from 'langchain/chat_models/base' import { flatten } from 'lodash' import { additionalCallbacks } from '../../../src/handler' @@ -29,7 +29,7 @@ class ConversationalAgent_Agents implements INode { constructor() { this.label = 'Conversational Agent' this.name = 'conversationalAgent' - this.version = 1.0 + this.version = 2.0 this.type = 'AgentExecutor' this.category = 'Agents' this.icon = 'agent.svg' @@ -45,7 +45,7 @@ class ConversationalAgent_Agents implements INode { { label: 'Language Model', name: 'model', - type: 'BaseLanguageModel' + type: 'BaseChatModel' }, { label: 'Memory', @@ -65,7 +65,7 @@ class ConversationalAgent_Agents implements INode { } async init(nodeData: INodeData): Promise { - const model = nodeData.inputs?.model as BaseLanguageModel + const model = nodeData.inputs?.model as BaseChatModel let tools = nodeData.inputs?.tools as Tool[] tools = flatten(tools) const memory = nodeData.inputs?.memory as BaseChatMemory @@ -92,8 +92,6 @@ class ConversationalAgent_Agents implements INode { const executor = nodeData.instance as AgentExecutor const memory = nodeData.inputs?.memory as BaseChatMemory - const callbacks = await additionalCallbacks(nodeData, options) - if (options && options.chatHistory) { const chatHistoryClassName = memory.chatHistory.constructor.name // Only replace when its In-Memory @@ -103,6 +101,10 @@ class ConversationalAgent_Agents implements INode { } } + ;(executor.memory as any).returnMessages = true // Return true for BaseChatModel + + const callbacks = await additionalCallbacks(nodeData, options) + const result = await executor.call({ input }, [...callbacks]) return result?.output } diff --git a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts index 7b71cb5f..3c956833 100644 --- a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts +++ b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts @@ -82,6 +82,8 @@ class ConversationalRetrievalAgent_Agents implements INode { if (executor.memory) { ;(executor.memory as any).memoryKey = 'chat_history' ;(executor.memory as any).outputKey = 'output' + ;(executor.memory as any).returnMessages = true + const chatHistoryClassName = (executor.memory as any).chatHistory.constructor.name // Only replace when its In-Memory if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index ce6f576f..0f5b9aec 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -87,6 +87,8 @@ class OpenAIFunctionAgent_Agents implements INode { } } + ;(executor.memory as any).returnMessages = true // Return true for BaseChatModel + const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 92a0b5ea..7887ce97 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -106,16 +106,18 @@ class ConversationChain_Chains implements INode { async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { const chain = nodeData.instance as ConversationChain const memory = nodeData.inputs?.memory as BufferMemory + memory.returnMessages = true // Return true for BaseChatModel if (options && options.chatHistory) { const chatHistoryClassName = memory.chatHistory.constructor.name // Only replace when its In-Memory if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { memory.chatHistory = mapChatHistory(options) - chain.memory = memory } } + chain.memory = memory + const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 68b09b7b..ac4f7602 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -109,9 +109,8 @@ const initalizeDynamoDB = async (nodeData: INodeData, options: ICommonObject): P }) const memory = new BufferMemoryExtended({ - memoryKey, + memoryKey: memoryKey ?? 'chat_history', chatHistory: dynamoDb, - returnMessages: true, isSessionIdUsingChatMessageId }) return memory diff --git a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts index 7de2ec34..6f800cdc 100644 --- a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts +++ b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts @@ -123,9 +123,8 @@ const initializeMongoDB = async (nodeData: INodeData, options: ICommonObject): P } return new BufferMemoryExtended({ - memoryKey, + memoryKey: memoryKey ?? 'chat_history', chatHistory: mongoDBChatMessageHistory, - returnMessages: true, isSessionIdUsingChatMessageId }) } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index c65d729b..bdb62911 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -137,7 +137,7 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom } const memory = new BufferMemoryExtended({ - memoryKey, + memoryKey: memoryKey ?? 'chat_history', chatHistory: redisChatMessageHistory, isSessionIdUsingChatMessageId }) diff --git a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts index 6b5fdf66..2b8b4650 100644 --- a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts @@ -95,6 +95,7 @@ const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject }) const memory = new BufferMemoryExtended({ + memoryKey: 'chat_history', chatHistory: redisChatMessageHistory, isSessionIdUsingChatMessageId }) diff --git a/packages/components/nodes/moderation/OpenAIModeration/OpenAIModeration.ts b/packages/components/nodes/moderation/OpenAIModeration/OpenAIModeration.ts index 51578630..85b27907 100644 --- a/packages/components/nodes/moderation/OpenAIModeration/OpenAIModeration.ts +++ b/packages/components/nodes/moderation/OpenAIModeration/OpenAIModeration.ts @@ -20,7 +20,7 @@ class OpenAIModeration implements INode { this.name = 'inputModerationOpenAI' this.version = 1.0 this.type = 'Moderation' - this.icon = 'openai-moderation.png' + this.icon = 'openai.png' this.category = 'Moderation' this.description = 'Check whether content complies with OpenAI usage policies.' this.baseClasses = [this.type, ...getBaseClasses(Moderation)] diff --git a/packages/components/nodes/moderation/OpenAIModeration/openai-moderation.png b/packages/components/nodes/moderation/OpenAIModeration/openai-moderation.png deleted file mode 100644 index e3b1b282a70fdcbf36da31b2856b7a0ade9c45c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47939 zcmV)#K##wPP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vG=N&o;XN&$CzbWH#Ny4XoXK~#8N?Y#$l zRo8YV{^mcKmPuwNGf5`D$&}1wGD&_fc`j6edY9-O_xjxFAQ07n!NvwRY+NwC_uj$u zUJa(0-mAKFb?;SM|L7rXP@@(@%{Dk{pP zLQ15(!jI68(1*Pxfqjgtyu1{DEAS7$@TUyXbGx)m#9w0emzH81raOiVBUFa0~Zq`hehbRR%8!x$KerLgf#ZgyvjJ3lh(#APLPr zC^|?`21Z$hCrWUr*jp;Mvhw7_PiJM{zGJd}{VrLvYKwfoey42NyjyndJSazxos#P} zawNCFD;|HDcu8Ddk4Ol{RaVA~;7();eYPO(eBC4YCCRUXgw|>p(24|6Ue4oY+nygp zDXg0zAFZFUfQFbQWqt*(DlIPq`O0D4O6B6EYqEFWAz8EbdzmnCnv5FtnPgB6(z!uvW48?%-GnUzv5G*5A|<0GAj#*a${W8}G)UX^C8+DdA())F0;ERiwE5)Dmo)?9@5Q^Z1mEM3D~!N7Bf>u>7+J zs=_1r#mldTgl@tGtzHNTLqN+}0n6tr@kmi|z7nytyaZ(Aez=aXg4b`{lBLU5NY`$? zBt9WU8bm}&WNd;YrZkuI7Hy?j%MOy9hOk)&g_1F`1&Em@ammf)iN?`VziFH#gN$7> zGG*T4<#HA-Z4nBIBw;=X=`9VqrJ7WP7WO~|Or=HeWS;Oye(CZnAR(32kd3aV8-{>% zQ9&vf%F9X=rKKnb3M)-~uul0J`dR*~Fy*%Mm#@f@6|1Eu$djB7VkIO?TJtuFsMsX6 zgi*06(l8=cA|eyiDmINyL6|DB3C$&?S$j!q-C1H%TSz0EpVGR6^y>ev%v-t|M7$xz za77D>;g*7gbc9)9!*ZhX%y}M?t{ncVO|!r{D=Z-&$uC}h6(ppD644Nl7SWXbps@j1 zB9;-bdj5(MkinzmqYd;z$#3Q6$);`FrQg6I(xOd!iAzeAq_pO+dM!b)RB6;C2G%Y` zEn%y+Pf4ezUy=@;Uyv59p8`oc;QW@-BrZ+j)7nU4i}n(g+)SF_y3|%3q-W+3S@Zoi zxt?7p9v{nyrWu1oR04vG_IUzU(8)lw4Fnm5If)O>r+*~BWck&QkYZiX%0g@5M({j> zO%OgPHc3EBejr_Y^pj^_d{dgnrz?_Mb(G{boupx0iagmgUgDdzm5)CER`wnFNs7Qg zKkmzWGYw2V)1(Ac38r01s00LXj_wbATt{wy50B(wm0t}Bt>wF2f=X;)6qM)E>aeEL z^JMR#AGM5k z^+ujtyO}R%&R>&Vdk@Q$84Kl|UIUbH4Wg4IGNpwyNooeSG)0;wHj_8I_mgRJ7t6J* zJP^^84>cV~lckSZbjJ-Pl!PQ7p+sb=gW}Fy9+B{uBfoeOhAN(t3(8wsTB=a10=2fF zYFf~vW6flNw?sTJMa3wa$u029iPKkP+1f4AqyKP;O=~L=C?m$Ex04ubH%)9I zZ92a!qdxr__qwR}XB(myX{SXb*fvS9JWID(3CK(&#H|h$KL3Lx;GOREAk*~fA@koAU zWkM_bxFnv}?+aK$+6;okvmjyEovaw)&3vyE;e_+ovSsFiW%AN%Zz%~|wC$)?kR)v0 zrh|MucAV_ocTjSRyc(Zc(qgaV6nK=Bw1!3A3hWcf_lfNK;iQcEe7q#J>a65z0xS4L z(?qEknIzBtzN@TQzfCUQ$khyhXa@fJ&Nex8E0o!7Infn2$f&GK8WT)LhmH}Z;=fM+h>l&jf=a_IOO`C|MOdFuIB zrAbVJ5->icnIxn(mluEkrYu{%PHyDnOCi`s&~>EcV*gM=tssdA0_J%|u4EO^pJ_lKUREEa>rg^-3FXFdvlra}d-E-bAfqp@TJEn#+1ndEpxw(dJBZ}%Aj zYxtBBk*;aOm{dt@(NX#h86{I@FO~J1_9BgYWy7Z3^8MyrvUTfT*}P?^Y}tzKZM&T9 zow9BF9&GQB9}XXv+`J+wF2;>mRs>jf4Bd8j(F>2DYgkjbi5_~)3eT|!J@!b9+&2IyNScGaHN^3=1xS9E^nRe2g|>io>h8b0;xE4tlpz%W_3XqjBUep5RgkWgN) z2k!w{0=E5rxXWc;-R8)Uu&9!VcAsr;y}eJa1%>B^?$_&ajOBBBC0bFY6-|%G7%F!l zAzyG1p)ZavlP{Prm)mY}pUco%D7&xnhe8M-@7BS%tuLJePevSFBtuufFlNR;JLUY?_#+mMyMXdx-`jL9`|yV^ff=iG!wW zIskY~i3t+YbtDm+#-~bhvo`YD+udaS#%)>w!&*#M$mAF1Y1*xjggPn$+bkDexlts` z*6onjyA70-HqRkW5IzNj2kDw7@IDB!kKlGxa)$t-Q#$Io(Wyu`h!~Xw5;B88x)PhU zgN1)XmM&W*w{B(YH!$zrWK@=fBpV4>Qd)@2!2`92bqv4^-?AB;LP^R@&Y-*uWOcUP zWDI12&iAt4%=Wz{@dy%6$>@-h9V6&9JR)IGYCuAZpy&s{i)0O?ZP&83k$k-t_8j-V zAh_QRXFuY$;0Lx!#t-fH`vb&>#y}@bx=2C}H<~tmrnGJk*RM&mJQ)!qO%l>29^^|v z87!uG2PI&`#8wgkOG7kCZRdcN5v0U%OF}lR(=Ck#SW`NA#vD0);j&soTEe1YC_7zI zgtWpOaZeJ`66Sj<tEtrbd87YPXxlO&{7 zBnjE($mR|9#7Sx1RwhlJF6Yl*)Mw{Av1BS?XLPuuBw<-OUDIL>tu-Xn5`q|f@Amr! z$mqbhq9$H@jvMap!!*z{AiVx!g`0!~32KOHzV#!i1_^I}`9TRKA#*o(o18`HetRxM zn{!&2ea50Pek-9QWT+%%n;Obu+-7aziJwl%SK}vYua?NT6cEU?$N`KkD2w30XnN%9rr zgJWN@r$k4+e>moINrC$ro7h5I&V*Nlt48m8iJ46C?ha zOxCRrw_DanQf(53g1I8yjiTa09f5A$4{mc*x*eJBCSfS}aeOFixDn{#;tc5_36+Q- z83~9(N=AnK&2Q;vBe}OsiVA(QZvAHI+kdDMFb2d+NH^svB_OR$N(Ti=SDAnY5lKX_ zj7FDHiI@xmB(;!6O%tVE=jY|)&&SL1H5=vFPiNIC7UTY$@CkY-H?lmkdi_r6KlCGM z)9Dpco&%9+4H+lX(Kxn+R?g7Mai1ldn}iC7q)da&5JMSvI9nYb*Et-sXW`y& zj`3W?kJ~c0wIM$~6@laN)W_O8Itss7OM2O$lgVtq{v@v_f$pLws60X&7Z(+)mHGA>I29k&nlIBeUi&l_ks9 z$-ITDWa5;$GUk)<(yQ+NQPO0w%P0N=ZjjHHv9&YDW=oB@*H|LpOMS zpkfATA)3TBlP1wA`mD2N&(}6XwN3=yH#0L@Lw+-og5(H%FNPo*->rS$7VK=mN+<1f z;DqeS!wv}UP|xAD%pgff+|R~ubi=r#>J~eMBdiC-kSTp6H7FB?l91pL{c!jg1an9> zUfb&X+`=O5sX}Ds<%dOfeu1L$?;=$yA}_yK@(a9@ol~eN$oInSMDB`j2%f72M)91<4&%GuQQK`}t?q3vKPqk*Yj0NHm;YnzS*d!SQBngQG(_9dr+EJ35cSZ>7 z8Jn(Tj7n@N&09Yu&%N}P{Qiv|^5QG+NUJu_Dfy#t??zE+(jY2bo{UVD*mPJp$Lf(- z5pgJQ!V+2n+Wl@=LNa8TFfy@~f+UPgfF(sn^ZZM1$izw0_1%zw){>DJBn6uTSc99D zlP%e~Ie4c9l9QXSVP3vR-*t8#LI+v7#c~VpoxOK%<`x2;K*;@Bc>mc2__naT?7GM4 zIp_+j6?O>7kd~O<5Zf-oekvJqoc{(7rz-@^}+SzTsV18)kpmb3T@hWr_ zKu#e)T7g`?ktJs?UXddwPNCd%NY-!O2Dfd!EM2u$)^6M)o3`&#Y}~dp5ZdkFZ-iTR z%J-Xhn4Tk>cPrMf-zIza98x0sytuy)_b|H46I`;2-)$em*ulBzLBdp za^P_UEgHA!Y2+3b$nO37WZlM1I!Jlr)*UFTnjS=(x8c14o3`%3`{wpuoU=0h#&IGOC_HV;BbA z&dQm3aNCHxqUR4yZnK2pNXVRx;7kB9mv#lTW`KFYk~3 zT;3h=k@O!jLVEQZEM0o`lefEPNVneo^>>eqfzrLt01bO&^mpLiH&A-^87RFn21)ns z{iIvBz8d!FGf2MqdZL^=ds$siT0aty2}6-YE?>@)VIw}4cn}~dt%D?_fn;&4Nrb{D zq8tb^HU_!eB(#>%jUWj-8W)yibTy{4l7|GRiy8}p&@x8GHUj}WNGb`2`!@mE=suEg zBoWJ*Pe!L}c`&i%(;6RL(s0BJ-A7mwTDMf)Y*^xEEj!7`QJ>0%%QtkCKATPq0oh~6 zBR?GdQNI3ey!0J_cbbtY-Fjt6kKPD-_C+{A-*b=NgADrM+W=kr43RFqP1vK~Fu2`A z72PuMEqf1C^y)thW#0Gktv}Vl&r@g2mF26~$$^7M1a335+jQrMM$zn%LYRGxBa`4tCM#IxX~K}dBjO}6vAOig7$83$JqdTp zV`?K&9wZryipu2Z(KFJg-%yEyl_Cl0LN<+uLW6vkglhd#I!hyvtvV!Zk^oXT7Ex<4 zQI4Z4NfITs=&Y8pQ4HKuxSS*KR^;wo8jHY=(HS7qmvw@NCe+^Q@-~g z_$-Z?VZ=38=<`LxwRSq56OG^sv)Lvgy|uJz-&vk{@ikt{(E9KykAGO1L zk%v8sOhLw$8@WwABPb-9yGe?n8}4JjM){3>HU_p5gf0c!y*SdFACzEKJj)D)U?K;i zPM@;~x$ArK{HxuiRfkugfKbx7R;qlF@l+DJOi&&twA<<)IxxRmDY#9=q8k^(-*hFR z04X3-N;)h~Y^u)XIsCm*a_xFH${@@rkS>%DSvJhi_sj0xKOl!6AWfk(2~=FfNfNTQ zk%S@%NkF0@h}ICqa=V~zSP}*Z2#ZLNd|H+R=xPSaYDh21MJvdKr= zT~qMAQm`+%IWpZ8?a;s}9-n^hWf?GJm`t5HOO74?NeYYDV+U3mAF9w(q$ISJIF@8= zm$YQFzwJKvG0LxvkiW6k&yvp!Dnfq{7*zZS;NU%92_1y;|$`gSjA-tIYd z`I^Gzi-^HTLKwx03@E!rBuEgG1Z9O{Gvsl54u3}`U|(X#z;AOMu8GG-hGJV1Hi=4< zwjG|8(PKWBtn31$6}de$t_0SQxRsSJt5K;wj;DAFm4{ zl!VOm67X&sL?p`dFTbr5M{%kQ)@X8wl?R1nj?}-Jl_j5!8z=2LKcn6FViJ)-rM84+ zG-b+ISoRoLcE0OaSp8T$dmOxhSRfAX$-F!BE|_`Hy^e3rN+x!SyDIa=M4Ce2-ec^_zCf;9(z0i#E?ny(Y+2p)hgDPay}yM@xE264Rbj#3jQe zO@y+?LSf@UK5X+i&x?!$@c{1E?F58?`OWhUQDYO@t5T$-b(91sZ@q>wI)dQqZzf81 zPJvoMrkSoNUD8XJZ^(?9^W>#hyC?~1QE8PMBXgi4(aj_YBh%mt0@&S_D8)A#}%&lLPkJZ2DkF_<-4g<<@r}$$M;B-CNW7MTr;h>vLuX-r8NW@nek~Rh-bw& zP&m)ecNmk}Ni!j)wK0kr4N(?u5S5DKtt5`#N*dCY3{MCaF)AMJd2+gRdin+FKX|CD zT(wS4pE;*x#amf9l9ykoZH$(5ZjxCNvIBrU=05(bC7~_%X;&;LxE<+T=&Mi*IAQvH zdGYmb$RTOn&a(@fi-B_S;#Nf?3shIp`SAtba=7Zm3Pi0C2=6_OUgl8_dP?wY&&#&bwO zbwvXNLueHp+m#Wteo8)v(>+#6E zB}?R;9(_Ozl&?X;n1trAgzez6!=;7G9EZ$KfoHYe2)z^QlCx%+Bvh-2G}4-DhXW8X zCXJmCkYR#^w1_MxMkh5>*OWuRA|m5-s>rvy^^^%yrpuAzC$(9GWmeWXgd-tCOG0j2 z{8~jxs1-2?Nj|pwaL_7q)ZF4S`SH{RnKpN+yxuiK3*#gp+uvCBVI@UVSgt0quqbh` zav&m!M8XmLZ4pX1H}UK?Noq+*OURr#fffa2mWC1W(tprYowyt;$aokh6wiPEb5(=v4AXgPfR zl)x#_!Nxk)5!7AC@U|Eos&~#HX<+>p3@2bMe z;Pv6a^Fx2%5zG|Az>+_RR)*XJELvk&wxkxVrCZEZ0;QMj(AR7d7QTE!l z?|=;Za17o_3zXSdW1Vi^5r`NMB9VM_{Y_;VtQyPjfMyan7J4s#tPSPVe+96FHwwJ6e8V>B()V4w;4~c(&H{OCI$ykTyI5;x-4y1U{7B3- z8OEhQBMHrk=QQMR?r#DMXL41BbocB&v!4WHPOpv8xDVYX?TDS;T0Z}3yqr3FDKMKE zD~_0cFMjOUxldkv*&8z@`n>hJ%z7QT|PBCNIA6 zmTcU)+Yk^h#gdTQM^2rQkH>wZB;){P&I}%%#A{n>1r|wY+*@XR_yz!Lm077pi>==U znSquI(_krGB(&+T7nLIQqf@oEn7ToIAu*{h>FuA_kn`)rq_&c783SeZf+ceC>J4po zwj{LVv-pD|A*+j_(^@XfFD{WQ*?F?#;E(d&m@gy^xtN(H3~mNo4_X(#Tw25?@hxx+ zE(qNc6xy}wofZcB8bjIDb+I1~A(e>kAx9JNxK%iA6Wq@>IJQkOZqCmc4TWQ0&1S7T z$~P0I>O{MoUV;Rq>&$Y+&0E>BV&z(S>e-jn^`Ua=T%RC`x|C^9Bq#?*Ntlk>Ce-~p zcJSW7Yt_|+(pgyd)54TDjSE{X;QhG~u!xAuz_`C0{m+UJ&KeMxoF?s`dRA7f-5}Y8 zh3bm3W|YnxUDH$NFUhx)rb${$d?%2WL%dinRCg3{vh2-@9@}(e2x&;DnIed&eQ5C= znFd;98%AMYOtVh-cFmNC)Dh05W8GAJ2S_WwMI%^8j_qvLwzCZXV6^N#cv!M?@~DR@ zbU{N2_Xj~jb}b?iNkV?O0&l4tKX*|+`FfH({mNTv1v%vdKRn$H=E5W-iPAWhjm>P3 z<_9taJyB9Y=wv7^(*}zH1>j)a2oQ!K@febb97WG`*9KI=NV>ib_}h&d zlaO$c&}ME1MzNKEu=JeWo6~p1B&JBqb{%E@lI3#o`gO_oqF4dt<$Gd94QGd&yJ(p< zs?$>-5xG`I0^UO+tXTr?!=?$_p380fJ2Bl9I})2er9-di{g8Y*C>htXGeSIy0Q8dV zccnh4$AmP}3wS&-RqC-W0qN$vtIQ-{cL2PL*HhmsH<(+;m;n-Q%SeE0((7Oz;XBxE}2K9htj+@HI2 zS!T^!C>@@DPDvOMpMrFu+yfUj3gC1VO;SMYG$;?0fT5Y*f*ArUQtlmU4a2}@Hik5} zgYU0uIF<*XYtGtMTjQ!71Jo5|(@S%ZFd6Q8lC)~qUgj@eBA2dR*G77dW?|n^7BSeF ze$9qW^3w0$kT&g~l6D=R(FIJ~;+?na^fK^@wCV7Yv~K^RhWy>W^Q#7(UenPaEpe<_ zo9868<SrgHqmob|_|mBev^bo@ zg>6G|>8+&gQ!mIHJu>9Ik3N&HCeM)R^Onf;1xsbd{H1|##=>Pf!7hI@;XAKuNjx*O+71C3<6Ii1h8oHs~_(DUt z`s_BwkhRgQQl^JV6o#WH)|a+x`Ig-oBlOs37kH$ceWv*xXI!d37xmK)4pDRUOA#y4FdUw=1C zMvNXOZ}%Rg8LSR`hI?!)WvDlev8`S}GZaOf;s7%!wUi(%-O|ZZX2^*XCt(3ewqV7K zCEXtc3E4EkvLHj2wN@a<>@#qfx;lKB%;geWc2*M7%_R9qLMjYjIt%)Zpa{v$TFJ|A zb&-)DeIhd!ES8=74#~-jSKta2$PEscDJYh#BCigC$zjbV%AUDiPott*M-0HJw-aVXz%|0wP#3j%hqtATB2;yMpD|K zjM9>>8_GXGVvDwt&>U_Nwr!}}u$cUv*sM*B!i47S?g)#MdlUnQG7+o=XX7|)lQ{sG z9Uk(F@EwXufpWa=Np@Bk@HfjX+D#6j9uEUrXo5Ak9Q2D_feye7 zxu2Gty)ag-TQ8HQ&6E$ud@B70y{i~FbOi98WDXvtzd3DT{~^O9Ug@uJWQvVOe67r?mTC`8b zeJ3s2KW$!MLUS#HupCHmn-wxFo6!B>i%x;Xd8d0fnKyr-{CMKHc1xng(rFK1vDhEg zl85(UNYc1}+we}H*f3PMpVKQ`zj;GWo;oAD_aBhW+jq#ut=nYBp8c|GA8`=dd-eCu zz58&^Aw7@#?eUt2wa1>lH4mBooxAqPw(UFRha-pO)S1(AvWe3Mzh8!2e3%K3+qs#qNWg0`fSDF{~Wpa&q*F=pAuUR1%J5XBK5_r5>ACbn-3e zSBusiO1n<6y@PvaT_6)Gex@kus4hd6FDGdQ{hZU^TN&|L5VnGd_KRstgG2 ztEnoy7qK}cU%ct+RHm+tP7I|5{b2m6aQMIsxu42JvBw|AkdLQC0Bp$p`n)cp={|c6 z{&K$^HWVOWC<*bK=T&(e2uniz<$Ko}O}uX+D?3*X9y%=Fj-MbezxJj?M8`?v*aR)I z*a?+MKvvpNuQ>XGJ$E>wWAKpoa4qyFuSl&kNl4vc+9-bzB&6GSEh|@+tXwPa^vX2N z0?etLnmP_^p$lsJM{4aT+*DR|k9>cW?Ay0TiVE`)6Fy8S+)oc&39rWt;=~JMSmK)E zAS~Qp`5s`WKJ*oPH3RSznspkulo*wz3}07g1oM|@Sot?T_(Kr}(^pl%9PFBtM9Hlghd^-%05x)a{S1wdmYJMvdk_dXeb1&XMKR;h~?%XNwjd)*T z6H|oiIkK{j^$R-nKYRFqgv`J#3AyI~JKcKAsdkC;Pb~9ujvAAJ5X>Ch1A%|F zB;*Q3Q)bSUm)__OpFTCmIVg9J1rL>dscJ#Me7^sRs(AwUv`u)P1}%a>*9^l3WzGFPOVVQA=(4)lWI$-W-pS9S8l2H>+-bvemXbH7}7BD^dTWi%hN%-oU-DK73wOSsd)v-!oKOleeg;*uw zK1!t*#ULE+L2SIdk4*=yBdrLPH{e?_1XP)wYwi^`uBs63ntOqLrxsS}b(+Bt>*qel zE#cyIE4D$na}11BCL@I|KJQOGV7YYd`gOVtL3%3`YvNP2mkbHWrY`lO*qi|pvSNlL zeD;Oc=zIy>rh-tcQdp_GdFbNOjGFKk{x zzPgcHcI=dHz57aP>vnKIleN8-l`$OhWHNTRr!39@?3mw)L$St9kaHKWY3-}Eg#1Ld z%=)V)p=~zcfY49B{6^Y8^NLzR=2!#?NYEuE2{}ZFQ~7ZY&OZI$m2KO1t1^&q3@Mfj zB!b<(mlwk;?gqph2>HQyFDj;0&`?EX$lrD!ch?R<1+5O4PB*aYo`A>1vjyL+#_f7` zi@OTqs)P`6-_zd5E&N5WBQ{6QvoefrwmmX3CAlU02B&DJ1~yf&n;@IISX0Vk4N1s3 zh1zv`Q9d2}t(-Z3#Wa-wtenxBQYB%iD{4p>nifj9G(PO|8u!E5laI zn9kJ35|W6VJc%=jB{u6I83TsOuHAd}!;^4yizxg}p;EYArJBARdp5(nfeNi34EM2$ z8194EwAz9{57!Rn9@T{r%XyLv$ahq_L8)1Z(1yT7VTXrL9 z?Dn=@yS1BPQge1=Jl>2sG&TP5Nl0olx@gA0D` zB*fzdEaB+SzETo4h)h<$ov>|%EE8%?DF?eM3ESyF(Ve^Ys3OoECE+XytYUCKVXb1Q z@O9nsl~r*t+;#Q?xZxJw-&%-pX?YkW+%vghrKI zrK_^=ySwr8%Wv3_UYUfiIwWkRaFdXOJlV6J{TVF@ckJA)AKG0z=T>xg#X-22Q`O51 z9H`=i(A|NL1Mt2U_L%)0s*LXIeyOm}W#7B|8SQi1-*-ZFB=j0Y>+FY0W1A#mr-*(7 zhe%2*Si*SIBth#(5^6g$GItV?pe1Y%chr)QL{>7>9R(5Xv)z_{-qYN=lGx)_3lr?d z&o6f+p*GrALBf86hRXIGy8;Tp(7nxL@Vx>RMpE29H`=i(B1ashr%ASX|wyR zxX*nUaW~;PR#C%|FjQ>3JRYjp)e-mk?&}!p{ZS-fk0N%fW5o=o-{6om+hDKV)lk%7 zodWyBa&B3=qaTbJD@RYBRdVBxf9}^dgsc*>K9gerCi(qLit*>wC`#r=%-xvLWUZtiy8oh0;nZmTB*ecE$i zzYKhLxRQ|lNJ&D|W2S`;V`7h)xKuM|Dc93r#SBUK<4QI!PJ zaN7m;V>k%x|8>aydhb?j{LFN@~wGm;eEt#(I9EUtXn7vLrKSNei-}veklWEwb0!0ZufFupr`$^NqDDcy$!*o2z=dKG0kdPB7RYk%;69uQjLz$3`^?ZRGqvtLY zS`t$1KKoMaiwXT5B*481V{s!?0=Kbp+XeRi4g&js9TI%c_eb2(^Ijz3)}4E#PrpIh zL}3Q`nz@K{C{uh3?KQ)NgXoez*(gf8x4k!Nj80zc&`e3lp-hbPS5Lybl?mOJkR-g_ z67Iu8<8`=5=zej0QO13!>cvG14p(!1YKnKyrl{CM;yU0=lKce<(}4!(Pm&~#ff--&W(J>qfdnk8U3JbdyC zBcX}Qkk7*Xf`j+B5_(_v2QpR^`|f~8QZpfO*nvBv{~&4Ai3`S>DKXhZL9jf?Wfew` zogiyA?2z*pu1aBn*C8Ovfgm#D;JfEE`J6L{WkV9tela5a_rZ}661um|Z+DU8;gAPH zLW3$K{M!udb{`JDU`s$d1|)=xdR}Ox<`_SrDMzBvufL%U!n4|kgyU#^eviAx7gjm z!!{oKQu&fyFB&gDaE@+=iaBOT$d5#YAsvG;+7CXY#H%=*s4^DXL1 z9=JPdj`3V>^Dr;wLz&+e&TdmfhBVq2&6itV;g|9X6Ggzx8W zs9Jb`z9qku;(K_C^Tb!e3Bz{Ez@czQJ9m%{&%GccKKV?RZrCU%E?tpqFwcXl3Sj{` zw`@T%T~R|mB_M961jHk9F`J??SVV>ncx|x*o~Q3s^~`|!!L*c>bA2jDR~(r4W`xg0 zy|8*qgg!T6CEPKhE(9brN*5%d8Op?=CiLa2CSiM!aCis_9n!fU9{Z^Ls)+bz+A){a z&j4%G_+E4l6&y1NDlG}Z=@Yd~ptWQG1Ls9gRekbt;%38z^2e;dLJ?ycXsSm7@z5MODL9Qa|6j2r)@j2!)eOr1Ad z4xBhHSMu{EAMdviSLNdy<>R|5ZJc*vGecTAk}VhcX&$1_!bvxlz8vIDc*!m+1N|Y&_!aouZ-`?a6dwfVgL?QNc)^I*!&#(#&U`AqA}KE^1aWh5 z%`Nd2UqhG!Lgt$ZrBJT$6yaQEgoqp8S9|xDA#MCX{xXl0lo#V3hzpjp5Q!)RQ8mW} zm;>7!nJ?XpN`TTM9j1YpiRQK;A&xd^$hTvxYRo*KUkK~b1LP+?9lCV)zvmU)6 zp;?88B;?9Kl}I??eUNZ}kc7w$=`fTF$_A1T5*DhmlT_Bl;7}DEuHpdttt8Z0#Gts$ zt^BZ3gckq>G^gYMPflZ4R7mSrfO{bR@?5w-*&r6IBCH(U8cx&h0Rh295C|X%aXqa^ zSvjX)fW<_}T+@%7QqzT0lM*h9@sTuB;EZPFWj+Tu)1s8WX^~70sf6WSG)E7@WIRDj zX)Pe}NFE8L)+F4y^B@!vN=q{irKU>L5|V(lgtTcS5|vS>yoc^_dUa06uB+_g=H)0K zn6jL4C+I>jF1it_=p;B?#8S8(K7SF&4A&B+0yrL0fNO4eJaQ$kNUr4i%HtX-MX3a7 zG{8QbW6lj;tJmXM3EbEKQ7O2Mv?(6RBZ1V4g#8A-FWYw=RI6cgcox#jKo2FLl8|-~ z)(kgOB0AO(?l=fVVmZnR?Ih`Ze)gIGN#MrP;#ByHr2=j!*r?|)wnDga+2tj24HqA~ znJ;S&o|HN3_sZN22V~W*({kuSp^5DV78G7=+ud~LGu-)AKNa#WNVxx?<096lzSjyTdkN0 zlJEzR%@B-k7iq$Z4_o;`5}NXoa}M;-0=lX$BSRcgqOf4EB&3z`!P=mlC&eBmBdfuS z5LY(NJqzD<|E*$~v-_;PJ9VABHgb~ue)t5*96MhouQ@0OFMH(%c+A2*D*;$AM})W$ z;`8MI`A7t<8r@B#8N?v*m?;pr1m#cXw-VG;HZhZ-wO5Nz%c_|To)e%2x3Dg(v7`oC zu5?1(ek6|sQbQ7Q71vM_9;`${bvFY90TPe*V%e|=`pKoXZo!4jDfDZl z2=pG{3|k-!tdtakj79J-IeWLyqigMvfR}+CH%ny7-V5^1H*2NEfJqYh`iBzp+WXR? z>ln%SY`!erbwo&0-L%7;Y)yL1y8F>#YDGeXQ_tK&!4*<+#RJ+sa^ftVi-lC_3d~^+;&e ztXC4oq=AI(LBbIr;eO+ef?#w>NjieAsZOn~zgaC}$d}_Omvh(iW#6&$vT4sTS-s_e zY}$81j$O&p>S$0zm7TSq;w_T$0$44CtU}Jm^%oJ*&Mc8{c3qUWr)-skK{F(xCrH@s zTS@HkwWN3dLf#rRQ|4?uCO_Ts2T16JC1n{OtDL_^FRBDccE2M5nyzFV2-xxTRX&gL}wg zng@q+akHB^?JD>RuU0Ms=Yg|0b_UOI25dS5f}KUiaJJMd=V4_p0GC1Jt6=YSZ;9OW zmIH$A3c1d>uuWV6*RL=He$B#)xDjZVWM*c{#~*(z3l=Pp z)2B~6Xa8YKwMoeFVC)G`5^@*{SB}jbI9zt^+5@GA_5ytI6cRB|9q+_ngbmmq*>dEJ zd@^;hyfNrwY4^^%(&n9^@^r?>GI-)DnY-tL94QdFL?Xf*6`*WY2#Z(%|L_*BKLlTN z+QBUO{e&$NJ!FnN-gkyH9JolLhb@!nelsQFtCQH_x%a%1~b7b{7H}Yi7 zdD_!okI+-Oe*LD!m`d9x-e)F5(R2zTtCx0T{x^X2}#?B2D*u`K>TWrMb_IV9vBx?XfMZ^4Qz*?veeKbK2)K=!Wctmv|Axxd!%-j5k1}!@014J1@xFleS6X@P$%;z#MsU;5?~6 zaK1DdG*_DTo+Qb=zm|UAtd^xa&dPxf#9@$*moTO%0M;0X*-rzE|b>%Al=6mamFNR(q|GfgDD2Rrb?6EQ>AgA z>Cy;jm@!l8!EGkEP5hFDy_a5k>7}qlstf$lAN`Sh^wCEF@9NhqH6)=~(n?9#tPMyw z*pQGlj^!*@F{a9vkYz%))3H*6{r@(dxGbYk9_-M6j3jmYK$3cWA*q9>OXFUXX zRkC3JRXKjMRI*w3!lZz86)2;Xb7?euOj>1T2Jm<#7cTRSa+KGwO{=>bg}*_tUd?)p zlcXLKC9cmbiR!aZp6s|cs>ES83Smw<>% zB%;qUMdLopLA2%4AY-LG*>9yh(Qmcl@xH6%$-b+Rk*vm@f0@GaTsY#~FC0I9+`03w zQ>sBi_8o0i4H8;QND`WIm~lrdq`0gEWv&XjiG9ZlN@dxRi_+_x`I6S>V~Kcsv?L9I z!Vj7!PxOYR=`&kee!58puevC6&WViMl`XyJ9+I^87HcKU?|M#?rbCxX!Y68KB&c4c2%nya5u@EoN!VlJF|h1j8#^&lO3}FBVDsTOUh8 zPhl27Nap!`LiO0FU?ID!&`BRl_GTw<1oww_ljB+}E&S!>~lT-+1PkXX>Q9$Tr{X z>})x3;DBZ(Q>ILjMT-{6!Gi~NKi%sGE%!jeRsj-n;O6e#o0Wv@yv>RaikpPS9j%Ze z;1;&o#&-y1weg#Ol-IvpAn|>_k*09#8up(jPYzftzs*=Ak7X>B<{xdA%oR7})rp5B zedKD8VYWP;0qfU)fixX5U!sT1kmSLW0x6(d-Ag!n1zJiKw8g%r`s>&%7F1pCAAw!*!?Ssfjb$|bCx{WeU8-6SPC+& zkSF@W3c|`g4hzTaCt&UB_gMpXb&WLYwFcIFjYRZVr)bn`Ju;2;u%a8}v3?umcNyQy zKZB$U9|xZ7{XJ6f%Mq@?OBXR5K^_$T*Z=xox)^OugiCl&o;+D1BO}AcSrwW#ZK?## z%cG{;U#S5JTi%6){s0Nx?S?v>2jwrVeMC-r3CDlJX=^VR$)}r+OQ$i5pu`j4vdxi* z_tr_1kz3@SGFHexyt4q-af8GU+XQ7^BLCEL0bIYu(qPa6Sj>r%Fl3s%{N+-azWXu= z2)7b(^7F9rMcV?kYZ`o6e=bN^pbh6t2b^Xt6B2pIBJz+$+Pwek8SKrkyL5hlsJMQPRw;Lx06ttD9lhM647l5?Q&@(VIoM_Z}4E*wVQ4g zyKs?&U><}dzVv&AJuo(t}==}pJsm9POm7sb>p zB%o0Hcrtw)ctj$4DoSCQSx)syPD!5Rl;+EIpGVdmIwBv=SS(#XnJO=hoGcxOOps0^ zX30~d7Rl42mq^F=7fIXU^Q7H-^QHZW1=0cN^d7NLIuBncPoX?ZJUx7obRMxtI*nW` z9Y!sYwxbqH>kpSI+KyT(?LJs4?cZOD)IR)j>Cz?nm;dr#hE>j=3;*`t{#z+5q(5`J zj2}O~#!V#E#eet@|3SX~`fKM-btfR9QB2cLSb0z{;U3g$m&YPfY0YT;ZsXO`H0--bA_uQU zxd}w<4dP`kl$arlB&y$3l-0hI7e>#KuQ!~K-B*2b6%pr^mMa0ZV*q^q62DiwqG{)E zc30CbVyv|_oe0>Ehr^@_K+wF>TPPLUtZkK${Klh7z)K+DEw5LKN{WSpWSIsf zAqv+VHLYD>2%2UlibT{BDv8Je!}%a%7PfQos}TP@a;wB6SA4~C-s6)~MSiVFI#y7w zI9iD9LOc@GY%4sgtH{n9q>uDOIWF2ic3A>DfLPr z%4Zx0$TH%27^xpogj;y{x(t}QSz7j=BJn+^N@Ta`(xlrAiS9KA<*SJj)peY_2n+bd z>I1UxhF7j2{wzeFi}D=fR01M1U=1FLsAB=uFJ_|nsKyeiTMDqVwXZZ!yuRzeRq^_7 zNpV@8cFZ;n`DK!YOoScAS)mjTaCRjJV9}k;9&WIA5G$kTG?~7p<~)MeFy4n*UU}t} zu!>hx&^7$~zyEtBOloSXR*?M7-~3ITS=fK^FaAZ?{SOL_8Z}aVtBq8TgzAz8N!TDJ zRY}+zB-{iNqIjq7COM~iZwaWDh!)8Q7qLj^{qUCLL)i;p5ep=*yhxRjZFPsTd@_IU zdFelXy|nH#Nupl)LZaXJR$|}!R+77aD=&xd>d(e;kCK#~ui4q01B2Q3l!S5<%y!-` z1mN^)We-Q_n`tdzV*!rZcf;=IImJjPuX$L7u3b2Vt1jrS_3G72D|GH8SFT)* zV|mm7`xK_kYQ_W_+pXR0*tQ8Wi_W#dhrkgqu-H>7#b6yCya zA&Z4DPWXXPN05_*$PwZCRe*#QW!X?a;uh|l1!aZ{h`+Qb*HQjDez8cFZ9XMm&)Oj$ ze!EUSpRrZuZ8$0i&lkvLxUKmhN_KIf6!{Bu(k7M#*&oykx0FP5lh8~vVMu6Z*)ns@ zn1NM@!77T#bjcumt4&+=OS74WTo@IRku^hz?0v zLG&;RM?Qp8vb*8`{lEWLD{DYGd-kl>K!%I|mjJrm_ab#b!k|0KfxB)J>JCKf9DaPFCW5be?pza0MQmTcdDUUnb7BB!o-BoD{b0zt`( zKtj%OS?uLxPjLOf5Ki($f{=g#%LJ%<3Is^#kV7YJBE!^bBk+PeK3vRnJLzYeKVhe6 zWc*AgK}%*}PFLbP_oB74WT*Q}kbpYfV!$HuMh~C-&;R*9!z$RF@c848J6G3@Jg8dE z`+79+ zz;bR8I8q1{;8ND85*n@gx=?jk6PEK>JH`fjOF;Y&63|5qOG4xXNJv$HsXbAJCWdSI^ z>X({FGKZixV<&w)Elm{}RIqvi3aRA4A%f&#Gz5MJEe@=ZbBc;{J{7A=!g!Sv|c1&baQc~Fa)E11lrp*X` zzh4?RZX7ng>Y#b^=1xp^ms*o>`<`u3VM9W75vh!pgao$@8&qMb@a~EY>(1dSBMu1_ z&iUa;sBm8dv81r1Qt0(TvRDL(;E+Yd-W>Y_=GX-uv*+7H1aIWp2JpWU+{g4d<9c|6 z-N3>rQ&srWKmAkZ%G%4MNt5nN*SP=tfB&y`Jg<%H+qX~t&A<6KVdK9W*m=A0RkG`)7=QQ%3rq)5qTM*=el*>A(f$`r4$bZw=66P4X_8`8HAMp z5$OgqftHK`AXRQ?VKEH!TqM*@EC;;5ZtD$AP-Xa#2%8qdDNp~b31DyY(lx0bjHWWJ2Bi@YDvPOC=>3&)uvA-%S|dI5A?G#nT%e#Nyyqx-n+Ua zBSx$q*up!_b)|WH!JNhE>B%?XUH+1{^zSp}$$iGlW*)-i+T6-N@~s zeU67mI(P0IR*}Mi<)y&4z6YVJSHFJ!uyNNGoJguRX{$W)BIAn7xeF)qnRA{6@u_+-5S(dG+D5?Y{9#ZC> z;MXER$N&iq5#1FofwUkEhK#{y4`%d`$^Us~DtCm`maP#iHRDIJ}@94bhKY0M&@Woq&Bv`hi3(9*E;oMOJjR{cO zSsCgoGNi&+R{pRg)QF7)X#mUZToVeL)==2vmE2a6QixDPSvQEG!9C0uhi*cOYS)Y-W|;f$qaKyjHJgh`bMxeK^MOA(EPmLT6iA zP2^sB;oJ$Yzy5lS`={2Wu-q9gT~*=6jT_FD;U$EGUpOQ*(_0u4wsw)QT_qCs9Xv9) zDw8U%DX+2o#5Dl9%$fF?B(WS(C1wcNGNhrQv6$3c+qKLP&LO6wz1m>T_DCfn32Q@~ zTh3lNbPq{Dl;@BJCwmCo)6EAG!L~dY!rYEvgMIKkH#7&bU^1Nj_B!p3qdkTkI43;l z9Q)kAU+!FWce#11MxzYH!h2v2M2fZHTDEuUF~5YaKM2+0kNl6>|f zM)vN}gkYaV=gQ8+%FRD-Mcqz95n&wc-gdRlM~~^ zAvG!A{_c0bbFK<6H6)?i9W^90Yf*q0uukk0tu>70`B26@g<2-;=V$^h^2wZxgdhQ_ z<92;`5``qxj@4Fi%L;W{QdM@b=R(4+DU-Z9bUL_!8uZWdG5LA z!lpSK{@Z{1Z_ZWWB|yS6SFX#b&+bk_x~N8&5n9a&l~n0SzS3NS zGA_6R%cw*$ZHe^KXq9NiJjEzGs()`RsZP>_YmD;dONlQ_e7+pf6?Q=|5|PB^A}@K~ zau5(?M11U22g@)%Q#0q5>f7j%jgGfb-W|37D%XNH@cGmT}yC_ zl{89T0Nk#Gp!-SCZKk_v+8k-^NH#O+zOjDB0&>y|W*0w?u%dv<$f+5qAT@2byPqUv zCC-^MXPm3+PBw4eEdS|0{im?^wBUQ-B*WF|XG#kiE{<^Eh>$zUZ&-~%+>L~h@vS8; z#YMvOj*>ZOWF-=E^n8GXWjO{Mz66Uv68imKUFehR$a|pBT!xH9c%q(aqNy2WFNS4nhH;OCd7Ovys zGRLz-mhHYEAJ5z*eLkKlJx6~dL%*FOvo>y&qc^U}O%SR8*A;>ow4$5X<*Wp~af@CXkflIGxj9O0R z95kl00=?Y_AuRuJXi!}?l>ZV)NUw*MzNS{*4Z5gRO>M}|l+(yFwgWm@U_v-^w!AV5ZrHj@NM67@dMpCMBn&}R?+AbI1qzicor(M8lS8xf} zm#344pFoM^mT?NgLMS{431!ZL<+=_6bE4lXuttY&i7ecIQwDsuL7pEnS(frx*U~}kdScO)EzY$^GFx_P7*@Gu?`92t4YF9vSarFxM0v})?Bho zh1j%)6hy4hk@QqeC?qUL0Z5nw5?;dLLl=Fr?uQ$)=7(Fd^;DrQ=5iIs*!z!D0_H+p zixfqY1NW3u)nCW)i@=YFcJ`iZ={Iq^wCp!ol6rhC@!dX^gl->5+&cs1g%3W)@x5{+ zFH3IX?z!s9`qfgEmUv*XK?R+B3M4PSB?X?FlJAAv3wM;nA^CD(t(X?~6o)qfu3d2x zq$6%A%+!lI`x}1eIU!+Y(lOWF!Z`%v%tgp~SMQfxyLRoq&YJzOlCUnCL+*s3Lx-xt zq142}vUB{M#8okKXA;sSH5D`MLBi%BVQ1+#WRz^*a|n4oNXVWrtR;mKYv*TmL1`b1 zdq)e94Fa9YE|rZ(Z_0!fKghe|*GS(lmdi(Tw#%Zu7v#WouTE!3<;_PNh4`C?GT|){ zBnKqtT7sN3cfo;d88~f^v>iN8nsl8YQN1QfY`+N-+4EzG>@i%P`RGfTao~{rSX3z2 zkb-Pn%S?dfH#Q+~R&IjJaQjNRYHy*=T}r2w=_EC;rUc7j*bGN(8gCOCpTeoyB({ORtH8{3# z+O%n5k5vV(_;e?!9SH}2s3g?BqfkyZxVtMT=%BGeg%u-I?wcUkrlVKo^SRsPo%d%* zhhASv>RV%^^MLU(Z2Cr7cKE8ChFPXU=h7nKssdP3T0*+1r(sH$AG#`;6E;eR!E+_H z`xJ@kJ6j?%XG&z>Nz%C2XOcE-ob;T&NS24JPUwjc(3F;aTizVbhG4Z^a7|0h>}-X&PcCrPTOoVT+>p7Oe~_^Yx5|6dH^{rwHcS60 znRndFO7GRU~nYvjrrn$DKZj^pgHb~}VgT9l$mp+p>>G{1J^hSIcQ*nIC zS{XQbCEtIYNlhvWYKxzngv>nY8itGGVMBFRk*YBXqm$cJBH`v;hftO>?SAa}qP=I} zmU5PBl908G?8!n)#)*69ZaFDkKAbBp-uhgcb^Tgez=9?A9EbAU=h9)=BpI=Ex2(Qc zAU`2ax^Y}e?O0){EdSx044b-2o*Fbk61sdTaowg!eD66Dp8-qQeUc<(PLkKZT_xjo zoRn?3J~@r+F2l;*Dn$kW*R#aww@G)DL}c#7_C~Je#Iok8{0dpL`=ku|ZizfMWW2QK z{iU=*oUI4VlIDYEOWMGBlIkF3&>Tqx(gx2)I78BaW&@{7^MO;PcCx@gn^+b(=16J?FwA zPLcFJljZlHEthY%oso@Ky~3tmlJ~0LgEFN@d?g-jNVok^*T3K=y-sz4@ zg)(~nW_fn#caqlQ3yFUF3yJK;m4K&9qwX`LUe}pYulp>i-($Aa>p5FPu2a~c=M-tw z1FkSKf+j%IZp0Lc=!)yQ&XPvm=1YTa3#ESdh0>t=d}-WmwlwWJO`^NrxgDyy!iMc| zAOC*h=O7`U^Y8xd@4{a9FrzwS9YaVMY@(OxSW9Yz&Dbb&0c@IJ?Q0G! z8*2s+UayepTh2xznCxSC|5S`KT%Tp&yd)Db0l`qa*61_TpIRU zE{!r*N}~a*6%G5ZlE(d4O2mLg(saN)iR?d5qB7@63=oycW!#oZWIr75w+`4KjWajs zc4WU*D90{`1z&_a*IwAUIo!v;pLmcYWDz1dIy&t6wFUch@}A+2)fK8EztKt+C86!W zZAhr@s5aIc654KS+}9{39VF}^8G}a1HntPudeZ?LOatlI5KfDulRx1%>rB~3b^~m^ z06y%#?3D@Yj>rqcr%5DT%l@;pa-~7vS-N6xli@2R{p;P*WnGqZm~up7-d`h6^qMA( z`%DKhW`T&4LBJ`}1{SpMH!Eb;mg92Zx?h({<3gltlOqASV4Izmf*={yO-71XTWXhQ zI|&l5J$Xa=j$b0pdVem7y}y|WacUj8w^}4^)i=B z;{l67#05&k0QxTgaThB|8}?r(4f=1ChM5};5pjKF=28%K0a95zY1gh@*biS_PyxGj z>sI3##U2z1x%AlmAPLK9w#Jf`JI_A*Y}oT^3-;8hPXE_1By_s1-Hn9J+Jl6{W&7@Z zYWH*m1e++J+)~bNY#hqNzLX>bNQisf#Eq!*=b-f8|8!GE%z=wJe3r!b1G)OomWCNK z<%z!Y$k$=osD8K6ia%Rql6`85aSU1m{DsO+jOlE?ByDwzR z6_A_dPP)Fdyd~^Bz=6Hahzunm++&Wdu;tY3(o%ItFCxi1FXhVU1)Jo#VQ@z?zLLi8 zd?Am&@s%!--58cFV&F26X_-9Pcd^v#w?t9DF9?c^qjAOzY20_3M8KL8+~$4~nCsow z&%m{PRshS8kt~!5JWFKnX-MV03)=$kRY5s2eC*hcseLNT~am!WR;Xl zHtxl7##}6O*^%oqcD0{Jc6w#PFUO4A{W zC3?VYiS9K)V!Mx%-+#JP#;!apd#)7AdGL(xC%XnH0eM%XAxJ{po#_Gn=pri#%SyEy z94#l^*Auy3S+x6@44=AEUL7?}S`U~YNqxpkLdFzHL85KqWXM`veb07h)?#JEB^!{HXH!gZOAgX zaI>XJk8dTR*H`lDrwe54x}&o3T#=jt^R9#UY+z@P97{s&Sj{ab9fpMLNI)8v>JX>w zVlFh}SMuIM25~CKBb$G^BD1y~lL;G+$(O5tl+nw6lu^r%$*2`4<-=8H<%88{<^5G> zfK&3(8esKN8NK?5;^S38jOPATKgma{P6DSLoYZ|EuR1E9th)E*vN`8xxDS0daFU{` zU#Zx~O$T!rRM)K@ytoY_x|(Z~@tO^cD`Y;ht@Q z2f&`02QNs*)OFJ8-N_Qu3q*wVq19=UF;`*+ERr~svl@5#N(so-dcWOtTsB|v$QhIY z*}Omk+J2cNph*whV-6a0yT*=tOoFm@v;ZXZmYEvdBAln?R$sZCxLG7W-t@_ln`N-H zKDerW*>Sl{c3c(NaUF_v&0q&i`mP&gK#A-|&a&5yYh{Yv*UM!Wf%A4Fv4lB?@8;fw ziWTky-w9KuOmVIbFTXYt{@4HdUv*uJ+6eoL-jAiYIjSNrkA){AwIX4k!G?rXd>t78 z1W3qsIg*f-GYDyaJY3ouhjq<*gg3yJv%pc9uNiwzOZTsrNJ{UqAlo;RoC#O1&lG9e zb-YA(g9YsIrM&hDEZ~|0vi+(@PLY7P8y|%hkY!FIlNL)t-A5Y2HSzh1 zCBGm`atm%rPTno?loX2x<;1LFpXA~4e8kCy{9Cw$X=M}3b?hUqVZVag0L!|!9Ax1< zUIPwmS382o*;S1pyTlRqDm5LB6crWaTv@gJT1d!pC5IW+rEt2&dz}iuD)1Y!yB(!k zB(%6E5(=9Iyr6|&ihYIJb45o}l$OgaAIgb1gJr}6H;ZNR=EL&(M<|c=9Vdz1K9lIT zK9Q!s|47n%ekpH$JP+>S0oigfUrr-x_QB+}Bw)V3RLR3;5pXSpl#_zG6OJ*g%YM1cFb*F+ z{9s7P=`Cmtef~o2xl-iwN}k83WkkA?YGHuGAmTSGcS+ZeXGq)LpGbO_QPRHeSn2Y~ zZ24~8A=z{)OD-a6czH@d5`Yyxw6G*M)mlNH8-yd#0wiSGZ2BB>a^4%JPv^2BDmE<6$AC?u5Ek+oqx0P#qG6b4TOS+ez<1oOjfc z5PBR+LMWk>;U(x8Lo?p6#E){A7f|AqNnS~*Q!yfP(NiWrUN4lH8}`b8FK0@xk0;5n z2@7TN`aQDie3qO?badl58Y36wPg)czvzGr@5d&fcn@VsJ(?W~mK&K$2MI_jl)aOx> zm6f41$aE?kLUO7O1sqavQw-S&?C#@n8-DEU`)bW!b zp+2(%y|0oGacXKwKnqR1UQ<)y6cu4VVin*dwbhlHkZ{3*>UT8wxk+e2GJf{iXHKf> zPFAm8U89MU!{UCDkZzo9r&A)*iqN%#;@g@^^1{qJgYp-y#aT);_GZZ{tx&7SZ4%U| zFB{}xOF&+h+dReju&&14MG92;&AmZF*qhr)h;95QXtj(5b1W!`tYp+QlVpx1w6G*p zBHHHynRrhnV9*k3^N3~)Ofge#!QUh<-&)P2rVEnPWGcU!kdXMNfBGkF;J%wYEF`ob z8PAQ&bVv8bJx&L?l>;=_syR42X{N!?payiV)fl zq5#&4;QTITpK;=BP0LM`V#kgz7BCD`M{>*f8bCp9G@L4_VTaG(=IczM`J zXz`E#_>Yp6#hQh>6D|tcsZ*!0_q!wRm4sR`6p5uRR=F6mfUF=S$r8Md+lQr_u~o|0i7!}y$0;P>MRSc*O|RaPQ6pjgl=Hij~E6dRZZkxNNDj#fAmMP zZQC{{rpi*=VOE@z)=ffMB11x> z$W@V$uBjyy6%}O6bAZv#WY?><*khaG>C9StJT_TR!%)>qvpQ)x&lW7(1HrHjS+-*+I z>VR>CPM}*GOUOQ?ZehRAaOc$p+O%oo+@)@Wdcjc|;qG-CYD+@ff!j?&iV}sSfVu_U zJL`%CtwK<#4b-w|jE0Uzd5r=a#dn=g65}3jE8`}iLa#9iV|?^Rss41Iz1XlMB$z^` z)Wk$$8M1IXLlTGdu^EKjQWD}?^OWo(TMoyQAstZDgkq*S#C@G-;dS>y!WlDWg#U

_wG68x><=35`nmw$-Nua@ojcWD{>y*)FJbQ! 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

I{36t7LlSB{^VRUNy;FO1H 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 P00000NkvXXu0mjf)=Eu{ diff --git a/packages/components/nodes/moderation/OpenAIModeration/openai.png b/packages/components/nodes/moderation/OpenAIModeration/openai.png new file mode 100644 index 0000000000000000000000000000000000000000..de08a05b28979826c4cc669c4899789763a938a1 GIT binary patch literal 3991 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 literal 0 HcmV?d00001 diff --git a/packages/components/package.json b/packages/components/package.json index 5566218c..bb074392 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.4.3", + "version": "1.4.4", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", diff --git a/packages/server/marketplaces/chatflows/API Agent OpenAI.json b/packages/server/marketplaces/chatflows/API Agent OpenAI.json index 4950a6a6..0e9aa091 100644 --- a/packages/server/marketplaces/chatflows/API Agent OpenAI.json +++ b/packages/server/marketplaces/chatflows/API Agent OpenAI.json @@ -3,7 +3,7 @@ "nodes": [ { "width": 300, - "height": 510, + "height": 491, "id": "openApiChain_1", "position": { "x": 1203.1825726424859, @@ -13,8 +13,8 @@ "data": { "id": "openApiChain_1", "label": "OpenAPI Chain", - "name": "openApiChain", "version": 1, + "name": "openApiChain", "type": "OpenAPIChain", "baseClasses": ["OpenAPIChain", "BaseChain"], "category": "Chains", @@ -78,7 +78,7 @@ }, { "width": 300, - "height": 523, + "height": 574, "id": "chatOpenAI_1", "position": { "x": 792.3201947594027, @@ -88,8 +88,8 @@ "data": { "id": "chatOpenAI_1", "label": "ChatOpenAI", - "name": "chatOpenAI", "version": 2, + "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -259,8 +259,8 @@ "data": { "id": "chainTool_0", "label": "Chain Tool", - "name": "chainTool", "version": 1, + "name": "chainTool", "type": "ChainTool", "baseClasses": ["ChainTool", "DynamicTool", "Tool", "StructuredTool"], "category": "Tools", @@ -333,8 +333,8 @@ "data": { "id": "openAIFunctionAgent_0", "label": "OpenAI Function Agent", - "name": "openAIFunctionAgent", "version": 2, + "name": "openAIFunctionAgent", "type": "AgentExecutor", "baseClasses": ["AgentExecutor", "BaseChain"], "category": "Agents", @@ -397,7 +397,7 @@ }, { "width": 300, - "height": 523, + "height": 574, "id": "chatOpenAI_2", "position": { "x": 1645.450699499575, @@ -407,8 +407,8 @@ "data": { "id": "chatOpenAI_2", "label": "ChatOpenAI", - "name": "chatOpenAI", "version": 2, + "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -578,8 +578,8 @@ "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "name": "bufferMemory", "version": 1, + "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", @@ -657,17 +657,6 @@ "label": "" } }, - { - "source": "chatOpenAI_2", - "sourceHandle": "chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", - "target": "openAIFunctionAgent_0", - "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", - "type": "buttonedge", - "id": "chatOpenAI_2-chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", - "data": { - "label": "" - } - }, { "source": "bufferMemory_0", "sourceHandle": "bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory", @@ -678,6 +667,17 @@ "data": { "label": "" } + }, + { + "source": "chatOpenAI_2", + "sourceHandle": "chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "target": "openAIFunctionAgent_0", + "targetHandle": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "type": "buttonedge", + "id": "chatOpenAI_2-chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "data": { + "label": "" + } } ] } diff --git a/packages/server/marketplaces/chatflows/API Agent.json b/packages/server/marketplaces/chatflows/API Agent.json index d8fa22ad..93270848 100644 --- a/packages/server/marketplaces/chatflows/API Agent.json +++ b/packages/server/marketplaces/chatflows/API Agent.json @@ -13,8 +13,8 @@ "data": { "id": "getApiChain_0", "label": "GET API Chain", - "name": "getApiChain", "version": 1, + "name": "getApiChain", "type": "GETApiChain", "baseClasses": ["GETApiChain", "BaseChain", "BaseLangChain"], "category": "Chains", @@ -102,8 +102,8 @@ "data": { "id": "chainTool_0", "label": "Chain Tool", - "name": "chainTool", "version": 1, + "name": "chainTool", "type": "ChainTool", "baseClasses": ["ChainTool", "DynamicTool", "Tool", "StructuredTool", "BaseLangChain"], "category": "Tools", @@ -176,8 +176,8 @@ "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "name": "bufferMemory", "version": 1, + "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", @@ -233,8 +233,8 @@ "data": { "id": "chainTool_1", "label": "Chain Tool", - "name": "chainTool", "version": 1, + "name": "chainTool", "type": "ChainTool", "baseClasses": ["ChainTool", "DynamicTool", "Tool", "StructuredTool", "BaseLangChain"], "category": "Tools", @@ -307,8 +307,8 @@ "data": { "id": "postApiChain_0", "label": "POST API Chain", - "name": "postApiChain", "version": 1, + "name": "postApiChain", "type": "POSTApiChain", "baseClasses": ["POSTApiChain", "BaseChain", "BaseLangChain"], "category": "Chains", @@ -386,7 +386,7 @@ }, { "width": 300, - "height": 523, + "height": 574, "id": "chatOpenAI_2", "position": { "x": 572.8941615312035, @@ -396,8 +396,8 @@ "data": { "id": "chatOpenAI_2", "label": "ChatOpenAI", - "name": "chatOpenAI", "version": 2, + "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -557,7 +557,7 @@ }, { "width": 300, - "height": 523, + "height": 574, "id": "chatOpenAI_1", "position": { "x": 828.7788305309582, @@ -567,8 +567,8 @@ "data": { "id": "chatOpenAI_1", "label": "ChatOpenAI", - "name": "chatOpenAI", "version": 2, + "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -728,7 +728,7 @@ }, { "width": 300, - "height": 523, + "height": 574, "id": "chatOpenAI_3", "position": { "x": 1148.338912314111, @@ -738,8 +738,8 @@ "data": { "id": "chatOpenAI_3", "label": "ChatOpenAI", - "name": "chatOpenAI", "version": 2, + "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -869,7 +869,7 @@ } ], "inputs": { - "modelName": "gpt-3.5-turbo", + "modelName": "gpt-3.5-turbo-16k", "temperature": 0.9, "maxTokens": "", "topP": "", @@ -902,17 +902,17 @@ "height": 383, "id": "conversationalAgent_0", "position": { - "x": 2114.071431691489, - "y": 941.7926368551367 + "x": 2090.570467632979, + "y": 969.5131357270544 }, "type": "customNode", "data": { "id": "conversationalAgent_0", "label": "Conversational Agent", + "version": 2, "name": "conversationalAgent", - "version": 1, "type": "AgentExecutor", - "baseClasses": ["AgentExecutor", "BaseChain"], + "baseClasses": ["AgentExecutor", "BaseChain", "Runnable"], "category": "Agents", "description": "Conversational agent for a chat model. It will utilize chat specific prompts", "inputParams": [ @@ -938,8 +938,8 @@ { "label": "Language Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalAgent_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalAgent_0-input-model-BaseChatModel" }, { "label": "Memory", @@ -956,21 +956,21 @@ }, "outputAnchors": [ { - "id": "conversationalAgent_0-output-conversationalAgent-AgentExecutor|BaseChain", + "id": "conversationalAgent_0-output-conversationalAgent-AgentExecutor|BaseChain|Runnable", "name": "conversationalAgent", "label": "AgentExecutor", - "type": "AgentExecutor | BaseChain" + "type": "AgentExecutor | BaseChain | Runnable" } ], "outputs": {}, "selected": false }, "selected": false, - "dragging": false, "positionAbsolute": { - "x": 2114.071431691489, - "y": 941.7926368551367 - } + "x": 2090.570467632979, + "y": 969.5131357270544 + }, + "dragging": false } ], "edges": [ @@ -1044,9 +1044,9 @@ "source": "chatOpenAI_3", "sourceHandle": "chatOpenAI_3-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "target": "conversationalAgent_0", - "targetHandle": "conversationalAgent_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalAgent_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_3-chatOpenAI_3-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalAgent_0-conversationalAgent_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_3-chatOpenAI_3-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalAgent_0-conversationalAgent_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Conversational Agent.json b/packages/server/marketplaces/chatflows/Conversational Agent.json index 2232ade0..8994594a 100644 --- a/packages/server/marketplaces/chatflows/Conversational Agent.json +++ b/packages/server/marketplaces/chatflows/Conversational Agent.json @@ -13,8 +13,8 @@ "data": { "id": "calculator_1", "label": "Calculator", - "name": "calculator", "version": 1, + "name": "calculator", "type": "Calculator", "baseClasses": ["Calculator", "Tool", "StructuredTool", "BaseLangChain"], "category": "Tools", @@ -52,8 +52,8 @@ "data": { "id": "bufferMemory_1", "label": "Buffer Memory", - "name": "bufferMemory", "version": 1, + "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", @@ -109,8 +109,8 @@ "data": { "id": "serpAPI_0", "label": "Serp API", - "name": "serpAPI", "version": 1, + "name": "serpAPI", "type": "SerpAPI", "baseClasses": ["SerpAPI", "Tool", "StructuredTool"], "category": "Tools", @@ -146,7 +146,7 @@ }, { "width": 300, - "height": 523, + "height": 574, "id": "chatOpenAI_0", "position": { "x": 97.01321406237057, @@ -156,8 +156,8 @@ "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", - "name": "chatOpenAI", "version": 2, + "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -287,7 +287,7 @@ } ], "inputs": { - "modelName": "gpt-3.5-turbo", + "modelName": "gpt-3.5-turbo-16k", "temperature": 0.9, "maxTokens": "", "topP": "", @@ -320,17 +320,17 @@ "height": 383, "id": "conversationalAgent_0", "position": { - "x": 1164.4550359451973, - "y": 283.40041124403075 + "x": 1191.1524476753796, + "y": 324.2479396683294 }, "type": "customNode", "data": { "id": "conversationalAgent_0", "label": "Conversational Agent", + "version": 2, "name": "conversationalAgent", - "version": 1, "type": "AgentExecutor", - "baseClasses": ["AgentExecutor", "BaseChain"], + "baseClasses": ["AgentExecutor", "BaseChain", "Runnable"], "category": "Agents", "description": "Conversational agent for a chat model. It will utilize chat specific prompts", "inputParams": [ @@ -356,8 +356,8 @@ { "label": "Language Model", "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalAgent_0-input-model-BaseLanguageModel" + "type": "BaseChatModel", + "id": "conversationalAgent_0-input-model-BaseChatModel" }, { "label": "Memory", @@ -374,10 +374,10 @@ }, "outputAnchors": [ { - "id": "conversationalAgent_0-output-conversationalAgent-AgentExecutor|BaseChain", + "id": "conversationalAgent_0-output-conversationalAgent-AgentExecutor|BaseChain|Runnable", "name": "conversationalAgent", "label": "AgentExecutor", - "type": "AgentExecutor | BaseChain" + "type": "AgentExecutor | BaseChain | Runnable" } ], "outputs": {}, @@ -385,8 +385,8 @@ }, "selected": false, "positionAbsolute": { - "x": 1164.4550359451973, - "y": 283.40041124403075 + "x": 1191.1524476753796, + "y": 324.2479396683294 }, "dragging": false } @@ -418,9 +418,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "target": "conversationalAgent_0", - "targetHandle": "conversationalAgent_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalAgent_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalAgent_0-conversationalAgent_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalAgent_0-conversationalAgent_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json index 800ae300..68618a63 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json @@ -642,9 +642,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalAgent_0", - "targetHandle": "conversationalRetrievalAgent_0-input-model-ChatOpenAI", + "targetHandle": "conversationalRetrievalAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalAgent_0-conversationalRetrievalAgent_0-input-model-ChatOpenAI", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalAgent_0-conversationalRetrievalAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/Multiple VectorDB.json b/packages/server/marketplaces/chatflows/Multiple VectorDB.json index e7718616..789b0c08 100644 --- a/packages/server/marketplaces/chatflows/Multiple VectorDB.json +++ b/packages/server/marketplaces/chatflows/Multiple VectorDB.json @@ -1127,81 +1127,6 @@ }, "dragging": false }, - { - "width": 300, - "height": 383, - "id": "conversationalAgent_0", - "position": { - "x": 2506.011817109287, - "y": -241.58006840004734 - }, - "type": "customNode", - "data": { - "id": "conversationalAgent_0", - "label": "Conversational Agent", - "version": 1, - "name": "conversationalAgent", - "type": "AgentExecutor", - "baseClasses": ["AgentExecutor", "BaseChain", "Runnable"], - "category": "Agents", - "description": "Conversational agent for a chat model. It will utilize chat specific prompts", - "inputParams": [ - { - "label": "System Message", - "name": "systemMessage", - "type": "string", - "rows": 4, - "default": "Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.\n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist.", - "optional": true, - "additionalParams": true, - "id": "conversationalAgent_0-input-systemMessage-string" - } - ], - "inputAnchors": [ - { - "label": "Allowed Tools", - "name": "tools", - "type": "Tool", - "list": true, - "id": "conversationalAgent_0-input-tools-Tool" - }, - { - "label": "Language Model", - "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalAgent_0-input-model-BaseLanguageModel" - }, - { - "label": "Memory", - "name": "memory", - "type": "BaseChatMemory", - "id": "conversationalAgent_0-input-memory-BaseChatMemory" - } - ], - "inputs": { - "tools": ["{{chainTool_2.data.instance}}", "{{chainTool_3.data.instance}}"], - "model": "{{chatOpenAI_2.data.instance}}", - "memory": "{{bufferMemory_0.data.instance}}", - "systemMessage": "Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.\n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist." - }, - "outputAnchors": [ - { - "id": "conversationalAgent_0-output-conversationalAgent-AgentExecutor|BaseChain|Runnable", - "name": "conversationalAgent", - "label": "AgentExecutor", - "type": "AgentExecutor | BaseChain | Runnable" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 2506.011817109287, - "y": -241.58006840004734 - }, - "dragging": false - }, { "width": 300, "height": 574, @@ -1602,6 +1527,81 @@ "y": 75.96855802341503 }, "dragging": false + }, + { + "width": 300, + "height": 383, + "id": "conversationalAgent_0", + "position": { + "x": 2432.125364763489, + "y": -105.27942167533908 + }, + "type": "customNode", + "data": { + "id": "conversationalAgent_0", + "label": "Conversational Agent", + "version": 2, + "name": "conversationalAgent", + "type": "AgentExecutor", + "baseClasses": ["AgentExecutor", "BaseChain", "Runnable"], + "category": "Agents", + "description": "Conversational agent for a chat model. It will utilize chat specific prompts", + "inputParams": [ + { + "label": "System Message", + "name": "systemMessage", + "type": "string", + "rows": 4, + "default": "Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.\n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist.", + "optional": true, + "additionalParams": true, + "id": "conversationalAgent_0-input-systemMessage-string" + } + ], + "inputAnchors": [ + { + "label": "Allowed Tools", + "name": "tools", + "type": "Tool", + "list": true, + "id": "conversationalAgent_0-input-tools-Tool" + }, + { + "label": "Language Model", + "name": "model", + "type": "BaseChatModel", + "id": "conversationalAgent_0-input-model-BaseChatModel" + }, + { + "label": "Memory", + "name": "memory", + "type": "BaseChatMemory", + "id": "conversationalAgent_0-input-memory-BaseChatMemory" + } + ], + "inputs": { + "tools": ["{{chainTool_2.data.instance}}", "{{chainTool_3.data.instance}}"], + "model": "{{chatOpenAI_2.data.instance}}", + "memory": "{{bufferMemory_0.data.instance}}", + "systemMessage": "Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.\n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist." + }, + "outputAnchors": [ + { + "id": "conversationalAgent_0-output-conversationalAgent-AgentExecutor|BaseChain|Runnable", + "name": "conversationalAgent", + "label": "AgentExecutor", + "type": "AgentExecutor | BaseChain | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 2432.125364763489, + "y": -105.27942167533908 + }, + "dragging": false } ], "edges": [ @@ -1704,6 +1704,28 @@ "label": "" } }, + { + "source": "plainText_1", + "sourceHandle": "plainText_1-output-document-Document", + "target": "faiss_0", + "targetHandle": "faiss_0-input-document-Document", + "type": "buttonedge", + "id": "plainText_1-plainText_1-output-document-Document-faiss_0-faiss_0-input-document-Document", + "data": { + "label": "" + } + }, + { + "source": "recursiveCharacterTextSplitter_0", + "sourceHandle": "recursiveCharacterTextSplitter_0-output-recursiveCharacterTextSplitter-RecursiveCharacterTextSplitter|TextSplitter|BaseDocumentTransformer|Runnable", + "target": "plainText_1", + "targetHandle": "plainText_1-input-textSplitter-TextSplitter", + "type": "buttonedge", + "id": "recursiveCharacterTextSplitter_0-recursiveCharacterTextSplitter_0-output-recursiveCharacterTextSplitter-RecursiveCharacterTextSplitter|TextSplitter|BaseDocumentTransformer|Runnable-plainText_1-plainText_1-input-textSplitter-TextSplitter", + "data": { + "label": "" + } + }, { "source": "chainTool_2", "sourceHandle": "chainTool_2-output-chainTool-ChainTool|DynamicTool|Tool|StructuredTool|BaseLangChain", @@ -1730,9 +1752,9 @@ "source": "chatOpenAI_2", "sourceHandle": "chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalAgent_0", - "targetHandle": "conversationalAgent_0-input-model-BaseLanguageModel", + "targetHandle": "conversationalAgent_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_2-chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalAgent_0-conversationalAgent_0-input-model-BaseLanguageModel", + "id": "chatOpenAI_2-chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalAgent_0-conversationalAgent_0-input-model-BaseChatModel", "data": { "label": "" } @@ -1747,28 +1769,6 @@ "data": { "label": "" } - }, - { - "source": "plainText_1", - "sourceHandle": "plainText_1-output-document-Document", - "target": "faiss_0", - "targetHandle": "faiss_0-input-document-Document", - "type": "buttonedge", - "id": "plainText_1-plainText_1-output-document-Document-faiss_0-faiss_0-input-document-Document", - "data": { - "label": "" - } - }, - { - "source": "recursiveCharacterTextSplitter_0", - "sourceHandle": "recursiveCharacterTextSplitter_0-output-recursiveCharacterTextSplitter-RecursiveCharacterTextSplitter|TextSplitter|BaseDocumentTransformer|Runnable", - "target": "plainText_1", - "targetHandle": "plainText_1-input-textSplitter-TextSplitter", - "type": "buttonedge", - "id": "recursiveCharacterTextSplitter_0-recursiveCharacterTextSplitter_0-output-recursiveCharacterTextSplitter-RecursiveCharacterTextSplitter|TextSplitter|BaseDocumentTransformer|Runnable-plainText_1-plainText_1-input-textSplitter-TextSplitter", - "data": { - "label": "" - } } ] } diff --git a/packages/server/marketplaces/chatflows/OpenAI Agent.json b/packages/server/marketplaces/chatflows/OpenAI Agent.json index bc27a9fe..d2c842c6 100644 --- a/packages/server/marketplaces/chatflows/OpenAI Agent.json +++ b/packages/server/marketplaces/chatflows/OpenAI Agent.json @@ -13,8 +13,8 @@ "data": { "id": "calculator_0", "label": "Calculator", - "name": "calculator", "version": 1, + "name": "calculator", "type": "Calculator", "baseClasses": ["Calculator", "Tool", "StructuredTool", "BaseLangChain", "Serializable"], "category": "Tools", @@ -52,8 +52,8 @@ "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "name": "bufferMemory", "version": 1, + "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", @@ -109,8 +109,8 @@ "data": { "id": "customTool_0", "label": "Custom Tool", - "name": "customTool", "version": 1, + "name": "customTool", "type": "CustomTool", "baseClasses": ["CustomTool", "Tool", "StructuredTool"], "category": "Tools", @@ -158,8 +158,8 @@ "data": { "id": "serper_0", "label": "Serper", - "name": "serper", "version": 1, + "name": "serper", "type": "Serper", "baseClasses": ["Serper", "Tool", "StructuredTool"], "category": "Tools", @@ -205,8 +205,8 @@ "data": { "id": "openAIFunctionAgent_0", "label": "OpenAI Function Agent", - "name": "openAIFunctionAgent", "version": 2, + "name": "openAIFunctionAgent", "type": "AgentExecutor", "baseClasses": ["AgentExecutor", "BaseChain"], "category": "Agents", @@ -269,7 +269,7 @@ }, { "width": 300, - "height": 523, + "height": 574, "id": "chatOpenAI_0", "position": { "x": 817.8210275868742, @@ -279,8 +279,8 @@ "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", - "name": "chatOpenAI", "version": 2, + "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -473,17 +473,6 @@ "label": "" } }, - { - "source": "chatOpenAI_0", - "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", - "target": "openAIFunctionAgent_0", - "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", - "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", - "data": { - "label": "" - } - }, { "source": "bufferMemory_0", "sourceHandle": "bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory", @@ -494,6 +483,17 @@ "data": { "label": "" } + }, + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "target": "openAIFunctionAgent_0", + "targetHandle": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "data": { + "label": "" + } } ] } diff --git a/packages/server/marketplaces/chatflows/WebBrowser.json b/packages/server/marketplaces/chatflows/WebBrowser.json index 2f6fb721..0547366a 100644 --- a/packages/server/marketplaces/chatflows/WebBrowser.json +++ b/packages/server/marketplaces/chatflows/WebBrowser.json @@ -13,8 +13,8 @@ "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "name": "bufferMemory", "version": 1, + "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", @@ -70,8 +70,8 @@ "data": { "id": "webBrowser_0", "label": "Web Browser", - "name": "webBrowser", "version": 1, + "name": "webBrowser", "type": "WebBrowser", "baseClasses": ["WebBrowser", "Tool", "StructuredTool", "BaseLangChain"], "category": "Tools", @@ -115,82 +115,7 @@ }, { "width": 300, - "height": 383, - "id": "conversationalAgent_0", - "position": { - "x": 1464.513303631911, - "y": 155.73036805253955 - }, - "type": "customNode", - "data": { - "id": "conversationalAgent_0", - "label": "Conversational Agent", - "name": "conversationalAgent", - "version": 1, - "type": "AgentExecutor", - "baseClasses": ["AgentExecutor", "BaseChain"], - "category": "Agents", - "description": "Conversational agent for a chat model. It will utilize chat specific prompts", - "inputParams": [ - { - "label": "System Message", - "name": "systemMessage", - "type": "string", - "rows": 4, - "default": "Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.\n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist.", - "optional": true, - "additionalParams": true, - "id": "conversationalAgent_0-input-systemMessage-string" - } - ], - "inputAnchors": [ - { - "label": "Allowed Tools", - "name": "tools", - "type": "Tool", - "list": true, - "id": "conversationalAgent_0-input-tools-Tool" - }, - { - "label": "Language Model", - "name": "model", - "type": "BaseLanguageModel", - "id": "conversationalAgent_0-input-model-BaseLanguageModel" - }, - { - "label": "Memory", - "name": "memory", - "type": "BaseChatMemory", - "id": "conversationalAgent_0-input-memory-BaseChatMemory" - } - ], - "inputs": { - "tools": ["{{webBrowser_0.data.instance}}"], - "model": "{{chatOpenAI_1.data.instance}}", - "memory": "{{bufferMemory_0.data.instance}}", - "systemMessage": "Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.\n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist." - }, - "outputAnchors": [ - { - "id": "conversationalAgent_0-output-conversationalAgent-AgentExecutor|BaseChain", - "name": "conversationalAgent", - "label": "AgentExecutor", - "type": "AgentExecutor | BaseChain" - } - ], - "outputs": {}, - "selected": false - }, - "selected": false, - "positionAbsolute": { - "x": 1464.513303631911, - "y": 155.73036805253955 - }, - "dragging": false - }, - { - "width": 300, - "height": 523, + "height": 574, "id": "chatOpenAI_0", "position": { "x": 734.7477982032904, @@ -200,8 +125,8 @@ "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", - "name": "chatOpenAI", "version": 2, + "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -371,8 +296,8 @@ "data": { "id": "openAIEmbeddings_0", "label": "OpenAI Embeddings", - "name": "openAIEmbeddings", "version": 1, + "name": "openAIEmbeddings", "type": "OpenAIEmbeddings", "baseClasses": ["OpenAIEmbeddings", "Embeddings"], "category": "Embeddings", @@ -445,7 +370,7 @@ }, { "width": 300, - "height": 523, + "height": 574, "id": "chatOpenAI_1", "position": { "x": 68.312124033115, @@ -455,8 +380,8 @@ "data": { "id": "chatOpenAI_1", "label": "ChatOpenAI", - "name": "chatOpenAI", "version": 2, + "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -586,7 +511,7 @@ } ], "inputs": { - "modelName": "gpt-3.5-turbo", + "modelName": "gpt-3.5-turbo-16k", "temperature": 0.9, "maxTokens": "", "topP": "", @@ -613,6 +538,81 @@ "y": -239.65476709991256 }, "dragging": false + }, + { + "width": 300, + "height": 383, + "id": "conversationalAgent_0", + "position": { + "x": 1518.944765840293, + "y": 212.2513364217197 + }, + "type": "customNode", + "data": { + "id": "conversationalAgent_0", + "label": "Conversational Agent", + "version": 2, + "name": "conversationalAgent", + "type": "AgentExecutor", + "baseClasses": ["AgentExecutor", "BaseChain", "Runnable"], + "category": "Agents", + "description": "Conversational agent for a chat model. It will utilize chat specific prompts", + "inputParams": [ + { + "label": "System Message", + "name": "systemMessage", + "type": "string", + "rows": 4, + "default": "Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.\n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist.", + "optional": true, + "additionalParams": true, + "id": "conversationalAgent_0-input-systemMessage-string" + } + ], + "inputAnchors": [ + { + "label": "Allowed Tools", + "name": "tools", + "type": "Tool", + "list": true, + "id": "conversationalAgent_0-input-tools-Tool" + }, + { + "label": "Language Model", + "name": "model", + "type": "BaseChatModel", + "id": "conversationalAgent_0-input-model-BaseChatModel" + }, + { + "label": "Memory", + "name": "memory", + "type": "BaseChatMemory", + "id": "conversationalAgent_0-input-memory-BaseChatMemory" + } + ], + "inputs": { + "tools": ["{{webBrowser_0.data.instance}}"], + "model": "{{chatOpenAI_1.data.instance}}", + "memory": "{{bufferMemory_0.data.instance}}", + "systemMessage": "Assistant is a large language model trained by OpenAI.\n\nAssistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.\n\nAssistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.\n\nOverall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist." + }, + "outputAnchors": [ + { + "id": "conversationalAgent_0-output-conversationalAgent-AgentExecutor|BaseChain|Runnable", + "name": "conversationalAgent", + "label": "AgentExecutor", + "type": "AgentExecutor | BaseChain | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1518.944765840293, + "y": 212.2513364217197 + }, + "dragging": false } ], "edges": [ @@ -638,17 +638,6 @@ "label": "" } }, - { - "source": "chatOpenAI_1", - "sourceHandle": "chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", - "target": "conversationalAgent_0", - "targetHandle": "conversationalAgent_0-input-model-BaseLanguageModel", - "type": "buttonedge", - "id": "chatOpenAI_1-chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalAgent_0-conversationalAgent_0-input-model-BaseLanguageModel", - "data": { - "label": "" - } - }, { "source": "webBrowser_0", "sourceHandle": "webBrowser_0-output-webBrowser-WebBrowser|Tool|StructuredTool|BaseLangChain", @@ -660,6 +649,17 @@ "label": "" } }, + { + "source": "chatOpenAI_1", + "sourceHandle": "chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", + "target": "conversationalAgent_0", + "targetHandle": "conversationalAgent_0-input-model-BaseChatModel", + "type": "buttonedge", + "id": "chatOpenAI_1-chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-conversationalAgent_0-conversationalAgent_0-input-model-BaseChatModel", + "data": { + "label": "" + } + }, { "source": "bufferMemory_0", "sourceHandle": "bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory", From 887b003cdd9bfdd9a064bf65879ef4322b9d47db Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Tue, 28 Nov 2023 20:53:33 +0530 Subject: [PATCH 03/16] Bugfix: Simple Moderation, making the checks case-insensitive --- .../SimplePromptModeration/SimplePromptModerationRunner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts b/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts index 94967ba2..08f9ed1e 100644 --- a/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts +++ b/packages/components/nodes/moderation/SimplePromptModeration/SimplePromptModerationRunner.ts @@ -14,7 +14,7 @@ export class SimplePromptModerationRunner implements Moderation { async checkForViolations(input: string): Promise { this.denyList.split('\n').forEach((denyListItem) => { - if (denyListItem && denyListItem !== '' && input.includes(denyListItem)) { + if (denyListItem && denyListItem !== '' && input.toLowerCase().includes(denyListItem.toLowerCase())) { throw Error(this.moderationErrorMessage) } }) From 549a68d118aa0ba1fa79d2cc0ead227813731988 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Tue, 28 Nov 2023 20:58:39 +0530 Subject: [PATCH 04/16] Bugfix: UI Fix - left align long chat flow names for consistency. --- packages/ui/src/ui-component/table/FlowListTable.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/ui-component/table/FlowListTable.js b/packages/ui/src/ui-component/table/FlowListTable.js index e3baa2e2..358fc965 100644 --- a/packages/ui/src/ui-component/table/FlowListTable.js +++ b/packages/ui/src/ui-component/table/FlowListTable.js @@ -69,7 +69,9 @@ export const FlowListTable = ({ data, images, filterFunction, updateFlowsApi }) - + From 7362dec5c13d3170558141fc6cfdc980b8754b09 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 28 Nov 2023 16:08:35 +0000 Subject: [PATCH 05/16] add disable file download and regex to remove citation --- .../agents/OpenAIAssistant/OpenAIAssistant.ts | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index b119599d..2dc5a95d 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -41,6 +41,15 @@ class OpenAIAssistant_Agents implements INode { name: 'tools', type: 'Tool', list: true + }, + { + label: 'Disable File Download', + name: 'disableFileDownload', + type: 'boolean', + description: + 'Messages can contain text, images, or files. In some cases, you may want to prevent others from downloading the files. Learn more from OpenAI File Annotation docs', + optional: true, + additionalParams: true } ] } @@ -119,6 +128,8 @@ class OpenAIAssistant_Agents implements INode { const selectedAssistantId = nodeData.inputs?.selectedAssistant as string const appDataSource = options.appDataSource as DataSource const databaseEntities = options.databaseEntities as IDatabaseEntity + const disableFileDownload = nodeData.inputs?.disableFileDownload as boolean + let tools = nodeData.inputs?.tools tools = flatten(tools) const formattedTools = tools?.map((tool: any) => formatToOpenAIAssistantTool(tool)) ?? [] @@ -310,7 +321,7 @@ class OpenAIAssistant_Agents implements INode { const dirPath = path.join(getUserHome(), '.flowise', 'openai-assistant') - // Iterate over the annotations and add footnotes + // Iterate over the annotations for (let index = 0; index < annotations.length; index++) { const annotation = annotations[index] let filePath = '' @@ -323,11 +334,13 @@ class OpenAIAssistant_Agents implements INode { // eslint-disable-next-line no-useless-escape const fileName = cited_file.filename.split(/[\/\\]/).pop() ?? cited_file.filename filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', fileName) - await downloadFile(cited_file, filePath, dirPath, openAIApiKey) - fileAnnotations.push({ - filePath, - fileName - }) + if (!disableFileDownload) { + await downloadFile(cited_file, filePath, dirPath, openAIApiKey) + fileAnnotations.push({ + filePath, + fileName + }) + } } else { const file_path = (annotation as OpenAI.Beta.Threads.Messages.MessageContentText.Text.FilePath).file_path if (file_path) { @@ -335,11 +348,13 @@ class OpenAIAssistant_Agents implements INode { // eslint-disable-next-line no-useless-escape const fileName = cited_file.filename.split(/[\/\\]/).pop() ?? cited_file.filename filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', fileName) - await downloadFile(cited_file, filePath, dirPath, openAIApiKey) - fileAnnotations.push({ - filePath, - fileName - }) + if (!disableFileDownload) { + await downloadFile(cited_file, filePath, dirPath, openAIApiKey) + fileAnnotations.push({ + filePath, + fileName + }) + } } } @@ -351,6 +366,9 @@ class OpenAIAssistant_Agents implements INode { } else { returnVal += content.text.value } + + const lenticularBracketRegex = /【[^】]*】/g + returnVal = returnVal.replace(lenticularBracketRegex, '') } else { const content = assistantMessages[0].content[i] as MessageContentImageFile const fileId = content.image_file.file_id From 1cd465a08ead29087932def8d640e71363106ae1 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 28 Nov 2023 16:23:11 +0000 Subject: [PATCH 06/16] update openai assistant version --- .../nodes/agents/OpenAIAssistant/OpenAIAssistant.ts | 2 +- .../marketplaces/chatflows/OpenAI Assistant.json | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index 2dc5a95d..35299057 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -23,7 +23,7 @@ class OpenAIAssistant_Agents implements INode { constructor() { this.label = 'OpenAI Assistant' this.name = 'openAIAssistant' - this.version = 1.0 + this.version = 2.0 this.type = 'OpenAIAssistant' this.category = 'Agents' this.icon = 'openai.png' diff --git a/packages/server/marketplaces/chatflows/OpenAI Assistant.json b/packages/server/marketplaces/chatflows/OpenAI Assistant.json index 2f9a860c..ba4c6134 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": 1, + "version": 2, "name": "openAIAssistant", "type": "OpenAIAssistant", "baseClasses": ["OpenAIAssistant"], @@ -27,6 +27,15 @@ "type": "asyncOptions", "loadMethod": "listAssistants", "id": "openAIAssistant_0-input-selectedAssistant-asyncOptions" + }, + { + "label": "Disable File Download", + "name": "disableFileDownload", + "type": "boolean", + "description": "Messages can contain text, images, or files. In some cases, you may want to prevent others from downloading the files. Learn more from OpenAI File Annotation docs", + "optional": true, + "additionalParams": true, + "id": "openAIAssistant_0-input-disableFileDownload-boolean" } ], "inputAnchors": [ From 0ee47c2ccf87716cd954b1f09dc429e568636fdb Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 28 Nov 2023 16:58:16 +0000 Subject: [PATCH 07/16] remove import/export database --- packages/server/src/Interface.ts | 6 -- packages/server/src/index.ts | 54 +--------- packages/ui/src/api/database.js | 9 -- .../MainLayout/Header/ProfileSection/index.js | 100 +----------------- 4 files changed, 3 insertions(+), 166 deletions(-) delete mode 100644 packages/ui/src/api/database.js diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index c562b4ee..d5890ab6 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -190,12 +190,6 @@ export interface IOverrideConfig { type: string } -export interface IDatabaseExport { - chatmessages: IChatMessage[] - chatflows: IChatFlow[] - apikeys: ICommonObject[] -} - export type ICredentialDataDecrypted = ICommonObject // Plain credential object sent to server diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 50169c3c..1ebd9312 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -17,7 +17,6 @@ import { IReactFlowNode, IReactFlowObject, INodeData, - IDatabaseExport, ICredentialReturnResponse, chatType, IChatMessage, @@ -57,7 +56,7 @@ import { ChatflowPool } from './ChatflowPool' import { CachePool } from './CachePool' import { ICommonObject, INodeOptionsValue } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' -import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, replaceAllAPIKeys, updateAPIKey } from './utils/apiKey' +import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey' export class App { app: express.Application @@ -1019,57 +1018,6 @@ export class App { } }) - // ---------------------------------------- - // Export Load Chatflow & ChatMessage & Apikeys - // ---------------------------------------- - - this.app.get('/api/v1/database/export', async (req: Request, res: Response) => { - const chatmessages = await this.AppDataSource.getRepository(ChatMessage).find() - const chatflows = await this.AppDataSource.getRepository(ChatFlow).find() - const apikeys = await getAPIKeys() - const result: IDatabaseExport = { - chatmessages, - chatflows, - apikeys - } - return res.json(result) - }) - - this.app.post('/api/v1/database/load', async (req: Request, res: Response) => { - const databaseItems: IDatabaseExport = req.body - - await this.AppDataSource.getRepository(ChatFlow).delete({}) - await this.AppDataSource.getRepository(ChatMessage).delete({}) - - let error = '' - - // Get a new query runner instance - const queryRunner = this.AppDataSource.createQueryRunner() - - // Start a new transaction - await queryRunner.startTransaction() - - try { - const chatflows: ChatFlow[] = databaseItems.chatflows - const chatmessages: ChatMessage[] = databaseItems.chatmessages - - await queryRunner.manager.insert(ChatFlow, chatflows) - await queryRunner.manager.insert(ChatMessage, chatmessages) - - await queryRunner.commitTransaction() - } catch (err: any) { - error = err?.message ?? 'Error loading database' - await queryRunner.rollbackTransaction() - } finally { - await queryRunner.release() - } - - await replaceAllAPIKeys(databaseItems.apikeys) - - if (error) return res.status(500).send(error) - return res.status(201).send('OK') - }) - // ---------------------------------------- // Upsert // ---------------------------------------- diff --git a/packages/ui/src/api/database.js b/packages/ui/src/api/database.js deleted file mode 100644 index f36fb72c..00000000 --- a/packages/ui/src/api/database.js +++ /dev/null @@ -1,9 +0,0 @@ -import client from './client' - -const getExportDatabase = () => client.get('/database/export') -const createLoadDatabase = (body) => client.post('/database/load', body) - -export default { - getExportDatabase, - createLoadDatabase -} diff --git a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js index c10b3289..c2de41b2 100644 --- a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js +++ b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js @@ -1,7 +1,6 @@ import { useState, useRef, useEffect } from 'react' import PropTypes from 'prop-types' -import { useSelector, useDispatch } from 'react-redux' -import { useNavigate } from 'react-router-dom' +import { useSelector } from 'react-redux' // material-ui import { useTheme } from '@mui/material/styles' @@ -26,16 +25,10 @@ import PerfectScrollbar from 'react-perfect-scrollbar' // project imports import MainCard from 'ui-component/cards/MainCard' import Transitions from 'ui-component/extended/Transitions' -import { BackdropLoader } from 'ui-component/loading/BackdropLoader' import AboutDialog from 'ui-component/dialog/AboutDialog' // assets -import { IconLogout, IconSettings, IconFileExport, IconFileDownload, IconInfoCircle } from '@tabler/icons' - -// API -import databaseApi from 'api/database' - -import { SET_MENU } from 'store/actions' +import { IconLogout, IconSettings } from '@tabler/icons' import './index.css' @@ -43,17 +36,13 @@ import './index.css' const ProfileSection = ({ username, handleLogout }) => { const theme = useTheme() - const dispatch = useDispatch() - const navigate = useNavigate() const customization = useSelector((state) => state.customization) const [open, setOpen] = useState(false) - const [loading, setLoading] = useState(false) const [aboutDialogOpen, setAboutDialogOpen] = useState(false) const anchorRef = useRef(null) - const uploadRef = useRef(null) const handleClose = (event) => { if (anchorRef.current && anchorRef.current.contains(event.target)) { @@ -66,56 +55,6 @@ const ProfileSection = ({ username, handleLogout }) => { setOpen((prevOpen) => !prevOpen) } - const handleExportDB = async () => { - setOpen(false) - try { - const response = await databaseApi.getExportDatabase() - const exportItems = response.data - let dataStr = JSON.stringify(exportItems, null, 2) - let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr) - - let exportFileDefaultName = `DB.json` - - let linkElement = document.createElement('a') - linkElement.setAttribute('href', dataUri) - linkElement.setAttribute('download', exportFileDefaultName) - linkElement.click() - } catch (e) { - console.error(e) - } - } - - const handleFileUpload = (e) => { - if (!e.target.files) return - - const file = e.target.files[0] - const reader = new FileReader() - reader.onload = async (evt) => { - if (!evt?.target?.result) { - return - } - const { result } = evt.target - - if (result.includes(`"chatmessages":[`) && result.includes(`"chatflows":[`) && result.includes(`"apikeys":[`)) { - dispatch({ type: SET_MENU, opened: false }) - setLoading(true) - - try { - await databaseApi.createLoadDatabase(JSON.parse(result)) - setLoading(false) - navigate('/', { replace: true }) - navigate(0) - } catch (e) { - console.error(e) - setLoading(false) - } - } else { - alert('Incorrect Flowise Database Format') - } - } - reader.readAsText(file) - } - const prevOpen = useRef(open) useEffect(() => { if (prevOpen.current === true && open === false) { @@ -196,39 +135,6 @@ const ProfileSection = ({ username, handleLogout }) => { } }} > - { - setOpen(false) - uploadRef.current.click() - }} - > - - - - Load Database} /> - - - - - - Export Database} /> - - { - setOpen(false) - setAboutDialogOpen(true) - }} - > - - - - About Flowise} /> - {localStorage.getItem('username') && localStorage.getItem('password') && ( { )} - handleFileUpload(e)} /> - setAboutDialogOpen(false)} /> ) From 147526ff72802a00132d41eedcc2ef9ee0d1c7c7 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 28 Nov 2023 17:00:10 +0000 Subject: [PATCH 08/16] remove zapier NLA as its not supported anymore --- .../nodes/tools/ZapierNLA/ZapierNLA.ts | 52 ------------------- .../nodes/tools/ZapierNLA/zapier.svg | 8 --- 2 files changed, 60 deletions(-) delete mode 100644 packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts delete mode 100644 packages/components/nodes/tools/ZapierNLA/zapier.svg diff --git a/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts b/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts deleted file mode 100644 index 31ac989b..00000000 --- a/packages/components/nodes/tools/ZapierNLA/ZapierNLA.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ZapierNLAWrapper, ZapierNLAWrapperParams } from 'langchain/tools' -import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { ZapierToolKit } from 'langchain/agents' -import { getCredentialData, getCredentialParam } from '../../../src' - -class ZapierNLA_Tools implements INode { - label: string - name: string - version: number - description: string - type: string - icon: string - category: string - badge: string - baseClasses: string[] - inputs: INodeParams[] - credential: INodeParams - - constructor() { - this.label = 'Zapier NLA' - this.name = 'zapierNLA' - this.version = 1.0 - this.type = 'ZapierNLA' - this.icon = 'zapier.svg' - this.category = 'Tools' - this.description = "Access to apps and actions on Zapier's platform through a natural language API interface" - this.badge = 'DEPRECATING' - this.inputs = [] - this.credential = { - label: 'Connect Credential', - name: 'credential', - type: 'credential', - credentialNames: ['zapierNLAApi'] - } - this.baseClasses = [this.type, 'Tool'] - } - - async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const credentialData = await getCredentialData(nodeData.credential ?? '', options) - const zapierNLAApiKey = getCredentialParam('zapierNLAApiKey', credentialData, nodeData) - - const obj: Partial = { - apiKey: zapierNLAApiKey - } - const zapier = new ZapierNLAWrapper(obj) - const toolkit = await ZapierToolKit.fromZapierNLAWrapper(zapier) - - return toolkit.tools - } -} - -module.exports = { nodeClass: ZapierNLA_Tools } diff --git a/packages/components/nodes/tools/ZapierNLA/zapier.svg b/packages/components/nodes/tools/ZapierNLA/zapier.svg deleted file mode 100644 index 6ed35f29..00000000 --- a/packages/components/nodes/tools/ZapierNLA/zapier.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file From 464a23e0f49b63553faca6222e42c733b78fbc2a Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 29 Nov 2023 00:25:08 +0000 Subject: [PATCH 09/16] update agent model --- .../ConversationalRetrievalAgent.ts | 4 ++-- .../agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts | 4 ++-- .../marketplaces/chatflows/API Agent OpenAI.json | 6 +++--- .../chatflows/Conversational Retrieval Agent.json | 10 +++++----- .../server/marketplaces/chatflows/OpenAI Agent.json | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts index 7b71cb5f..c6354e18 100644 --- a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts +++ b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts @@ -21,7 +21,7 @@ class ConversationalRetrievalAgent_Agents implements INode { constructor() { this.label = 'Conversational Retrieval Agent' this.name = 'conversationalRetrievalAgent' - this.version = 2.0 + this.version = 3.0 this.type = 'AgentExecutor' this.category = 'Agents' this.icon = 'agent.svg' @@ -42,7 +42,7 @@ class ConversationalRetrievalAgent_Agents implements INode { { label: 'OpenAI/Azure Chat Model', name: 'model', - type: 'ChatOpenAI | AzureChatOpenAI' + type: 'BaseChatModel' }, { label: 'System Message', diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index ce6f576f..25a540e8 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -20,7 +20,7 @@ class OpenAIFunctionAgent_Agents implements INode { constructor() { this.label = 'OpenAI Function Agent' this.name = 'openAIFunctionAgent' - this.version = 2.0 + this.version = 3.0 this.type = 'AgentExecutor' this.category = 'Agents' this.icon = 'openai.png' @@ -41,7 +41,7 @@ class OpenAIFunctionAgent_Agents implements INode { { label: 'OpenAI/Azure Chat Model', name: 'model', - type: 'ChatOpenAI | AzureChatOpenAI' + type: 'BaseChatModel' }, { label: 'System Message', diff --git a/packages/server/marketplaces/chatflows/API Agent OpenAI.json b/packages/server/marketplaces/chatflows/API Agent OpenAI.json index 4950a6a6..1a07e60d 100644 --- a/packages/server/marketplaces/chatflows/API Agent OpenAI.json +++ b/packages/server/marketplaces/chatflows/API Agent OpenAI.json @@ -334,7 +334,7 @@ "id": "openAIFunctionAgent_0", "label": "OpenAI Function Agent", "name": "openAIFunctionAgent", - "version": 2, + "version": 3, "type": "AgentExecutor", "baseClasses": ["AgentExecutor", "BaseChain"], "category": "Agents", @@ -367,8 +367,8 @@ { "label": "OpenAI/Azure Chat Model", "name": "model", - "type": "ChatOpenAI | AzureChatOpenAI", - "id": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI" + "type": "BaseChatModel", + "id": "openAIFunctionAgent_0-input-model-BaseChatModel" } ], "inputs": { diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json index 800ae300..0e9e41bd 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json @@ -98,7 +98,7 @@ "data": { "id": "conversationalRetrievalAgent_0", "label": "Conversational Retrieval Agent", - "version": 2, + "version": 3, "name": "conversationalRetrievalAgent", "type": "AgentExecutor", "baseClasses": ["AgentExecutor", "BaseChain", "Runnable"], @@ -132,8 +132,8 @@ { "label": "OpenAI/Azure Chat Model", "name": "model", - "type": "ChatOpenAI | AzureChatOpenAI", - "id": "conversationalRetrievalAgent_0-input-model-ChatOpenAI | AzureChatOpenAI" + "type": "BaseChatModel", + "id": "conversationalRetrievalAgent_0-input-model-BaseChatModel" } ], "inputs": { @@ -642,9 +642,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", "target": "conversationalRetrievalAgent_0", - "targetHandle": "conversationalRetrievalAgent_0-input-model-ChatOpenAI", + "targetHandle": "conversationalRetrievalAgent_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalAgent_0-conversationalRetrievalAgent_0-input-model-ChatOpenAI", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-conversationalRetrievalAgent_0-conversationalRetrievalAgent_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/OpenAI Agent.json b/packages/server/marketplaces/chatflows/OpenAI Agent.json index bc27a9fe..931bae0c 100644 --- a/packages/server/marketplaces/chatflows/OpenAI Agent.json +++ b/packages/server/marketplaces/chatflows/OpenAI Agent.json @@ -206,7 +206,7 @@ "id": "openAIFunctionAgent_0", "label": "OpenAI Function Agent", "name": "openAIFunctionAgent", - "version": 2, + "version": 3, "type": "AgentExecutor", "baseClasses": ["AgentExecutor", "BaseChain"], "category": "Agents", @@ -239,8 +239,8 @@ { "label": "OpenAI/Azure Chat Model", "name": "model", - "type": "ChatOpenAI | AzureChatOpenAI", - "id": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI" + "type": "BaseChatModel", + "id": "openAIFunctionAgent_0-input-model-BaseChatModel" } ], "inputs": { From 6a7726956c76c27da01413520c534339243ad229 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 29 Nov 2023 00:52:48 +0000 Subject: [PATCH 10/16] update marketplace templates --- packages/server/marketplaces/chatflows/API Agent OpenAI.json | 4 ++-- packages/server/marketplaces/chatflows/OpenAI Agent.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/marketplaces/chatflows/API Agent OpenAI.json b/packages/server/marketplaces/chatflows/API Agent OpenAI.json index 26c1961e..002c08c1 100644 --- a/packages/server/marketplaces/chatflows/API Agent OpenAI.json +++ b/packages/server/marketplaces/chatflows/API Agent OpenAI.json @@ -672,9 +672,9 @@ "source": "chatOpenAI_2", "sourceHandle": "chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "target": "openAIFunctionAgent_0", - "targetHandle": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_2-chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "id": "chatOpenAI_2-chatOpenAI_2-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", "data": { "label": "" } diff --git a/packages/server/marketplaces/chatflows/OpenAI Agent.json b/packages/server/marketplaces/chatflows/OpenAI Agent.json index 0cb3c3b3..17e59236 100644 --- a/packages/server/marketplaces/chatflows/OpenAI Agent.json +++ b/packages/server/marketplaces/chatflows/OpenAI Agent.json @@ -488,9 +488,9 @@ "source": "chatOpenAI_0", "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel", "target": "openAIFunctionAgent_0", - "targetHandle": "openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "targetHandle": "openAIFunctionAgent_0-input-model-BaseChatModel", "type": "buttonedge", - "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-ChatOpenAI | AzureChatOpenAI", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel-openAIFunctionAgent_0-openAIFunctionAgent_0-input-model-BaseChatModel", "data": { "label": "" } From 5a5097e9970be804642c714d99c99ababc1b82d9 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 29 Nov 2023 13:42:17 +0000 Subject: [PATCH 11/16] update flowise-components to 1.4.5 --- packages/components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/package.json b/packages/components/package.json index bb074392..bea9a7a0 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.4.4", + "version": "1.4.5", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", From 4b5f7028e3320d72d92d60deb55ebee44b56deb8 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 30 Nov 2023 16:01:16 +0000 Subject: [PATCH 12/16] fix chat history --- .../agents/OpenAIAssistant/OpenAIAssistant.ts | 70 ++++++++++--------- .../nodes/memory/DynamoDb/DynamoDb.ts | 35 +++++++--- .../memory/MongoDBMemory/MongoDBMemory.ts | 35 +++++++--- .../memory/MotorheadMemory/MotorheadMemory.ts | 25 +++++-- .../RedisBackedChatMemory.ts | 26 ++++--- .../UpstashRedisBackedChatMemory.ts | 25 ++++--- .../nodes/memory/ZepMemory/ZepMemory.ts | 27 +++++-- .../components/nodes/tools/ChainTool/core.ts | 4 +- packages/components/src/Interface.ts | 5 +- packages/components/src/utils.ts | 12 ++++ packages/server/src/index.ts | 38 +++++++--- packages/server/src/utils/index.ts | 58 ++++++++++++--- 12 files changed, 262 insertions(+), 98 deletions(-) diff --git a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts index 35299057..7f2377bd 100644 --- a/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts +++ b/packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts @@ -85,43 +85,46 @@ class OpenAIAssistant_Agents implements INode { return null } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const selectedAssistantId = nodeData.inputs?.selectedAssistant as string - const appDataSource = options.appDataSource as DataSource - const databaseEntities = options.databaseEntities as IDatabaseEntity - let sessionId = nodeData.inputs?.sessionId as string + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const selectedAssistantId = nodeData.inputs?.selectedAssistant as string + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + let sessionId = nodeData.inputs?.sessionId as string - const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({ - id: selectedAssistantId - }) - - if (!assistant) { - options.logger.error(`Assistant ${selectedAssistantId} not found`) - return - } - - if (!sessionId && options.chatId) { - const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({ - chatId: options.chatId + const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({ + id: selectedAssistantId }) - if (!chatmsg) { - options.logger.error(`Chat Message with Chat Id: ${options.chatId} not found`) + + if (!assistant) { + options.logger.error(`Assistant ${selectedAssistantId} not found`) return } - sessionId = chatmsg.sessionId - } - const credentialData = await getCredentialData(assistant.credential ?? '', options) - const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) - if (!openAIApiKey) { - options.logger.error(`OpenAI ApiKey not found`) - return - } + if (!sessionId && options.chatId) { + const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({ + chatId: options.chatId + }) + if (!chatmsg) { + options.logger.error(`Chat Message with Chat Id: ${options.chatId} not found`) + return + } + sessionId = chatmsg.sessionId + } - const openai = new OpenAI({ apiKey: openAIApiKey }) - options.logger.info(`Clearing OpenAI Thread ${sessionId}`) - if (sessionId) await openai.beta.threads.del(sessionId) - options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`) + const credentialData = await getCredentialData(assistant.credential ?? '', options) + const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) + if (!openAIApiKey) { + options.logger.error(`OpenAI ApiKey not found`) + return + } + + const openai = new OpenAI({ apiKey: openAIApiKey }) + options.logger.info(`Clearing OpenAI Thread ${sessionId}`) + if (sessionId) await openai.beta.threads.del(sessionId) + options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`) + } } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { @@ -359,7 +362,10 @@ class OpenAIAssistant_Agents implements INode { } // Replace the text with a footnote - message_content.value = message_content.value.replace(`${annotation.text}`, `${filePath}`) + message_content.value = message_content.value.replace( + `${annotation.text}`, + `${disableFileDownload ? '' : filePath}` + ) } returnVal += message_content.value diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index ac4f7602..8ca6cf9e 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -1,4 +1,13 @@ -import { ICommonObject, INode, INodeData, INodeParams, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src' +import { + ICommonObject, + INode, + INodeData, + INodeParams, + getBaseClasses, + getCredentialData, + getCredentialParam, + serializeChatHistory +} from '../../../src' import { DynamoDBChatMessageHistory } from 'langchain/stores/message/dynamodb' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' @@ -70,13 +79,23 @@ class DynamoDb_Memory implements INode { return initalizeDynamoDB(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const dynamodbMemory = await initalizeDynamoDB(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing DynamoDb memory session ${sessionId ? sessionId : chatId}`) - await dynamodbMemory.clear() - options.logger.info(`Successfully cleared DynamoDb memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const dynamodbMemory = await initalizeDynamoDB(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing DynamoDb memory session ${sessionId ? sessionId : chatId}`) + await dynamodbMemory.clear() + options.logger.info(`Successfully cleared DynamoDb memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const dynamodbMemory = await initalizeDynamoDB(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await dynamodbMemory.loadMemoryVariables({}) + return serializeChatHistory(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts index 6f800cdc..76cb7e31 100644 --- a/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts +++ b/packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts @@ -1,4 +1,13 @@ -import { getBaseClasses, getCredentialData, getCredentialParam, ICommonObject, INode, INodeData, INodeParams } from '../../../src' +import { + 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' @@ -67,13 +76,23 @@ class MongoDB_Memory implements INode { return initializeMongoDB(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const mongodbMemory = await initializeMongoDB(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing MongoDB memory session ${sessionId ? sessionId : chatId}`) - await mongodbMemory.clear() - options.logger.info(`Successfully cleared MongoDB memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const mongodbMemory = await initializeMongoDB(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing MongoDB memory session ${sessionId ? sessionId : chatId}`) + await mongodbMemory.clear() + options.logger.info(`Successfully cleared MongoDB memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const mongodbMemory = await initializeMongoDB(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await mongodbMemory.loadMemoryVariables({}) + return serializeChatHistory(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 0ec2f42a..9cdbcd5c 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -3,6 +3,7 @@ import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../ import { ICommonObject } from '../../../src' import { MotorheadMemory, MotorheadMemoryInput } from 'langchain/memory' import fetch from 'node-fetch' +import { getBufferString } from 'langchain/memory' class MotorMemory_Memory implements INode { label: string @@ -64,13 +65,23 @@ class MotorMemory_Memory implements INode { return initalizeMotorhead(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const motorhead = await initalizeMotorhead(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Motorhead memory session ${sessionId ? sessionId : chatId}`) - await motorhead.clear() - options.logger.info(`Successfully cleared Motorhead memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const motorhead = await initalizeMotorhead(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Motorhead memory session ${sessionId ? sessionId : chatId}`) + await motorhead.clear() + options.logger.info(`Successfully cleared Motorhead memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const motorhead = await initalizeMotorhead(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await motorhead.loadMemoryVariables({}) + return getBufferString(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index bdb62911..7fe447ad 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -1,5 +1,5 @@ import { INode, INodeData, INodeParams, ICommonObject } from '../../../src/Interface' -import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { 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' @@ -65,13 +65,23 @@ class RedisBackedChatMemory_Memory implements INode { return await initalizeRedis(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const redis = await initalizeRedis(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Redis memory session ${sessionId ? sessionId : chatId}`) - await redis.clear() - options.logger.info(`Successfully cleared Redis memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const redis = await initalizeRedis(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Redis memory session ${sessionId ? sessionId : chatId}`) + await redis.clear() + options.logger.info(`Successfully cleared Redis memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const redis = await initalizeRedis(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await redis.loadMemoryVariables({}) + return serializeChatHistory(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts index 2b8b4650..8bca0440 100644 --- a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts @@ -1,5 +1,5 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' -import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { getBaseClasses, getCredentialData, getCredentialParam, serializeChatHistory } from '../../../src/utils' import { ICommonObject } from '../../../src' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { UpstashRedisChatMessageHistory } from 'langchain/stores/message/upstash_redis' @@ -63,13 +63,22 @@ class UpstashRedisBackedChatMemory_Memory implements INode { return initalizeUpstashRedis(nodeData, options) } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const redis = await initalizeUpstashRedis(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`) - await redis.clear() - options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const redis = await initalizeUpstashRedis(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`) + await redis.clear() + options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const redis = await initalizeUpstashRedis(nodeData, options) + const key = 'chat_history' + const memoryResult = await redis.loadMemoryVariables({}) + return serializeChatHistory(memoryResult[key]) + } } } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index c4498644..ced871a1 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -3,6 +3,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep' import { ICommonObject } from '../../../src' +import { getBufferString } from 'langchain/memory' class ZepMemory_Memory implements INode { label: string @@ -140,13 +141,25 @@ class ZepMemory_Memory implements INode { return zep } - async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { - const zep = await initalizeZep(nodeData, options) - const sessionId = nodeData.inputs?.sessionId as string - const chatId = options?.chatId as string - options.logger.info(`Clearing Zep memory session ${sessionId ? sessionId : chatId}`) - await zep.clear() - options.logger.info(`Successfully cleared Zep memory session ${sessionId ? sessionId : chatId}`) + //@ts-ignore + memoryMethods = { + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const zep = await initalizeZep(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Zep memory session ${sessionId ? sessionId : chatId}`) + await zep.clear() + options.logger.info(`Successfully cleared Zep memory session ${sessionId ? sessionId : chatId}`) + }, + async getChatMessages(nodeData: INodeData, options: ICommonObject): Promise { + const memoryKey = nodeData.inputs?.memoryKey as string + const aiPrefix = nodeData.inputs?.aiPrefix as string + const humanPrefix = nodeData.inputs?.humanPrefix as string + const zep = await initalizeZep(nodeData, options) + const key = memoryKey ?? 'chat_history' + const memoryResult = await zep.loadMemoryVariables({}) + return getBufferString(memoryResult[key], humanPrefix, aiPrefix) + } } } diff --git a/packages/components/nodes/tools/ChainTool/core.ts b/packages/components/nodes/tools/ChainTool/core.ts index 6c3dba55..5520d6df 100644 --- a/packages/components/nodes/tools/ChainTool/core.ts +++ b/packages/components/nodes/tools/ChainTool/core.ts @@ -1,5 +1,6 @@ import { DynamicTool, DynamicToolInput } from 'langchain/tools' import { BaseChain } from 'langchain/chains' +import { handleEscapeCharacters } from '../../../src/utils' export interface ChainToolInput extends Omit { chain: BaseChain @@ -14,7 +15,8 @@ export class ChainTool extends DynamicTool { func: async (input, runManager) => { // To enable LLM Chain which has promptValues if ((chain as any).prompt && (chain as any).prompt.promptValues) { - const values = await chain.call((chain as any).prompt.promptValues, runManager?.getChild()) + const promptValues = handleEscapeCharacters((chain as any).prompt.promptValues, true) + const values = await chain.call(promptValues, runManager?.getChild()) return values?.text } return chain.run(input, runManager?.getChild()) diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index af304272..6752f944 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -107,9 +107,12 @@ export interface INode extends INodeProperties { search: (nodeData: INodeData, options?: ICommonObject) => Promise delete: (nodeData: INodeData, options?: ICommonObject) => Promise } + memoryMethods?: { + clearSessionMemory: (nodeData: INodeData, options?: ICommonObject) => Promise + getChatMessages: (nodeData: INodeData, options?: ICommonObject) => Promise + } init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise - clearSessionMemory?(nodeData: INodeData, options?: ICommonObject): Promise } export interface INodeData extends INodeProperties { diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 69f8b268..404f7c75 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -549,6 +549,18 @@ export const convertChatHistoryToText = (chatHistory: IMessage[] = []): string = .join('\n') } +/** + * Serialize array chat history to string + * @param {IMessage[]} chatHistory + * @returns {string} + */ +export const serializeChatHistory = (chatHistory: string | Array) => { + if (Array.isArray(chatHistory)) { + return chatHistory.join('\n') + } + return chatHistory +} + /** * Convert schema to zod schema * @param {string | object} schema diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 1ebd9312..d87d2c0a 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -42,7 +42,8 @@ import { getEncryptionKey, checkMemorySessionId, clearSessionMemoryFromViewMessageDialog, - getUserHome + getUserHome, + replaceChatHistory } from './utils' import { cloneDeep, omit, uniqWith, isEqual } from 'lodash' import { getDataSource } from './DataSource' @@ -54,7 +55,7 @@ import { Tool } from './database/entities/Tool' import { Assistant } from './database/entities/Assistant' import { ChatflowPool } from './ChatflowPool' import { CachePool } from './CachePool' -import { ICommonObject, INodeOptionsValue } from 'flowise-components' +import { ICommonObject, IMessage, INodeOptionsValue } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey' @@ -1267,14 +1268,14 @@ export class App { * @param {IReactFlowEdge[]} edges * @returns {string | undefined} */ - findMemoryLabel(nodes: IReactFlowNode[], edges: IReactFlowEdge[]): string | undefined { + findMemoryLabel(nodes: IReactFlowNode[], edges: IReactFlowEdge[]): IReactFlowNode | undefined { const memoryNodes = nodes.filter((node) => node.data.category === 'Memory') const memoryNodeIds = memoryNodes.map((mem) => mem.data.id) for (const edge of edges) { if (memoryNodeIds.includes(edge.source)) { const memoryNode = nodes.find((node) => node.data.id === edge.source) - return memoryNode ? memoryNode.data.label : undefined + return memoryNode } } return undefined @@ -1395,6 +1396,19 @@ export class App { isStreamValid = isFlowValidForStream(nodes, endingNodeData) + let chatHistory: IMessage[] | string = incomingInput.history + if ( + endingNodeData.inputs?.memory && + !incomingInput.history && + (incomingInput.chatId || incomingInput.overrideConfig?.sessionId) + ) { + const memoryNodeId = endingNodeData.inputs?.memory.split('.')[0].replace('{{', '') + const memoryNode = nodes.find((node) => node.data.id === memoryNodeId) + if (memoryNode) { + chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger) + } + } + /*** Get Starting Nodes with Non-Directed Graph ***/ const constructedObj = constructGraphs(nodes, edges, true) const nonDirectedGraph = constructedObj.graph @@ -1409,7 +1423,7 @@ export class App { depthQueue, this.nodesPool.componentNodes, incomingInput.question, - incomingInput.history, + chatHistory, chatId, chatflowid, this.AppDataSource, @@ -1429,7 +1443,7 @@ export class App { nodeToExecute.data, reactFlowNodes, incomingInput.question, - incomingInput.history + chatHistory ) nodeToExecuteData = reactFlowNodeData @@ -1446,11 +1460,17 @@ export class App { let sessionId = undefined if (nodeToExecuteData.instance) sessionId = checkMemorySessionId(nodeToExecuteData.instance, chatId) - const memoryType = this.findMemoryLabel(nodes, edges) + const memoryNode = this.findMemoryLabel(nodes, edges) + const memoryType = memoryNode?.data.label + + let chatHistory: IMessage[] | string = incomingInput.history + if (memoryNode && !incomingInput.history && (incomingInput.chatId || incomingInput.overrideConfig?.sessionId)) { + chatHistory = await replaceChatHistory(memoryNode, incomingInput, this.AppDataSource, databaseEntities, logger) + } let result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { - chatHistory: incomingInput.history, + chatHistory, socketIO, socketIOClientId: incomingInput.socketIOClientId, logger, @@ -1460,7 +1480,7 @@ export class App { chatId }) : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { - chatHistory: incomingInput.history, + chatHistory, logger, appDataSource: this.AppDataSource, databaseEntities, diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 423d32af..0b1e62d2 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -15,7 +15,8 @@ import { IOverrideConfig, IReactFlowEdge, IReactFlowNode, - IVariableDict + IVariableDict, + IncomingInput } from '../Interface' import { cloneDeep, get, isEqual } from 'lodash' import { @@ -216,7 +217,7 @@ export const buildLangchain = async ( depthQueue: IDepthQueue, componentNodes: IComponentNodes, question: string, - chatHistory: IMessage[], + chatHistory: IMessage[] | string, chatId: string, chatflowid: string, appDataSource: DataSource, @@ -347,8 +348,8 @@ export const clearAllSessionMemory = async ( node.data.inputs.sessionId = sessionId } - if (newNodeInstance.clearSessionMemory) { - await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) + if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) { + await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) } } } @@ -380,8 +381,8 @@ export const clearSessionMemoryFromViewMessageDialog = async ( if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId - if (newNodeInstance.clearSessionMemory) { - await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) + if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.clearSessionMemory) { + await newNodeInstance.memoryMethods.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger }) return } } @@ -399,7 +400,7 @@ export const getVariableValue = ( paramValue: string, reactFlowNodes: IReactFlowNode[], question: string, - chatHistory: IMessage[], + chatHistory: IMessage[] | string, isAcceptVariable = false ) => { let returnVal = paramValue @@ -432,7 +433,10 @@ export const getVariableValue = ( } if (isAcceptVariable && variableFullPath === CHAT_HISTORY_VAR_PREFIX) { - variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(convertChatHistoryToText(chatHistory), false) + variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters( + typeof chatHistory === 'string' ? chatHistory : convertChatHistoryToText(chatHistory), + false + ) } // Split by first occurrence of '.' to get just nodeId @@ -475,7 +479,7 @@ export const resolveVariables = ( reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[], question: string, - chatHistory: IMessage[] + chatHistory: IMessage[] | string ): INodeData => { let flowNodeData = cloneDeep(reactFlowNodeData) const types = 'inputs' @@ -873,3 +877,39 @@ export const checkMemorySessionId = (instance: any, chatId: string): string | un return instance.memory.chatHistory.sessionId return undefined } + +/** + * Replace chatHistory if incomingInput.history is empty and sessionId/chatId is provided + * @param {IReactFlowNode} memoryNode + * @param {IncomingInput} incomingInput + * @param {DataSource} appDataSource + * @param {IDatabaseEntity} databaseEntities + * @param {any} logger + * @returns {string} + */ +export const replaceChatHistory = async ( + memoryNode: IReactFlowNode, + incomingInput: IncomingInput, + appDataSource: DataSource, + databaseEntities: IDatabaseEntity, + logger: any +): Promise => { + const nodeInstanceFilePath = memoryNode.data.filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const newNodeInstance = new nodeModule.nodeClass() + + if (incomingInput.overrideConfig?.sessionId && memoryNode.data.inputs) { + memoryNode.data.inputs.sessionId = incomingInput.overrideConfig.sessionId + } + + if (newNodeInstance.memoryMethods && newNodeInstance.memoryMethods.getChatMessages) { + return await newNodeInstance.memoryMethods.getChatMessages(memoryNode.data, { + chatId: incomingInput.chatId, + appDataSource, + databaseEntities, + logger + }) + } + + return '' +} From 674b8996f013ec8d9ea3c3db9f13ddf43dac0b38 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 30 Nov 2023 16:57:34 +0000 Subject: [PATCH 13/16] add back accidentally removed about dialog --- .../MainLayout/Header/ProfileSection/index.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js index c2de41b2..ac114c6c 100644 --- a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js +++ b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.js @@ -28,7 +28,7 @@ import Transitions from 'ui-component/extended/Transitions' import AboutDialog from 'ui-component/dialog/AboutDialog' // assets -import { IconLogout, IconSettings } from '@tabler/icons' +import { IconLogout, IconSettings, IconInfoCircle } from '@tabler/icons' import './index.css' @@ -135,6 +135,18 @@ const ProfileSection = ({ username, handleLogout }) => { } }} > + { + setOpen(false) + setAboutDialogOpen(true) + }} + > + + + + About Flowise} /> + {localStorage.getItem('username') && localStorage.getItem('password') && ( Date: Fri, 1 Dec 2023 15:35:00 +0000 Subject: [PATCH 14/16] added fix to restart flow when prompt is question prefix --- packages/server/src/utils/index.ts | 43 ++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 0b1e62d2..2bf1c04a 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -558,9 +558,20 @@ export const isStartNodeDependOnInput = (startingNodes: IReactFlowNode[], nodes: if (inputVariables.length > 0) return true } } - const whitelistNodeNames = ['vectorStoreToDocument', 'autoGPT'] + const whitelistNodeNames = ['vectorStoreToDocument', 'autoGPT', 'chatPromptTemplate', 'promptTemplate'] //If these nodes are found, chatflow cannot be reused for (const node of nodes) { - if (whitelistNodeNames.includes(node.data.name)) return true + if (node.data.name === 'chatPromptTemplate' || node.data.name === 'promptTemplate') { + let promptValues: ICommonObject = {} + const promptValuesRaw = node.data.inputs?.promptValues + if (promptValuesRaw) { + try { + promptValues = typeof promptValuesRaw === 'object' ? promptValuesRaw : JSON.parse(promptValuesRaw) + } catch (exception) { + console.error(exception) + } + } + if (getAllValuesFromJson(promptValues).includes(`{{${QUESTION_VAR_PREFIX}}}`)) return true + } else if (whitelistNodeNames.includes(node.data.name)) return true } return false } @@ -913,3 +924,31 @@ export const replaceChatHistory = async ( return '' } + +/** + * Get all values from a JSON object + * @param {any} obj + * @returns {any[]} + */ +export const getAllValuesFromJson = (obj: any): any[] => { + const values: any[] = [] + + function extractValues(data: any) { + if (typeof data === 'object' && data !== null) { + if (Array.isArray(data)) { + for (const item of data) { + extractValues(item) + } + } else { + for (const key in data) { + extractValues(data[key]) + } + } + } else { + values.push(data) + } + } + + extractValues(obj) + return values +} From 7df2e3fe6d7058649ac2c33d9dc5bf548043fe23 Mon Sep 17 00:00:00 2001 From: automaton82 Date: Fri, 1 Dec 2023 20:23:29 -0500 Subject: [PATCH 15/16] Fix for issue#1323 - delete duplicate chatflow --- packages/ui/src/views/canvas/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/views/canvas/index.js b/packages/ui/src/views/canvas/index.js index 29602a4f..08698398 100644 --- a/packages/ui/src/views/canvas/index.js +++ b/packages/ui/src/views/canvas/index.js @@ -170,7 +170,7 @@ const Canvas = () => { try { await chatflowsApi.deleteChatflow(chatflow.id) localStorage.removeItem(`${chatflow.id}_INTERNAL`) - navigate(-1) + navigate('/') } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ From f60360f3b94ba8e2ec080f44d683902eebb5e16e Mon Sep 17 00:00:00 2001 From: automaton82 Date: Fri, 1 Dec 2023 21:07:43 -0500 Subject: [PATCH 16/16] Fixing #1324 by lifting ConfirmDialog up. --- packages/ui/src/ui-component/button/FlowListMenu.js | 2 -- packages/ui/src/views/chatflows/index.js | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/ui-component/button/FlowListMenu.js b/packages/ui/src/ui-component/button/FlowListMenu.js index b242d2cb..94ecdd01 100644 --- a/packages/ui/src/ui-component/button/FlowListMenu.js +++ b/packages/ui/src/ui-component/button/FlowListMenu.js @@ -22,7 +22,6 @@ import useConfirm from 'hooks/useConfirm' import { uiBaseURL } from '../../store/constant' import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '../../store/actions' -import ConfirmDialog from '../dialog/ConfirmDialog' import SaveChatflowDialog from '../dialog/SaveChatflowDialog' import TagDialog from '../dialog/TagDialog' @@ -264,7 +263,6 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) { Delete - { )} + ) }