From a504e4b38f72345d5be9c4f527f7f959eaa9d292 Mon Sep 17 00:00:00 2001 From: kzhang Date: Tue, 12 Dec 2023 23:46:15 -0600 Subject: [PATCH 01/14] Add windowSize input to avoid "token exceeded error" --- .../RedisBackedChatMemory/RedisBackedChatMemory.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 7fe447ad5..a92d9bbb3 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -57,6 +57,14 @@ class RedisBackedChatMemory_Memory implements INode { type: 'string', default: 'chat_history', additionalParams: true + }, + { + label: 'Window Size', + name: 'windowSize', + type: 'number', + description: 'Window of size k to surface the last k back-and-forth to use as memory.', + additionalParams: true, + optional: true } ] } @@ -89,6 +97,7 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom const sessionId = nodeData.inputs?.sessionId as string const sessionTTL = nodeData.inputs?.sessionTTL as number const memoryKey = nodeData.inputs?.memoryKey as string + const windowSize = nodeData.inputs?.windowSize as number const chatId = options?.chatId as string let isSessionIdUsingChatMessageId = false @@ -129,7 +138,7 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom const redisChatMessageHistory = new RedisChatMessageHistory(obj) redisChatMessageHistory.getMessages = async (): Promise => { - const rawStoredMessages = await client.lrange((redisChatMessageHistory as any).sessionId, 0, -1) + const rawStoredMessages = await client.lrange((redisChatMessageHistory as any).sessionId, windowSize ? -windowSize : 0, -1) const orderedMessages = rawStoredMessages.reverse().map((message) => JSON.parse(message)) return orderedMessages.map(mapStoredMessageToChatMessage) } From 8f4dd8b50926842d6efc4b9600f4a52ef7892de2 Mon Sep 17 00:00:00 2001 From: tirongi Date: Wed, 13 Dec 2023 10:47:39 +0100 Subject: [PATCH 02/14] Fix wrong elasti client setup when use custom URL --- .../Elasticsearch/Elasticsearch.ts | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch.ts b/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch.ts index 5f3cf2066..04c90c6b0 100644 --- a/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch.ts +++ b/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch.ts @@ -183,13 +183,26 @@ const prepareConnectionOptions = ( } else if (cloudId) { let username = getCredentialParam('username', credentialData, nodeData) let password = getCredentialParam('password', credentialData, nodeData) - elasticSearchClientOptions = { - cloud: { - id: cloudId - }, - auth: { - username: username, - password: password + if (cloudId.startsWith('http')) { + elasticSearchClientOptions = { + node: cloudId, + auth: { + username: username, + password: password + }, + tls: { + rejectUnauthorized: false + } + } + } else { + elasticSearchClientOptions = { + cloud: { + id: cloudId + }, + auth: { + username: username, + password: password + } } } } From 871dea249cdaa2ce2dcaf1e48577c03de5ff8471 Mon Sep 17 00:00:00 2001 From: tuxBurner Date: Wed, 13 Dec 2023 18:47:25 +0100 Subject: [PATCH 03/14] Added boolean flag which enables ssl connection for redis nodes. This is needed for example connecting to the azure redis sever. --- packages/components/credentials/RedisCacheApi.credential.ts | 5 +++++ packages/components/nodes/cache/RedisCache/RedisCache.ts | 6 +++++- .../nodes/cache/RedisCache/RedisEmbeddingsCache.ts | 6 +++++- .../memory/RedisBackedChatMemory/RedisBackedChatMemory.ts | 6 +++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/components/credentials/RedisCacheApi.credential.ts b/packages/components/credentials/RedisCacheApi.credential.ts index 4d1a2498f..2b4ad6187 100644 --- a/packages/components/credentials/RedisCacheApi.credential.ts +++ b/packages/components/credentials/RedisCacheApi.credential.ts @@ -35,6 +35,11 @@ class RedisCacheApi implements INodeCredential { name: 'redisCachePwd', type: 'password', placeholder: '' + }, + { + label: 'Use SSL', + name: 'redisCacheSslEnabled', + type: 'boolean' } ] } diff --git a/packages/components/nodes/cache/RedisCache/RedisCache.ts b/packages/components/nodes/cache/RedisCache/RedisCache.ts index 8128b6e32..657fd1bc3 100644 --- a/packages/components/nodes/cache/RedisCache/RedisCache.ts +++ b/packages/components/nodes/cache/RedisCache/RedisCache.ts @@ -56,12 +56,16 @@ class RedisCache implements INode { const password = getCredentialParam('redisCachePwd', credentialData, nodeData) const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + const sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData) + + const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}; client = new Redis({ port: portStr ? parseInt(portStr) : 6379, host, username, - password + password, + ...tlsOptions }) } else { client = new Redis(redisUrl) diff --git a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts index f15869d79..5d6c4bfb8 100644 --- a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts +++ b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts @@ -71,12 +71,16 @@ class RedisEmbeddingsCache implements INode { const password = getCredentialParam('redisCachePwd', credentialData, nodeData) const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + const sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData) + + const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}; client = new Redis({ port: portStr ? parseInt(portStr) : 6379, host, username, - password + password, + ...tlsOptions }) } else { client = new Redis(redisUrl) diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 7fe447ad5..08a166318 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -103,12 +103,16 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom const password = getCredentialParam('redisCachePwd', credentialData, nodeData) const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + const sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData) + + const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}; client = new Redis({ port: portStr ? parseInt(portStr) : 6379, host, username, - password + password, + ...tlsOptions }) } else { client = new Redis(redisUrl) From a2404afc41c7dcbbfbf3e3a13a94d5ccb02212f9 Mon Sep 17 00:00:00 2001 From: tuxBurner Date: Thu, 14 Dec 2023 10:43:50 +0100 Subject: [PATCH 04/14] #1385 fixed linting --- packages/components/nodes/cache/RedisCache/RedisCache.ts | 2 +- .../components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts | 2 +- .../nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/cache/RedisCache/RedisCache.ts b/packages/components/nodes/cache/RedisCache/RedisCache.ts index 657fd1bc3..4e61c239e 100644 --- a/packages/components/nodes/cache/RedisCache/RedisCache.ts +++ b/packages/components/nodes/cache/RedisCache/RedisCache.ts @@ -58,7 +58,7 @@ class RedisCache implements INode { const host = getCredentialParam('redisCacheHost', credentialData, nodeData) const sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData) - const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}; + const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {} client = new Redis({ port: portStr ? parseInt(portStr) : 6379, diff --git a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts index 5d6c4bfb8..fe1b4df8a 100644 --- a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts +++ b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts @@ -73,7 +73,7 @@ class RedisEmbeddingsCache implements INode { const host = getCredentialParam('redisCacheHost', credentialData, nodeData) const sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData) - const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}; + const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {} client = new Redis({ port: portStr ? parseInt(portStr) : 6379, diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 08a166318..f772ae565 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -105,7 +105,7 @@ const initalizeRedis = async (nodeData: INodeData, options: ICommonObject): Prom const host = getCredentialParam('redisCacheHost', credentialData, nodeData) const sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData) - const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}; + const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {} client = new Redis({ port: portStr ? parseInt(portStr) : 6379, From e7f13c87d8b76909b31234447802aa7061b09038 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 15 Dec 2023 20:58:08 +0530 Subject: [PATCH 05/14] Addition of Google Gemini Chat Model, Credential and Associated Embeddings --- .../GoogleGenerativeAI.credential.ts | 25 ++++ .../ChatGoogleGenerativeAI.ts | 108 ++++++++++++++++++ .../ChatGoogleGenerativeAI/gemini.png | Bin 0 -> 57143 bytes .../GoogleGenerativeAIEmbedding.ts | 105 +++++++++++++++++ .../GoogleGenerativeAIEmbedding/gemini.png | Bin 0 -> 57143 bytes packages/components/package.json | 1 + 6 files changed, 239 insertions(+) create mode 100644 packages/components/credentials/GoogleGenerativeAI.credential.ts create mode 100644 packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts create mode 100644 packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/gemini.png create mode 100644 packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts create mode 100644 packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/gemini.png diff --git a/packages/components/credentials/GoogleGenerativeAI.credential.ts b/packages/components/credentials/GoogleGenerativeAI.credential.ts new file mode 100644 index 000000000..9a1f3f285 --- /dev/null +++ b/packages/components/credentials/GoogleGenerativeAI.credential.ts @@ -0,0 +1,25 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class GoogleGenerativeAICredential implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Google Generative AI' + this.name = 'googleGenerativeAI' + this.version = 1.0 + this.description = 'Get your API Key here.' + this.inputs = [ + { + label: 'Google AI API Key', + name: 'googleGenerativeAPIKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: GoogleGenerativeAICredential } diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts new file mode 100644 index 000000000..26913424c --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts @@ -0,0 +1,108 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { BaseCache } from 'langchain/schema' +import { ChatGoogleGenerativeAI } from '@langchain/google-genai' + +class GoogleGenerativeAI_ChatModels implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'ChatGoogleGenerativeAI' + this.name = 'chatGoogleGenerativeAI' + this.version = 2.0 + this.type = 'ChatGoogleGenerativeAI' + this.icon = 'gemini.png' + this.category = 'Chat Models' + this.description = 'Wrapper around Google Gemini large language models that use the Chat endpoint' + this.baseClasses = [this.type, ...getBaseClasses(ChatGoogleGenerativeAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['googleGenerativeAI'], + optional: true, + description: 'Google Generative AI credential.' + } + this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, + { + label: 'Model Name', + name: 'modelName', + type: 'options', + options: [ + { + label: 'gemini-pro', + name: 'gemini-pro' + } + ], + default: 'gemini-pro', + optional: true + }, + { + label: 'Temperature', + name: 'temperature', + type: 'number', + step: 0.1, + default: 0.9, + optional: true + }, + { + label: 'Max Output Tokens', + name: 'maxOutputTokens', + type: 'number', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Top Probability', + name: 'topP', + type: 'number', + step: 0.1, + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('googleGenerativeAPIKey', credentialData, nodeData) + + const temperature = nodeData.inputs?.temperature as string + const modelName = nodeData.inputs?.modelName as string + const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string + const topP = nodeData.inputs?.topP as string + const cache = nodeData.inputs?.cache as BaseCache + + const obj = { + apiKey: apiKey, + modelName: modelName, + maxOutputTokens: 2048 + } + + if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) + + const model = new ChatGoogleGenerativeAI(obj) + if (topP) model.topP = parseFloat(topP) + if (cache) model.cache = cache + if (temperature) model.temperature = parseInt(temperature) + return model + } +} + +module.exports = { nodeClass: GoogleGenerativeAI_ChatModels } diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/gemini.png b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/gemini.png new file mode 100644 index 0000000000000000000000000000000000000000..6c0d60f44817cdbf1bd5483d88c1929831a84eed GIT binary patch literal 57143 zcmd4%gw>JzALxVKZjRH!8^iWcYba!`44n1_2ARyh{NY^0UA_CHbARR+W2s|^t zbMAAW_kI3>XFi|Jb+LQJ{;svxx?rWVIXR}O~d*C zfM=5SGBRrKWn>uC++A(#ovZgntLV;|@(o zM#FkJETz_19EVe9%wZ;@D@Q8E;5|59^y}V2T27xF*ZYq$&R%DIfNQVo$-lq({7)ht zCzCxs05h8152d~pWPsaGZ}kK82pz2)sNyb3zXMQ{QHV0(=;#_MPEMqd(!K|OUmseA z*z4HTw}<%;Pc3%1a_B%nvR*WQIC^M6^j}nnE_O-NTR=3v%U{OdEFaPcr{<0^ugU0 z(>$(19wQ2`dGLO_yqEWUHYiFMOo;pWny~n%lP~zwIGq3)shjyUL}HNZZIIiG!C}HE z#q*-#p8@h^!fBYiMvQweJ~DC>S)fMm#^Agq{wYXpwY4?h`~6OdGNX8B=GJs&({ zdaswPNY6f=7BO6XJaLYDw;Sg?N_J5+4|X;8wrGDXsTT*SkEfqT?|Sjc?bDr?EIM=X z-7XU;dQ`PsBe(=}RJ{&#D@QwX?{pTSqu$ea&&!ffj#5i_ot0Kpli!?>BEJ9zPVEj5_GNhPMV+nJ2-yk-}(#UQopsmkdj2QgHL6atN5D>AEx672acXzYH5m zCVCnWqt`)+7b4J+d+OW0Upjq;$*1lhzkZrBwAc0`k}xc0b5N~F+$H>es~1e#jq&Xu zw)x}fqi;YPD~Rzc2_11AktkN%aLflQNjVXqr__U*7Z+d9B)SW#fH<6Cm7q$-+K65V zsM0QWIna+D%4=dsqIOP5X*tHZIaMzG&iijrYDKWnpC_kXWWg$sL8~ zj>A&D9jM}x_q&hDfs9Vf!L5Tk=JyT!zp#&JyeS+397hxj(JdAVq+l~Iah zx<*)V&VGp46k#UtgBj-T77i$c8w-t*0XPVeO~x7iw%5EVnh_FX_Bsg6KDjTK%6Nt|kkxM8 zl#`mXj?C1tQ>S!6x|Z6YXIbgke3^Iob{Y3;n`8c5P?^k+##!uHqJzoVr4kQ~T_O@o z1HKL=6c0fU{tMD(&5R1i>WR6jSy10RH%UX+;2EWOKse!3KJJeGvlahwN3|{D` z=^Zt-+rDqHGPE(^YIABmYUFFCG8dSyS~zGG7|L?ab?*6b7k}kXZJ14(-64o4IOBxp z$IqT(?YeymZ}h?Rd;BQh-l~zl8r8pPW`*K~3p9*awgFMdM#ypme+-))li+HVEV(C4Qz=doW?Y8*9eZyY`de!{5`8^WBx1zT@cP@Ve1y{Y>?QV89lm2o1 zy9uIx5dEkBxc^A~PYgJTdV?~LLV(JN8WCa$%y&N$5y_#=$&lufZYn^PKJAcLwO`%p z*K^Zz(|0qBR(Yo}nw5NCNLHv{*itBNPG_$9EisDZ8}mR$_y+6?9IczmL^WJ@@N0DX zI|Hk?+gYPg>iFtS6=g(w(a}T`@~a80iRrRJgAfXn$)-&I#@0p>3xXk{EsL$!TX};7 zarsoiEFoWbxXk^(bqy8_%EkD6I{LKzerha!dv;7<>{K&UGri0+o<`Jtc@(!&zVi3a z;Wruv?#t<4*VP$|b>a_bj(IyhFRyr8Em;yogcj_a)_*Z?QEc68U6+#l(EQO>9=tgM zufFY}?2fOBr$fqL~*??z&*(CQ935^h3*f&b(2}wertTgyz#m%^@%B1ZQE2!|I*yzd@wNeE9-lf78UPIQ1JQO@9)1i%3zvknpx#p zs<{GJ{confCd{SmQ_f}E>P=_z7kk+JnsjciXm+=KF!`;~^mY6AC~ZmELuqF+=VC9T z!PMb{zz6G6p`E*FV@ZO1*1Sc*4d=&$+l}#X>)i?CarW^(4nhv$r^5zQ-G}8i9uM;s zrWv_IG7|C+WTvLX4OS}}$E8Cb=sBqyYl1`^eV5ML*oSL{bvI2KgI;=-23U_&RcI$v z*XnSaJXkAVXE}bB{@hg+U%jK-W6Eyi?`sU}?7U(*oLCHAW>|Ogu>3WA=a796t30SI zm!0RNvL3J_bnPl&7~h)adfTbp)M9D)uQi>jJ}c{Zc!hkvsO5aQj<(pxXqmFRxpBJE zWsB><_)k~A%k9ao|Bt^$8`K?bzV!a4Ye9z(+L)P`;|D2sV^`vMOL(*il?lg~%=m&r zw}Q07+W~fm<`)B>6)qDwL>0wFckMPBe_Qs9l#X<%TIGU&g6eLk$5L49TL)Xd=Ej1& zu08kmQ*DK96LYV`{BLq*I>L6BcB#3*CKaw%cH;1^kyFfN(8>X~pRrT-c3@>?AozZE z0wqJ$2)oz)?)B+GgyZbuay_eYpReU*LSpu}>=~CoYkpo`d)K>S{HMex>1%VHx*s=g zg0ASVgO3j2N5ba}H`0d#;ZZ(x*mON$BlyjS-5l0S*5|5Ic?%$qK<{?;uZKS_)Mvy@ z-6qfde!lav(J4%iW)l`7C?O)P)ZdYf<0sV zQ@!dUzlouWznVDYDO}rD*7^*s0RORX#A~20n;LMm{w&E+VwG%%hx$_HWG2+4Wv{9`=HH!M)|!jx=#0(KGvsEd*bKoK{JSZlraHpey)6(7A+Qq}p)iZm!7>uaEc6+Dq0RWJ_ zL>83y8ZY38`4{Z9^gQ*Hl|(IFow>}dTrI4*z|L;Sc>o}=DB{)G+S8l??Cj*?Aqo~} z`cDs0#5=N@n~C8+T|6Ddne>#^7-U@Ctr-Nlc({0&ByboQ7(nh;HliA`^8Y;?@t-)8 zou{XpC^xr{j}Mm*KbNb!EjO=-hzK_iA2%N#C!zT6&wRmoYwee=`%}gkD6%dtO>8lr-km@Ol`^BH7?LR+QDbE5a+#E#j{~hknxym# zIN)D68g!r+UpS2C)6oA@3*k_MYLdT2w5gG?;Rfj7lEMRYJan!9djO(w5jpVxV~sFz z4(QbC;=;muSn<0F1a)$VeRCL;{ICx3;2<<5d<7F3k@{>*)?R{+1Lm@?U}DE51mCpv z_L>Z{;ijQWd;_9`=*_z=j5%?0a&naZt*ovZ)kf+%P5^bCLV$Vff_5=oJ!A}fH1zbU zhK7c1Z7K6jO;FngDU=yo9(V1JVjNQ6V~AA6cROeU4Qg-r$k<@FeBSj2t_A3ciTEg> z@;S2d-cD_j}vT;l*uN3#er)g`t{$xSMj^kxnj*P=g~nzghb&!J6Z2BgnFRfRcm$kA~x318>pFO%#V9?e5|aXpfGoR?Ts=c zaG0&JYolKP*|oXSwZBK_Z0PC|`xw$6hDm?}?lqItnitwY@JOhSpc*}Y3@9ch2F!~h zpeKU|uw?5Bw9O&f+L}$Pwez9C)AgS0_NL>*`aUk$E!s6_O0^|oL-YHG4*LoQu;Z0B zV4iE=ZCScza0zEzU<;=jw0Zf#w6?DVyUhPqFIP}dkZVle3ZZwn!uk z-!wrn^$=vC#_MoY+oAxch9sy%JIJK7tm-*9%c@6cj%qa zEJP^y4G)sfnQO6*LmbH=Q3W#a%KvD6CSo@dlP*Io7Gw-?Q1GKsz6=8#UN4Ag+!$Ao zd+8NVTZjB@MkvqyQgwyyD3TdbyGzZseo?GhPXteJ0+K#i10-$W zU<7U5eN(ZPatH=AGtnAP{X0VL?1-fv$2AN6H!U4dJ-oj6uF8Z7&+=yn2Rska>Z=;| z{PsW}k}m@@#b3?MVqbZUM2%Bd1pEKC>3^(2vPP$7pt>V}6`>IMtApD8%B_I%#hX9s z3E*KRY6&!5?v(ZIAkMYE8bsILnuqFZM;8>}jBDRRnf1IQHn@H84zbUu{gWR5#cuRc z6MfhnQYr7Q-rIX7a7aOX|J|H@?QNA%w*QEdq37}bYV8=9=lIjObR4e{K{wuhg~r8% z9t1i-L=a-jS*Zq1jaQ#-g8CaECK1Qe*+%H%90_Qn-=S5v-qg~c1~>zuQ|UjO!@2_@ zXwX7}0T+^-iIplg-AN$p$gkI!_su;kzCzA5t~pDjX@)z5;FHxq_!xKrIAJJUwGE`5@%4i#h6(h@R=Hlt>Z7Q(smYB4cagM!CbS;M4PiI% zcVZf+$HxwX122DyxE&-E=hpY#`5A1idd}v&NY(8|h{+DY^vVx~9Bzr|-W$hCuIt@j z5)%^vpj+%DO1QYN6RDri-GL);MrQ(~pl6+dki{B;?`x#bU*S%27VL_-ZtTz@o zRFua7c~OA81Ylmr*=udGb||7BIYoJ_&I*t_Pcq-zM`UWpR?Lwk|8UT-gbrn`!{5g* z8czlfL;;-FT5&LaGk^C1xp@tn*G1aRP~Ngd6Hsu7eXC4@Q_%y>A7KEP)HJ*Iap$l# zOqz69#O}}tVLhx6N@nG#)y?spqao~-;K_`)pI>MW#UD36ypO-fU&%`g*P{g9#P6DH zh(<-AgQnuWUgL5gp;4n*Yv;8gYD`pj22)QW(GOmzy`{9pOQdN) z0=iQ|YzBIRyK@@~!C(7qlq9JUhO~Zbye_JM`>5c|R>4yxR@M>l>{rC>B~4H{gcv$T zMYUV~$n2m4e|@k(CQ{$!LA!vNv$I!Z6aitz&*%hU_S~s;c~@0K)*=6wQ%%q=p~s(B zOmK>D9-QArK&9#-trcGA!AZ~Yz9A$@(P6MzG2=ta6)NHsUZBXcryDw<9KCxun6I|S zqz*d|oR#?9Z>Qimzg)qp+R|a=e76|K;Vo=%Qc6+&?+#; zX3K6DR?s8dNeUJ&>er&?jYbip&M_;we4eiD!HUYo#|CS(KhPu7vq7w6ty9_kxdr;+ zNT;E#O^BABo)SbBqL40fRo*b52rlyoy>0Qod3OD9j5)WUgQA*QhCn1FBI?_G9yip- z1qJ>?E6zm)m2dvc(jkJ9*;m!-+ZaQ((K0mi5pt?eBL=s&oi0Y(TGtmi+*l*QnUeka z)VLmOQ6Z_!^2q3W`S@3PSO83&SAtmRqdH2>*Mxaz0{S3<8{J{|`R`58KSw5t4Jw0d#r`r89BJGxi6mHh*Z1rn+*_y}i9zf^L)z*U{(#0C4!yjJQ&p zIQRz8{!LvKW#*4xL50p3;z(BiT$x`eC4<=%tR~#iN`yhAac!4q(os|mWqRb_CWQ<_ zv0K1gp0Vt%4}5;Y8u3 z(5)f>Wf__lUaiWR#LUjl@a5HFz;sw;o0t#BsMEgQe_--C(sIOlfdzbAP)R*n7v?~jTjI>#1qZ< zKuIu;{k)tX#{0|Z#WSLl=pQ<3dLtf8iGC-mxpXl7qzzmP$}ge3BU8E~P0%WY17WM} z*SWmJ3EdSv1W>9p6M5bYsYiN2A~p*Wtdd7ADPjHmbW)`Hq|it$xD-a-Qw`A|JYaVv z_@C|fhWOxgt}^{q5ibthnpmWXEG@6jwdH`yq1T@s)2g)b5|Uvt5uuR?2VlYN_RwPf zy}0g7*CvOD8zW&9R_SQ=k`ub$O3rO!S5-@(V-^GQ+q{XIv!h1?B@az`wVq05o=8T| zJHPQ_$Tmx$M7a0twn!yDJa^#Xgwj^zO9o4v4Y(fK+h1y<&WM(+^xo|p+LO%qW9+Gg z0qG11zd0c6qpiSwV5#BM?6l#L26PQarNJ>I|LE6?3=Fz@HbTuv!lX~#ngab1aeF6{ z&RCj8ns0RO(azBjM+WGHdo*6SS0?tKu#@pCCeaMQ)STql*Q74X-Z`NnNLVY4cP zwRLVYdKZg1Pr1gyAEG`Z9+2yqK%ZbphJf42*PnepATi>DSZh`b68@OsZsnm^x;P;4 z^}tvwhCPW-FAL$?K!xpnzf8B*xUx(}!Q`i!@AU{ITR}qL%0lsco(CtCTtVc=COrz{ zH*Hjc{5x4#!MK=?D-cY#XDA-f@sqJoITph;cLLZpk5I$57TaoLgv8aS*}HkN6vSTK zr9owG%K@NX!om7a63~h$O;@_B7hm72pCf#bRU0HHcGhcKr#PUh^n_sH^Ren5b#!3j zShqnLPM*spXq8}sA3(}+H9qab28E|0Xu8%?AGUz>au&Ru1vm^LW&^o6SQ2QoC-Y>R z;~}g(Xb9h#LXZ5fOIO%hRokiSVKeq>zh5{FeaCZl+*Ntxg2N`Yg1L>*^>ve{aW?7Q zW&lAXUqJ61DhfgM@{P$%PuB`A`zj8&GxxRoA(DfKUF}q(v-qYIe~Ln70*;(fr=PaU zmTyK`A_aV3Nl(zrIp<9UN?{oou0Z|8ec%y4?wzS~_ws>x4YIkeeMrd#)7N|Aip&oU zq0PV>g@Q0b7?Zp+gZe53-KFG#bPD24L=4t2-i{={vaR}5_lxghD=Wp9q)-pKje;sY zeV7i?vDfjm6tMV$jwMQS>?Vz(078oZX~f)EhbVZxfnvuuD5V<_gQetQ%y1{k0O6lp zFv`}7U$t!rq^mvsJ@3RI9Tgm4ez`wa4lI!Skb?OoLU83*i>=m;5FnOed zCGVyC%3mMh{kMNgm~%2xgYJr zRGI&5(H(C_u00cM%vE_yi~H^4yJP+DcK9a6B^K5p*~t4>_MC3%zP}!1#=D)Bx<3j! za5(}jul27xn+D%=hUcNDF?zk}hS>fpvJhHCD6ml5sp17X z?%)N9O`q?D4tG3XQUM8bEFeKR)%)XMSet8R?b&qZa@GLg7Ml+Tn?G|`JvGjv{2=|4 z(y++yO)&{ahR2<3*K*|`z7TEC!ktWdeK2ExPPc}2h@%5ydydPzr3?3l43MZfhnf67 zsK3~0XYw;HV8V8v!qWb;H#Z?|vJ2L{e5iDrA+N9vc0;HX*o~P+E4dqY=%jWx7iq2- z<}7Ty63}qDUr>}FRNBZAq0&ZM)34?wCV{JDQ7c!{hc%Y@Xf01+Eqs$~H=*eVH z0FzAiQI2wQ+;Q9j?*!$=iL=t;6#X83t@*zxrlHrhAoR4(>u;L-sUwO+b9=%IB7Yp(S>WpkM{jeWzAPp? zYT99niYITbP#XaXLK8@yR`)5cY&1eorq?fT&PW0lmaBh-<`~ZZnz$FI>NstEmPKNQ zZ>s>-wTi6)`$67wvSY!m$B{Q9Ri; zoP4lb#@fo`5q4Q3*Qs zrQvsx1w^=b0Kr(Cw?X7!S%|0vI-Fv-$Qn-$AhELYONx{^U*cdN5QZg0GNp*_j}*=* za>9yc#rbA3hJd3}dIbA&bB#+nGZqZ_`#)MjFmYU+)@E_q@FWg5?P`Q7A*^@J(?ToA zQC&|u2L7S=f#+N0GhfB0UzHVr#L4oO?WydI2XX|*$C&AoKaKZmx~Ba8sk==A@Y7&l zEOJW33meWk+&51?>b`|#f zpjl-ki;-C*&|)H<`}5R3#&c=blzmH22aaMnaRR~}eP0Zpt?yIzzd7okJv;NP+7)X<1xK^~vmwa;>$KY6B|ZzI zoF;|m_ylS_ULglwo#g*;=?D4=M5$2BS&NC?j2y*bmz!oZ%xhKw*{`|k=ro-DbRc#*4#t|`$JXq{WMJ~N#c{zL_lBoG38nZ^7o4n@Zpp0N^+{RId|LG%z z`IQ`TLx>gTZfN0aeX{mpq}mVXYE~gUh*m+8Q5rfrWyyax%&a}zkU!U61v2G^<@~!i zLghW*63~3BluNHSYOA_`IGIJJ^qX0}&8U3KX+dF)k-*{tz~DpNYm&13dJ^?y2A44Y zolz18yuc|lhrcTlqa01@B-yM%xf^g3+2ogyb zE`uhfQ1bAdhFfNv-%eq$G5i)3U7@)G*e$gGf`mBFe?un{t538Jp|(IwMEyg(y2b{B zwsUFiVQW9bG^oEhSG@apCI#{UrvVF-F|me3^LuPt@g==`x8+X$$?xYvh;#r4bKfgt z8-wwn4I)+Immx-dQk$PvqTW(8o4b6p^guP-2ceC+2*5&!(Lqgyv@T^xyKB?8=2sha zpD%O;gMsfT?;CptUblKB0xS;RPo$!wxUUalhF~$pC)smJV20Y-vV`s#tnRBf_eVrb z;<`z9hh^AvJO>~{Un;&C2V9tDBA1Yc~?TkCUHv3FXPPHdOfKyqJ<`hn> z7S#m3LR#Fb)8fxRleyMh0^j7HT;0YA8H|+?$qTQ+`-7H$Kt`r>O|k!5_O;i>cCr(co$tP9}<6KG@g%j(Rumag4J=p#9df|EZwm!JwEEu%`U|+K;fmZ zXmhv;JiDkVXd)Z2hYLtKUJ>XKsuweR+T{5fMeIW>rQ(XG7mBO=A4-lrZ;t6y zddyo;&{^Nd^Yvu5NokBVLCL=&)+c`xq}{U$^lae$ZT|1i1Z~Ll<9aXMWc|PrK4HPe zA2wS6!Ca8eIXZ+jOBVjFSMqS>J#ol;JWe2=70(dV7F14AI>z-Y)^MV_F?V{cE61H3 z*7*4VoQgCDseXqG2UJ|?cr<-S*b)lD|L*o;dSCl_hC6?3XR1L-%Jqu36Si<6DiNOBCzFYlm zrJe~MYzf*0GQ2S%$k1~&YgthmBg}h&PubJJWF5kd^gFm69;;T#;9t|W4xK+uR*tBe zs;cIT=O&_v;-gU|V$&?#c1M|HLw=Bv?@~mF`v;=t?pUA-A=VS2&%V|a zoR0%d2N4#-bnr-fe-&sO62)_IFeS0RbSqcDPxBEH`7#F=1RxDbp^&79b0ikT2YI*^ z?}UcF(V#lEWDK!Q-x~g15x9i~y4fmk5;-3J?6P}mrmQo+V69cc0WU@P=NF~^1qV^+ z!`xm;(mX?vt4}vGeM(Uj`%V@Puabbnqam)_@%H>(i;Y#aVE`PP)3Zx$=Ncc076Gf& z5iYS#wv4IYKO~o7{Fqul6++JP_Vh<_^W+y*^sl>LXyAe2=NyPTHxnX1&=}_xD8cwl zYuC{Q>By~1R~ORJ!r_ztQn?C!;$MOujvIr7@&jOH9Nb*+P40zNTW)B52eh9JE;r|jDv;Zq&_8O3c%-ju& zdN-`+ootcdko?B)(2n1|lyk4H;ehWWY2J4>5-_qu5i^YEmPK4E`ow)j!~-8`{F`W% zvVSqjt914Jd%FRWxRkHkR2x{bHFKw1tsCSP_71_92fgo`K?l|FE&h?FzbsaXWBYuW zt$;1w03h_?5QMIK{hq0%5_}EFGHlQ=9Y#uS_=Me%-S`O$16cNaz;0)%t7m^E_%=wi zj9~a)G{F3vbOLW%^b6+lQA2ZTdUeFsG*lMj;?f|}@+l2EK-SV(3Q37jjK9;QsbWap zeSnO7hWpM$nsEFX*=lH|X}TFn=f>FKoG>k^_SA>XA@2rrORX=2a7WAY8Zqco6~?k! z2bMM@zg<#!vx3hIPHVxce8Or0;jHrf-aFno?uGWAW>oFkvVN3IiJB;?wY(!vc{Ybk z1u7UVnVq=vcLK`G-p{*6_x@)*-tAB0%;QC~w;Gy7_~5j>0#5TBfz@H^;}c7U(Of`&Zd*V4KE> zzdn040+IGmbGnZ&cBvU;vPH84(8xlQP=-M-?D$0c1V$M?m7idNex@>7$%$UHqnD91 z;$L*0zYTpR>_lU|%QN1y%$F+jTSqrA8r21dT=;d=>abZ_yf$159E z(Lu6GCr;2^aU(Z~uh4v@%J=@cz?#0d`Y;cq1MFcKFm%8#>y|a9smw}}xABJh-ya0I zy7k_hVM1u4<>ey)nj(Dp=QL-@_=)OjLr$U&))8olda^HL;twL!c;ZyP_7Cvi+|-ng z&5nkEigtX>2UGX6IFbZ~e`9mSG&|W7VYaPmhSUgoCc487&F5!2WxQQTJjB?&1Q?sS zv82O|@p#T}@3LLX@M^%CFohuDK5XfqY^vHO3=Og4KwYG2vJ2Q^r=n*ieNNJpkN4iI zY4oJ>m3)Nbd??Rn-XZ!PQ?>;AF2E{}f{~B+pz-%oN7yQC!tEd7t0<2BSbhvjw}$OP z>TB{3M1@8+Y{GP(GHWZl%7WqebU1BXzE|1CU`vNjW0n#UVQ;&OF}<8c-}CeDeMtwa zO$Y3^vt6t@wY{QZ5yVMOw#O|Vi~6PHsR;`MrFT`#M{|@6;4eg1%5TRb)VXdFAuC;* zapIqx-~U+ylJeG|F|*beL*H3dy-`4YzgI*rL+F;HBUFrg6JL#V&^~J(T%&OWs4_%P z90|+e!kZ|9lcHWyejhDA*NNI%$gBPaWY#nO&dbF0T;niVTB6m77aY#o-~^IR31Piy zClw7n_e|DiGTi;{oE{UdGRKZh zS>0qcCSr*)fSF5`+mQbX6xb{I0Dman2bdC{ z3ss+izEbz^N_aP|{}u`$yg5>a=t7zOe{NnN!SrIu?~el&bc+>~L?=FQxjbR-^A`)B z{4|r$Dx3>YfG;t*73^oN5@_x;`y+sk@jIo{S{aHwDYNMLT;uD6jT2I0-&p1tvn37> z_(2;PX4{8ukI?9ssWV`nBD3dMg{hh!m6+*>1T3Cwy38qC7v}rcvTyBB+W(xtuHpY9 zT=jmIt8q#Jv=I2?bz6P!qkR0oik$q)!p{zNUo(a#-45l95MJ|hWIzumLBDA4?8t5Z zYZ|cV`$vh9-Rh^yjo@s2)%H%Dm0J&*xg6T=c4bUa5&aiuwaVMRvGI>-q#Rpcx}^DC zg>A^(mHxPkIf#1yi22@k^yjEhJ5>hz*|sM1)r58LS{ukWbx1g*{46;?AU!z|7n-U_ z@v@6^JCsB9?&a+)#sWKCHGeAGIZXZ59k!|1cCWWTyeqM~p`0Z{-W2c0mc=Q$@;V(6 zvkX8cbR`x-hc*zGa5>wF2sF>+9EKIOBXp_Vq~JShhz$u8b#ZL=t?+v{JL}K?Xz)kd zCbqixQNeoH`Ev~%W$RpMdDlh}(?5&$qqDbMeAC9@jnILzhD5ByU@BOQU^CbJoK&=^ zXEqm5^&7)^tz|!k5J?~$&d%~22mFoL>yZWtf;2k4wO*r!a$A{*k2O?T^#mGvdm|#y z4(GaZ2lh`lp7?seJz+l*_R;7*oz_#}xiyW1&3Mw8zg1UyHEViK86GF>$0+OeRhs^T zJmzN)(w~pMmk@Vc@?cEzP}S$Aw! z_+!x7e4w$1XTa5FN^?*o{t<}E-_*Pg>rvG!p!(+hgUkX8bN4Q(9W#3%~d;!X+Bp z+PyskGtC7~@rw=u_~vf2Ek-5^QK=3au2BNwpwtZ~va?tVD+#S89_{UUE|2V1JzY*@ z??-K{+o3gukV}aW&wfJ{mGt#!SKCJa9vlvb3jk{OgM?n_B1R>cf;^ z{_@@XPoD1aUes&WHOTA`v>O&{S~5U?s5v@VkK z1z^o7z23kad$%8L;Ir6;1uEI(r(5>Em-y?|mGnlpi#PmI&e_-ik=Kbr0+0-6TT_6i znRBO-AS`@d^x9yc4lUE<{wylL{3)ng=-An8jLXd;*OXu=G|cNb1*1F`^yuEl0f!z6 z5{^S1~{4zl4x!J*^A1KUGMUl5i^)6v2_ls zM&xD)klD+qC1<&gJ&JuWDj18=h_6SaXGtWB?je3g=jqPEulEc9Z5DS1ItezM?cTjI zr9cOqo$><+#6}8aYItY@Uc=OlYw;H+IxztO9B?%oeEYC4cIq?F+r1p+5Q6fxlNN$8 zIeQdUy`pJ)JzY!%3i{UcU0mEqpja#?qI0=RK&PQEwG90%TG$#zYb5|df@-w`_QZB_xGe{|M3A6K(Ex5Vij$uR)p)2!r|Ij^TQei;YO9$ zg@4e4m_!9{YGKhGE?fIy`qw?&KRp~?5;Nz?#0Mn8W5Tdu1GEc*Jm2(po9uc)Z}TyB z6=8)nnGSK|B8th(WVzB&)5bG_nHeDZKs%)nN&p*u(^b+{@gQ%7#XXb;|Eu$HfQa_1kk*fb|QIv}6}CO>h$8 zvl{iR3H-eY=84VLG&O{0!yEFq@-iWA?DxbU@Ij7W!)0+bJI99QLdDC-?3Um2= z_d$NQJG2sftqDFpdidpGvlxi{Q}(5_1#252WJuhTm0+fx2&XbjhMBUzJWOMPo|#f? zag#75ou=aZ^sPTA&TCzNB`6B7(v6$%p{^3t5$h(6AwoDk`r=0@I*E*cW;^ zkgX_n)tH+3yBXWWuW#Q@`vm+7y;9Rzcy;4c-cNfw`9*iqr~XE~#D68zdu8~mA;`?& zCu1B;K&_AiE{3$Dw5YW%XKqHG&1lq*v$g+XRdt^B>m^mWH+sVy!{Wl24F=vAwreK= z+K(XmL1oN5kT4ttCpQT5=S4vPmfc{OAHe9`HjVR^!wtq#xK_!+S}`S~PK^J{CGGRu zyznos9iRz(lQOiRP3S9@MeE~1*(G9EBL2=vX77C!zr=4Ed(NSWxW~#`m9!#@=QfIO zzB7KlqNbYmqPle%dN;yBgRten!p>Xb^sXFP2Q0o#akc5=>;`0d$T;dtdHo@d&h2gY zWSrgUjcE+JBMAS)^|{rZJ0Y+E@SZR}=wCM_-R+Z0w2-nd?JX|EZ5Qy8;;m$`yAf`F5xaz%KY1z5p(a*^~0AN(-_UtKUOYkZQHX*9wxqB z3A24U>T>H8cZ5mXKQ~wjq8H8FRT`r>H?q3tT|Yhu^|)$gQRfe_9W{#+SwHwJKpc~7&s2v0vBaUp zo%5-UMhF^=9Xg|#lD~ame6vh<$A@dKvU_L5$Bhdhpo>dd$HoVm49?_v7?S3-EZvP;j(B$?>8kN-0$Cw=)d(nSLBxoV0CyrCQz&_0CkBSzRgL5L2jJv{Ub zL9&f!`ZZ~;nQh1x2`HmKN1U}!_}k`1j*x%(oR1_3aiQNA0uXMP?8<)%#gIj(E+}s< zO_l3~DC{x1NteN6c5j<(JO;UaLex?}WXRb%wN2T|y*gG}ECYLACTf;RCB~=8K^;FH zx`^TkqNAiRP;4HNX6+l%HDOI*r0H84?=>?!nwnOf(T@3V`=;W12k zY>Oo+v{YOq1PnVZreuuCXERvp0Pe~Tjl{Ms@m2b8&ER6-1s{Sg8YgFG^ZGVrBLyQc z0^oQfC3DITLtkA?ZE?I=)4LM7VX}E(8fsQ3v3ef8T@>B? zo)L7uy!61coR(EcPb6LUZ~dLOd>TjI`xwc%!Fz*jsw=0prL2<+k7eUwCezXhATr5@ zV}f1o{B2!=jG69;L3~ql{@#s6QZiWRA^+*dWmyDbxzs61`}<~NaLxPeAD@1qRfPROUk~!~tv~4eus={P}@buDc zS03QaIP0I}3DW&9GezI!d^uzr!e~Ln(v;D^oU>Fk^=UJg1a~he+-d1=Q3l7&#kgTH zOnJo#PI0W<*^>;hRv#{`jMox%YEqZ;*i|Cx*sV+Ue4U#ZF*l7$8E0?RI|ZF3!3FhS zTr8HAPo3mu30q!xzbbke@Yzcfag+1V^gl7`5$ELRraL(xRsHx+Vb{x6RCF~fXkGlv z|L&!BEz8||B6%O1p~oF*bh%eLs12yNwS{)r%%Cy%j5Z~6!Hrps7jaKpALRv&T={7 zBy@73tz1i|j{_fPl9C9@f}bw9^^_nfGmPuYD6bTRFJrwSJ8`pN_3#e(g-%n{Kvncc3xCn^N=Aha8^15Rq!>EGiT#c*St3NeW3ckJH+TRFbb0!J$nF!M06v z{@&<05+nSC3Vvs6G9vI>ozBsNPG32SKQpHl_hL9Lev1IT#L`zw5MXAmg&G^~@9b%g z-n^dnMvBDCj>1Cp+?KMK>pf|Pnp}`*>>|xNo!#&Z{7)+i;t~DaK}?~1?&+~Fsm!*1eRB@uMvXde3$~A2_8p)& z63}2-ZeyENBxqp4wT7CD9~k`)Yvx8MuaJU4Y5{^t_o45{RCcg=R5M-%bBB5qB+`p8 zdXJD(_ewJ^ZKPB0Bqb`B)e*OYqb0cXZ;9hzpU!Bi?yr>CD?BdXr&MJdZcF15!%0j@ z_gzEyv%Oey8+}#$?G%-?a_>JHVfmL1Rr}$0%8Imh*&QeR*rZ|;X1{Ar_=%Ta@mja6 zs`5L$v|0j9zLX4itEP3~xq~tKwD0`*Cuc&$WutRPh79p8c%2Z#q^Wrmzcf@{UheJp zUyk%Wz7Y?)rqTFRa9wu38F(nM z|CmSd40w_b(kYy<+Z}Qi zSq7k94y#mCO4ENC>jhK3D5qMf5pZh$(!UI0{>0dPsMq9V1 zDsuZRP{4D8`#5|-@<(V50RyLb=JfhxefqawUQK`f%dgV`mP#KT;(MX^QrqShp8DWj zozqji_y^!2=s3_Zc@)7j9iYI0q6rRiOib|q)d7#>!OP*obUi?faC!!q+RFQi7Hb^kfk}V`5Ft{LV(ExSj^AKLS+0X;=2<>T zx?B>qx?=GG)6z#}O@<^fp38tLGtMM`=wlXXSD3ImW;DEh8vtIH*13u%xI z%8?2Ro^4yVX`U|~aL96oU7#0rLogkWPAhZCffOQXU_0c(Mu}eWph@|RnrBdt7z;H% zGY1N0Wk_+_?G9GDapc1#*WB%Zwq2yOfI8k*3l#9&sGglWm2f#c`}Zq=1n=3L9-mCR z`}=sFbAS5#uP>(m{l_1sqpb&cLG(_)$F&HSjsZ7)EOn-9WU#N~sxmuRjy^c-ki%0s zV&XM1-G--%1|2<)Xei?X;PG(APR|a_t%}hN^P2yYuBuBJrW=a^LPpDn0&WeIqkd#; z!R=!LJk*oQBJdb(R9a<>IY>X*q1Dw3+*T$nBMOjC6`v10p`Udh+|ed$X#2P{Y{fo` z5P27Yk6Jodw2;K~$i#n@Qy|;f6H$ZDGd-BqGAIA^uPY|r|J7gkEgrXMfeck&{<#K zmS>?e(6?3)3FpvV1oGst^$Z28)np$!K>nFZJmN)Rb1qj4JTU@5eN)e}j|SYw zwKb<1Dbj8LfVS`BrBf_E;0UO~YFMCv=f?GO@)~p#9dytT+{lly>%foCj;25T@ML=Y z^7XXNUy{ez`V&)WYuH(Ot*o#)Y(6eazdO@pGhTQ+AdTGjM3 z_yi?NY+h&NIJpv!Fsre#-n_V=W&1>3q}eQ2iACVaWXUXvk-p`F5=|1A2vqW05 zbfSVtHXZ{RD~P%%+vO789I1du{H(FjM!h1WP}gOO)E?2ZEG8iByiLc#>^7l0^;IAM zP#^2WHbqJ2Y2|4kqqYw^iZ?$+W=CvbG^Iz7nXXHaCO&YwlQN)3(oxZ#Hf7LR_aT?1 zRA-rVm329;t2G_<(PqoT(B*7-9^uJ)tzyP|RSRrdMTtU!j=pe;5jvj*F<|5L9v16e zOn1VHTzd-?@Lc;YT2v5TgqwOIoZy`te#YR{;pz0>-{ECYe8S+_{>ik7H^%q(c6qDo zLtSg9youofaC38nV_#rzq!AB=xmaZ?hqt10r1Oc93~zLnjkDO4zV*N(8?p7aeFh%B zJS&@thW5#hzn3&yMw%c;28ZQNbO2AMBQk(*8NKsZcdmZ{bVE=(4>?x@Ji`#yaWs*@ zGg4U%{%~SoIiC^T!^kHVALzZGCRAie#(DBAt&Z2(0tGzRvUjrM$T#cA=NN{d^Xu!= z>3{yef194aJ)G9=VgUhGwQ+}%_0zrS7%zhoK8`W+A^6Ze5+M9b7P25(bNmtuN_axn zraw8)1>ivm?|c4=k0~W`D352_QlpHZYqB_3LZ^aaI$P28ddNft%o9%YYDPS2m_+7! zLl@S}dM_UFP}?R)^o|c6+Ay_=?TZGAejJ$Htb{X1WTPz08@Y>hG0h%D$jp(HjZH(6 z2<)v?Rb=ZtoOhG!Dk0u|KO>F=>scnS@eVQ&bvp9&1W4w@O!4|V!NQmm40Efsko z@8d&r5zN0RCrayllPplcbM1PIw;=!8s75ehsL)28*kSGU>DKgk2Onzt{?+tffBtbg z-TVrppL+lp+?1PpxYv*HhhMNr^AiYtx(7ckDT#l&iRl+KvH>6e33$RG(h)HZBB(=` z;Xl*a0kVLc1QZ?_g5Po1*W@6JG8rhBEWxF7OYo#JL@eEO;$fKt3^`0PxLuA*cSmvL z=z@~W6FLXwOm{s{FkR#apKQ@&a<(#pRs}ZQndi}8l#Vmo)U=l_PLH;^wCN}|#%U+p zYO#Fiv&aK3B5e!&4bf9QZk|jR^gOf8c?X7h840??T!idl81-GFbJK$-^o{dOgc52S zc=?gJdlDvqHXL0D;}ahtdx4;m*U|z7JlC>YxRqx>k6pZ<(oEAHwvGPFuiV)4 z(e(8BtLg6cSK5E=L@$5BA8cUc)1Ga}aSC|c$TiT!(rMq=^dAHmxd_DV;Or}y-v9&3 z@V4%B-KiPeluur1JwQedpAM|av1_of!9(35&wBF6DyRqP7VdgEud zx%`^baSWs>;90cX%}5vUT+@Ey&e8ni_MHsxPS&RHetS3l=MT@P$4_?f*#f+igZu9k zAak+?%J^BUuZ7wv&~Z9O8655Kw>ZE8$$n!O@LV2v)Du3~ezvcH37`QqqP_Eo=Oh6c zc~~x9^s9lo@yJ0hamc+7TfGTj$fEu=l9-?!T?Z4P<0uxn4$wjMjxuqmPU;81RUN8j z5G~8=rKBD7S$>X>pH-c4u1nO>M6nfo*N4llx%!P@6ble2VJ>^ftl0ImKmpJ7>=*sU z=BA!_9cTLO^$9lh{BC;jW_NnGdw>^1A7YaM?0E&y;kR|x)-biUPVnJ>PDcQd5Ffzf zMtVCK1CM^qlYSZSh@6gx}JhYj3T*pxt`_5_ba_Fv?Z zm+J1I)AHiE+2Y70_$-Y&vfU1h^{-_B04RM)L_t*9&Wn6~(FjO;`=ZSf(d?IuB-zaF zI<<=0DddnK`eaVQ)QqYnt@F^AbgrMV2=j8}N{=Cr8Qk-YqaUt*BLD>tK`7!Oug=%k z0tGzRq;L2@_4t_I!|}3GPVW4S=Q#iM-Q(%cSR%c1sJjrOm1BG{^dX+-+<`Oo5`&F3 zfDeHO!N9|t5OEyFZOai4Q*@He5sQY7)^zcUeelqEkCbF~2g}-6ABEV-+bP-syHrJ} zH=HtaSzptw>*uMg>^PD?L_5qw?%)l_waCNr2wivhILGbmG7sBC{UT)ku*;%TXIj80 zVo)_u3VYj{mKak(Of3Rq)}!mHpB$kL&Zr~0U;+D5|F)^X#5UyF)=?yt)m$8q$aLx( zP0fnRb<5(4h6vT$6Bk6n9&K(f@wp!^U|m||=SR4XG1W%dTz<_32n3-R{bZXi6;hek z!U6?6SKJNpJ;v;O-lqyWs<&_7PJey2KmE@?znI>=d+XhN;T}&gw|0z|>rV0cf>X`2 z(P`q{9RA5U)4_Z7njKcw;sW4lo-Lfv-03$bghp^|L2$`8cQ_Y0Vma_p9-MT{20il+ z-9kTrA&HwFimEOgcvjn5Txn9+d1YO!gXwsT_K?)^vplTwBH*##C0`C>L>BcE6l4p? zqjnKa+tPQYXLe-dOy^5Q(&yQp8Z*OaQI3DHU9t}C9Gka7*WvOL

1U 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 literal 0 HcmV?d00001 diff --git a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts new file mode 100644 index 000000000..7682b2802 --- /dev/null +++ b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts @@ -0,0 +1,105 @@ +import { GoogleVertexAIEmbeddings } from 'langchain/embeddings/googlevertexai' +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { GoogleGenerativeAIEmbeddings, GoogleGenerativeAIEmbeddingsParams } from '@langchain/google-genai' +import { TaskType } from '@google/generative-ai' + +class GoogleGenerativeAIEmbedding_Embeddings implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'GoogleGenerativeAI Embeddings' + this.name = 'googleGenerativeAiEmbeddings' + this.version = 1.0 + this.type = 'GoogleGenerativeAiEmbeddings' + this.icon = 'gemini.png' + this.category = 'Embeddings' + this.description = 'Google Generative API to generate embeddings for a given text' + this.baseClasses = [this.type, ...getBaseClasses(GoogleVertexAIEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['googleGenerativeAI'], + optional: true, + description: 'Google Generative AI credential.' + } + this.inputs = [ + { + label: 'Model Name', + name: 'modelName', + type: 'options', + options: [ + { + label: 'embedding-001', + name: 'embedding-001' + } + ], + default: 'embedding-001' + }, + { + label: 'Task Type', + name: 'tasktype', + type: 'options', + description: 'Type of task for which the embedding will be used', + options: [ + { label: 'TASK_TYPE_UNSPECIFIED', name: 'TASK_TYPE_UNSPECIFIED' }, + { label: 'RETRIEVAL_QUERY', name: 'RETRIEVAL_QUERY' }, + { label: 'RETRIEVAL_DOCUMENT', name: 'RETRIEVAL_DOCUMENT' }, + { label: 'SEMANTIC_SIMILARITY', name: 'SEMANTIC_SIMILARITY' }, + { label: 'CLASSIFICATION', name: 'CLASSIFICATION' }, + { label: 'CLUSTERING', name: 'CLUSTERING' } + ], + default: 'TASK_TYPE_UNSPECIFIED' + } + ] + } + + // eslint-disable-next-line unused-imports/no-unused-vars + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const modelName = nodeData.inputs?.modelName as string + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('googleGenerativeAPIKey', credentialData, nodeData) + + let taskType: TaskType + switch (nodeData.inputs?.tasktype as string) { + case 'RETRIEVAL_QUERY': + taskType = TaskType.RETRIEVAL_QUERY + break + case 'RETRIEVAL_DOCUMENT': + taskType = TaskType.RETRIEVAL_DOCUMENT + break + case 'SEMANTIC_SIMILARITY': + taskType = TaskType.SEMANTIC_SIMILARITY + break + case 'CLASSIFICATION': + taskType = TaskType.CLASSIFICATION + break + case 'CLUSTERING': + taskType = TaskType.CLUSTERING + break + default: + taskType = TaskType.TASK_TYPE_UNSPECIFIED + break + } + const obj: GoogleGenerativeAIEmbeddingsParams = { + apiKey: apiKey, + modelName: modelName, + taskType: taskType + } + + const model = new GoogleGenerativeAIEmbeddings(obj) + return model + } +} + +module.exports = { nodeClass: GoogleGenerativeAIEmbedding_Embeddings } diff --git a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/gemini.png b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/gemini.png new file mode 100644 index 0000000000000000000000000000000000000000..6c0d60f44817cdbf1bd5483d88c1929831a84eed GIT binary patch literal 57143 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

1U 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 literal 0 HcmV?d00001 diff --git a/packages/components/package.json b/packages/components/package.json index 1874ca104..6bdc908a3 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -26,6 +26,7 @@ "@gomomento/sdk-core": "^1.51.1", "@google-ai/generativelanguage": "^0.2.1", "@huggingface/inference": "^2.6.1", + "@langchain/google-genai": "^0.0.3", "@notionhq/client": "^2.2.8", "@opensearch-project/opensearch": "^1.2.0", "@pinecone-database/pinecone": "^1.1.1", From f475732e6ba1aa5e78d6b352519e86cca5f13a08 Mon Sep 17 00:00:00 2001 From: vinodkiran Date: Fri, 15 Dec 2023 22:20:51 +0530 Subject: [PATCH 06/14] Support for Google Gemini Models: Minor fixes --- .../ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts | 6 +++--- .../GoogleGenerativeAIEmbedding.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts index 26913424c..95ee0575d 100644 --- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts @@ -18,7 +18,7 @@ class GoogleGenerativeAI_ChatModels implements INode { constructor() { this.label = 'ChatGoogleGenerativeAI' this.name = 'chatGoogleGenerativeAI' - this.version = 2.0 + this.version = 1.0 this.type = 'ChatGoogleGenerativeAI' this.icon = 'gemini.png' this.category = 'Chat Models' @@ -29,7 +29,7 @@ class GoogleGenerativeAI_ChatModels implements INode { name: 'credential', type: 'credential', credentialNames: ['googleGenerativeAI'], - optional: true, + optional: false, description: 'Google Generative AI credential.' } this.inputs = [ @@ -100,7 +100,7 @@ class GoogleGenerativeAI_ChatModels implements INode { const model = new ChatGoogleGenerativeAI(obj) if (topP) model.topP = parseFloat(topP) if (cache) model.cache = cache - if (temperature) model.temperature = parseInt(temperature) + if (temperature) model.temperature = parseFloat(temperature) return model } } diff --git a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts index 7682b2802..86921b42d 100644 --- a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts @@ -24,13 +24,13 @@ class GoogleGenerativeAIEmbedding_Embeddings implements INode { this.icon = 'gemini.png' this.category = 'Embeddings' this.description = 'Google Generative API to generate embeddings for a given text' - this.baseClasses = [this.type, ...getBaseClasses(GoogleVertexAIEmbeddings)] + this.baseClasses = [this.type, ...getBaseClasses(GoogleGenerativeAIEmbeddings)] this.credential = { label: 'Connect Credential', name: 'credential', type: 'credential', credentialNames: ['googleGenerativeAI'], - optional: true, + optional: false, description: 'Google Generative AI credential.' } this.inputs = [ From 87233a0e365e7949b8d58aff270768f37ebf1f2d Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 15 Dec 2023 16:59:08 +0000 Subject: [PATCH 07/14] Update GoogleGenerativeAIEmbedding.ts --- .../GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts index 86921b42d..fa5cff450 100644 --- a/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts +++ b/packages/components/nodes/embeddings/GoogleGenerativeAIEmbedding/GoogleGenerativeAIEmbedding.ts @@ -1,4 +1,3 @@ -import { GoogleVertexAIEmbeddings } from 'langchain/embeddings/googlevertexai' import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { GoogleGenerativeAIEmbeddings, GoogleGenerativeAIEmbeddingsParams } from '@langchain/google-genai' From 911b4fe7fb77c9cfbf34d4929358c34a847e435c Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 15 Dec 2023 17:15:00 +0000 Subject: [PATCH 08/14] add new utilties - customfunction, setter/getter, replace code editor --- .../components/nodes/tools/CustomTool/core.ts | 32 +- .../CustomFunction/CustomFunction.ts | 124 ++++++++ .../CustomFunction/customfunction.svg | 1 + .../utilities/GetVariable/GetVariable.ts | 52 ++++ .../nodes/utilities/GetVariable/getvar.svg | 1 + .../utilities/SetVariable/SetVariable.ts | 56 ++++ .../nodes/utilities/SetVariable/setvar.svg | 1 + packages/components/src/utils.ts | 57 ++++ packages/server/src/index.ts | 22 ++ packages/server/src/utils/index.ts | 23 +- packages/ui/package.json | 11 +- packages/ui/src/api/nodes.js | 5 +- .../ui-component/dialog/ExpandTextDialog.js | 134 ++++++--- .../ui/src/ui-component/editor/CodeEditor.js | 48 +++ .../src/ui-component/editor/DarkCodeEditor.js | 43 --- .../ui-component/editor/LightCodeEditor.js | 43 --- .../ui/src/ui-component/editor/prism-dark.css | 275 ------------------ .../src/ui-component/editor/prism-light.css | 207 ------------- packages/ui/src/ui-component/input/Input.js | 32 +- .../src/ui-component/json/SelectVariable.js | 7 +- .../ui/src/views/canvas/NodeInputHandler.js | 40 ++- packages/ui/src/views/tools/ToolDialog.js | 39 +-- 22 files changed, 543 insertions(+), 710 deletions(-) create mode 100644 packages/components/nodes/utilities/CustomFunction/CustomFunction.ts create mode 100644 packages/components/nodes/utilities/CustomFunction/customfunction.svg create mode 100644 packages/components/nodes/utilities/GetVariable/GetVariable.ts create mode 100644 packages/components/nodes/utilities/GetVariable/getvar.svg create mode 100644 packages/components/nodes/utilities/SetVariable/SetVariable.ts create mode 100644 packages/components/nodes/utilities/SetVariable/setvar.svg create mode 100644 packages/ui/src/ui-component/editor/CodeEditor.js delete mode 100644 packages/ui/src/ui-component/editor/DarkCodeEditor.js delete mode 100644 packages/ui/src/ui-component/editor/LightCodeEditor.js delete mode 100644 packages/ui/src/ui-component/editor/prism-dark.css delete mode 100644 packages/ui/src/ui-component/editor/prism-light.css diff --git a/packages/components/nodes/tools/CustomTool/core.ts b/packages/components/nodes/tools/CustomTool/core.ts index 12dd72f19..2aa06b547 100644 --- a/packages/components/nodes/tools/CustomTool/core.ts +++ b/packages/components/nodes/tools/CustomTool/core.ts @@ -2,37 +2,7 @@ import { z } from 'zod' import { CallbackManagerForToolRun } from 'langchain/callbacks' import { StructuredTool, ToolParams } from 'langchain/tools' import { NodeVM } from 'vm2' - -/* - * List of dependencies allowed to be import in vm2 - */ -const availableDependencies = [ - '@dqbd/tiktoken', - '@getzep/zep-js', - '@huggingface/inference', - '@pinecone-database/pinecone', - '@supabase/supabase-js', - 'axios', - 'cheerio', - 'chromadb', - 'cohere-ai', - 'd3-dsv', - 'form-data', - 'graphql', - 'html-to-text', - 'langchain', - 'linkifyjs', - 'mammoth', - 'moment', - 'node-fetch', - 'pdf-parse', - 'pdfjs-dist', - 'playwright', - 'puppeteer', - 'srt-parser-2', - 'typeorm', - 'weaviate-ts-client' -] +import { availableDependencies } from '../../../src/utils' export interface BaseDynamicToolInput extends ToolParams { name: string diff --git a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts new file mode 100644 index 000000000..b358b24b3 --- /dev/null +++ b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts @@ -0,0 +1,124 @@ +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { NodeVM } from 'vm2' +import { availableDependencies, handleEscapeCharacters } from '../../../src/utils' + +class CustomFunction_Utilities implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Custom JS Function' + this.name = 'customFunction' + this.version = 1.0 + this.type = 'CustomFunction' + this.icon = 'customfunction.svg' + this.category = 'Utilities' + this.description = `Execute custom javascript function` + this.baseClasses = [this.type, 'Utilities'] + this.inputs = [ + { + label: 'Input Variables', + name: 'functionInputVariables', + description: 'Input variables can be used in the function with prefix $. For example: $var', + type: 'json', + optional: true, + acceptVariable: true, + list: true + }, + { + label: 'Function Name', + name: 'functionName', + type: 'string', + optional: true, + placeholder: 'My Function' + }, + { + label: 'Javascript Function', + name: 'javascriptFunction', + type: 'code' + } + ] + this.outputs = [ + { + label: 'Output', + name: 'output', + baseClasses: ['string', 'number', 'boolean', 'json', 'array'] + } + ] + } + + async init(nodeData: INodeData, input: string): Promise { + const javascriptFunction = nodeData.inputs?.javascriptFunction as string + const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables + + let inputVars: ICommonObject = {} + if (functionInputVariablesRaw) { + try { + inputVars = + typeof functionInputVariablesRaw === 'object' ? functionInputVariablesRaw : JSON.parse(functionInputVariablesRaw) + } catch (exception) { + throw new Error("Invalid JSON in the PromptTemplate's promptValues: " + exception) + } + } + + let sandbox: any = { $input: input } + + if (Object.keys(inputVars).length) { + for (const item in inputVars) { + sandbox[`$${item}`] = inputVars[item] + } + } + + const defaultAllowBuiltInDep = [ + 'assert', + 'buffer', + 'crypto', + 'events', + 'http', + 'https', + 'net', + 'path', + 'querystring', + 'timers', + 'tls', + 'url', + 'zlib' + ] + + const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP + ? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(',')) + : defaultAllowBuiltInDep + const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : [] + const deps = availableDependencies.concat(externalDeps) + + const nodeVMOptions = { + console: 'inherit', + sandbox, + require: { + external: { modules: deps }, + builtin: builtinDeps + } + } as any + + const vm = new NodeVM(nodeVMOptions) + try { + const response = await vm.run(`module.exports = async function() {${javascriptFunction}}()`, __dirname) + if (typeof response === 'string') { + return handleEscapeCharacters(response, false) + } + return response + } catch (e) { + throw new Error(e) + } + } +} + +module.exports = { nodeClass: CustomFunction_Utilities } diff --git a/packages/components/nodes/utilities/CustomFunction/customfunction.svg b/packages/components/nodes/utilities/CustomFunction/customfunction.svg new file mode 100644 index 000000000..bf60fcae7 --- /dev/null +++ b/packages/components/nodes/utilities/CustomFunction/customfunction.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/utilities/GetVariable/GetVariable.ts b/packages/components/nodes/utilities/GetVariable/GetVariable.ts new file mode 100644 index 000000000..dde5a2d96 --- /dev/null +++ b/packages/components/nodes/utilities/GetVariable/GetVariable.ts @@ -0,0 +1,52 @@ +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' + +class GetVariable_Utilities implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Get Variable' + this.name = 'getVariable' + this.version = 1.0 + this.type = 'GetVariable' + this.icon = 'getvar.svg' + this.category = 'Utilities' + this.description = `Get variable that was saved using Set Variable node` + this.baseClasses = [this.type, 'Utilities'] + this.inputs = [ + { + label: 'Variable Name', + name: 'variableName', + type: 'string', + placeholder: 'var1' + } + ] + this.outputs = [ + { + label: 'Output', + name: 'output', + baseClasses: ['string', 'number', 'boolean', 'json', 'array'] + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const variableName = nodeData.inputs?.variableName as string + const dynamicVars = options.dynamicVariables as Record + + if (Object.prototype.hasOwnProperty.call(dynamicVars, variableName)) { + return dynamicVars[variableName] + } + return undefined + } +} + +module.exports = { nodeClass: GetVariable_Utilities } diff --git a/packages/components/nodes/utilities/GetVariable/getvar.svg b/packages/components/nodes/utilities/GetVariable/getvar.svg new file mode 100644 index 000000000..49e27ab13 --- /dev/null +++ b/packages/components/nodes/utilities/GetVariable/getvar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/utilities/SetVariable/SetVariable.ts b/packages/components/nodes/utilities/SetVariable/SetVariable.ts new file mode 100644 index 000000000..8542668ca --- /dev/null +++ b/packages/components/nodes/utilities/SetVariable/SetVariable.ts @@ -0,0 +1,56 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' + +class SetVariable_Utilities implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Set Variable' + this.name = 'setVariable' + this.version = 1.0 + this.type = 'SetVariable' + this.icon = 'setvar.svg' + this.category = 'Utilities' + this.description = `Set variable which can be retrieved at a later stage. Variable is only available during runtime.` + this.baseClasses = [this.type, 'Utilities'] + this.inputs = [ + { + label: 'Input', + name: 'input', + type: 'string | number | boolean | json | array', + optional: true, + list: true + }, + { + label: 'Variable Name', + name: 'variableName', + type: 'string', + placeholder: 'var1' + } + ] + this.outputs = [ + { + label: 'Output', + name: 'output', + baseClasses: ['string', 'number', 'boolean', 'json', 'array'] + } + ] + } + + async init(nodeData: INodeData): Promise { + const inputRaw = nodeData.inputs?.input + const variableName = nodeData.inputs?.variableName as string + + return { output: inputRaw, dynamicVariables: { [variableName]: inputRaw } } + } +} + +module.exports = { nodeClass: SetVariable_Utilities } diff --git a/packages/components/nodes/utilities/SetVariable/setvar.svg b/packages/components/nodes/utilities/SetVariable/setvar.svg new file mode 100644 index 000000000..c8d643c9b --- /dev/null +++ b/packages/components/nodes/utilities/SetVariable/setvar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 404f7c75d..239b13ca8 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -12,6 +12,63 @@ import { AIMessage, HumanMessage } from 'langchain/schema' export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}} export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank +/* + * List of dependencies allowed to be import in vm2 + */ +export const availableDependencies = [ + '@aws-sdk/client-bedrock-runtime', + '@aws-sdk/client-dynamodb', + '@aws-sdk/client-s3', + '@elastic/elasticsearch', + '@dqbd/tiktoken', + '@getzep/zep-js', + '@gomomento/sdk', + '@gomomento/sdk-core', + '@google-ai/generativelanguage', + '@huggingface/inference', + '@notionhq/client', + '@opensearch-project/opensearch', + '@pinecone-database/pinecone', + '@qdrant/js-client-rest', + '@supabase/supabase-js', + '@upstash/redis', + '@zilliz/milvus2-sdk-node', + 'apify-client', + 'axios', + 'cheerio', + 'chromadb', + 'cohere-ai', + 'd3-dsv', + 'faiss-node', + 'form-data', + 'google-auth-library', + 'graphql', + 'html-to-text', + 'ioredis', + 'langchain', + 'langfuse', + 'langsmith', + 'linkifyjs', + 'llmonitor', + 'mammoth', + 'moment', + 'mongodb', + 'mysql2', + 'node-fetch', + 'node-html-markdown', + 'notion-to-md', + 'openai', + 'pdf-parse', + 'pdfjs-dist', + 'pg', + 'playwright', + 'puppeteer', + 'redis', + 'replicate', + 'srt-parser-2', + 'typeorm', + 'weaviate-ts-client' +] /** * Get base classes of components diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index fb4a5f5a0..85e9eac45 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -281,6 +281,28 @@ export class App { } }) + // execute custom function node + this.app.post('/api/v1/node-custom-function', async (req: Request, res: Response) => { + const body = req.body + const nodeData = { inputs: body } + if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentNodes, 'customFunction')) { + try { + const nodeInstanceFilePath = this.nodesPool.componentNodes['customFunction'].filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const newNodeInstance = new nodeModule.nodeClass() + + const returnOptions: INodeOptionsValue[] = await newNodeInstance.init(nodeData) + + return res.json(returnOptions) + } catch (error) { + return res.status(500).send(`Error running custom function: ${error}`) + } + } else { + res.status(404).send(`Node customFunction not found`) + return + } + }) + // ---------------------------------------- // Chatflows // ---------------------------------------- diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 2bf1c04a4..d411050c5 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -231,6 +231,7 @@ export const buildLangchain = async ( // Create a Queue and add our initial node in it const nodeQueue = [] as INodeQueue[] const exploredNode = {} as IExploredNode + const dynamicVariables = {} as Record // In the case of infinite loop, only max 3 loops will be executed const maxLoop = 3 @@ -267,20 +268,36 @@ export const buildLangchain = async ( appDataSource, databaseEntities, logger, - cachePool + cachePool, + dynamicVariables }) logger.debug(`[server]: Finished upserting ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) break } else { logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) - flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { + let outputResult = await newNodeInstance.init(reactFlowNodeData, question, { chatId, chatflowid, appDataSource, databaseEntities, logger, - cachePool + cachePool, + dynamicVariables }) + + // Save dynamic variables + if (reactFlowNode.data.name === 'setVariable') { + const dynamicVars = outputResult?.dynamicVariables ?? {} + + for (const variableKey in dynamicVars) { + dynamicVariables[variableKey] = dynamicVars[variableKey] + } + + outputResult = outputResult?.output + } + + flowNodes[nodeIndex].data.instance = outputResult + logger.debug(`[server]: Finished initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) } } catch (e: any) { diff --git a/packages/ui/package.json b/packages/ui/package.json index 7a739978e..2aed7d977 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -8,13 +8,20 @@ "email": "henryheng@flowiseai.com" }, "dependencies": { + "@codemirror/lang-javascript": "^6.2.1", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/view": "^6.22.3", "@emotion/cache": "^11.4.0", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "@mui/icons-material": "^5.0.3", - "@mui/material": "^5.11.12", + "@mui/lab": "^5.0.0-alpha.156", + "@mui/material": "^5.15.0", "@mui/x-data-grid": "^6.8.0", "@tabler/icons": "^1.39.1", + "@uiw/codemirror-theme-sublime": "^4.21.21", + "@uiw/codemirror-theme-vscode": "^4.21.21", + "@uiw/react-codemirror": "^4.21.21", "clsx": "^1.1.1", "flowise-embed": "*", "flowise-embed-react": "*", @@ -26,7 +33,6 @@ "lodash": "^4.17.21", "moment": "^2.29.3", "notistack": "^2.0.4", - "prismjs": "^1.28.0", "prop-types": "^15.7.2", "react": "^18.2.0", "react-code-blocks": "^0.0.9-0", @@ -39,7 +45,6 @@ "react-redux": "^8.0.5", "react-router": "~6.3.0", "react-router-dom": "~6.3.0", - "react-simple-code-editor": "^0.11.2", "react-syntax-highlighter": "^15.5.0", "reactflow": "^11.5.6", "redux": "^4.0.5", diff --git a/packages/ui/src/api/nodes.js b/packages/ui/src/api/nodes.js index 7eb4c3518..3b7eacc5e 100644 --- a/packages/ui/src/api/nodes.js +++ b/packages/ui/src/api/nodes.js @@ -4,7 +4,10 @@ const getAllNodes = () => client.get('/nodes') const getSpecificNode = (name) => client.get(`/nodes/${name}`) +const executeCustomFunctionNode = (body) => client.post(`/node-custom-function`, body) + export default { getAllNodes, - getSpecificNode + getSpecificNode, + executeCustomFunctionNode } diff --git a/packages/ui/src/ui-component/dialog/ExpandTextDialog.js b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js index 2a4ec4f5a..0ef70e29e 100644 --- a/packages/ui/src/ui-component/dialog/ExpandTextDialog.js +++ b/packages/ui/src/ui-component/dialog/ExpandTextDialog.js @@ -2,14 +2,24 @@ import { createPortal } from 'react-dom' import { useState, useEffect } from 'react' import { useSelector, useDispatch } from 'react-redux' import PropTypes from 'prop-types' +import PerfectScrollbar from 'react-perfect-scrollbar' + +// MUI import { Button, Dialog, DialogActions, DialogContent, Typography } from '@mui/material' import { useTheme } from '@mui/material/styles' -import PerfectScrollbar from 'react-perfect-scrollbar' +import { LoadingButton } from '@mui/lab' + +// Project Import import { StyledButton } from 'ui-component/button/StyledButton' -import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' -import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' +import { CodeEditor } from 'ui-component/editor/CodeEditor' + +// Store import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' +// API +import nodesApi from 'api/nodes' +import useApi from 'hooks/useApi' + import './ExpandTextDialog.css' const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { @@ -18,18 +28,30 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const theme = useTheme() const dispatch = useDispatch() const customization = useSelector((state) => state.customization) - const languageType = 'json' const [inputValue, setInputValue] = useState('') const [inputParam, setInputParam] = useState(null) + const [languageType, setLanguageType] = useState('json') + const [loading, setLoading] = useState(false) + const [codeExecutedResult, setCodeExecutedResult] = useState('') + + const executeCustomFunctionNodeApi = useApi(nodesApi.executeCustomFunctionNode) useEffect(() => { if (dialogProps.value) setInputValue(dialogProps.value) - if (dialogProps.inputParam) setInputParam(dialogProps.inputParam) + if (dialogProps.inputParam) { + setInputParam(dialogProps.inputParam) + if (dialogProps.inputParam.type === 'code') { + setLanguageType('js') + } + } return () => { setInputValue('') + setLoading(false) setInputParam(null) + setLanguageType('json') + setCodeExecutedResult('') } }, [dialogProps]) @@ -39,11 +61,31 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { return () => dispatch({ type: HIDE_CANVAS_DIALOG }) }, [show, dispatch]) + useEffect(() => { + setLoading(executeCustomFunctionNodeApi.loading) + }, [executeCustomFunctionNodeApi.loading]) + + useEffect(() => { + if (executeCustomFunctionNodeApi.data) { + setCodeExecutedResult(executeCustomFunctionNodeApi.data) + } + }, [executeCustomFunctionNodeApi.data]) + + useEffect(() => { + if (executeCustomFunctionNodeApi.error) { + if (typeof executeCustomFunctionNodeApi.error === 'object' && executeCustomFunctionNodeApi.error?.response?.data) { + setCodeExecutedResult(executeCustomFunctionNodeApi.error?.response?.data) + } else if (typeof executeCustomFunctionNodeApi.error === 'string') { + setCodeExecutedResult(executeCustomFunctionNodeApi.error) + } + } + }, [executeCustomFunctionNodeApi.error]) + const component = show ? (

- {inputParam && inputParam.type === 'string' && ( + {inputParam && (inputParam.type === 'string' || inputParam.type === 'code') && (
{inputParam.label} @@ -54,42 +96,66 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { borderColor: theme.palette.grey['500'], borderRadius: '12px', height: '100%', - maxHeight: 'calc(100vh - 220px)', + maxHeight: languageType === 'js' ? 'calc(100vh - 250px)' : 'calc(100vh - 220px)', overflowX: 'hidden', backgroundColor: 'white' }} > - {customization.isDarkMode ? ( - setInputValue(code)} - placeholder={inputParam.placeholder} - type={languageType} - style={{ - fontSize: '0.875rem', - minHeight: 'calc(100vh - 220px)', - width: '100%' - }} - /> - ) : ( - setInputValue(code)} - placeholder={inputParam.placeholder} - type={languageType} - style={{ - fontSize: '0.875rem', - minHeight: 'calc(100vh - 220px)', - width: '100%' - }} - /> - )} + setInputValue(code)} + />
)}
+ {languageType === 'js' && ( + { + setLoading(true) + executeCustomFunctionNodeApi.request({ javascriptFunction: inputValue }) + }} + > + Execute + + )} + {codeExecutedResult && ( +
+ +
+ )}
diff --git a/packages/ui/src/ui-component/editor/CodeEditor.js b/packages/ui/src/ui-component/editor/CodeEditor.js new file mode 100644 index 000000000..120e19a01 --- /dev/null +++ b/packages/ui/src/ui-component/editor/CodeEditor.js @@ -0,0 +1,48 @@ +import PropTypes from 'prop-types' +import CodeMirror from '@uiw/react-codemirror' +import { javascript } from '@codemirror/lang-javascript' +import { json } from '@codemirror/lang-json' +import { vscodeDark } from '@uiw/codemirror-theme-vscode' +import { sublime } from '@uiw/codemirror-theme-sublime' +import { EditorView } from '@codemirror/view' + +export const CodeEditor = ({ value, height, theme, lang, placeholder, disabled = false, basicSetup = {}, onValueChange }) => { + const customStyle = EditorView.baseTheme({ + '&': { + color: '#191b1f', + padding: '10px' + }, + '.cm-placeholder': { + color: 'rgba(120, 120, 120, 0.5)' + } + }) + + return ( + + ) +} + +CodeEditor.propTypes = { + value: PropTypes.string, + height: PropTypes.string, + theme: PropTypes.string, + lang: PropTypes.string, + placeholder: PropTypes.string, + disabled: PropTypes.bool, + basicSetup: PropTypes.object, + onValueChange: PropTypes.func +} diff --git a/packages/ui/src/ui-component/editor/DarkCodeEditor.js b/packages/ui/src/ui-component/editor/DarkCodeEditor.js deleted file mode 100644 index bf0719dd9..000000000 --- a/packages/ui/src/ui-component/editor/DarkCodeEditor.js +++ /dev/null @@ -1,43 +0,0 @@ -import Editor from 'react-simple-code-editor' -import { highlight, languages } from 'prismjs/components/prism-core' -import 'prismjs/components/prism-clike' -import 'prismjs/components/prism-javascript' -import 'prismjs/components/prism-json' -import 'prismjs/components/prism-markup' -import './prism-dark.css' -import PropTypes from 'prop-types' -import { useTheme } from '@mui/material/styles' - -export const DarkCodeEditor = ({ value, placeholder, disabled = false, type, style, onValueChange, onMouseUp, onBlur }) => { - const theme = useTheme() - - return ( - highlight(code, type === 'json' ? languages.json : languages.js)} - padding={10} - onValueChange={onValueChange} - onMouseUp={onMouseUp} - onBlur={onBlur} - tabSize={4} - style={{ - ...style, - background: theme.palette.codeEditor.main - }} - textareaClassName='editor__textarea' - /> - ) -} - -DarkCodeEditor.propTypes = { - value: PropTypes.string, - placeholder: PropTypes.string, - disabled: PropTypes.bool, - type: PropTypes.string, - style: PropTypes.object, - onValueChange: PropTypes.func, - onMouseUp: PropTypes.func, - onBlur: PropTypes.func -} diff --git a/packages/ui/src/ui-component/editor/LightCodeEditor.js b/packages/ui/src/ui-component/editor/LightCodeEditor.js deleted file mode 100644 index 14dcbf29a..000000000 --- a/packages/ui/src/ui-component/editor/LightCodeEditor.js +++ /dev/null @@ -1,43 +0,0 @@ -import Editor from 'react-simple-code-editor' -import { highlight, languages } from 'prismjs/components/prism-core' -import 'prismjs/components/prism-clike' -import 'prismjs/components/prism-javascript' -import 'prismjs/components/prism-json' -import 'prismjs/components/prism-markup' -import './prism-light.css' -import PropTypes from 'prop-types' -import { useTheme } from '@mui/material/styles' - -export const LightCodeEditor = ({ value, placeholder, disabled = false, type, style, onValueChange, onMouseUp, onBlur }) => { - const theme = useTheme() - - return ( - highlight(code, type === 'json' ? languages.json : languages.js)} - padding={10} - onValueChange={onValueChange} - onMouseUp={onMouseUp} - onBlur={onBlur} - tabSize={4} - style={{ - ...style, - background: theme.palette.card.main - }} - textareaClassName='editor__textarea' - /> - ) -} - -LightCodeEditor.propTypes = { - value: PropTypes.string, - placeholder: PropTypes.string, - disabled: PropTypes.bool, - type: PropTypes.string, - style: PropTypes.object, - onValueChange: PropTypes.func, - onMouseUp: PropTypes.func, - onBlur: PropTypes.func -} diff --git a/packages/ui/src/ui-component/editor/prism-dark.css b/packages/ui/src/ui-component/editor/prism-dark.css deleted file mode 100644 index c4bfb4132..000000000 --- a/packages/ui/src/ui-component/editor/prism-dark.css +++ /dev/null @@ -1,275 +0,0 @@ -pre[class*='language-'], -code[class*='language-'] { - color: #d4d4d4; - font-size: 13px; - text-shadow: none; - font-family: Menlo, Monaco, Consolas, 'Andale Mono', 'Ubuntu Mono', 'Courier New', monospace; - direction: ltr; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - line-height: 1.5; - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -pre[class*='language-']::selection, -code[class*='language-']::selection, -pre[class*='language-'] *::selection, -code[class*='language-'] *::selection { - text-shadow: none; - background: #264f78; -} - -@media print { - pre[class*='language-'], - code[class*='language-'] { - text-shadow: none; - } -} - -pre[class*='language-'] { - padding: 1em; - margin: 0.5em 0; - overflow: auto; - background: #1e1e1e; -} - -:not(pre) > code[class*='language-'] { - padding: 0.1em 0.3em; - border-radius: 0.3em; - color: #db4c69; - background: #1e1e1e; -} -/********************************************************* -* Tokens -*/ -.namespace { - opacity: 0.7; -} - -.token.doctype .token.doctype-tag { - color: #569cd6; -} - -.token.doctype .token.name { - color: #9cdcfe; -} - -.token.comment, -.token.prolog { - color: #6a9955; -} - -.token.punctuation, -.language-html .language-css .token.punctuation, -.language-html .language-javascript .token.punctuation { - color: #d4d4d4; -} - -.token.property, -.token.tag, -.token.boolean, -.token.number, -.token.constant, -.token.symbol, -.token.inserted, -.token.unit { - color: #b5cea8; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.char, -.token.builtin, -.token.deleted { - color: #ce9178; -} - -.language-css .token.string.url { - text-decoration: underline; -} - -.token.operator, -.token.entity { - color: #d4d4d4; -} - -.token.operator.arrow { - color: #569cd6; -} - -.token.atrule { - color: #ce9178; -} - -.token.atrule .token.rule { - color: #c586c0; -} - -.token.atrule .token.url { - color: #9cdcfe; -} - -.token.atrule .token.url .token.function { - color: #dcdcaa; -} - -.token.atrule .token.url .token.punctuation { - color: #d4d4d4; -} - -.token.keyword { - color: #569cd6; -} - -.token.keyword.module, -.token.keyword.control-flow { - color: #c586c0; -} - -.token.function, -.token.function .token.maybe-class-name { - color: #dcdcaa; -} - -.token.regex { - color: #d16969; -} - -.token.important { - color: #569cd6; -} - -.token.italic { - font-style: italic; -} - -.token.constant { - color: #9cdcfe; -} - -.token.class-name, -.token.maybe-class-name { - color: #4ec9b0; -} - -.token.console { - color: #9cdcfe; -} - -.token.parameter { - color: #9cdcfe; -} - -.token.interpolation { - color: #9cdcfe; -} - -.token.punctuation.interpolation-punctuation { - color: #569cd6; -} - -.token.boolean { - color: #569cd6; -} - -.token.property, -.token.variable, -.token.imports .token.maybe-class-name, -.token.exports .token.maybe-class-name { - color: #9cdcfe; -} - -.token.selector { - color: #d7ba7d; -} - -.token.escape { - color: #d7ba7d; -} - -.token.tag { - color: #569cd6; -} - -.token.tag .token.punctuation { - color: #808080; -} - -.token.cdata { - color: #808080; -} - -.token.attr-name { - color: #9cdcfe; -} - -.token.attr-value, -.token.attr-value .token.punctuation { - color: #ce9178; -} - -.token.attr-value .token.punctuation.attr-equals { - color: #d4d4d4; -} - -.token.entity { - color: #569cd6; -} - -.token.namespace { - color: #4ec9b0; -} -/********************************************************* -* Language Specific -*/ - -pre[class*='language-javascript'], -code[class*='language-javascript'], -pre[class*='language-jsx'], -code[class*='language-jsx'], -pre[class*='language-typescript'], -code[class*='language-typescript'], -pre[class*='language-tsx'], -code[class*='language-tsx'] { - color: #9cdcfe; -} - -pre[class*='language-css'], -code[class*='language-css'] { - color: #ce9178; -} - -pre[class*='language-html'], -code[class*='language-html'] { - color: #d4d4d4; -} - -.language-regex .token.anchor { - color: #dcdcaa; -} - -.language-html .token.punctuation { - color: #808080; -} -/********************************************************* -* Line highlighting -*/ -pre[class*='language-'] > code[class*='language-'] { - position: relative; - z-index: 1; -} - -.line-highlight.line-highlight { - background: #f7ebc6; - box-shadow: inset 5px 0 0 #f7d87c; - z-index: 0; -} diff --git a/packages/ui/src/ui-component/editor/prism-light.css b/packages/ui/src/ui-component/editor/prism-light.css deleted file mode 100644 index 95d6d6eba..000000000 --- a/packages/ui/src/ui-component/editor/prism-light.css +++ /dev/null @@ -1,207 +0,0 @@ -code[class*='language-'], -pre[class*='language-'] { - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - color: #90a4ae; - background: #fafafa; - font-family: Roboto Mono, monospace; - font-size: 1em; - line-height: 1.5em; - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -code[class*='language-']::-moz-selection, -pre[class*='language-']::-moz-selection, -code[class*='language-'] ::-moz-selection, -pre[class*='language-'] ::-moz-selection { - background: #cceae7; - color: #263238; -} - -code[class*='language-']::selection, -pre[class*='language-']::selection, -code[class*='language-'] ::selection, -pre[class*='language-'] ::selection { - background: #cceae7; - color: #263238; -} - -:not(pre) > code[class*='language-'] { - white-space: normal; - border-radius: 0.2em; - padding: 0.1em; -} - -pre[class*='language-'] { - overflow: auto; - position: relative; - margin: 0.5em 0; - padding: 1.25em 1em; -} - -.language-css > code, -.language-sass > code, -.language-scss > code { - color: #f76d47; -} - -[class*='language-'] .namespace { - opacity: 0.7; -} - -.token.atrule { - color: #7c4dff; -} - -.token.attr-name { - color: #39adb5; -} - -.token.attr-value { - color: #f6a434; -} - -.token.attribute { - color: #f6a434; -} - -.token.boolean { - color: #7c4dff; -} - -.token.builtin { - color: #39adb5; -} - -.token.cdata { - color: #39adb5; -} - -.token.char { - color: #39adb5; -} - -.token.class { - color: #39adb5; -} - -.token.class-name { - color: #6182b8; -} - -.token.comment { - color: #aabfc9; -} - -.token.constant { - color: #7c4dff; -} - -.token.deleted { - color: #e53935; -} - -.token.doctype { - color: #aabfc9; -} - -.token.entity { - color: #e53935; -} - -.token.function { - color: #7c4dff; -} - -.token.hexcode { - color: #f76d47; -} - -.token.id { - color: #7c4dff; - font-weight: bold; -} - -.token.important { - color: #7c4dff; - font-weight: bold; -} - -.token.inserted { - color: #39adb5; -} - -.token.keyword { - color: #7c4dff; -} - -.token.number { - color: #f76d47; -} - -.token.operator { - color: #39adb5; -} - -.token.prolog { - color: #aabfc9; -} - -.token.property { - color: #39adb5; -} - -.token.pseudo-class { - color: #f6a434; -} - -.token.pseudo-element { - color: #f6a434; -} - -.token.punctuation { - color: #39adb5; -} - -.token.regex { - color: #6182b8; -} - -.token.selector { - color: #e53935; -} - -.token.string { - color: #f6a434; -} - -.token.symbol { - color: #7c4dff; -} - -.token.tag { - color: #e53935; -} - -.token.unit { - color: #f76d47; -} - -.token.url { - color: #e53935; -} - -.token.variable { - color: #e53935; -} diff --git a/packages/ui/src/ui-component/input/Input.js b/packages/ui/src/ui-component/input/Input.js index 6993847b0..3e5759386 100644 --- a/packages/ui/src/ui-component/input/Input.js +++ b/packages/ui/src/ui-component/input/Input.js @@ -1,23 +1,10 @@ import { useState, useEffect, useRef } from 'react' import PropTypes from 'prop-types' import { FormControl, OutlinedInput, Popover } from '@mui/material' -import ExpandTextDialog from 'ui-component/dialog/ExpandTextDialog' import SelectVariable from 'ui-component/json/SelectVariable' import { getAvailableNodesForVariable } from 'utils/genericHelper' -export const Input = ({ - inputParam, - value, - nodes, - edges, - nodeId, - onChange, - disabled = false, - showDialog, - dialogProps, - onDialogCancel, - onDialogConfirm -}) => { +export const Input = ({ inputParam, value, nodes, edges, nodeId, onChange, disabled = false }) => { const [myValue, setMyValue] = useState(value ?? '') const [anchorEl, setAnchorEl] = useState(null) const [availableNodesForVariable, setAvailableNodesForVariable] = useState([]) @@ -86,17 +73,6 @@ export const Input = ({ }} /> - {showDialog && ( - { - setMyValue(newValue) - onDialogConfirm(newValue, inputParamName) - }} - > - )}
{inputParam?.acceptVariable && ( diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 892a6273d..18e823386 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -22,8 +22,11 @@ import { flowContext } from 'store/context/ReactFlowContext' import { isValidConnection } from 'utils/genericHelper' import { JsonEditorInput } from 'ui-component/json/JsonEditor' import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' +import { CodeEditor } from 'ui-component/editor/CodeEditor' + import ToolDialog from 'views/tools/ToolDialog' import AssistantDialog from 'views/assistants/AssistantDialog' +import ExpandTextDialog from 'ui-component/dialog/ExpandTextDialog' import FormatPromptValuesDialog from 'ui-component/dialog/FormatPromptValuesDialog' import CredentialInputHandler from './CredentialInputHandler' @@ -83,7 +86,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA } } } - const onFormatPromptValuesClicked = (value, inputParam) => { + const onEditJSONClicked = (value, inputParam) => { // Preset values if the field is format prompt values let inputValue = value if (inputParam.name === 'promptValues' && !value) { @@ -255,7 +258,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA {inputParam.description && }
- {inputParam.type === 'string' && inputParam.rows && ( + {((inputParam.type === 'string' && inputParam.rows) || inputParam.type === 'code') && ( (data.inputs[inputParam.name] = newValue)} /> )} + {inputParam.type === 'code' && ( + <> +
+
+ (data.inputs[inputParam.name] = code)} + basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }} + /> +
+ + )} {(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && ( setShowExpandDialog(false)} - onDialogConfirm={(newValue, inputParamName) => onExpandDialogSave(newValue, inputParamName)} /> )} {inputParam.type === 'json' && ( @@ -353,11 +369,11 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA {inputParam?.acceptVariable && ( <> setAsyncOptionEditDialog('')} onConfirm={onConfirmAsyncOption} > + setShowExpandDialog(false)} + onConfirm={(newValue, inputParamName) => onExpandDialogSave(newValue, inputParamName)} + > ) } diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js index 398e9eb8d..6272e05fa 100644 --- a/packages/ui/src/views/tools/ToolDialog.js +++ b/packages/ui/src/views/tools/ToolDialog.js @@ -12,9 +12,7 @@ import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' import { GridActionsCellItem } from '@mui/x-data-grid' import DeleteIcon from '@mui/icons-material/Delete' import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' -import { DarkCodeEditor } from 'ui-component/editor/DarkCodeEditor' -import { LightCodeEditor } from 'ui-component/editor/LightCodeEditor' -import { useTheme } from '@mui/material/styles' +import { CodeEditor } from 'ui-component/editor/CodeEditor' // Icons import { IconX, IconFileExport } from '@tabler/icons' @@ -56,7 +54,6 @@ try { const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) => { const portalElement = document.getElementById('portal') - const theme = useTheme() const customization = useSelector((state) => state.customization) const dispatch = useDispatch() @@ -490,32 +487,14 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) = See Example )} - {customization.isDarkMode ? ( - setToolFunc(code)} - style={{ - fontSize: '0.875rem', - minHeight: 'calc(100vh - 220px)', - width: '100%', - borderRadius: 5 - }} - /> - ) : ( - setToolFunc(code)} - style={{ - fontSize: '0.875rem', - minHeight: 'calc(100vh - 220px)', - width: '100%', - border: `1px solid ${theme.palette.grey[300]}`, - borderRadius: 5 - }} - /> - )} + setToolFunc(code)} + /> From 6e4822c3bb9d4062528817dc157f69e86bebeda2 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 15 Dec 2023 17:49:29 +0000 Subject: [PATCH 09/14] update bedrock --- .../chatmodels/AWSBedrock/AWSChatBedrock.ts | 24 +++++++++++++------ .../AWSBedrockEmbedding.ts | 12 ++++++++-- .../nodes/llms/AWSBedrock/AWSBedrock.ts | 14 +++++++++-- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts b/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts index 956fcdb33..29faf5241 100644 --- a/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts +++ b/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts @@ -1,9 +1,9 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' -import { ChatBedrock } from 'langchain/chat_models/bedrock' +import { BedrockChat } from 'langchain/chat_models/bedrock' import { BaseBedrockInput } from 'langchain/dist/util/bedrock' import { BaseCache } from 'langchain/schema' -import { BaseLLMParams } from 'langchain/llms/base' +import { BaseChatModelParams } from 'langchain/chat_models/base' /** * I had to run the following to build the component @@ -25,14 +25,14 @@ class AWSChatBedrock_ChatModels implements INode { inputs: INodeParams[] constructor() { - this.label = 'AWS Bedrock' + this.label = 'AWS ChatBedrock' this.name = 'awsChatBedrock' this.version = 3.0 this.type = 'AWSChatBedrock' this.icon = 'awsBedrock.png' this.category = 'Chat Models' this.description = 'Wrapper around AWS Bedrock large language models that use the Chat endpoint' - this.baseClasses = [this.type, ...getBaseClasses(ChatBedrock)] + this.baseClasses = [this.type, ...getBaseClasses(BedrockChat)] this.credential = { label: 'AWS Credential', name: 'credential', @@ -102,6 +102,13 @@ class AWSChatBedrock_ChatModels implements INode { ], default: 'anthropic.claude-v2' }, + { + label: 'Custom Model Name', + name: 'customModel', + description: 'If provided, will override model selected from Model Name option', + type: 'string', + optional: true + }, { label: 'Temperature', name: 'temperature', @@ -109,6 +116,7 @@ class AWSChatBedrock_ChatModels implements INode { step: 0.1, description: 'Temperature parameter may not apply to certain model. Please check available model parameters', optional: true, + additionalParams: true, default: 0.7 }, { @@ -118,6 +126,7 @@ class AWSChatBedrock_ChatModels implements INode { step: 10, description: 'Max Tokens parameter may not apply to certain model. Please check available model parameters', optional: true, + additionalParams: true, default: 200 } ] @@ -126,14 +135,15 @@ class AWSChatBedrock_ChatModels implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const iRegion = nodeData.inputs?.region as string const iModel = nodeData.inputs?.model as string + const customModel = nodeData.inputs?.customModel as string const iTemperature = nodeData.inputs?.temperature as string const iMax_tokens_to_sample = nodeData.inputs?.max_tokens_to_sample as string const cache = nodeData.inputs?.cache as BaseCache const streaming = nodeData.inputs?.streaming as boolean - const obj: BaseBedrockInput & BaseLLMParams = { + const obj: BaseBedrockInput & BaseChatModelParams = { region: iRegion, - model: iModel, + model: customModel ?? iModel, maxTokens: parseInt(iMax_tokens_to_sample, 10), temperature: parseFloat(iTemperature), streaming: streaming ?? true @@ -160,7 +170,7 @@ class AWSChatBedrock_ChatModels implements INode { } if (cache) obj.cache = cache - const amazonBedrock = new ChatBedrock(obj) + const amazonBedrock = new BedrockChat(obj) return amazonBedrock } } diff --git a/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts b/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts index 8249d5121..5f7ce17c6 100644 --- a/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts +++ b/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts @@ -18,7 +18,7 @@ class AWSBedrockEmbedding_Embeddings implements INode { constructor() { this.label = 'AWS Bedrock Embeddings' this.name = 'AWSBedrockEmbeddings' - this.version = 2.0 + this.version = 3.0 this.type = 'AWSBedrockEmbeddings' this.icon = 'awsBedrock.png' this.category = 'Embeddings' @@ -86,6 +86,13 @@ class AWSBedrockEmbedding_Embeddings implements INode { { label: 'cohere.embed-multilingual-v3', name: 'cohere.embed-multilingual-v3' } ], default: 'amazon.titan-embed-text-v1' + }, + { + label: 'Custom Model Name', + name: 'customModel', + description: 'If provided, will override model selected from Model Name option', + type: 'string', + optional: true } ] } @@ -93,9 +100,10 @@ class AWSBedrockEmbedding_Embeddings implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const iRegion = nodeData.inputs?.region as string const iModel = nodeData.inputs?.model as string + const customModel = nodeData.inputs?.customModel as string const obj: BedrockEmbeddingsParams = { - model: iModel, + model: customModel ?? iModel, region: iRegion } diff --git a/packages/components/nodes/llms/AWSBedrock/AWSBedrock.ts b/packages/components/nodes/llms/AWSBedrock/AWSBedrock.ts index 177a32ef9..459c42964 100644 --- a/packages/components/nodes/llms/AWSBedrock/AWSBedrock.ts +++ b/packages/components/nodes/llms/AWSBedrock/AWSBedrock.ts @@ -27,7 +27,7 @@ class AWSBedrock_LLMs implements INode { constructor() { this.label = 'AWS Bedrock' this.name = 'awsBedrock' - this.version = 2.0 + this.version = 3.0 this.type = 'AWSBedrock' this.icon = 'awsBedrock.png' this.category = 'LLMs' @@ -105,6 +105,13 @@ class AWSBedrock_LLMs implements INode { { label: 'ai21.j2-ultra', name: 'ai21.j2-ultra' } ] }, + { + label: 'Custom Model Name', + name: 'customModel', + description: 'If provided, will override model selected from Model Name option', + type: 'string', + optional: true + }, { label: 'Temperature', name: 'temperature', @@ -112,6 +119,7 @@ class AWSBedrock_LLMs implements INode { step: 0.1, description: 'Temperature parameter may not apply to certain model. Please check available model parameters', optional: true, + additionalParams: true, default: 0.7 }, { @@ -121,6 +129,7 @@ class AWSBedrock_LLMs implements INode { step: 10, description: 'Max Tokens parameter may not apply to certain model. Please check available model parameters', optional: true, + additionalParams: true, default: 200 } ] @@ -129,11 +138,12 @@ class AWSBedrock_LLMs implements INode { async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const iRegion = nodeData.inputs?.region as string const iModel = nodeData.inputs?.model as string + const customModel = nodeData.inputs?.customModel as string const iTemperature = nodeData.inputs?.temperature as string const iMax_tokens_to_sample = nodeData.inputs?.max_tokens_to_sample as string const cache = nodeData.inputs?.cache as BaseCache const obj: Partial & BaseLLMParams = { - model: iModel, + model: customModel ?? iModel, region: iRegion, temperature: parseFloat(iTemperature), maxTokens: parseInt(iMax_tokens_to_sample, 10) From 05db5333968012abb18ea9e41c825c1e23b3f93f Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 15 Dec 2023 18:59:11 +0000 Subject: [PATCH 10/14] add mistral --- .../GoogleGenerativeAI.credential.ts | 3 +- .../credentials/MistralApi.credential.ts | 25 +++ .../ChatGoogleGenerativeAI.ts | 3 +- .../chatmodels/ChatMistral/ChatMistral.ts | 151 ++++++++++++++++++ .../chatmodels/ChatMistral/mistralai.png | Bin 0 -> 4542 bytes .../MistralEmbedding/MistralEmbedding.ts | 95 +++++++++++ .../embeddings/MistralEmbedding/mistralai.png | Bin 0 -> 4542 bytes packages/components/package.json | 1 + packages/server/src/utils/index.ts | 1 + 9 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 packages/components/credentials/MistralApi.credential.ts create mode 100644 packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts create mode 100644 packages/components/nodes/chatmodels/ChatMistral/mistralai.png create mode 100644 packages/components/nodes/embeddings/MistralEmbedding/MistralEmbedding.ts create mode 100644 packages/components/nodes/embeddings/MistralEmbedding/mistralai.png diff --git a/packages/components/credentials/GoogleGenerativeAI.credential.ts b/packages/components/credentials/GoogleGenerativeAI.credential.ts index 9a1f3f285..e5ad45bfa 100644 --- a/packages/components/credentials/GoogleGenerativeAI.credential.ts +++ b/packages/components/credentials/GoogleGenerativeAI.credential.ts @@ -11,7 +11,8 @@ class GoogleGenerativeAICredential implements INodeCredential { this.label = 'Google Generative AI' this.name = 'googleGenerativeAI' this.version = 1.0 - this.description = 'Get your API Key here.' + this.description = + 'You can get your API key from official page here.' this.inputs = [ { label: 'Google AI API Key', diff --git a/packages/components/credentials/MistralApi.credential.ts b/packages/components/credentials/MistralApi.credential.ts new file mode 100644 index 000000000..a254f6659 --- /dev/null +++ b/packages/components/credentials/MistralApi.credential.ts @@ -0,0 +1,25 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class MistralAICredential implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'MistralAI API' + this.name = 'mistralAIApi' + this.version = 1.0 + this.description = 'You can get your API key from official console here.' + this.inputs = [ + { + label: 'MistralAI API Key', + name: 'mistralAIAPIKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: MistralAICredential } diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts index 95ee0575d..7044645f6 100644 --- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts +++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts @@ -49,8 +49,7 @@ class GoogleGenerativeAI_ChatModels implements INode { name: 'gemini-pro' } ], - default: 'gemini-pro', - optional: true + default: 'gemini-pro' }, { label: 'Temperature', diff --git a/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts new file mode 100644 index 000000000..d9db85cd5 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts @@ -0,0 +1,151 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { BaseCache } from 'langchain/schema' +import { ChatMistralAI, ChatMistralAIInput } from '@langchain/mistralai' + +class ChatMistral_ChatModels implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'ChatMistralAI' + this.name = 'chatMistralAI' + this.version = 1.0 + this.type = 'ChatMistralAI' + this.icon = 'mistralai.png' + this.category = 'Chat Models' + this.description = 'Wrapper around Mistral large language models that use the Chat endpoint' + this.baseClasses = [this.type, ...getBaseClasses(ChatMistralAI)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['mistralAIApi'] + } + this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, + { + label: 'Model Name', + name: 'modelName', + type: 'options', + options: [ + { + label: 'mistral-tiny', + name: 'mistral-tiny' + }, + { + label: 'mistral-small', + name: 'mistral-small' + }, + { + label: 'mistral-medium', + name: 'mistral-medium' + } + ], + default: 'mistral-tiny' + }, + { + label: 'Temperature', + name: 'temperature', + type: 'number', + description: + 'What sampling temperature to use, between 0.0 and 1.0. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.', + step: 0.1, + default: 0.9, + optional: true + }, + { + label: 'Max Output Tokens', + name: 'maxOutputTokens', + type: 'number', + description: 'The maximum number of tokens to generate in the completion.', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Top Probability', + name: 'topP', + type: 'number', + description: + 'Nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.', + step: 0.1, + optional: true, + additionalParams: true + }, + { + label: 'Random Seed', + name: 'randomSeed', + type: 'number', + description: 'The seed to use for random sampling. If set, different calls will generate deterministic results.', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Safe Mode', + name: 'safeMode', + type: 'boolean', + description: 'Whether to inject a safety prompt before all conversations.', + optional: true, + additionalParams: true + }, + { + label: 'Override Endpoint', + name: 'overrideEndpoint', + type: 'string', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('mistralAIAPIKey', credentialData, nodeData) + + const temperature = nodeData.inputs?.temperature as string + const modelName = nodeData.inputs?.modelName as string + const maxOutputTokens = nodeData.inputs?.maxOutputTokens as string + const topP = nodeData.inputs?.topP as string + const safeMode = nodeData.inputs?.safeMode as boolean + const randomSeed = nodeData.inputs?.safeMode as string + const overrideEndpoint = nodeData.inputs?.overrideEndpoint as string + // Waiting fix from langchain to enable streaming + const streaming = nodeData.inputs?.streaming as boolean + + const cache = nodeData.inputs?.cache as BaseCache + + const obj: ChatMistralAIInput = { + apiKey: apiKey, + modelName: modelName + } + + if (maxOutputTokens) obj.maxTokens = parseInt(maxOutputTokens, 10) + if (topP) obj.topP = parseFloat(topP) + if (cache) obj.cache = cache + if (temperature) obj.temperature = parseFloat(temperature) + if (randomSeed) obj.randomSeed = parseFloat(randomSeed) + if (safeMode) obj.safeMode = safeMode + if (overrideEndpoint) obj.endpoint = overrideEndpoint + + const model = new ChatMistralAI(obj) + + return model + } +} + +module.exports = { nodeClass: ChatMistral_ChatModels } diff --git a/packages/components/nodes/chatmodels/ChatMistral/mistralai.png b/packages/components/nodes/chatmodels/ChatMistral/mistralai.png new file mode 100644 index 0000000000000000000000000000000000000000..1019f495d4d690dd639aa9f4e5751c403b2eff27 GIT binary patch literal 4542 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

GOhkykkrx z5<$gl4%{{1&%57c5Qv$XoY%TcILb6UdoL+VK0U}ckiTqHx51ZU zmwjXW&11_L&eEgCFq@TP2dU4yzzlh^iUi9p_NpSM8p1Mj^7zmjK?2}Op8*u3nr2;h Kf#<=y@BRlu0mfYb literal 0 HcmV?d00001 diff --git a/packages/components/nodes/embeddings/MistralEmbedding/MistralEmbedding.ts b/packages/components/nodes/embeddings/MistralEmbedding/MistralEmbedding.ts new file mode 100644 index 000000000..d0a0198c3 --- /dev/null +++ b/packages/components/nodes/embeddings/MistralEmbedding/MistralEmbedding.ts @@ -0,0 +1,95 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { MistralAIEmbeddings, MistralAIEmbeddingsParams } from '@langchain/mistralai' + +class MistralEmbedding_Embeddings implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'MistralAI Embeddings' + this.name = 'mistralAI Embeddings' + this.version = 1.0 + this.type = 'MistralAIEmbeddings' + this.icon = 'mistralai.png' + this.category = 'Embeddings' + this.description = 'MistralAI API to generate embeddings for a given text' + this.baseClasses = [this.type, ...getBaseClasses(MistralAIEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['mistralAIApi'] + } + this.inputs = [ + { + label: 'Model Name', + name: 'modelName', + type: 'options', + options: [ + { + label: 'mistral-embed', + name: 'mistral-embed' + } + ], + default: 'mistral-embed' + }, + { + label: 'Batch Size', + name: 'batchSize', + type: 'number', + step: 1, + default: 512, + optional: true, + additionalParams: true + }, + { + label: 'Strip New Lines', + name: 'stripNewLines', + type: 'boolean', + default: true, + optional: true, + additionalParams: true + }, + { + label: 'Override Endpoint', + name: 'overrideEndpoint', + type: 'string', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const modelName = nodeData.inputs?.modelName as string + const batchSize = nodeData.inputs?.batchSize as string + const stripNewLines = nodeData.inputs?.stripNewLines as boolean + const overrideEndpoint = nodeData.inputs?.overrideEndpoint as string + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('mistralAIAPIKey', credentialData, nodeData) + + const obj: MistralAIEmbeddingsParams = { + apiKey: apiKey, + modelName: modelName + } + + if (batchSize) obj.batchSize = parseInt(batchSize, 10) + if (stripNewLines) obj.stripNewLines = stripNewLines + if (overrideEndpoint) obj.endpoint = overrideEndpoint + + const model = new MistralAIEmbeddings(obj) + return model + } +} + +module.exports = { nodeClass: MistralEmbedding_Embeddings } diff --git a/packages/components/nodes/embeddings/MistralEmbedding/mistralai.png b/packages/components/nodes/embeddings/MistralEmbedding/mistralai.png new file mode 100644 index 0000000000000000000000000000000000000000..1019f495d4d690dd639aa9f4e5751c403b2eff27 GIT binary patch literal 4542 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

GOhkykkrx z5<$gl4%{{1&%57c5Qv$XoY%TcILb6UdoL+VK0U}ckiTqHx51ZU zmwjXW&11_L&eEgCFq@TP2dU4yzzlh^iUi9p_NpSM8p1Mj^7zmjK?2}Op8*u3nr2;h Kf#<=y@BRlu0mfYb literal 0 HcmV?d00001 diff --git a/packages/components/package.json b/packages/components/package.json index 6bdc908a3..cbc347ff6 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -27,6 +27,7 @@ "@google-ai/generativelanguage": "^0.2.1", "@huggingface/inference": "^2.6.1", "@langchain/google-genai": "^0.0.3", + "@langchain/mistralai": "^0.0.2", "@notionhq/client": "^2.2.8", "@opensearch-project/opensearch": "^1.2.0", "@pinecone-database/pinecone": "^1.1.1", diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 2bf1c04a4..5f12c66ec 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -711,6 +711,7 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component /** * Check to see if flow valid for stream + * TODO: perform check from component level. i.e: set streaming on component, and check here * @param {IReactFlowNode[]} reactFlowNodes * @param {INodeData} endingNodeData * @returns {boolean} From 5a66d238699212e1a759aa7d86261fbb8f065aeb Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 15 Dec 2023 23:53:47 +0000 Subject: [PATCH 11/14] Update package.json --- 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 cbc347ff6..52e59b41e 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -27,7 +27,7 @@ "@google-ai/generativelanguage": "^0.2.1", "@huggingface/inference": "^2.6.1", "@langchain/google-genai": "^0.0.3", - "@langchain/mistralai": "^0.0.2", + "@langchain/mistralai": "^0.0.3", "@notionhq/client": "^2.2.8", "@opensearch-project/opensearch": "^1.2.0", "@pinecone-database/pinecone": "^1.1.1", From 0707072a3e389dbeddf42a2a339ed40eb58fe6ca Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 15 Dec 2023 23:54:39 +0000 Subject: [PATCH 12/14] Update ChatMistral.ts --- packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts index d9db85cd5..9e12251fb 100644 --- a/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts +++ b/packages/components/nodes/chatmodels/ChatMistral/ChatMistral.ts @@ -124,7 +124,7 @@ class ChatMistral_ChatModels implements INode { const safeMode = nodeData.inputs?.safeMode as boolean const randomSeed = nodeData.inputs?.safeMode as string const overrideEndpoint = nodeData.inputs?.overrideEndpoint as string - // Waiting fix from langchain to enable streaming + // Waiting fix from langchain + mistral to enable streaming - https://github.com/mistralai/client-js/issues/18 const streaming = nodeData.inputs?.streaming as boolean const cache = nodeData.inputs?.cache as BaseCache From e8af8b007a8f8b06d5014f59f2bacd0bf6376414 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 17 Dec 2023 18:58:37 +0000 Subject: [PATCH 13/14] add marketplace template --- .../marketplaces/chatflows/SQL Prompt.json | 1237 +++++++++++++++++ packages/server/src/index.ts | 7 +- .../ui/src/views/canvas/NodeInputHandler.js | 1 + 3 files changed, 1242 insertions(+), 3 deletions(-) create mode 100644 packages/server/marketplaces/chatflows/SQL Prompt.json diff --git a/packages/server/marketplaces/chatflows/SQL Prompt.json b/packages/server/marketplaces/chatflows/SQL Prompt.json new file mode 100644 index 000000000..9244e8dec --- /dev/null +++ b/packages/server/marketplaces/chatflows/SQL Prompt.json @@ -0,0 +1,1237 @@ +{ + "description": "Manually construct prompts to query a SQL database", + "badge": "new", + "nodes": [ + { + "width": 300, + "height": 511, + "id": "promptTemplate_0", + "position": { + "x": 638.5481508577102, + "y": 84.0454315632386 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_0", + "label": "Prompt Template", + "version": 1, + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate", "Runnable"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_0-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_0-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "Based on the provided SQL table schema and question below, return a SQL SELECT ALL query that would answer the user's question. For example: SELECT * FROM table WHERE id = '1'.\n------------\nSCHEMA: {schema}\n------------\nQUESTION: {question}\n------------\nSQL QUERY:", + "promptValues": "{\"schema\":\"{{setVariable_0.data.instance}}\",\"question\":\"{{question}}\"}" + }, + "outputAnchors": [ + { + "id": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 638.5481508577102, + "y": 84.0454315632386 + }, + "dragging": false + }, + { + "width": 300, + "height": 507, + "id": "llmChain_0", + "position": { + "x": 1095.1973126620626, + "y": -83.98379829183628 + }, + "type": "customNode", + "data": { + "id": "llmChain_0", + "label": "LLM Chain", + "version": 3, + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain", "Runnable"], + "category": "Chains", + "description": "Chain to run queries against LLMs", + "inputParams": [ + { + "label": "Chain Name", + "name": "chainName", + "type": "string", + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_0-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_0-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_0-input-prompt-BasePromptTemplate" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_0-input-outputParser-BaseLLMOutputParser" + }, + { + "label": "Input Moderation", + "description": "Detect text that could generate harmful output and prevent it from being sent to the language model", + "name": "inputModeration", + "type": "Moderation", + "optional": true, + "list": true, + "id": "llmChain_0-input-inputModeration-Moderation" + } + ], + "inputs": { + "model": "{{chatOpenAI_0.data.instance}}", + "prompt": "{{promptTemplate_0.data.instance}}", + "outputParser": "", + "inputModeration": "", + "chainName": "SQL Query Chain" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_0-output-llmChain-LLMChain|BaseChain|Runnable", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain | Runnable" + }, + { + "id": "llmChain_0-output-outputPrediction-string|json", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string | json" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "outputPrediction" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1095.1973126620626, + "y": -83.98379829183628 + }, + "dragging": false + }, + { + "width": 300, + "height": 574, + "id": "chatOpenAI_0", + "position": { + "x": 636.5762708317321, + "y": -543.3151550847003 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_0", + "label": "ChatOpenAI", + "version": 2, + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["openAIApi"], + "id": "chatOpenAI_0-input-credential-credential" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-1106-preview", + "name": "gpt-4-1106-preview" + }, + { + "label": "gpt-4-vision-preview", + "name": "gpt-4-vision-preview" + }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-1106", + "name": "gpt-3.5-turbo-1106" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_0-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "step": 0.1, + "default": 0.9, + "optional": true, + "id": "chatOpenAI_0-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-basepath-string" + }, + { + "label": "BaseOptions", + "name": "baseOptions", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_0-input-baseOptions-json" + } + ], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], + "inputs": { + "cache": "", + "modelName": "gpt-3.5-turbo-16k", + "temperature": "0", + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "", + "baseOptions": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 636.5762708317321, + "y": -543.3151550847003 + }, + "dragging": false + }, + { + "width": 300, + "height": 574, + "id": "chatOpenAI_1", + "position": { + "x": 2636.1598769864936, + "y": -653.0025971757484 + }, + "type": "customNode", + "data": { + "id": "chatOpenAI_1", + "label": "ChatOpenAI", + "version": 2, + "name": "chatOpenAI", + "type": "ChatOpenAI", + "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"], + "category": "Chat Models", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputParams": [ + { + "label": "Connect Credential", + "name": "credential", + "type": "credential", + "credentialNames": ["openAIApi"], + "id": "chatOpenAI_1-input-credential-credential" + }, + { + "label": "Model Name", + "name": "modelName", + "type": "options", + "options": [ + { + "label": "gpt-4", + "name": "gpt-4" + }, + { + "label": "gpt-4-1106-preview", + "name": "gpt-4-1106-preview" + }, + { + "label": "gpt-4-vision-preview", + "name": "gpt-4-vision-preview" + }, + { + "label": "gpt-4-0613", + "name": "gpt-4-0613" + }, + { + "label": "gpt-4-32k", + "name": "gpt-4-32k" + }, + { + "label": "gpt-4-32k-0613", + "name": "gpt-4-32k-0613" + }, + { + "label": "gpt-3.5-turbo", + "name": "gpt-3.5-turbo" + }, + { + "label": "gpt-3.5-turbo-1106", + "name": "gpt-3.5-turbo-1106" + }, + { + "label": "gpt-3.5-turbo-0613", + "name": "gpt-3.5-turbo-0613" + }, + { + "label": "gpt-3.5-turbo-16k", + "name": "gpt-3.5-turbo-16k" + }, + { + "label": "gpt-3.5-turbo-16k-0613", + "name": "gpt-3.5-turbo-16k-0613" + } + ], + "default": "gpt-3.5-turbo", + "optional": true, + "id": "chatOpenAI_1-input-modelName-options" + }, + { + "label": "Temperature", + "name": "temperature", + "type": "number", + "step": 0.1, + "default": 0.9, + "optional": true, + "id": "chatOpenAI_1-input-temperature-number" + }, + { + "label": "Max Tokens", + "name": "maxTokens", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-maxTokens-number" + }, + { + "label": "Top Probability", + "name": "topP", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-topP-number" + }, + { + "label": "Frequency Penalty", + "name": "frequencyPenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-frequencyPenalty-number" + }, + { + "label": "Presence Penalty", + "name": "presencePenalty", + "type": "number", + "step": 0.1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-presencePenalty-number" + }, + { + "label": "Timeout", + "name": "timeout", + "type": "number", + "step": 1, + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-timeout-number" + }, + { + "label": "BasePath", + "name": "basepath", + "type": "string", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-basepath-string" + }, + { + "label": "BaseOptions", + "name": "baseOptions", + "type": "json", + "optional": true, + "additionalParams": true, + "id": "chatOpenAI_1-input-baseOptions-json" + } + ], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_1-input-cache-BaseCache" + } + ], + "inputs": { + "cache": "", + "modelName": "gpt-3.5-turbo-16k", + "temperature": "0", + "maxTokens": "", + "topP": "", + "frequencyPenalty": "", + "presencePenalty": "", + "timeout": "", + "basepath": "", + "baseOptions": "" + }, + "outputAnchors": [ + { + "id": "chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "name": "chatOpenAI", + "label": "ChatOpenAI", + "type": "ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 2636.1598769864936, + "y": -653.0025971757484 + }, + "dragging": false + }, + { + "width": 300, + "height": 507, + "id": "llmChain_1", + "position": { + "x": 3089.9937691022837, + "y": -109.24001734925716 + }, + "type": "customNode", + "data": { + "id": "llmChain_1", + "label": "LLM Chain", + "version": 3, + "name": "llmChain", + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain", "Runnable"], + "category": "Chains", + "description": "Chain to run queries against LLMs", + "inputParams": [ + { + "label": "Chain Name", + "name": "chainName", + "type": "string", + "placeholder": "Name Your Chain", + "optional": true, + "id": "llmChain_1-input-chainName-string" + } + ], + "inputAnchors": [ + { + "label": "Language Model", + "name": "model", + "type": "BaseLanguageModel", + "id": "llmChain_1-input-model-BaseLanguageModel" + }, + { + "label": "Prompt", + "name": "prompt", + "type": "BasePromptTemplate", + "id": "llmChain_1-input-prompt-BasePromptTemplate" + }, + { + "label": "Output Parser", + "name": "outputParser", + "type": "BaseLLMOutputParser", + "optional": true, + "id": "llmChain_1-input-outputParser-BaseLLMOutputParser" + }, + { + "label": "Input Moderation", + "description": "Detect text that could generate harmful output and prevent it from being sent to the language model", + "name": "inputModeration", + "type": "Moderation", + "optional": true, + "list": true, + "id": "llmChain_1-input-inputModeration-Moderation" + } + ], + "inputs": { + "model": "{{chatOpenAI_1.data.instance}}", + "prompt": "{{promptTemplate_1.data.instance}}", + "outputParser": "", + "inputModeration": "", + "chainName": "Final Chain" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "llmChain_1-output-llmChain-LLMChain|BaseChain|Runnable", + "name": "llmChain", + "label": "LLM Chain", + "type": "LLMChain | BaseChain | Runnable" + }, + { + "id": "llmChain_1-output-outputPrediction-string|json", + "name": "outputPrediction", + "label": "Output Prediction", + "type": "string | json" + } + ], + "default": "llmChain" + } + ], + "outputs": { + "output": "llmChain" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 3089.9937691022837, + "y": -109.24001734925716 + }, + "dragging": false + }, + { + "width": 300, + "height": 669, + "id": "customFunction_2", + "position": { + "x": -152.63957160907668, + "y": -212.74538890862547 + }, + "type": "customNode", + "data": { + "id": "customFunction_2", + "label": "Custom JS Function", + "version": 1, + "name": "customFunction", + "type": "CustomFunction", + "baseClasses": ["CustomFunction", "Utilities"], + "category": "Utilities", + "description": "Execute custom javascript function", + "inputParams": [ + { + "label": "Input Variables", + "name": "functionInputVariables", + "description": "Input variables can be used in the function with prefix $. For example: $var", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "customFunction_2-input-functionInputVariables-json" + }, + { + "label": "Function Name", + "name": "functionName", + "type": "string", + "placeholder": "My Function", + "id": "customFunction_2-input-functionName-string" + }, + { + "label": "Javascript Function", + "name": "javascriptFunction", + "type": "code", + "id": "customFunction_2-input-javascriptFunction-code" + } + ], + "inputAnchors": [], + "inputs": { + "functionInputVariables": "", + "functionName": "Get SQL Schema Prompt", + "javascriptFunction": "const HOST = 'singlestore-host';\nconst USER = 'admin';\nconst PASSWORD = 'mypassword';\nconst DATABASE = 'mydb';\nconst TABLE = 'samples';\nconst mysql = require('mysql2/promise');\n\nlet sqlSchemaPrompt;\n\n/**\n * Ideal prompt contains schema info and examples\n * Follows best practices as specified form https://arxiv.org/abs/2204.00498\n * =========================================\n * CREATE TABLE samples (firstName varchar NOT NULL, lastName varchar)\n * SELECT * FROM samples LIMIT 3\n * firstName lastName\n * Stephen Tyler\n * Jack McGinnis\n * Steven Repici\n * =========================================\n*/\nfunction getSQLPrompt() {\n return new Promise(async (resolve, reject) => {\n \n const singleStoreConnection = mysql.createPool({\n host: HOST,\n user: USER,\n password: PASSWORD,\n database: DATABASE,\n });\n\n // Get schema info\n const [schemaInfo] = await singleStoreConnection.execute(\n `SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = \"${TABLE}\"`\n );\n\n const createColumns = [];\n const columnNames = [];\n\n for (const schemaData of schemaInfo) {\n columnNames.push(`${schemaData['COLUMN_NAME']}`);\n createColumns.push(`${schemaData['COLUMN_NAME']} ${schemaData['COLUMN_TYPE']} ${schemaData['IS_NULLABLE'] === 'NO' ? 'NOT NULL' : ''}`);\n }\n\n const sqlCreateTableQuery = `CREATE TABLE samples (${createColumns.join(', ')})`;\n const sqlSelectTableQuery = `SELECT * FROM samples LIMIT 3`;\n\n // Get first 3 rows\n const [rows] = await singleStoreConnection.execute(\n sqlSelectTableQuery,\n );\n \n const allValues = [];\n for (const row of rows) {\n const rowValues = [];\n for (const colName in row) {\n rowValues.push(row[colName]);\n }\n allValues.push(rowValues.join(' '));\n }\n\n sqlSchemaPrompt = sqlCreateTableQuery + '\\n' + sqlSelectTableQuery + '\\n' + columnNames.join(' ') + '\\n' + allValues.join('\\n');\n \n resolve();\n });\n}\n\nasync function main() {\n await getSQLPrompt();\n}\n\nawait main();\n\nreturn sqlSchemaPrompt;" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "customFunction_2-output-output-string|number|boolean|json|array", + "name": "output", + "label": "Output", + "type": "string | number | boolean | json | array" + } + ], + "default": "output" + } + ], + "outputs": { + "output": "output" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": -152.63957160907668, + "y": -212.74538890862547 + }, + "dragging": false + }, + { + "width": 300, + "height": 669, + "id": "customFunction_1", + "position": { + "x": 1887.4670208331604, + "y": -275.95340782935716 + }, + "type": "customNode", + "data": { + "id": "customFunction_1", + "label": "Custom JS Function", + "version": 1, + "name": "customFunction", + "type": "CustomFunction", + "baseClasses": ["CustomFunction", "Utilities"], + "category": "Utilities", + "description": "Execute custom javascript function", + "inputParams": [ + { + "label": "Input Variables", + "name": "functionInputVariables", + "description": "Input variables can be used in the function with prefix $. For example: $var", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "customFunction_1-input-functionInputVariables-json" + }, + { + "label": "Function Name", + "name": "functionName", + "type": "string", + "placeholder": "My Function", + "id": "customFunction_1-input-functionName-string" + }, + { + "label": "Javascript Function", + "name": "javascriptFunction", + "type": "code", + "id": "customFunction_1-input-javascriptFunction-code" + } + ], + "inputAnchors": [], + "inputs": { + "functionInputVariables": "{\"sqlQuery\":\"{{setVariable_1.data.instance}}\"}", + "functionName": "Run SQL Query", + "javascriptFunction": "const HOST = 'singlestore-host';\nconst USER = 'admin';\nconst PASSWORD = 'mypassword';\nconst DATABASE = 'mydb';\nconst TABLE = 'samples';\nconst mysql = require('mysql2/promise');\n\nlet result;\n\nfunction getSQLResult() {\n return new Promise(async (resolve, reject) => {\n \n const singleStoreConnection = mysql.createPool({\n host: HOST,\n user: USER,\n password: PASSWORD,\n database: DATABASE,\n });\n \n const [rows] = await singleStoreConnection.execute(\n $sqlQuery\n );\n\n result = JSON.stringify(rows)\n \n resolve();\n });\n}\n\nasync function main() {\n await getSQLResult();\n}\n\nawait main();\n\nreturn result;" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "customFunction_1-output-output-string|number|boolean|json|array", + "name": "output", + "label": "Output", + "type": "string | number | boolean | json | array" + } + ], + "default": "output" + } + ], + "outputs": { + "output": "output" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1887.4670208331604, + "y": -275.95340782935716 + }, + "dragging": false + }, + { + "width": 300, + "height": 511, + "id": "promptTemplate_1", + "position": { + "x": 2638.3935631956588, + "y": -18.55855423639423 + }, + "type": "customNode", + "data": { + "id": "promptTemplate_1", + "label": "Prompt Template", + "version": 1, + "name": "promptTemplate", + "type": "PromptTemplate", + "baseClasses": ["PromptTemplate", "BaseStringPromptTemplate", "BasePromptTemplate", "Runnable"], + "category": "Prompts", + "description": "Schema to represent a basic prompt for an LLM", + "inputParams": [ + { + "label": "Template", + "name": "template", + "type": "string", + "rows": 4, + "placeholder": "What is a good name for a company that makes {product}?", + "id": "promptTemplate_1-input-template-string" + }, + { + "label": "Format Prompt Values", + "name": "promptValues", + "type": "json", + "optional": true, + "acceptVariable": true, + "list": true, + "id": "promptTemplate_1-input-promptValues-json" + } + ], + "inputAnchors": [], + "inputs": { + "template": "Based on the table schema below, question, SQL query, and SQL response, write a natural language response, be details as possible:\n------------\nSCHEMA: {schema}\n------------\nQUESTION: {question}\n------------\nSQL QUERY: {sqlQuery}\n------------\nSQL RESPONSE: {sqlResponse}\n------------\nNATURAL LANGUAGE RESPONSE:", + "promptValues": "{\"schema\":\"{{getVariable_0.data.instance}}\",\"question\":\"{{question}}\",\"sqlResponse\":\"{{customFunction_1.data.instance}}\",\"sqlQuery\":\"{{getVariable_1.data.instance}}\"}" + }, + "outputAnchors": [ + { + "id": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "name": "promptTemplate", + "label": "PromptTemplate", + "type": "PromptTemplate | BaseStringPromptTemplate | BasePromptTemplate | Runnable" + } + ], + "outputs": {}, + "selected": false + }, + "selected": false, + "dragging": false, + "positionAbsolute": { + "x": 2638.3935631956588, + "y": -18.55855423639423 + } + }, + { + "width": 300, + "height": 355, + "id": "setVariable_0", + "position": { + "x": 247.02296459986826, + "y": -60.27462140472403 + }, + "type": "customNode", + "data": { + "id": "setVariable_0", + "label": "Set Variable", + "version": 1, + "name": "setVariable", + "type": "SetVariable", + "baseClasses": ["SetVariable", "Utilities"], + "category": "Utilities", + "description": "Set variable which can be retrieved at a later stage. Variable is only available during runtime.", + "inputParams": [ + { + "label": "Variable Name", + "name": "variableName", + "type": "string", + "placeholder": "var1", + "id": "setVariable_0-input-variableName-string" + } + ], + "inputAnchors": [ + { + "label": "Input", + "name": "input", + "type": "string | number | boolean | json | array", + "optional": true, + "list": true, + "id": "setVariable_0-input-input-string | number | boolean | json | array" + } + ], + "inputs": { + "input": ["{{customFunction_2.data.instance}}"], + "variableName": "schemaPrompt" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "setVariable_0-output-output-string|number|boolean|json|array", + "name": "output", + "label": "Output", + "type": "string | number | boolean | json | array" + } + ], + "default": "output" + } + ], + "outputs": { + "output": "output" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 247.02296459986826, + "y": -60.27462140472403 + }, + "dragging": false + }, + { + "width": 300, + "height": 304, + "id": "getVariable_0", + "position": { + "x": 2248.4540716891547, + "y": -47.21232652005119 + }, + "type": "customNode", + "data": { + "id": "getVariable_0", + "label": "Get Variable", + "version": 1, + "name": "getVariable", + "type": "GetVariable", + "baseClasses": ["GetVariable", "Utilities"], + "category": "Utilities", + "description": "Get variable that was saved using Set Variable node", + "inputParams": [ + { + "label": "Variable Name", + "name": "variableName", + "type": "string", + "placeholder": "var1", + "id": "getVariable_0-input-variableName-string" + } + ], + "inputAnchors": [], + "inputs": { + "variableName": "schemaPrompt" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "getVariable_0-output-output-string|number|boolean|json|array", + "name": "output", + "label": "Output", + "type": "string | number | boolean | json | array" + } + ], + "default": "output" + } + ], + "outputs": { + "output": "output" + }, + "selected": false + }, + "positionAbsolute": { + "x": 2248.4540716891547, + "y": -47.21232652005119 + }, + "selected": false, + "dragging": false + }, + { + "width": 300, + "height": 304, + "id": "getVariable_1", + "position": { + "x": 2256.0258940322105, + "y": 437.4363694364632 + }, + "type": "customNode", + "data": { + "id": "getVariable_1", + "label": "Get Variable", + "version": 1, + "name": "getVariable", + "type": "GetVariable", + "baseClasses": ["GetVariable", "Utilities"], + "category": "Utilities", + "description": "Get variable that was saved using Set Variable node", + "inputParams": [ + { + "label": "Variable Name", + "name": "variableName", + "type": "string", + "placeholder": "var1", + "id": "getVariable_1-input-variableName-string" + } + ], + "inputAnchors": [], + "inputs": { + "variableName": "sqlQuery" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "getVariable_1-output-output-string|number|boolean|json|array", + "name": "output", + "label": "Output", + "type": "string | number | boolean | json | array" + } + ], + "default": "output" + } + ], + "outputs": { + "output": "output" + }, + "selected": false + }, + "positionAbsolute": { + "x": 2256.0258940322105, + "y": 437.4363694364632 + }, + "selected": false, + "dragging": false + }, + { + "width": 300, + "height": 355, + "id": "setVariable_1", + "position": { + "x": 1482.8091395089693, + "y": -33.943355212355016 + }, + "type": "customNode", + "data": { + "id": "setVariable_1", + "label": "Set Variable", + "version": 1, + "name": "setVariable", + "type": "SetVariable", + "baseClasses": ["SetVariable", "Utilities"], + "category": "Utilities", + "description": "Set variable which can be retrieved at a later stage. Variable is only available during runtime.", + "inputParams": [ + { + "label": "Variable Name", + "name": "variableName", + "type": "string", + "placeholder": "var1", + "id": "setVariable_1-input-variableName-string" + } + ], + "inputAnchors": [ + { + "label": "Input", + "name": "input", + "type": "string | number | boolean | json | array", + "optional": true, + "list": true, + "id": "setVariable_1-input-input-string | number | boolean | json | array" + } + ], + "inputs": { + "input": ["{{llmChain_0.data.instance}}"], + "variableName": "sqlQuery" + }, + "outputAnchors": [ + { + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "setVariable_1-output-output-string|number|boolean|json|array", + "name": "output", + "label": "Output", + "type": "string | number | boolean | json | array" + } + ], + "default": "output" + } + ], + "outputs": { + "output": "output" + }, + "selected": false + }, + "selected": false, + "positionAbsolute": { + "x": 1482.8091395089693, + "y": -33.943355212355016 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "promptTemplate_0", + "sourceHandle": "promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_0-promptTemplate_0-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable-llmChain_0-llmChain_0-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_0", + "sourceHandle": "chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "target": "llmChain_0", + "targetHandle": "llmChain_0-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-llmChain_0-llmChain_0-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "chatOpenAI_1", + "sourceHandle": "chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-model-BaseLanguageModel", + "type": "buttonedge", + "id": "chatOpenAI_1-chatOpenAI_1-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-llmChain_1-llmChain_1-input-model-BaseLanguageModel", + "data": { + "label": "" + } + }, + { + "source": "customFunction_1", + "sourceHandle": "customFunction_1-output-output-string|number|boolean|json|array", + "target": "promptTemplate_1", + "targetHandle": "promptTemplate_1-input-promptValues-json", + "type": "buttonedge", + "id": "customFunction_1-customFunction_1-output-output-string|number|boolean|json|array-promptTemplate_1-promptTemplate_1-input-promptValues-json", + "data": { + "label": "" + } + }, + { + "source": "promptTemplate_1", + "sourceHandle": "promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable", + "target": "llmChain_1", + "targetHandle": "llmChain_1-input-prompt-BasePromptTemplate", + "type": "buttonedge", + "id": "promptTemplate_1-promptTemplate_1-output-promptTemplate-PromptTemplate|BaseStringPromptTemplate|BasePromptTemplate|Runnable-llmChain_1-llmChain_1-input-prompt-BasePromptTemplate", + "data": { + "label": "" + } + }, + { + "source": "customFunction_2", + "sourceHandle": "customFunction_2-output-output-string|number|boolean|json|array", + "target": "setVariable_0", + "targetHandle": "setVariable_0-input-input-string | number | boolean | json | array", + "type": "buttonedge", + "id": "customFunction_2-customFunction_2-output-output-string|number|boolean|json|array-setVariable_0-setVariable_0-input-input-string | number | boolean | json | array", + "data": { + "label": "" + } + }, + { + "source": "setVariable_0", + "sourceHandle": "setVariable_0-output-output-string|number|boolean|json|array", + "target": "promptTemplate_0", + "targetHandle": "promptTemplate_0-input-promptValues-json", + "type": "buttonedge", + "id": "setVariable_0-setVariable_0-output-output-string|number|boolean|json|array-promptTemplate_0-promptTemplate_0-input-promptValues-json", + "data": { + "label": "" + } + }, + { + "source": "getVariable_0", + "sourceHandle": "getVariable_0-output-output-string|number|boolean|json|array", + "target": "promptTemplate_1", + "targetHandle": "promptTemplate_1-input-promptValues-json", + "type": "buttonedge", + "id": "getVariable_0-getVariable_0-output-output-string|number|boolean|json|array-promptTemplate_1-promptTemplate_1-input-promptValues-json", + "data": { + "label": "" + } + }, + { + "source": "getVariable_1", + "sourceHandle": "getVariable_1-output-output-string|number|boolean|json|array", + "target": "promptTemplate_1", + "targetHandle": "promptTemplate_1-input-promptValues-json", + "type": "buttonedge", + "id": "getVariable_1-getVariable_1-output-output-string|number|boolean|json|array-promptTemplate_1-promptTemplate_1-input-promptValues-json", + "data": { + "label": "" + } + }, + { + "source": "llmChain_0", + "sourceHandle": "llmChain_0-output-outputPrediction-string|json", + "target": "setVariable_1", + "targetHandle": "setVariable_1-input-input-string | number | boolean | json | array", + "type": "buttonedge", + "id": "llmChain_0-llmChain_0-output-outputPrediction-string|json-setVariable_1-setVariable_1-input-input-string | number | boolean | json | array", + "data": { + "label": "" + } + }, + { + "source": "setVariable_1", + "sourceHandle": "setVariable_1-output-output-string|number|boolean|json|array", + "target": "customFunction_1", + "targetHandle": "customFunction_1-input-functionInputVariables-json", + "type": "buttonedge", + "id": "setVariable_1-setVariable_1-output-output-string|number|boolean|json|array-customFunction_1-customFunction_1-input-functionInputVariables-json", + "data": { + "label": "" + } + } + ] +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 85e9eac45..9c31a3337 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -55,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, IMessage, INodeOptionsValue } from 'flowise-components' +import { ICommonObject, IMessage, INodeOptionsValue, handleEscapeCharacters } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey' import { sanitizeMiddleware } from './utils/XSS' @@ -291,9 +291,10 @@ export class App { const nodeModule = await import(nodeInstanceFilePath) const newNodeInstance = new nodeModule.nodeClass() - const returnOptions: INodeOptionsValue[] = await newNodeInstance.init(nodeData) + const returnData = await newNodeInstance.init(nodeData) + const result = typeof returnData === 'string' ? handleEscapeCharacters(returnData, true) : returnData - return res.json(returnOptions) + return res.json(result) } catch (error) { return res.status(500).send(`Error running custom function: ${error}`) } diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 18e823386..92a43cf80 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -371,6 +371,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA