diff --git a/README.md b/README.md index 9f685846..25026237 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,10 @@ Flowise support different environment variables to configure your instance. You [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://docs.flowiseai.com/deployment/render) +### [Elestio](https://elest.io/open-source/flowiseai) + +[![Deploy](https://pub-da36157c854648669813f3f76c526c2b.r2.dev/deploy-on-elestio-black.png)](https://elest.io/open-source/flowiseai) + ### [HuggingFace Spaces](https://docs.flowiseai.com/deployment/hugging-face) HuggingFace Spaces diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 137b1183..8e0e1af5 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,6 +10,12 @@ services: - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} - DEBUG=${DEBUG} - DATABASE_PATH=${DATABASE_PATH} + - DATABASE_TYPE=${DATABASE_TYPE} + - DATABASE_PORT=${DATABASE_PORT} + - DATABASE_HOST=${DATABASE_HOST} + - DATABASE_NAME=${DATABASE_NAME} + - DATABASE_USER=${DATABASE_USER} + - DATABASE_PASSWORD=${DATABASE_PASSWORD} - APIKEY_PATH=${APIKEY_PATH} - SECRETKEY_PATH=${SECRETKEY_PATH} - FLOWISE_SECRETKEY_OVERWRITE=${FLOWISE_SECRETKEY_OVERWRITE} diff --git a/package.json b/package.json index 210a95c4..13db19d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.6", + "version": "1.3.8", "private": true, "homepage": "https://flowiseai.com", "workspaces": [ diff --git a/packages/components/credentials/ElasticsearchAPI.credential.ts b/packages/components/credentials/ElasticsearchAPI.credential.ts new file mode 100644 index 00000000..fbba76f4 --- /dev/null +++ b/packages/components/credentials/ElasticsearchAPI.credential.ts @@ -0,0 +1,31 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class ElectricsearchAPI implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Elasticsearch API' + this.name = 'elasticsearchApi' + this.version = 1.0 + this.description = + 'Refer to official guide on how to get an API Key from ElasticSearch' + this.inputs = [ + { + label: 'Elasticsearch Endpoint', + name: 'endpoint', + type: 'string' + }, + { + label: 'Elasticsearch API Key', + name: 'apiKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: ElectricsearchAPI } diff --git a/packages/components/credentials/ElectricsearchUserPassword.credential.ts b/packages/components/credentials/ElectricsearchUserPassword.credential.ts new file mode 100644 index 00000000..6c47f7b1 --- /dev/null +++ b/packages/components/credentials/ElectricsearchUserPassword.credential.ts @@ -0,0 +1,36 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class ElasticSearchUserPassword implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'ElasticSearch User Password' + this.name = 'elasticSearchUserPassword' + this.version = 1.0 + this.description = + 'Refer to official guide on how to get User Password from ElasticSearch' + this.inputs = [ + { + label: 'Cloud ID', + name: 'cloudId', + type: 'string' + }, + { + label: 'ElasticSearch User', + name: 'username', + type: 'string' + }, + { + label: 'ElasticSearch Password', + name: 'password', + type: 'password' + } + ] + } +} + +module.exports = { credClass: ElasticSearchUserPassword } diff --git a/packages/components/credentials/MomentoCacheApi.credential.ts b/packages/components/credentials/MomentoCacheApi.credential.ts new file mode 100644 index 00000000..038f826d --- /dev/null +++ b/packages/components/credentials/MomentoCacheApi.credential.ts @@ -0,0 +1,36 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class MomentoCacheApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Momento Cache API' + this.name = 'momentoCacheApi' + this.version = 1.0 + this.description = + 'Refer to official guide on how to get API key on Momento' + this.inputs = [ + { + label: 'Cache', + name: 'momentoCache', + type: 'string' + }, + { + label: 'API Key', + name: 'momentoApiKey', + type: 'password' + }, + { + label: 'Endpoint', + name: 'momentoEndpoint', + type: 'string' + } + ] + } +} + +module.exports = { credClass: MomentoCacheApi } diff --git a/packages/components/credentials/RedisCacheApi.credential.ts b/packages/components/credentials/RedisCacheApi.credential.ts new file mode 100644 index 00000000..e09a94e7 --- /dev/null +++ b/packages/components/credentials/RedisCacheApi.credential.ts @@ -0,0 +1,43 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class RedisCacheApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Redis Cache API' + this.name = 'redisCacheApi' + this.version = 1.0 + this.inputs = [ + { + label: 'Redis Host', + name: 'redisCacheHost', + type: 'string', + default: '127.0.0.1' + }, + { + label: 'Port', + name: 'redisCachePort', + type: 'number', + default: '6789' + }, + { + label: 'User', + name: 'redisCacheUser', + type: 'string', + placeholder: '' + }, + { + label: 'Password', + name: 'redisCachePwd', + type: 'password', + placeholder: '' + } + ] + } +} + +module.exports = { credClass: RedisCacheApi } diff --git a/packages/components/credentials/UnstructuredApi.credential.ts b/packages/components/credentials/UnstructuredApi.credential.ts new file mode 100644 index 00000000..5c77895a --- /dev/null +++ b/packages/components/credentials/UnstructuredApi.credential.ts @@ -0,0 +1,26 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class UnstructuredApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Unstructured API' + this.name = 'unstructuredApi' + this.version = 1.0 + this.description = + 'Refer to official guide on how to get api key on Unstructured' + this.inputs = [ + { + label: 'API Key', + name: 'unstructuredAPIKey', + type: 'password' + } + ] + } +} + +module.exports = { credClass: UnstructuredApi } diff --git a/packages/components/credentials/UpstashRedisApi.credential.ts b/packages/components/credentials/UpstashRedisApi.credential.ts new file mode 100644 index 00000000..7e2b367f --- /dev/null +++ b/packages/components/credentials/UpstashRedisApi.credential.ts @@ -0,0 +1,31 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class UpstashRedisApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Upstash Redis API' + this.name = 'upstashRedisApi' + this.version = 1.0 + this.description = + 'Refer to official guide on how to create redis instance and get redis REST URL and Token' + this.inputs = [ + { + label: 'Upstash Redis REST URL', + name: 'upstashConnectionUrl', + type: 'string' + }, + { + label: 'Token', + name: 'upstashConnectionToken', + type: 'password' + } + ] + } +} + +module.exports = { credClass: UpstashRedisApi } diff --git a/packages/components/credentials/UpstashRedisMemoryApi.credential.ts b/packages/components/credentials/UpstashRedisMemoryApi.credential.ts new file mode 100644 index 00000000..8d3e9528 --- /dev/null +++ b/packages/components/credentials/UpstashRedisMemoryApi.credential.ts @@ -0,0 +1,26 @@ +import { INodeParams, INodeCredential } from '../src/Interface' + +class UpstashRedisMemoryApi implements INodeCredential { + label: string + name: string + version: number + description: string + inputs: INodeParams[] + + constructor() { + this.label = 'Upstash Redis Memory API' + this.name = 'upstashRedisMemoryApi' + this.version = 1.0 + this.description = + 'Refer to official guide on how to create redis instance and get redis REST Token' + this.inputs = [ + { + label: 'Upstash Redis REST Token', + name: 'upstashRestToken', + type: 'password' + } + ] + } +} + +module.exports = { credClass: UpstashRedisMemoryApi } diff --git a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts index 661ef151..00f825d4 100644 --- a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts +++ b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts @@ -95,8 +95,12 @@ class ConversationalAgent_Agents implements INode { const callbacks = await additionalCallbacks(nodeData, options) if (options && options.chatHistory) { - memory.chatHistory = mapChatHistory(options) - executor.memory = memory + const chatHistoryClassName = memory.chatHistory.constructor.name + // Only replace when its In-Memory + if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { + memory.chatHistory = mapChatHistory(options) + executor.memory = memory + } } const result = await executor.call({ input }, [...callbacks]) diff --git a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts index 3d70a2d3..4a908d7f 100644 --- a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts +++ b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts @@ -82,7 +82,11 @@ class ConversationalRetrievalAgent_Agents implements INode { if (executor.memory) { ;(executor.memory as any).memoryKey = 'chat_history' ;(executor.memory as any).outputKey = 'output' - ;(executor.memory as any).chatHistory = mapChatHistory(options) + const chatHistoryClassName = (executor.memory as any).chatHistory.constructor.name + // Only replace when its In-Memory + if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { + ;(executor.memory as any).chatHistory = mapChatHistory(options) + } } const loggerHandler = new ConsoleCallbackHandler(options.logger) diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index c1bd32ec..c920c399 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -81,8 +81,12 @@ class OpenAIFunctionAgent_Agents implements INode { const memory = nodeData.inputs?.memory as BaseChatMemory if (options && options.chatHistory) { - memory.chatHistory = mapChatHistory(options) - executor.memory = memory + const chatHistoryClassName = memory.chatHistory.constructor.name + // Only replace when its In-Memory + if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { + memory.chatHistory = mapChatHistory(options) + executor.memory = memory + } } const loggerHandler = new ConsoleCallbackHandler(options.logger) diff --git a/packages/components/nodes/cache/InMemoryCache/InMemoryCache.ts b/packages/components/nodes/cache/InMemoryCache/InMemoryCache.ts new file mode 100644 index 00000000..1ea03566 --- /dev/null +++ b/packages/components/nodes/cache/InMemoryCache/InMemoryCache.ts @@ -0,0 +1,65 @@ +import { getBaseClasses, ICommonObject, INode, INodeData, INodeParams } from '../../../src' +import { BaseCache } from 'langchain/schema' +import hash from 'object-hash' + +class InMemoryCache implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'InMemory Cache' + this.name = 'inMemoryCache' + this.version = 1.0 + this.type = 'InMemoryCache' + this.description = 'Cache LLM response in memory, will be cleared once app restarted' + this.icon = 'inmemorycache.png' + this.category = 'Cache' + this.baseClasses = [this.type, ...getBaseClasses(InMemoryCacheExtended)] + this.inputs = [] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const memoryMap = options.cachePool.getLLMCache(options.chatflowid) ?? new Map() + const inMemCache = new InMemoryCacheExtended(memoryMap) + + inMemCache.lookup = async (prompt: string, llmKey: string): Promise => { + const memory = options.cachePool.getLLMCache(options.chatflowid) ?? inMemCache.cache + return Promise.resolve(memory.get(getCacheKey(prompt, llmKey)) ?? null) + } + + inMemCache.update = async (prompt: string, llmKey: string, value: any): Promise => { + inMemCache.cache.set(getCacheKey(prompt, llmKey), value) + options.cachePool.addLLMCache(options.chatflowid, inMemCache.cache) + } + return inMemCache + } +} + +const getCacheKey = (...strings: string[]): string => hash(strings.join('_')) + +class InMemoryCacheExtended extends BaseCache { + cache: Map + + constructor(map: Map) { + super() + this.cache = map + } + + lookup(prompt: string, llmKey: string): Promise { + return Promise.resolve(this.cache.get(getCacheKey(prompt, llmKey)) ?? null) + } + + async update(prompt: string, llmKey: string, value: any): Promise { + this.cache.set(getCacheKey(prompt, llmKey), value) + } +} + +module.exports = { nodeClass: InMemoryCache } diff --git a/packages/components/nodes/cache/InMemoryCache/InMemoryEmbeddingCache.ts b/packages/components/nodes/cache/InMemoryCache/InMemoryEmbeddingCache.ts new file mode 100644 index 00000000..fad72482 --- /dev/null +++ b/packages/components/nodes/cache/InMemoryCache/InMemoryEmbeddingCache.ts @@ -0,0 +1,131 @@ +import { getBaseClasses, ICommonObject, INode, INodeData, INodeParams } from '../../../src' +import { CacheBackedEmbeddings } from 'langchain/embeddings/cache_backed' +import { Embeddings } from 'langchain/embeddings/base' +import { BaseStore } from 'langchain/schema/storage' + +class InMemoryEmbeddingCache implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'InMemory Embedding Cache' + this.name = 'inMemoryEmbeddingCache' + this.version = 1.0 + this.type = 'InMemoryEmbeddingCache' + this.description = 'Cache generated Embeddings in memory to avoid needing to recompute them.' + this.icon = 'inmemorycache.png' + this.category = 'Cache' + this.baseClasses = [this.type, ...getBaseClasses(CacheBackedEmbeddings)] + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Namespace', + name: 'namespace', + type: 'string', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const namespace = nodeData.inputs?.namespace as string + const underlyingEmbeddings = nodeData.inputs?.embeddings as Embeddings + const memoryMap = options.cachePool.getEmbeddingCache(options.chatflowid) ?? {} + const inMemCache = new InMemoryEmbeddingCacheExtended(memoryMap) + + inMemCache.mget = async (keys: string[]) => { + const memory = options.cachePool.getEmbeddingCache(options.chatflowid) ?? inMemCache.store + return keys.map((key) => memory[key]) + } + + inMemCache.mset = async (keyValuePairs: [string, any][]): Promise => { + for (const [key, value] of keyValuePairs) { + inMemCache.store[key] = value + } + options.cachePool.addEmbeddingCache(options.chatflowid, inMemCache.store) + } + + inMemCache.mdelete = async (keys: string[]): Promise => { + for (const key of keys) { + delete inMemCache.store[key] + } + options.cachePool.addEmbeddingCache(options.chatflowid, inMemCache.store) + } + + return CacheBackedEmbeddings.fromBytesStore(underlyingEmbeddings, inMemCache, { + namespace: namespace + }) + } +} + +class InMemoryEmbeddingCacheExtended extends BaseStore { + lc_namespace = ['langchain', 'storage', 'in_memory'] + + store: Record = {} + + constructor(map: Record) { + super() + this.store = map + } + + /** + * Retrieves the values associated with the given keys from the store. + * @param keys Keys to retrieve values for. + * @returns Array of values associated with the given keys. + */ + async mget(keys: string[]) { + return keys.map((key) => this.store[key]) + } + + /** + * Sets the values for the given keys in the store. + * @param keyValuePairs Array of key-value pairs to set in the store. + * @returns Promise that resolves when all key-value pairs have been set. + */ + async mset(keyValuePairs: [string, T][]): Promise { + for (const [key, value] of keyValuePairs) { + this.store[key] = value + } + } + + /** + * Deletes the given keys and their associated values from the store. + * @param keys Keys to delete from the store. + * @returns Promise that resolves when all keys have been deleted. + */ + async mdelete(keys: string[]): Promise { + for (const key of keys) { + delete this.store[key] + } + } + + /** + * Asynchronous generator that yields keys from the store. If a prefix is + * provided, it only yields keys that start with the prefix. + * @param prefix Optional prefix to filter keys. + * @returns AsyncGenerator that yields keys from the store. + */ + async *yieldKeys(prefix?: string | undefined): AsyncGenerator { + const keys = Object.keys(this.store) + for (const key of keys) { + if (prefix === undefined || key.startsWith(prefix)) { + yield key + } + } + } +} + +module.exports = { nodeClass: InMemoryEmbeddingCache } diff --git a/packages/components/nodes/cache/InMemoryCache/inmemorycache.png b/packages/components/nodes/cache/InMemoryCache/inmemorycache.png new file mode 100644 index 00000000..1e5fe6d1 Binary files /dev/null and b/packages/components/nodes/cache/InMemoryCache/inmemorycache.png differ diff --git a/packages/components/nodes/cache/MomentoCache/MomentoCache.ts b/packages/components/nodes/cache/MomentoCache/MomentoCache.ts new file mode 100644 index 00000000..2bd2625b --- /dev/null +++ b/packages/components/nodes/cache/MomentoCache/MomentoCache.ts @@ -0,0 +1,58 @@ +import { getBaseClasses, getCredentialData, getCredentialParam, ICommonObject, INode, INodeData, INodeParams } from '../../../src' +import { MomentoCache as LangchainMomentoCache } from 'langchain/cache/momento' +import { CacheClient, Configurations, CredentialProvider } from '@gomomento/sdk' + +class MomentoCache implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'Momento Cache' + this.name = 'momentoCache' + this.version = 1.0 + this.type = 'MomentoCache' + this.description = 'Cache LLM response using Momento, a distributed, serverless cache' + this.icon = 'momento.png' + this.category = 'Cache' + this.baseClasses = [this.type, ...getBaseClasses(LangchainMomentoCache)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + credentialNames: ['momentoCacheApi'] + } + this.inputs = [] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('momentoApiKey', credentialData, nodeData) + const cacheName = getCredentialParam('momentoCache', credentialData, nodeData) + + // See https://github.com/momentohq/client-sdk-javascript for connection options + const client = new CacheClient({ + configuration: Configurations.Laptop.v1(), + credentialProvider: CredentialProvider.fromString({ + apiKey: apiKey + }), + defaultTtlSeconds: 60 * 60 * 24 + }) + + let momentoCache = await LangchainMomentoCache.fromProps({ + client, + cacheName: cacheName + }) + return momentoCache + } +} + +module.exports = { nodeClass: MomentoCache } diff --git a/packages/components/nodes/cache/MomentoCache/momento.png b/packages/components/nodes/cache/MomentoCache/momento.png new file mode 100644 index 00000000..0f2b54b6 Binary files /dev/null and b/packages/components/nodes/cache/MomentoCache/momento.png differ diff --git a/packages/components/nodes/cache/RedisCache/RedisCache.ts b/packages/components/nodes/cache/RedisCache/RedisCache.ts new file mode 100644 index 00000000..38998a46 --- /dev/null +++ b/packages/components/nodes/cache/RedisCache/RedisCache.ts @@ -0,0 +1,118 @@ +import { getBaseClasses, getCredentialData, getCredentialParam, ICommonObject, INode, INodeData, INodeParams } from '../../../src' +import { RedisCache as LangchainRedisCache } from 'langchain/cache/ioredis' +import { Redis } from 'ioredis' +import { Generation, ChatGeneration, StoredGeneration, mapStoredMessageToChatMessage } from 'langchain/schema' +import hash from 'object-hash' + +class RedisCache implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'Redis Cache' + this.name = 'redisCache' + this.version = 1.0 + this.type = 'RedisCache' + this.description = 'Cache LLM response in Redis, useful for sharing cache across multiple processes or servers' + this.icon = 'redis.svg' + this.category = 'Cache' + this.baseClasses = [this.type, ...getBaseClasses(LangchainRedisCache)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + credentialNames: ['redisCacheApi'] + } + this.inputs = [ + { + label: 'Time to Live (ms)', + name: 'ttl', + type: 'number', + step: 1, + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const ttl = nodeData.inputs?.ttl as string + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const username = getCredentialParam('redisCacheUser', credentialData, nodeData) + const password = getCredentialParam('redisCachePwd', credentialData, nodeData) + const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) + const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + + const client = new Redis({ + port: portStr ? parseInt(portStr) : 6379, + host, + username, + password + }) + + const redisClient = new LangchainRedisCache(client) + + redisClient.lookup = async (prompt: string, llmKey: string) => { + let idx = 0 + let key = getCacheKey(prompt, llmKey, String(idx)) + let value = await client.get(key) + const generations: Generation[] = [] + + while (value) { + const storedGeneration = JSON.parse(value) + generations.push(deserializeStoredGeneration(storedGeneration)) + idx += 1 + key = getCacheKey(prompt, llmKey, String(idx)) + value = await client.get(key) + } + + return generations.length > 0 ? generations : null + } + + redisClient.update = async (prompt: string, llmKey: string, value: Generation[]) => { + for (let i = 0; i < value.length; i += 1) { + const key = getCacheKey(prompt, llmKey, String(i)) + if (ttl !== undefined) { + await client.set(key, JSON.stringify(serializeGeneration(value[i])), 'EX', parseInt(ttl, 10)) + } else { + await client.set(key, JSON.stringify(serializeGeneration(value[i]))) + } + } + } + + return redisClient + } +} + +const getCacheKey = (...strings: string[]): string => hash(strings.join('_')) +const deserializeStoredGeneration = (storedGeneration: StoredGeneration) => { + if (storedGeneration.message !== undefined) { + return { + text: storedGeneration.text, + message: mapStoredMessageToChatMessage(storedGeneration.message) + } + } else { + return { text: storedGeneration.text } + } +} +const serializeGeneration = (generation: Generation) => { + const serializedValue: StoredGeneration = { + text: generation.text + } + if ((generation as ChatGeneration).message !== undefined) { + serializedValue.message = (generation as ChatGeneration).message.toDict() + } + return serializedValue +} + +module.exports = { nodeClass: RedisCache } diff --git a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts new file mode 100644 index 00000000..4eecb1f5 --- /dev/null +++ b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts @@ -0,0 +1,90 @@ +import { getBaseClasses, getCredentialData, getCredentialParam, ICommonObject, INode, INodeData, INodeParams } from '../../../src' +import { Redis } from 'ioredis' +import { CacheBackedEmbeddings } from 'langchain/embeddings/cache_backed' +import { RedisByteStore } from 'langchain/storage/ioredis' +import { Embeddings } from 'langchain/embeddings/base' + +class RedisEmbeddingsCache implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'Redis Embeddings Cache' + this.name = 'redisEmbeddingsCache' + this.version = 1.0 + this.type = 'RedisEmbeddingsCache' + this.description = 'Cache generated Embeddings in Redis to avoid needing to recompute them.' + this.icon = 'redis.svg' + this.category = 'Cache' + this.baseClasses = [this.type, ...getBaseClasses(CacheBackedEmbeddings)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + credentialNames: ['redisCacheApi'] + } + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Time to Live (ms)', + name: 'ttl', + type: 'number', + step: 10, + default: 60 * 60, + optional: true, + additionalParams: true + }, + { + label: 'Namespace', + name: 'namespace', + type: 'string', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + let ttl = nodeData.inputs?.ttl as string + const namespace = nodeData.inputs?.namespace as string + const underlyingEmbeddings = nodeData.inputs?.embeddings as Embeddings + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const username = getCredentialParam('redisCacheUser', credentialData, nodeData) + const password = getCredentialParam('redisCachePwd', credentialData, nodeData) + const portStr = getCredentialParam('redisCachePort', credentialData, nodeData) + const host = getCredentialParam('redisCacheHost', credentialData, nodeData) + + const client = new Redis({ + port: portStr ? parseInt(portStr) : 6379, + host, + username, + password + }) + ttl ??= '3600' + let ttlNumber = parseInt(ttl, 10) + const redisStore = new RedisByteStore({ + client: client, + ttl: ttlNumber + }) + + return CacheBackedEmbeddings.fromBytesStore(underlyingEmbeddings, redisStore, { + namespace: namespace + }) + } +} + +module.exports = { nodeClass: RedisEmbeddingsCache } diff --git a/packages/components/nodes/cache/RedisCache/redis.svg b/packages/components/nodes/cache/RedisCache/redis.svg new file mode 100644 index 00000000..90359069 --- /dev/null +++ b/packages/components/nodes/cache/RedisCache/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/cache/UpstashRedisCache/UpstashRedisCache.ts b/packages/components/nodes/cache/UpstashRedisCache/UpstashRedisCache.ts new file mode 100644 index 00000000..f4ed947b --- /dev/null +++ b/packages/components/nodes/cache/UpstashRedisCache/UpstashRedisCache.ts @@ -0,0 +1,50 @@ +import { getBaseClasses, getCredentialData, getCredentialParam, ICommonObject, INode, INodeData, INodeParams } from '../../../src' +import { UpstashRedisCache as LangchainUpstashRedisCache } from 'langchain/cache/upstash_redis' + +class UpstashRedisCache implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + + constructor() { + this.label = 'Upstash Redis Cache' + this.name = 'upstashRedisCache' + this.version = 1.0 + this.type = 'UpstashRedisCache' + this.description = 'Cache LLM response in Upstash Redis, serverless data for Redis and Kafka' + this.icon = 'upstash.png' + this.category = 'Cache' + this.baseClasses = [this.type, ...getBaseClasses(LangchainUpstashRedisCache)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + credentialNames: ['upstashRedisApi'] + } + this.inputs = [] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const upstashConnectionUrl = getCredentialParam('upstashConnectionUrl', credentialData, nodeData) + const upstashToken = getCredentialParam('upstashConnectionToken', credentialData, nodeData) + + const cache = new LangchainUpstashRedisCache({ + config: { + url: upstashConnectionUrl, + token: upstashToken + } + }) + return cache + } +} + +module.exports = { nodeClass: UpstashRedisCache } diff --git a/packages/components/nodes/cache/UpstashRedisCache/upstash.png b/packages/components/nodes/cache/UpstashRedisCache/upstash.png new file mode 100644 index 00000000..e27e02f4 Binary files /dev/null and b/packages/components/nodes/cache/UpstashRedisCache/upstash.png differ diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index b26603e2..f12cd30b 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -90,7 +90,7 @@ class ConversationChain_Chains implements INode { verbose: process.env.DEBUG === 'true' ? true : false } - const chatPrompt = ChatPromptTemplate.fromPromptMessages([ + const chatPrompt = ChatPromptTemplate.fromMessages([ SystemMessagePromptTemplate.fromTemplate(prompt ? `${prompt}\n${systemMessage}` : systemMessage), new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'), HumanMessagePromptTemplate.fromTemplate('{input}') @@ -106,8 +106,12 @@ class ConversationChain_Chains implements INode { const memory = nodeData.inputs?.memory as BufferMemory if (options && options.chatHistory) { - memory.chatHistory = mapChatHistory(options) - chain.memory = memory + const chatHistoryClassName = memory.chatHistory.constructor.name + // Only replace when its In-Memory + if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { + memory.chatHistory = mapChatHistory(options) + chain.memory = memory + } } const loggerHandler = new ConsoleCallbackHandler(options.logger) diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index 1b4675bd..9a8c1b18 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -179,7 +179,11 @@ class ConversationalRetrievalQAChain_Chains implements INode { const obj = { question: input } if (options && options.chatHistory && chain.memory) { - ;(chain.memory as any).chatHistory = mapChatHistory(options) + const chatHistoryClassName = (chain.memory as any).chatHistory.constructor.name + // Only replace when its In-Memory + if (chatHistoryClassName && chatHistoryClassName === 'ChatMessageHistory') { + ;(chain.memory as any).chatHistory = mapChatHistory(options) + } } const loggerHandler = new ConsoleCallbackHandler(options.logger) diff --git a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts index ba9cd7f8..ac33fa0e 100644 --- a/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts +++ b/packages/components/nodes/chains/SqlDatabaseChain/SqlDatabaseChain.ts @@ -1,5 +1,5 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' -import { SqlDatabaseChain, SqlDatabaseChainInput } from 'langchain/chains/sql_db' +import { SqlDatabaseChain, SqlDatabaseChainInput, DEFAULT_SQL_DATABASE_PROMPT } from 'langchain/chains/sql_db' import { getBaseClasses, getInputVariables } from '../../../src/utils' import { DataSource } from 'typeorm' import { SqlDatabase } from 'langchain/sql_db' @@ -10,25 +10,6 @@ import { DataSourceOptions } from 'typeorm/data-source' type DatabaseType = 'sqlite' | 'postgres' | 'mssql' | 'mysql' -const defaultPrompt = `Given an input question, first create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer. Unless the user specifies in his question a specific number of examples he wishes to obtain, always limit your query to at most {top_k} results. You can order the results by a relevant column to return the most interesting examples in the database. - -Never query for all the columns from a specific table, only ask for a the few relevant columns given the question. - -Pay attention to use only the column names that you can see in the schema description. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table. - -Use the following format: - -Question: "Question here" -SQLQuery: "SQL Query to run" -SQLResult: "Result of the SQLQuery" -Answer: "Final answer here" - -Only use the tables listed below. - -{table_info} - -Question: {input}` - class SqlDatabaseChain_Chains implements INode { label: string name: string @@ -131,7 +112,7 @@ class SqlDatabaseChain_Chains implements INode { warning: 'Prompt must include 3 input variables: {input}, {dialect}, {table_info}. You can refer to official guide from description above', rows: 4, - placeholder: defaultPrompt, + placeholder: DEFAULT_SQL_DATABASE_PROMPT.template + DEFAULT_SQL_DATABASE_PROMPT.templateFormat, additionalParams: true, optional: true } diff --git a/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts b/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts index f5fa8bba..ade46ab9 100644 --- a/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts +++ b/packages/components/nodes/chatmodels/AWSBedrock/AWSChatBedrock.ts @@ -2,6 +2,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Inter import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ChatBedrock } from 'langchain/chat_models/bedrock' import { BaseBedrockInput } from 'langchain/dist/util/bedrock' +import { BaseCache } from 'langchain/schema' +import { BaseLLMParams } from 'langchain/llms/base' /** * I had to run the following to build the component @@ -25,11 +27,11 @@ class AWSChatBedrock_ChatModels implements INode { constructor() { this.label = 'AWS Bedrock' this.name = 'awsChatBedrock' - this.version = 1.1 + this.version = 2.0 this.type = 'AWSChatBedrock' this.icon = 'awsBedrock.png' this.category = 'Chat Models' - this.description = 'Wrapper around AWS Bedrock large language models' + this.description = 'Wrapper around AWS Bedrock large language models that use the Chat endpoint' this.baseClasses = [this.type, ...getBaseClasses(ChatBedrock)] this.credential = { label: 'AWS Credential', @@ -39,6 +41,12 @@ class AWSChatBedrock_ChatModels implements INode { optional: true } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Region', name: 'region', @@ -80,27 +88,18 @@ class AWSChatBedrock_ChatModels implements INode { { label: 'us-west-1', name: 'us-west-1' }, { label: 'us-west-2', name: 'us-west-2' } ], - default: 'us-east-1', - optional: false + default: 'us-east-1' }, { label: 'Model Name', name: 'model', type: 'options', options: [ - { label: 'amazon.titan-tg1-large', name: 'amazon.titan-tg1-large' }, - { label: 'amazon.titan-e1t-medium', name: 'amazon.titan-e1t-medium' }, - { label: 'stability.stable-diffusion-xl', name: 'stability.stable-diffusion-xl' }, - { label: 'ai21.j2-grande-instruct', name: 'ai21.j2-grande-instruct' }, - { label: 'ai21.j2-jumbo-instruct', name: 'ai21.j2-jumbo-instruct' }, - { label: 'ai21.j2-mid', name: 'ai21.j2-mid' }, - { label: 'ai21.j2-ultra', name: 'ai21.j2-ultra' }, { label: 'anthropic.claude-instant-v1', name: 'anthropic.claude-instant-v1' }, { label: 'anthropic.claude-v1', name: 'anthropic.claude-v1' }, { label: 'anthropic.claude-v2', name: 'anthropic.claude-v2' } ], - default: 'anthropic.claude-v2', - optional: false + default: 'anthropic.claude-v2' }, { label: 'Temperature', @@ -109,8 +108,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, - default: 0.7, - additionalParams: false + default: 0.7 }, { label: 'Max Tokens to Sample', @@ -118,9 +116,8 @@ class AWSChatBedrock_ChatModels implements INode { type: 'number', step: 10, description: 'Max Tokens parameter may not apply to certain model. Please check available model parameters', - optional: false, - default: 200, - additionalParams: false + optional: true, + default: 200 } ] } @@ -130,8 +127,9 @@ class AWSChatBedrock_ChatModels implements INode { const iModel = nodeData.inputs?.model 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: BaseBedrockInput = { + const obj: BaseBedrockInput & BaseLLMParams = { region: iRegion, model: iModel, maxTokens: parseInt(iMax_tokens_to_sample, 10), @@ -157,6 +155,7 @@ class AWSChatBedrock_ChatModels implements INode { sessionToken: credentialApiSession } } + if (cache) obj.cache = cache const amazonBedrock = new ChatBedrock(obj) return amazonBedrock diff --git a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts index 90f430f0..99e151e6 100644 --- a/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/AzureChatOpenAI/AzureChatOpenAI.ts @@ -2,6 +2,8 @@ import { OpenAIBaseInput } from 'langchain/dist/types/openai-types' import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { AzureOpenAIInput, ChatOpenAI } from 'langchain/chat_models/openai' +import { BaseCache } from 'langchain/schema' +import { BaseLLMParams } from 'langchain/llms/base' class AzureChatOpenAI_ChatModels implements INode { label: string @@ -18,7 +20,7 @@ class AzureChatOpenAI_ChatModels implements INode { constructor() { this.label = 'Azure ChatOpenAI' this.name = 'azureChatOpenAI' - this.version = 1.0 + this.version = 2.0 this.type = 'AzureChatOpenAI' this.icon = 'Azure.svg' this.category = 'Chat Models' @@ -31,6 +33,12 @@ class AzureChatOpenAI_ChatModels implements INode { credentialNames: ['azureOpenAIApi'] } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model Name', name: 'modelName', @@ -107,6 +115,7 @@ class AzureChatOpenAI_ChatModels implements INode { const presencePenalty = nodeData.inputs?.presencePenalty as string const timeout = nodeData.inputs?.timeout as string const streaming = nodeData.inputs?.streaming as boolean + const cache = nodeData.inputs?.cache as BaseCache const credentialData = await getCredentialData(nodeData.credential ?? '', options) const azureOpenAIApiKey = getCredentialParam('azureOpenAIApiKey', credentialData, nodeData) @@ -114,7 +123,7 @@ class AzureChatOpenAI_ChatModels implements INode { const azureOpenAIApiDeploymentName = getCredentialParam('azureOpenAIApiDeploymentName', credentialData, nodeData) const azureOpenAIApiVersion = getCredentialParam('azureOpenAIApiVersion', credentialData, nodeData) - const obj: Partial & Partial = { + const obj: Partial & BaseLLMParams & Partial = { temperature: parseFloat(temperature), modelName, azureOpenAIApiKey, @@ -128,6 +137,7 @@ class AzureChatOpenAI_ChatModels implements INode { if (frequencyPenalty) obj.frequencyPenalty = parseFloat(frequencyPenalty) if (presencePenalty) obj.presencePenalty = parseFloat(presencePenalty) if (timeout) obj.timeout = parseInt(timeout, 10) + if (cache) obj.cache = cache const model = new ChatOpenAI(obj) return model diff --git a/packages/components/nodes/chatmodels/Bittensor/Bittensor.ts b/packages/components/nodes/chatmodels/Bittensor/Bittensor.ts index cfcac863..36b084e6 100644 --- a/packages/components/nodes/chatmodels/Bittensor/Bittensor.ts +++ b/packages/components/nodes/chatmodels/Bittensor/Bittensor.ts @@ -1,6 +1,7 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' import { NIBittensorChatModel, BittensorInput } from 'langchain/experimental/chat_models/bittensor' +import { BaseCache } from 'langchain/schema' class Bittensor_ChatModels implements INode { label: string @@ -16,13 +17,19 @@ class Bittensor_ChatModels implements INode { constructor() { this.label = 'NIBittensorChat' this.name = 'NIBittensorChatModel' - this.version = 1.0 + this.version = 2.0 this.type = 'BittensorChat' this.icon = 'logo.png' this.category = 'Chat Models' this.description = 'Wrapper around Bittensor subnet 1 large language models' this.baseClasses = [this.type, ...getBaseClasses(NIBittensorChatModel)] this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'System prompt', name: 'system_prompt', @@ -35,9 +42,13 @@ class Bittensor_ChatModels implements INode { async init(nodeData: INodeData, _: string): Promise { const system_prompt = nodeData.inputs?.system_prompt as string + const cache = nodeData.inputs?.cache as BaseCache + const obj: Partial = { systemPrompt: system_prompt } + if (cache) obj.cache = cache + const model = new NIBittensorChatModel(obj) return model } diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts index 12a33d99..f16968b6 100644 --- a/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts +++ b/packages/components/nodes/chatmodels/ChatAnthropic/ChatAnthropic.ts @@ -1,6 +1,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { AnthropicInput, ChatAnthropic } from 'langchain/chat_models/anthropic' +import { BaseCache } from 'langchain/schema' +import { BaseLLMParams } from 'langchain/llms/base' class ChatAnthropic_ChatModels implements INode { label: string @@ -17,7 +19,7 @@ class ChatAnthropic_ChatModels implements INode { constructor() { this.label = 'ChatAnthropic' this.name = 'chatAnthropic' - this.version = 1.0 + this.version = 2.0 this.type = 'ChatAnthropic' this.icon = 'chatAnthropic.png' this.category = 'Chat Models' @@ -30,6 +32,12 @@ class ChatAnthropic_ChatModels implements INode { credentialNames: ['anthropicApi'] } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model Name', name: 'modelName', @@ -135,11 +143,12 @@ class ChatAnthropic_ChatModels implements INode { const topP = nodeData.inputs?.topP as string const topK = nodeData.inputs?.topK as string const streaming = nodeData.inputs?.streaming as boolean + const cache = nodeData.inputs?.cache as BaseCache const credentialData = await getCredentialData(nodeData.credential ?? '', options) const anthropicApiKey = getCredentialParam('anthropicApiKey', credentialData, nodeData) - const obj: Partial & { anthropicApiKey?: string } = { + const obj: Partial & BaseLLMParams & { anthropicApiKey?: string } = { temperature: parseFloat(temperature), modelName, anthropicApiKey, @@ -149,6 +158,7 @@ class ChatAnthropic_ChatModels implements INode { if (maxTokensToSample) obj.maxTokensToSample = parseInt(maxTokensToSample, 10) if (topP) obj.topP = parseFloat(topP) if (topK) obj.topK = parseFloat(topK) + if (cache) obj.cache = cache const model = new ChatAnthropic(obj) return model diff --git a/packages/components/nodes/chatmodels/ChatGooglePaLM/ChatGooglePaLM.ts b/packages/components/nodes/chatmodels/ChatGooglePaLM/ChatGooglePaLM.ts index 642ddb5e..121406c7 100644 --- a/packages/components/nodes/chatmodels/ChatGooglePaLM/ChatGooglePaLM.ts +++ b/packages/components/nodes/chatmodels/ChatGooglePaLM/ChatGooglePaLM.ts @@ -1,6 +1,7 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ChatGooglePaLM, GooglePaLMChatInput } from 'langchain/chat_models/googlepalm' +import { BaseCache } from 'langchain/schema' class ChatGooglePaLM_ChatModels implements INode { label: string @@ -17,7 +18,7 @@ class ChatGooglePaLM_ChatModels implements INode { constructor() { this.label = 'ChatGooglePaLM' this.name = 'chatGooglePaLM' - this.version = 1.0 + this.version = 2.0 this.type = 'ChatGooglePaLM' this.icon = 'Google_PaLM_Logo.svg' this.category = 'Chat Models' @@ -30,6 +31,12 @@ class ChatGooglePaLM_ChatModels implements INode { credentialNames: ['googleMakerSuite'] } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model Name', name: 'modelName', @@ -96,6 +103,7 @@ class ChatGooglePaLM_ChatModels implements INode { const temperature = nodeData.inputs?.temperature as string const topP = nodeData.inputs?.topP as string const topK = nodeData.inputs?.topK as string + const cache = nodeData.inputs?.cache as BaseCache const credentialData = await getCredentialData(nodeData.credential ?? '', options) const googleMakerSuiteKey = getCredentialParam('googleMakerSuiteKey', credentialData, nodeData) @@ -108,6 +116,7 @@ class ChatGooglePaLM_ChatModels implements INode { if (topP) obj.topP = parseFloat(topP) if (topK) obj.topK = parseFloat(topK) + if (cache) obj.cache = cache const model = new ChatGooglePaLM(obj) return model diff --git a/packages/components/nodes/chatmodels/ChatGoogleVertexAI/ChatGoogleVertexAI.ts b/packages/components/nodes/chatmodels/ChatGoogleVertexAI/ChatGoogleVertexAI.ts index 4cb206f5..6b070bd9 100644 --- a/packages/components/nodes/chatmodels/ChatGoogleVertexAI/ChatGoogleVertexAI.ts +++ b/packages/components/nodes/chatmodels/ChatGoogleVertexAI/ChatGoogleVertexAI.ts @@ -2,6 +2,7 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Inter import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ChatGoogleVertexAI, GoogleVertexAIChatInput } from 'langchain/chat_models/googlevertexai' import { GoogleAuthOptions } from 'google-auth-library' +import { BaseCache } from 'langchain/schema' class GoogleVertexAI_ChatModels implements INode { label: string @@ -18,7 +19,7 @@ class GoogleVertexAI_ChatModels implements INode { constructor() { this.label = 'ChatGoogleVertexAI' this.name = 'chatGoogleVertexAI' - this.version = 1.0 + this.version = 2.0 this.type = 'ChatGoogleVertexAI' this.icon = 'vertexai.svg' this.category = 'Chat Models' @@ -34,6 +35,12 @@ class GoogleVertexAI_ChatModels implements INode { 'Google Vertex AI credential. If you are using a GCP service like Cloud Run, or if you have installed default credentials on your local machine, you do not need to set this credential.' } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model Name', name: 'modelName', @@ -113,6 +120,7 @@ class GoogleVertexAI_ChatModels implements INode { 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: GoogleVertexAIChatInput = { temperature: parseFloat(temperature), @@ -122,6 +130,7 @@ class GoogleVertexAI_ChatModels implements INode { if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) if (topP) obj.topP = parseFloat(topP) + if (cache) obj.cache = cache const model = new ChatGoogleVertexAI(obj) return model diff --git a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts index ee55c7bb..153c5d10 100644 --- a/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts +++ b/packages/components/nodes/chatmodels/ChatHuggingFace/ChatHuggingFace.ts @@ -1,6 +1,7 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { HFInput, HuggingFaceInference } from './core' +import { BaseCache } from 'langchain/schema' class ChatHuggingFace_ChatModels implements INode { label: string @@ -17,7 +18,7 @@ class ChatHuggingFace_ChatModels implements INode { constructor() { this.label = 'ChatHuggingFace' this.name = 'chatHuggingFace' - this.version = 1.0 + this.version = 2.0 this.type = 'ChatHuggingFace' this.icon = 'huggingface.png' this.category = 'Chat Models' @@ -30,6 +31,12 @@ class ChatHuggingFace_ChatModels implements INode { credentialNames: ['huggingFaceApi'] } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model', name: 'model', @@ -102,6 +109,7 @@ class ChatHuggingFace_ChatModels implements INode { const hfTopK = nodeData.inputs?.hfTopK as string const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string const endpoint = nodeData.inputs?.endpoint as string + const cache = nodeData.inputs?.cache as BaseCache const credentialData = await getCredentialData(nodeData.credential ?? '', options) const huggingFaceApiKey = getCredentialParam('huggingFaceApiKey', credentialData, nodeData) @@ -119,6 +127,7 @@ class ChatHuggingFace_ChatModels implements INode { if (endpoint) obj.endpoint = endpoint const huggingFace = new HuggingFaceInference(obj) + if (cache) huggingFace.cache = cache return huggingFace } } diff --git a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts index a6ddfae4..18ed409b 100644 --- a/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts +++ b/packages/components/nodes/chatmodels/ChatLocalAI/ChatLocalAI.ts @@ -2,6 +2,8 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' import { OpenAIChat } from 'langchain/llms/openai' import { OpenAIChatInput } from 'langchain/chat_models/openai' +import { BaseCache } from 'langchain/schema' +import { BaseLLMParams } from 'langchain/llms/base' class ChatLocalAI_ChatModels implements INode { label: string @@ -17,13 +19,19 @@ class ChatLocalAI_ChatModels implements INode { constructor() { this.label = 'ChatLocalAI' this.name = 'chatLocalAI' - this.version = 1.0 + this.version = 2.0 this.type = 'ChatLocalAI' this.icon = 'localai.png' this.category = 'Chat Models' this.description = 'Use local LLMs like llama.cpp, gpt4all using LocalAI' this.baseClasses = [this.type, 'BaseChatModel', ...getBaseClasses(OpenAIChat)] this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Base Path', name: 'basePath', @@ -78,8 +86,9 @@ class ChatLocalAI_ChatModels implements INode { const topP = nodeData.inputs?.topP as string const timeout = nodeData.inputs?.timeout as string const basePath = nodeData.inputs?.basePath as string + const cache = nodeData.inputs?.cache as BaseCache - const obj: Partial & { openAIApiKey?: string } = { + const obj: Partial & BaseLLMParams & { openAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, openAIApiKey: 'sk-' @@ -88,6 +97,7 @@ class ChatLocalAI_ChatModels implements INode { if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) if (topP) obj.topP = parseFloat(topP) if (timeout) obj.timeout = parseInt(timeout, 10) + if (cache) obj.cache = cache const model = new OpenAIChat(obj, { basePath }) diff --git a/packages/components/nodes/chatmodels/ChatOllama/ChatOllama.ts b/packages/components/nodes/chatmodels/ChatOllama/ChatOllama.ts new file mode 100644 index 00000000..31267743 --- /dev/null +++ b/packages/components/nodes/chatmodels/ChatOllama/ChatOllama.ts @@ -0,0 +1,241 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { ChatOllama } from 'langchain/chat_models/ollama' +import { BaseCache } from 'langchain/schema' +import { OllamaInput } from 'langchain/dist/util/ollama' +import { BaseLLMParams } from 'langchain/llms/base' + +class ChatOllama_ChatModels implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'ChatOllama' + this.name = 'chatOllama' + this.version = 2.0 + this.type = 'ChatOllama' + this.icon = 'ollama.png' + this.category = 'Chat Models' + this.description = 'Chat completion using open-source LLM on Ollama' + this.baseClasses = [this.type, ...getBaseClasses(ChatOllama)] + this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, + { + label: 'Base URL', + name: 'baseUrl', + type: 'string', + default: 'http://localhost:11434' + }, + { + label: 'Model Name', + name: 'modelName', + type: 'string', + placeholder: 'llama2' + }, + { + label: 'Temperature', + name: 'temperature', + type: 'number', + description: + 'The temperature of the model. Increasing the temperature will make the model answer more creatively. (Default: 0.8). Refer to docs for more details', + step: 0.1, + default: 0.9, + optional: true + }, + { + label: 'Top P', + name: 'topP', + type: 'number', + description: + 'Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9). Refer to docs for more details', + step: 0.1, + optional: true, + additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + type: 'number', + description: + 'Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40). Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Mirostat', + name: 'mirostat', + type: 'number', + description: + 'Enable Mirostat sampling for controlling perplexity. (default: 0, 0 = disabled, 1 = Mirostat, 2 = Mirostat 2.0). Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Mirostat ETA', + name: 'mirostatEta', + type: 'number', + description: + 'Influences how quickly the algorithm responds to feedback from the generated text. A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive. (Default: 0.1) Refer to docs for more details', + step: 0.1, + optional: true, + additionalParams: true + }, + { + label: 'Mirostat TAU', + name: 'mirostatTau', + type: 'number', + description: + 'Controls the balance between coherence and diversity of the output. A lower value will result in more focused and coherent text. (Default: 5.0) Refer to docs for more details', + step: 0.1, + optional: true, + additionalParams: true + }, + { + label: 'Context Window Size', + name: 'numCtx', + type: 'number', + description: + 'Sets the size of the context window used to generate the next token. (Default: 2048) Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Number of GQA groups', + name: 'numGqa', + type: 'number', + description: + 'The number of GQA groups in the transformer layer. Required for some models, for example it is 8 for llama2:70b. Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Number of GPU', + name: 'numGpu', + type: 'number', + description: + 'The number of layers to send to the GPU(s). On macOS it defaults to 1 to enable metal support, 0 to disable. Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Number of Thread', + name: 'numThread', + type: 'number', + description: + 'Sets the number of threads to use during computation. By default, Ollama will detect this for optimal performance. It is recommended to set this value to the number of physical CPU cores your system has (as opposed to the logical number of cores). Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Repeat Last N', + name: 'repeatLastN', + type: 'number', + description: + 'Sets how far back for the model to look back to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx). Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Repeat Penalty', + name: 'repeatPenalty', + type: 'number', + description: + 'Sets how strongly to penalize repetitions. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. (Default: 1.1). Refer to docs for more details', + step: 0.1, + optional: true, + additionalParams: true + }, + { + label: 'Stop Sequence', + name: 'stop', + type: 'string', + rows: 4, + placeholder: 'AI assistant:', + description: + 'Sets the stop sequences to use. Use comma to seperate different sequences. Refer to docs for more details', + optional: true, + additionalParams: true + }, + { + label: 'Tail Free Sampling', + name: 'tfsZ', + type: 'number', + description: + 'Tail free sampling is used to reduce the impact of less probable tokens from the output. A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting. (Default: 1). Refer to docs for more details', + step: 0.1, + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const temperature = nodeData.inputs?.temperature as string + const baseUrl = nodeData.inputs?.baseUrl as string + const modelName = nodeData.inputs?.modelName as string + const topP = nodeData.inputs?.topP as string + const topK = nodeData.inputs?.topK as string + const mirostat = nodeData.inputs?.mirostat as string + const mirostatEta = nodeData.inputs?.mirostatEta as string + const mirostatTau = nodeData.inputs?.mirostatTau as string + const numCtx = nodeData.inputs?.numCtx as string + const numGqa = nodeData.inputs?.numGqa as string + const numGpu = nodeData.inputs?.numGpu as string + const numThread = nodeData.inputs?.numThread as string + const repeatLastN = nodeData.inputs?.repeatLastN as string + const repeatPenalty = nodeData.inputs?.repeatPenalty as string + const stop = nodeData.inputs?.stop as string + const tfsZ = nodeData.inputs?.tfsZ as string + + const cache = nodeData.inputs?.cache as BaseCache + + const obj: OllamaInput & BaseLLMParams = { + baseUrl, + temperature: parseFloat(temperature), + model: modelName + } + + if (topP) obj.topP = parseFloat(topP) + if (topK) obj.topK = parseFloat(topK) + if (mirostat) obj.mirostat = parseFloat(mirostat) + if (mirostatEta) obj.mirostatEta = parseFloat(mirostatEta) + if (mirostatTau) obj.mirostatTau = parseFloat(mirostatTau) + if (numCtx) obj.numCtx = parseFloat(numCtx) + if (numGqa) obj.numGqa = parseFloat(numGqa) + if (numGpu) obj.numGpu = parseFloat(numGpu) + if (numThread) obj.numThread = parseFloat(numThread) + if (repeatLastN) obj.repeatLastN = parseFloat(repeatLastN) + if (repeatPenalty) obj.repeatPenalty = parseFloat(repeatPenalty) + if (tfsZ) obj.tfsZ = parseFloat(tfsZ) + if (stop) { + const stopSequences = stop.split(',') + obj.stop = stopSequences + } + if (cache) obj.cache = cache + + const model = new ChatOllama(obj) + return model + } +} + +module.exports = { nodeClass: ChatOllama_ChatModels } diff --git a/packages/components/nodes/chatmodels/ChatOllama/ollama.png b/packages/components/nodes/chatmodels/ChatOllama/ollama.png new file mode 100644 index 00000000..8cd2cf1e Binary files /dev/null and b/packages/components/nodes/chatmodels/ChatOllama/ollama.png differ diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts index ca081ff4..f74bd642 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts @@ -1,6 +1,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ChatOpenAI, OpenAIChatInput } from 'langchain/chat_models/openai' +import { BaseCache } from 'langchain/schema' +import { BaseLLMParams } from 'langchain/llms/base' class ChatOpenAI_ChatModels implements INode { label: string @@ -17,7 +19,7 @@ class ChatOpenAI_ChatModels implements INode { constructor() { this.label = 'ChatOpenAI' this.name = 'chatOpenAI' - this.version = 1.0 + this.version = 2.0 this.type = 'ChatOpenAI' this.icon = 'openai.png' this.category = 'Chat Models' @@ -30,6 +32,12 @@ class ChatOpenAI_ChatModels implements INode { credentialNames: ['openAIApi'] } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model Name', name: 'modelName', @@ -151,7 +159,9 @@ class ChatOpenAI_ChatModels implements INode { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) - const obj: Partial & { openAIApiKey?: string } = { + const cache = nodeData.inputs?.cache as BaseCache + + const obj: Partial & BaseLLMParams & { openAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, openAIApiKey, @@ -163,6 +173,7 @@ class ChatOpenAI_ChatModels implements INode { if (frequencyPenalty) obj.frequencyPenalty = parseFloat(frequencyPenalty) if (presencePenalty) obj.presencePenalty = parseFloat(presencePenalty) if (timeout) obj.timeout = parseInt(timeout, 10) + if (cache) obj.cache = cache let parsedBaseOptions: any | undefined = undefined diff --git a/packages/components/nodes/chatmodels/ChatOpenAICustom/ChatOpenAICustom.ts b/packages/components/nodes/chatmodels/ChatOpenAICustom/ChatOpenAICustom.ts index 29c1181a..2a01a2e5 100644 --- a/packages/components/nodes/chatmodels/ChatOpenAICustom/ChatOpenAICustom.ts +++ b/packages/components/nodes/chatmodels/ChatOpenAICustom/ChatOpenAICustom.ts @@ -1,6 +1,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { ChatOpenAI, OpenAIChatInput } from 'langchain/chat_models/openai' +import { BaseCache } from 'langchain/schema' +import { BaseLLMParams } from 'langchain/llms/base' class ChatOpenAICustom_ChatModels implements INode { label: string @@ -17,7 +19,7 @@ class ChatOpenAICustom_ChatModels implements INode { constructor() { this.label = 'ChatOpenAI Custom' this.name = 'chatOpenAICustom' - this.version = 1.0 + this.version = 2.0 this.type = 'ChatOpenAI-Custom' this.icon = 'openai.png' this.category = 'Chat Models' @@ -31,6 +33,12 @@ class ChatOpenAICustom_ChatModels implements INode { optional: true } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model Name', name: 'modelName', @@ -113,11 +121,12 @@ class ChatOpenAICustom_ChatModels implements INode { const streaming = nodeData.inputs?.streaming as boolean const basePath = nodeData.inputs?.basepath as string const baseOptions = nodeData.inputs?.baseOptions + const cache = nodeData.inputs?.cache as BaseCache const credentialData = await getCredentialData(nodeData.credential ?? '', options) const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) - const obj: Partial & { openAIApiKey?: string } = { + const obj: Partial & BaseLLMParams & { openAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, openAIApiKey, @@ -129,6 +138,7 @@ class ChatOpenAICustom_ChatModels implements INode { if (frequencyPenalty) obj.frequencyPenalty = parseFloat(frequencyPenalty) if (presencePenalty) obj.presencePenalty = parseFloat(presencePenalty) if (timeout) obj.timeout = parseInt(timeout, 10) + if (cache) obj.cache = cache let parsedBaseOptions: any | undefined = undefined diff --git a/packages/components/nodes/documentloaders/PlainText/PlainText.ts b/packages/components/nodes/documentloaders/PlainText/PlainText.ts index 261f2d98..c2adceeb 100644 --- a/packages/components/nodes/documentloaders/PlainText/PlainText.ts +++ b/packages/components/nodes/documentloaders/PlainText/PlainText.ts @@ -1,6 +1,7 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { Document } from 'langchain/document' +import { handleEscapeCharacters } from '../../../src' class PlainText_DocumentLoaders implements INode { label: string @@ -12,11 +13,12 @@ class PlainText_DocumentLoaders implements INode { category: string baseClasses: string[] inputs: INodeParams[] + outputs: INodeOutputsValue[] constructor() { this.label = 'Plain Text' this.name = 'plainText' - this.version = 1.0 + this.version = 2.0 this.type = 'Document' this.icon = 'plaintext.svg' this.category = 'Document Loaders' @@ -45,12 +47,25 @@ class PlainText_DocumentLoaders implements INode { additionalParams: true } ] + this.outputs = [ + { + label: 'Document', + name: 'document', + baseClasses: this.baseClasses + }, + { + label: 'Text', + name: 'text', + baseClasses: ['string', 'json'] + } + ] } async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const text = nodeData.inputs?.text as string const metadata = nodeData.inputs?.metadata + const output = nodeData.outputs?.output as string let alldocs: Document>[] = [] @@ -65,9 +80,9 @@ class PlainText_DocumentLoaders implements INode { ) } + let finaldocs: Document>[] = [] if (metadata) { const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) - let finaldocs: Document>[] = [] for (const doc of alldocs) { const newdoc = { ...doc, @@ -78,10 +93,19 @@ class PlainText_DocumentLoaders implements INode { } finaldocs.push(newdoc) } - return finaldocs + } else { + finaldocs = alldocs } - return alldocs + if (output === 'document') { + return finaldocs + } else { + let finaltext = '' + for (const doc of finaldocs) { + finaltext += `${doc.pageContent}\n` + } + return handleEscapeCharacters(finaltext, false) + } } } diff --git a/packages/components/nodes/documentloaders/S3File/S3File.ts b/packages/components/nodes/documentloaders/S3File/S3File.ts new file mode 100644 index 00000000..07295aba --- /dev/null +++ b/packages/components/nodes/documentloaders/S3File/S3File.ts @@ -0,0 +1,241 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { S3Loader } from 'langchain/document_loaders/web/s3' +import { UnstructuredLoader } from 'langchain/document_loaders/fs/unstructured' +import { getCredentialData, getCredentialParam } from '../../../src/utils' +import { S3Client, GetObjectCommand, S3ClientConfig } from '@aws-sdk/client-s3' +import { Readable } from 'node:stream' +import * as fsDefault from 'node:fs' +import * as path from 'node:path' +import * as os from 'node:os' + +type S3Config = S3ClientConfig & { + /** @deprecated Use the credentials object instead */ + accessKeyId?: string + /** @deprecated Use the credentials object instead */ + secretAccessKey?: string +} + +class S3_DocumentLoaders implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + credential: INodeParams + inputs?: INodeParams[] + + constructor() { + this.label = 'S3' + this.name = 'S3' + this.version = 1.0 + this.type = 'Document' + this.icon = 's3.svg' + this.category = 'Document Loaders' + this.description = 'Load Data from S3 Buckets' + this.baseClasses = [this.type] + this.credential = { + label: 'AWS Credential', + name: 'credential', + type: 'credential', + credentialNames: ['awsApi'] + } + this.inputs = [ + { + label: 'Bucket', + name: 'bucketName', + type: 'string' + }, + { + label: 'Object Key', + name: 'keyName', + type: 'string', + description: 'The object key (or key name) that uniquely identifies object in an Amazon S3 bucket', + placeholder: 'AI-Paper.pdf' + }, + { + label: 'Region', + name: 'region', + type: 'options', + options: [ + { label: 'af-south-1', name: 'af-south-1' }, + { label: 'ap-east-1', name: 'ap-east-1' }, + { label: 'ap-northeast-1', name: 'ap-northeast-1' }, + { label: 'ap-northeast-2', name: 'ap-northeast-2' }, + { label: 'ap-northeast-3', name: 'ap-northeast-3' }, + { label: 'ap-south-1', name: 'ap-south-1' }, + { label: 'ap-south-2', name: 'ap-south-2' }, + { label: 'ap-southeast-1', name: 'ap-southeast-1' }, + { label: 'ap-southeast-2', name: 'ap-southeast-2' }, + { label: 'ap-southeast-3', name: 'ap-southeast-3' }, + { label: 'ap-southeast-4', name: 'ap-southeast-4' }, + { label: 'ap-southeast-5', name: 'ap-southeast-5' }, + { label: 'ap-southeast-6', name: 'ap-southeast-6' }, + { label: 'ca-central-1', name: 'ca-central-1' }, + { label: 'ca-west-1', name: 'ca-west-1' }, + { label: 'cn-north-1', name: 'cn-north-1' }, + { label: 'cn-northwest-1', name: 'cn-northwest-1' }, + { label: 'eu-central-1', name: 'eu-central-1' }, + { label: 'eu-central-2', name: 'eu-central-2' }, + { label: 'eu-north-1', name: 'eu-north-1' }, + { label: 'eu-south-1', name: 'eu-south-1' }, + { label: 'eu-south-2', name: 'eu-south-2' }, + { label: 'eu-west-1', name: 'eu-west-1' }, + { label: 'eu-west-2', name: 'eu-west-2' }, + { label: 'eu-west-3', name: 'eu-west-3' }, + { label: 'il-central-1', name: 'il-central-1' }, + { label: 'me-central-1', name: 'me-central-1' }, + { label: 'me-south-1', name: 'me-south-1' }, + { label: 'sa-east-1', name: 'sa-east-1' }, + { label: 'us-east-1', name: 'us-east-1' }, + { label: 'us-east-2', name: 'us-east-2' }, + { label: 'us-gov-east-1', name: 'us-gov-east-1' }, + { label: 'us-gov-west-1', name: 'us-gov-west-1' }, + { label: 'us-west-1', name: 'us-west-1' }, + { label: 'us-west-2', name: 'us-west-2' } + ], + default: 'us-east-1' + }, + { + label: 'Unstructured API URL', + name: 'unstructuredAPIUrl', + description: + 'Your Unstructured.io URL. Read more on how to get started', + type: 'string', + default: 'http://localhost:8000/general/v0/general' + }, + { + label: 'Unstructured API KEY', + name: 'unstructuredAPIKey', + type: 'password', + optional: true + }, + { + label: 'NarrativeText Only', + name: 'narrativeTextOnly', + description: + 'Only load documents with NarrativeText metadata from Unstructured. See how Unstructured partition data here', + default: true, + type: 'boolean', + optional: true, + additionalParams: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const bucketName = nodeData.inputs?.bucketName as string + const keyName = nodeData.inputs?.keyName as string + const region = nodeData.inputs?.region as string + const unstructuredAPIUrl = nodeData.inputs?.unstructuredAPIUrl as string + const unstructuredAPIKey = nodeData.inputs?.unstructuredAPIKey as string + const metadata = nodeData.inputs?.metadata + const narrativeTextOnly = nodeData.inputs?.narrativeTextOnly as boolean + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const accessKeyId = getCredentialParam('awsKey', credentialData, nodeData) + const secretAccessKey = getCredentialParam('awsSecret', credentialData, nodeData) + + const loader = new S3Loader({ + bucket: bucketName, + key: keyName, + s3Config: { + region, + credentials: { + accessKeyId, + secretAccessKey + } + }, + unstructuredAPIURL: unstructuredAPIUrl, + unstructuredAPIKey: unstructuredAPIKey + }) + + const s3Config: S3Config & { + accessKeyId?: string + secretAccessKey?: string + } = { + accessKeyId, + secretAccessKey + } + + loader.load = async () => { + const tempDir = fsDefault.mkdtempSync(path.join(os.tmpdir(), 's3fileloader-')) + + const filePath = path.join(tempDir, keyName) + + try { + const s3Client = new S3Client(s3Config) + + const getObjectCommand = new GetObjectCommand({ + Bucket: bucketName, + Key: keyName + }) + + const response = await s3Client.send(getObjectCommand) + + const objectData = await new Promise((resolve, reject) => { + const chunks: Buffer[] = [] + + if (response.Body instanceof Readable) { + response.Body.on('data', (chunk: Buffer) => chunks.push(chunk)) + response.Body.on('end', () => resolve(Buffer.concat(chunks))) + response.Body.on('error', reject) + } else { + reject(new Error('Response body is not a readable stream.')) + } + }) + + fsDefault.mkdirSync(path.dirname(filePath), { recursive: true }) + + fsDefault.writeFileSync(filePath, objectData) + } catch (e: any) { + throw new Error(`Failed to download file ${keyName} from S3 bucket ${bucketName}: ${e.message}`) + } + + try { + const options = { + apiUrl: unstructuredAPIUrl, + apiKey: unstructuredAPIKey + } + + const unstructuredLoader = new UnstructuredLoader(filePath, options) + + const docs = await unstructuredLoader.load() + + fsDefault.rmdirSync(path.dirname(filePath), { recursive: true }) + + return docs + } catch { + fsDefault.rmdirSync(path.dirname(filePath), { recursive: true }) + throw new Error(`Failed to load file ${filePath} using unstructured loader.`) + } + } + + const docs = await loader.load() + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + const finaldocs = docs.map((doc) => { + return { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + }) + return narrativeTextOnly ? finaldocs.filter((doc) => doc.metadata.category === 'NarrativeText') : finaldocs + } + + return narrativeTextOnly ? docs.filter((doc) => doc.metadata.category === 'NarrativeText') : docs + } +} +module.exports = { nodeClass: S3_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/S3File/s3.svg b/packages/components/nodes/documentloaders/S3File/s3.svg new file mode 100644 index 00000000..cd203eaa --- /dev/null +++ b/packages/components/nodes/documentloaders/S3File/s3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/nodes/documentloaders/Text/Text.ts b/packages/components/nodes/documentloaders/Text/Text.ts index dacf087c..c3e3b61e 100644 --- a/packages/components/nodes/documentloaders/Text/Text.ts +++ b/packages/components/nodes/documentloaders/Text/Text.ts @@ -1,6 +1,8 @@ -import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import { TextSplitter } from 'langchain/text_splitter' import { TextLoader } from 'langchain/document_loaders/fs/text' +import { Document } from 'langchain/document' +import { handleEscapeCharacters } from '../../../src' class Text_DocumentLoaders implements INode { label: string @@ -12,11 +14,12 @@ class Text_DocumentLoaders implements INode { category: string baseClasses: string[] inputs: INodeParams[] + outputs: INodeOutputsValue[] constructor() { this.label = 'Text File' this.name = 'textFile' - this.version = 1.0 + this.version = 2.0 this.type = 'Document' this.icon = 'textFile.svg' this.category = 'Document Loaders' @@ -43,12 +46,25 @@ class Text_DocumentLoaders implements INode { additionalParams: true } ] + this.outputs = [ + { + label: 'Document', + name: 'document', + baseClasses: this.baseClasses + }, + { + label: 'Text', + name: 'text', + baseClasses: ['string', 'json'] + } + ] } async init(nodeData: INodeData): Promise { const textSplitter = nodeData.inputs?.textSplitter as TextSplitter const txtFileBase64 = nodeData.inputs?.txtFile as string const metadata = nodeData.inputs?.metadata + const output = nodeData.outputs?.output as string let alldocs = [] let files: string[] = [] @@ -75,9 +91,9 @@ class Text_DocumentLoaders implements INode { } } + let finaldocs: Document>[] = [] if (metadata) { const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) - let finaldocs = [] for (const doc of alldocs) { const newdoc = { ...doc, @@ -88,9 +104,19 @@ class Text_DocumentLoaders implements INode { } finaldocs.push(newdoc) } - return finaldocs + } else { + finaldocs = alldocs + } + + if (output === 'document') { + return finaldocs + } else { + let finaltext = '' + for (const doc of finaldocs) { + finaltext += `${doc.pageContent}\n` + } + return handleEscapeCharacters(finaltext, false) } - return alldocs } } diff --git a/packages/components/nodes/documentloaders/Unstructured/UnstructuredFile.ts b/packages/components/nodes/documentloaders/Unstructured/UnstructuredFile.ts new file mode 100644 index 00000000..3ee7ff73 --- /dev/null +++ b/packages/components/nodes/documentloaders/Unstructured/UnstructuredFile.ts @@ -0,0 +1,162 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { UnstructuredLoader, UnstructuredLoaderOptions } from 'langchain/document_loaders/fs/unstructured' +import { getCredentialData, getCredentialParam } from '../../../src/utils' + +class UnstructuredFile_DocumentLoaders implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'Unstructured File Loader' + this.name = 'unstructuredFileLoader' + this.version = 1.0 + this.type = 'Document' + this.icon = 'unstructured.png' + this.category = 'Document Loaders' + this.description = 'Use Unstructured.io to load data from a file path' + this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['unstructuredApi'], + optional: true + } + this.inputs = [ + { + label: 'File Path', + name: 'filePath', + type: 'string', + placeholder: '' + }, + { + label: 'Unstructured API URL', + name: 'unstructuredAPIUrl', + description: + 'Unstructured API URL. Read more on how to get started', + type: 'string', + default: 'http://localhost:8000/general/v0/general' + }, + { + label: 'Element Type', + name: 'elementType', + description: + 'Unstructured partition document into different types, select the types to return. If not selected, all types will be returned', + type: 'multiOptions', + options: [ + { + label: 'FigureCaption', + name: 'FigureCaption' + }, + { + label: 'NarrativeText', + name: 'NarrativeText' + }, + { + label: 'ListItem', + name: 'ListItem' + }, + { + label: 'Title', + name: 'Title' + }, + { + label: 'Address', + name: 'Address' + }, + { + label: 'Table', + name: 'Table' + }, + { + label: 'PageBreak', + name: 'PageBreak' + }, + { + label: 'Header', + name: 'Header' + }, + { + label: 'Footer', + name: 'Footer' + }, + { + label: 'UncategorizedText', + name: 'UncategorizedText' + }, + { + label: 'Image', + name: 'Image' + }, + { + label: 'Formula', + name: 'Formula' + } + ], + default: [], + optional: true, + additionalParams: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const filePath = nodeData.inputs?.filePath as string + const unstructuredAPIUrl = nodeData.inputs?.unstructuredAPIUrl as string + const elementType = nodeData.inputs?.elementType as string + const metadata = nodeData.inputs?.metadata + + const obj: UnstructuredLoaderOptions = { apiUrl: unstructuredAPIUrl } + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const unstructuredAPIKey = getCredentialParam('unstructuredAPIKey', credentialData, nodeData) + if (unstructuredAPIKey) obj.apiKey = unstructuredAPIKey + + const loader = new UnstructuredLoader(filePath, obj) + const docs = await loader.load() + + let elementTypes: string[] = [] + if (elementType) { + try { + elementTypes = JSON.parse(elementType) + } catch (e) { + elementTypes = [] + } + } + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + let finaldocs = [] + for (const doc of docs) { + const newdoc = { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + finaldocs.push(newdoc) + } + return elementTypes.length ? finaldocs.filter((doc) => elementTypes.includes(doc.metadata.category)) : finaldocs + } + + return elementTypes.length ? docs.filter((doc) => elementTypes.includes(doc.metadata.category)) : docs + } +} + +module.exports = { nodeClass: UnstructuredFile_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/Unstructured/UnstructuredFolder.ts b/packages/components/nodes/documentloaders/Unstructured/UnstructuredFolder.ts new file mode 100644 index 00000000..c56ff023 --- /dev/null +++ b/packages/components/nodes/documentloaders/Unstructured/UnstructuredFolder.ts @@ -0,0 +1,162 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { UnstructuredDirectoryLoader, UnstructuredLoaderOptions } from 'langchain/document_loaders/fs/unstructured' +import { getCredentialData, getCredentialParam } from '../../../src/utils' + +class UnstructuredFolder_DocumentLoaders implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'Unstructured Folder Loader' + this.name = 'unstructuredFolderLoader' + this.version = 1.0 + this.type = 'Document' + this.icon = 'unstructured.png' + this.category = 'Document Loaders' + this.description = 'Use Unstructured.io to load data from a folder' + this.baseClasses = [this.type] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['unstructuredApi'], + optional: true + } + this.inputs = [ + { + label: 'Folder Path', + name: 'folderPath', + type: 'string', + placeholder: '' + }, + { + label: 'Unstructured API URL', + name: 'unstructuredAPIUrl', + description: + 'Unstructured API URL. Read more on how to get started', + type: 'string', + default: 'http://localhost:8000/general/v0/general' + }, + { + label: 'Element Type', + name: 'elementType', + description: + 'Unstructured partition document into different types, select the types to return. If not selected, all types will be returned', + type: 'multiOptions', + options: [ + { + label: 'FigureCaption', + name: 'FigureCaption' + }, + { + label: 'NarrativeText', + name: 'NarrativeText' + }, + { + label: 'ListItem', + name: 'ListItem' + }, + { + label: 'Title', + name: 'Title' + }, + { + label: 'Address', + name: 'Address' + }, + { + label: 'Table', + name: 'Table' + }, + { + label: 'PageBreak', + name: 'PageBreak' + }, + { + label: 'Header', + name: 'Header' + }, + { + label: 'Footer', + name: 'Footer' + }, + { + label: 'UncategorizedText', + name: 'UncategorizedText' + }, + { + label: 'Image', + name: 'Image' + }, + { + label: 'Formula', + name: 'Formula' + } + ], + default: [], + optional: true, + additionalParams: true + }, + { + label: 'Metadata', + name: 'metadata', + type: 'json', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const folderPath = nodeData.inputs?.folderPath as string + const unstructuredAPIUrl = nodeData.inputs?.unstructuredAPIUrl as string + const metadata = nodeData.inputs?.metadata + const elementType = nodeData.inputs?.elementType as string + + const obj: UnstructuredLoaderOptions = { apiUrl: unstructuredAPIUrl } + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const unstructuredAPIKey = getCredentialParam('unstructuredAPIKey', credentialData, nodeData) + if (unstructuredAPIKey) obj.apiKey = unstructuredAPIKey + + const loader = new UnstructuredDirectoryLoader(folderPath, obj) + const docs = await loader.load() + + let elementTypes: string[] = [] + if (elementType) { + try { + elementTypes = JSON.parse(elementType) + } catch (e) { + elementTypes = [] + } + } + + if (metadata) { + const parsedMetadata = typeof metadata === 'object' ? metadata : JSON.parse(metadata) + let finaldocs = [] + for (const doc of docs) { + const newdoc = { + ...doc, + metadata: { + ...doc.metadata, + ...parsedMetadata + } + } + finaldocs.push(newdoc) + } + return elementTypes.length ? finaldocs.filter((doc) => elementTypes.includes(doc.metadata.category)) : finaldocs + } + + return elementTypes.length ? docs.filter((doc) => elementTypes.includes(doc.metadata.category)) : docs + } +} + +module.exports = { nodeClass: UnstructuredFolder_DocumentLoaders } diff --git a/packages/components/nodes/documentloaders/Unstructured/unstructured.png b/packages/components/nodes/documentloaders/Unstructured/unstructured.png new file mode 100644 index 00000000..435219bf Binary files /dev/null and b/packages/components/nodes/documentloaders/Unstructured/unstructured.png differ diff --git a/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts b/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts new file mode 100644 index 00000000..0ac26d85 --- /dev/null +++ b/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts @@ -0,0 +1,152 @@ +import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { BedrockEmbeddings, BedrockEmbeddingsParams } from 'langchain/embeddings/bedrock' +import { BedrockRuntimeClient, InvokeModelCommand } from '@aws-sdk/client-bedrock-runtime' + +class AWSBedrockEmbedding_Embeddings 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 = 'AWS Bedrock Embeddings' + this.name = 'AWSBedrockEmbeddings' + this.version = 1.0 + this.type = 'AWSBedrockEmbeddings' + this.icon = 'awsBedrock.png' + this.category = 'Embeddings' + this.description = 'AWSBedrock embedding models to generate embeddings for a given text' + this.baseClasses = [this.type, ...getBaseClasses(BedrockEmbeddings)] + this.credential = { + label: 'AWS Credential', + name: 'credential', + type: 'credential', + credentialNames: ['awsApi'], + optional: true + } + this.inputs = [ + { + label: 'Region', + name: 'region', + type: 'options', + options: [ + { label: 'af-south-1', name: 'af-south-1' }, + { label: 'ap-east-1', name: 'ap-east-1' }, + { label: 'ap-northeast-1', name: 'ap-northeast-1' }, + { label: 'ap-northeast-2', name: 'ap-northeast-2' }, + { label: 'ap-northeast-3', name: 'ap-northeast-3' }, + { label: 'ap-south-1', name: 'ap-south-1' }, + { label: 'ap-south-2', name: 'ap-south-2' }, + { label: 'ap-southeast-1', name: 'ap-southeast-1' }, + { label: 'ap-southeast-2', name: 'ap-southeast-2' }, + { label: 'ap-southeast-3', name: 'ap-southeast-3' }, + { label: 'ap-southeast-4', name: 'ap-southeast-4' }, + { label: 'ap-southeast-5', name: 'ap-southeast-5' }, + { label: 'ap-southeast-6', name: 'ap-southeast-6' }, + { label: 'ca-central-1', name: 'ca-central-1' }, + { label: 'ca-west-1', name: 'ca-west-1' }, + { label: 'cn-north-1', name: 'cn-north-1' }, + { label: 'cn-northwest-1', name: 'cn-northwest-1' }, + { label: 'eu-central-1', name: 'eu-central-1' }, + { label: 'eu-central-2', name: 'eu-central-2' }, + { label: 'eu-north-1', name: 'eu-north-1' }, + { label: 'eu-south-1', name: 'eu-south-1' }, + { label: 'eu-south-2', name: 'eu-south-2' }, + { label: 'eu-west-1', name: 'eu-west-1' }, + { label: 'eu-west-2', name: 'eu-west-2' }, + { label: 'eu-west-3', name: 'eu-west-3' }, + { label: 'il-central-1', name: 'il-central-1' }, + { label: 'me-central-1', name: 'me-central-1' }, + { label: 'me-south-1', name: 'me-south-1' }, + { label: 'sa-east-1', name: 'sa-east-1' }, + { label: 'us-east-1', name: 'us-east-1' }, + { label: 'us-east-2', name: 'us-east-2' }, + { label: 'us-gov-east-1', name: 'us-gov-east-1' }, + { label: 'us-gov-west-1', name: 'us-gov-west-1' }, + { label: 'us-west-1', name: 'us-west-1' }, + { label: 'us-west-2', name: 'us-west-2' } + ], + default: 'us-east-1' + }, + { + label: 'Model Name', + name: 'model', + type: 'options', + options: [{ label: 'amazon.titan-embed-text-v1', name: 'amazon.titan-embed-text-v1' }], + default: 'amazon.titan-embed-text-v1' + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const iRegion = nodeData.inputs?.region as string + const iModel = nodeData.inputs?.model as string + + const obj: BedrockEmbeddingsParams = { + model: iModel, + region: iRegion + } + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + if (credentialData && Object.keys(credentialData).length !== 0) { + const credentialApiKey = getCredentialParam('awsKey', credentialData, nodeData) + const credentialApiSecret = getCredentialParam('awsSecret', credentialData, nodeData) + const credentialApiSession = getCredentialParam('awsSession', credentialData, nodeData) + + obj.credentials = { + accessKeyId: credentialApiKey, + secretAccessKey: credentialApiSecret, + sessionToken: credentialApiSession + } + } + + const client = new BedrockRuntimeClient({ + region: obj.region, + credentials: obj.credentials + }) + + const model = new BedrockEmbeddings(obj) + + // Avoid Illegal Invocation + model.embedQuery = async (document: string): Promise => { + return await embedText(document, client, iModel) + } + + model.embedDocuments = async (documents: string[]): Promise => { + return Promise.all(documents.map((document) => embedText(document, client, iModel))) + } + return model + } +} + +const embedText = async (text: string, client: BedrockRuntimeClient, model: string): Promise => { + // replace newlines, which can negatively affect performance. + const cleanedText = text.replace(/\n/g, ' ') + + const res = await client.send( + new InvokeModelCommand({ + modelId: model, + body: JSON.stringify({ + inputText: cleanedText + }), + contentType: 'application/json', + accept: 'application/json' + }) + ) + + try { + const body = new TextDecoder().decode(res.body) + return JSON.parse(body).embedding + } catch (e) { + throw new Error('An invalid response was returned by Bedrock.') + } +} + +module.exports = { nodeClass: AWSBedrockEmbedding_Embeddings } diff --git a/packages/components/nodes/embeddings/AWSBedrockEmbedding/awsBedrock.png b/packages/components/nodes/embeddings/AWSBedrockEmbedding/awsBedrock.png new file mode 100644 index 00000000..483bc69a Binary files /dev/null and b/packages/components/nodes/embeddings/AWSBedrockEmbedding/awsBedrock.png differ diff --git a/packages/components/nodes/embeddings/OllamaEmbedding/OllamaEmbedding.ts b/packages/components/nodes/embeddings/OllamaEmbedding/OllamaEmbedding.ts new file mode 100644 index 00000000..eb528aff --- /dev/null +++ b/packages/components/nodes/embeddings/OllamaEmbedding/OllamaEmbedding.ts @@ -0,0 +1,95 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { OllamaEmbeddings } from 'langchain/embeddings/ollama' +import { OllamaInput } from 'langchain/dist/util/ollama' + +class OllamaEmbedding_Embeddings 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 = 'Ollama Embeddings' + this.name = 'ollamaEmbedding' + this.version = 1.0 + this.type = 'OllamaEmbeddings' + this.icon = 'ollama.png' + this.category = 'Embeddings' + this.description = 'Generate embeddings for a given text using open source model on Ollama' + this.baseClasses = [this.type, ...getBaseClasses(OllamaEmbeddings)] + this.inputs = [ + { + label: 'Base URL', + name: 'baseUrl', + type: 'string', + default: 'http://localhost:11434' + }, + { + label: 'Model Name', + name: 'modelName', + type: 'string', + placeholder: 'llama2' + }, + { + label: 'Number of GPU', + name: 'numGpu', + type: 'number', + description: + 'The number of layers to send to the GPU(s). On macOS it defaults to 1 to enable metal support, 0 to disable. Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Number of Thread', + name: 'numThread', + type: 'number', + description: + 'Sets the number of threads to use during computation. By default, Ollama will detect this for optimal performance. It is recommended to set this value to the number of physical CPU cores your system has (as opposed to the logical number of cores). Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Use MMap', + name: 'useMMap', + type: 'boolean', + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const modelName = nodeData.inputs?.modelName as string + const baseUrl = nodeData.inputs?.baseUrl as string + const numThread = nodeData.inputs?.numThread as string + const numGpu = nodeData.inputs?.numGpu as string + const useMMap = nodeData.inputs?.useMMap as boolean + + const obj = { + model: modelName, + baseUrl, + requestOptions: {} + } + + const requestOptions: OllamaInput = {} + if (numThread) requestOptions.numThread = parseFloat(numThread) + if (numGpu) requestOptions.numGpu = parseFloat(numGpu) + if (useMMap !== undefined) requestOptions.useMMap = useMMap + + if (Object.keys(requestOptions).length) obj.requestOptions = requestOptions + + const model = new OllamaEmbeddings(obj) + return model + } +} + +module.exports = { nodeClass: OllamaEmbedding_Embeddings } diff --git a/packages/components/nodes/embeddings/OllamaEmbedding/ollama.png b/packages/components/nodes/embeddings/OllamaEmbedding/ollama.png new file mode 100644 index 00000000..8cd2cf1e Binary files /dev/null and b/packages/components/nodes/embeddings/OllamaEmbedding/ollama.png differ diff --git a/packages/components/nodes/llms/AWSBedrock/AWSBedrock.ts b/packages/components/nodes/llms/AWSBedrock/AWSBedrock.ts index 68ee7ed2..b67219f3 100644 --- a/packages/components/nodes/llms/AWSBedrock/AWSBedrock.ts +++ b/packages/components/nodes/llms/AWSBedrock/AWSBedrock.ts @@ -2,6 +2,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Inter import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { Bedrock } from 'langchain/llms/bedrock' import { BaseBedrockInput } from 'langchain/dist/util/bedrock' +import { BaseCache } from 'langchain/schema' +import { BaseLLMParams } from 'langchain/llms/base' /** * I had to run the following to build the component @@ -39,6 +41,12 @@ class AWSBedrock_LLMs implements INode { optional: true } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Region', name: 'region', @@ -80,8 +88,7 @@ class AWSBedrock_LLMs implements INode { { label: 'us-west-1', name: 'us-west-1' }, { label: 'us-west-2', name: 'us-west-2' } ], - default: 'us-east-1', - optional: false + default: 'us-east-1' }, { label: 'Model Name', @@ -90,17 +97,12 @@ class AWSBedrock_LLMs implements INode { options: [ { label: 'amazon.titan-tg1-large', name: 'amazon.titan-tg1-large' }, { label: 'amazon.titan-e1t-medium', name: 'amazon.titan-e1t-medium' }, - { label: 'stability.stable-diffusion-xl', name: 'stability.stable-diffusion-xl' }, + { label: 'cohere.command-text-v14', name: 'cohere.command-text-v14' }, { label: 'ai21.j2-grande-instruct', name: 'ai21.j2-grande-instruct' }, { label: 'ai21.j2-jumbo-instruct', name: 'ai21.j2-jumbo-instruct' }, { label: 'ai21.j2-mid', name: 'ai21.j2-mid' }, - { label: 'ai21.j2-ultra', name: 'ai21.j2-ultra' }, - { label: 'anthropic.claude-instant-v1', name: 'anthropic.claude-instant-v1' }, - { label: 'anthropic.claude-v1', name: 'anthropic.claude-v1' }, - { label: 'anthropic.claude-v2', name: 'anthropic.claude-v2' } - ], - default: 'anthropic.claude-v2', - optional: false + { label: 'ai21.j2-ultra', name: 'ai21.j2-ultra' } + ] }, { label: 'Temperature', @@ -109,8 +111,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, - default: 0.7, - additionalParams: false + default: 0.7 }, { label: 'Max Tokens to Sample', @@ -118,9 +119,8 @@ class AWSBedrock_LLMs implements INode { type: 'number', step: 10, description: 'Max Tokens parameter may not apply to certain model. Please check available model parameters', - optional: false, - default: 200, - additionalParams: false + optional: true, + default: 200 } ] } @@ -130,8 +130,8 @@ class AWSBedrock_LLMs implements INode { const iModel = nodeData.inputs?.model as string const iTemperature = nodeData.inputs?.temperature as string const iMax_tokens_to_sample = nodeData.inputs?.max_tokens_to_sample as string - - const obj: Partial = { + const cache = nodeData.inputs?.cache as BaseCache + const obj: Partial & BaseLLMParams = { model: iModel, region: iRegion, temperature: parseFloat(iTemperature), @@ -157,6 +157,7 @@ class AWSBedrock_LLMs implements INode { sessionToken: credentialApiSession } } + if (cache) obj.cache = cache const amazonBedrock = new Bedrock(obj) return amazonBedrock diff --git a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts index f48c4642..f50e3d95 100644 --- a/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts +++ b/packages/components/nodes/llms/Azure OpenAI/AzureOpenAI.ts @@ -1,7 +1,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { AzureOpenAIInput, OpenAI, OpenAIInput } from 'langchain/llms/openai' - +import { BaseCache } from 'langchain/schema' +import { BaseLLMParams } from 'langchain/llms/base' class AzureOpenAI_LLMs implements INode { label: string name: string @@ -17,7 +18,7 @@ class AzureOpenAI_LLMs implements INode { constructor() { this.label = 'Azure OpenAI' this.name = 'azureOpenAI' - this.version = 1.0 + this.version = 2.0 this.type = 'AzureOpenAI' this.icon = 'Azure.svg' this.category = 'LLMs' @@ -30,6 +31,12 @@ class AzureOpenAI_LLMs implements INode { credentialNames: ['azureOpenAIApi'] } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model Name', name: 'modelName', @@ -163,7 +170,9 @@ class AzureOpenAI_LLMs implements INode { const azureOpenAIApiDeploymentName = getCredentialParam('azureOpenAIApiDeploymentName', credentialData, nodeData) const azureOpenAIApiVersion = getCredentialParam('azureOpenAIApiVersion', credentialData, nodeData) - const obj: Partial & Partial = { + const cache = nodeData.inputs?.cache as BaseCache + + const obj: Partial & BaseLLMParams & Partial = { temperature: parseFloat(temperature), modelName, azureOpenAIApiKey, @@ -179,6 +188,7 @@ class AzureOpenAI_LLMs implements INode { if (presencePenalty) obj.presencePenalty = parseFloat(presencePenalty) if (timeout) obj.timeout = parseInt(timeout, 10) if (bestOf) obj.bestOf = parseInt(bestOf, 10) + if (cache) obj.cache = cache const model = new OpenAI(obj) return model diff --git a/packages/components/nodes/llms/Bittensor/Bittensor.ts b/packages/components/nodes/llms/Bittensor/Bittensor.ts index a87a7e48..e6cc2bb6 100644 --- a/packages/components/nodes/llms/Bittensor/Bittensor.ts +++ b/packages/components/nodes/llms/Bittensor/Bittensor.ts @@ -1,6 +1,8 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' import { NIBittensorLLM, BittensorInput } from 'langchain/experimental/llms/bittensor' +import { BaseCache } from 'langchain/schema' +import { BaseLLMParams } from 'langchain/llms/base' class Bittensor_LLMs implements INode { label: string @@ -16,13 +18,19 @@ class Bittensor_LLMs implements INode { constructor() { this.label = 'NIBittensorLLM' this.name = 'NIBittensorLLM' - this.version = 1.0 + this.version = 2.0 this.type = 'Bittensor' this.icon = 'logo.png' this.category = 'LLMs' this.description = 'Wrapper around Bittensor subnet 1 large language models' this.baseClasses = [this.type, ...getBaseClasses(NIBittensorLLM)] this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'System prompt', name: 'system_prompt', @@ -44,10 +52,13 @@ class Bittensor_LLMs implements INode { async init(nodeData: INodeData, _: string): Promise { const system_prompt = nodeData.inputs?.system_prompt as string const topResponses = Number(nodeData.inputs?.topResponses as number) - const obj: Partial = { + const cache = nodeData.inputs?.cache as BaseCache + + const obj: Partial & BaseLLMParams = { systemPrompt: system_prompt, topResponses: topResponses } + if (cache) obj.cache = cache const model = new NIBittensorLLM(obj) return model diff --git a/packages/components/nodes/llms/Cohere/Cohere.ts b/packages/components/nodes/llms/Cohere/Cohere.ts index 4a3a8a80..3fde0af0 100644 --- a/packages/components/nodes/llms/Cohere/Cohere.ts +++ b/packages/components/nodes/llms/Cohere/Cohere.ts @@ -1,6 +1,7 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { Cohere, CohereInput } from './core' +import { BaseCache } from 'langchain/schema' class Cohere_LLMs implements INode { label: string @@ -17,7 +18,7 @@ class Cohere_LLMs implements INode { constructor() { this.label = 'Cohere' this.name = 'cohere' - this.version = 1.0 + this.version = 2.0 this.type = 'Cohere' this.icon = 'cohere.png' this.category = 'LLMs' @@ -30,6 +31,12 @@ class Cohere_LLMs implements INode { credentialNames: ['cohereApi'] } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model Name', name: 'modelName', @@ -85,7 +92,7 @@ class Cohere_LLMs implements INode { const temperature = nodeData.inputs?.temperature as string const modelName = nodeData.inputs?.modelName as string const maxTokens = nodeData.inputs?.maxTokens as string - + const cache = nodeData.inputs?.cache as BaseCache const credentialData = await getCredentialData(nodeData.credential ?? '', options) const cohereApiKey = getCredentialParam('cohereApiKey', credentialData, nodeData) @@ -96,7 +103,7 @@ class Cohere_LLMs implements INode { if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) if (modelName) obj.model = modelName if (temperature) obj.temperature = parseFloat(temperature) - + if (cache) obj.cache = cache const model = new Cohere(obj) return model } diff --git a/packages/components/nodes/llms/GooglePaLM/GooglePaLM.ts b/packages/components/nodes/llms/GooglePaLM/GooglePaLM.ts index 24630360..d3212a1c 100644 --- a/packages/components/nodes/llms/GooglePaLM/GooglePaLM.ts +++ b/packages/components/nodes/llms/GooglePaLM/GooglePaLM.ts @@ -1,7 +1,7 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { GooglePaLM, GooglePaLMTextInput } from 'langchain/llms/googlepalm' - +import { BaseCache } from 'langchain/schema' class GooglePaLM_LLMs implements INode { label: string name: string @@ -17,7 +17,7 @@ class GooglePaLM_LLMs implements INode { constructor() { this.label = 'GooglePaLM' this.name = 'GooglePaLM' - this.version = 1.0 + this.version = 2.0 this.type = 'GooglePaLM' this.icon = 'Google_PaLM_Logo.svg' this.category = 'LLMs' @@ -30,6 +30,12 @@ class GooglePaLM_LLMs implements INode { credentialNames: ['googleMakerSuite'] } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model Name', name: 'modelName', @@ -126,6 +132,7 @@ class GooglePaLM_LLMs implements INode { const topP = nodeData.inputs?.topP as string const topK = nodeData.inputs?.topK as string const stopSequencesObj = nodeData.inputs?.stopSequencesObj + const cache = nodeData.inputs?.cache as BaseCache const credentialData = await getCredentialData(nodeData.credential ?? '', options) const googleMakerSuiteKey = getCredentialParam('googleMakerSuiteKey', credentialData, nodeData) @@ -139,6 +146,7 @@ class GooglePaLM_LLMs implements INode { if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) if (topP) obj.topP = parseFloat(topP) if (topK) obj.topK = parseFloat(topK) + if (cache) obj.cache = cache let parsedStopSequences: any | undefined = undefined if (stopSequencesObj) { diff --git a/packages/components/nodes/llms/GoogleVertexAI/GoogleVertexAI.ts b/packages/components/nodes/llms/GoogleVertexAI/GoogleVertexAI.ts index 4d19d04f..6b6d534b 100644 --- a/packages/components/nodes/llms/GoogleVertexAI/GoogleVertexAI.ts +++ b/packages/components/nodes/llms/GoogleVertexAI/GoogleVertexAI.ts @@ -2,6 +2,7 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Inter import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { GoogleVertexAI, GoogleVertexAITextInput } from 'langchain/llms/googlevertexai' import { GoogleAuthOptions } from 'google-auth-library' +import { BaseCache } from 'langchain/schema' class GoogleVertexAI_LLMs implements INode { label: string @@ -18,7 +19,7 @@ class GoogleVertexAI_LLMs implements INode { constructor() { this.label = 'GoogleVertexAI' this.name = 'googlevertexai' - this.version = 1.0 + this.version = 2.0 this.type = 'GoogleVertexAI' this.icon = 'vertexai.svg' this.category = 'LLMs' @@ -34,6 +35,12 @@ class GoogleVertexAI_LLMs implements INode { 'Google Vertex AI credential. If you are using a GCP service like Cloud Run, or if you have installed default credentials on your local machine, you do not need to set this credential.' } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model Name', name: 'modelName', @@ -120,6 +127,7 @@ class GoogleVertexAI_LLMs implements INode { 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: Partial = { temperature: parseFloat(temperature), @@ -129,6 +137,7 @@ class GoogleVertexAI_LLMs implements INode { if (maxOutputTokens) obj.maxOutputTokens = parseInt(maxOutputTokens, 10) if (topP) obj.topP = parseFloat(topP) + if (cache) obj.cache = cache const model = new GoogleVertexAI(obj) return model diff --git a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts index c7f6a37e..8dcf021b 100644 --- a/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts +++ b/packages/components/nodes/llms/HuggingFaceInference/HuggingFaceInference.ts @@ -1,6 +1,7 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { HFInput, HuggingFaceInference } from './core' +import { BaseCache } from 'langchain/schema' class HuggingFaceInference_LLMs implements INode { label: string @@ -17,7 +18,7 @@ class HuggingFaceInference_LLMs implements INode { constructor() { this.label = 'HuggingFace Inference' this.name = 'huggingFaceInference_LLMs' - this.version = 1.0 + this.version = 2.0 this.type = 'HuggingFaceInference' this.icon = 'huggingface.png' this.category = 'LLMs' @@ -30,6 +31,12 @@ class HuggingFaceInference_LLMs implements INode { credentialNames: ['huggingFaceApi'] } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model', name: 'model', @@ -106,6 +113,8 @@ class HuggingFaceInference_LLMs implements INode { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const huggingFaceApiKey = getCredentialParam('huggingFaceApiKey', credentialData, nodeData) + const cache = nodeData.inputs?.cache as BaseCache + const obj: Partial = { model, apiKey: huggingFaceApiKey @@ -119,6 +128,8 @@ class HuggingFaceInference_LLMs implements INode { if (endpoint) obj.endpoint = endpoint const huggingFace = new HuggingFaceInference(obj) + if (cache) huggingFace.cache = cache + return huggingFace } } diff --git a/packages/components/nodes/llms/Ollama/Ollama.ts b/packages/components/nodes/llms/Ollama/Ollama.ts new file mode 100644 index 00000000..348b1883 --- /dev/null +++ b/packages/components/nodes/llms/Ollama/Ollama.ts @@ -0,0 +1,241 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses } from '../../../src/utils' +import { Ollama } from 'langchain/llms/ollama' +import { BaseCache } from 'langchain/schema' +import { OllamaInput } from 'langchain/dist/util/ollama' +import { BaseLLMParams } from 'langchain/llms/base' + +class Ollama_LLMs implements INode { + label: string + name: string + version: number + type: string + icon: string + category: string + description: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'Ollama' + this.name = 'ollama' + this.version = 2.0 + this.type = 'Ollama' + this.icon = 'ollama.png' + this.category = 'LLMs' + this.description = 'Wrapper around open source large language models on Ollama' + this.baseClasses = [this.type, ...getBaseClasses(Ollama)] + this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, + { + label: 'Base URL', + name: 'baseUrl', + type: 'string', + default: 'http://localhost:11434' + }, + { + label: 'Model Name', + name: 'modelName', + type: 'string', + placeholder: 'llama2' + }, + { + label: 'Temperature', + name: 'temperature', + type: 'number', + description: + 'The temperature of the model. Increasing the temperature will make the model answer more creatively. (Default: 0.8). Refer to docs for more details', + step: 0.1, + default: 0.9, + optional: true + }, + { + label: 'Top P', + name: 'topP', + type: 'number', + description: + 'Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9). Refer to docs for more details', + step: 0.1, + optional: true, + additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + type: 'number', + description: + 'Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40). Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Mirostat', + name: 'mirostat', + type: 'number', + description: + 'Enable Mirostat sampling for controlling perplexity. (default: 0, 0 = disabled, 1 = Mirostat, 2 = Mirostat 2.0). Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Mirostat ETA', + name: 'mirostatEta', + type: 'number', + description: + 'Influences how quickly the algorithm responds to feedback from the generated text. A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive. (Default: 0.1) Refer to docs for more details', + step: 0.1, + optional: true, + additionalParams: true + }, + { + label: 'Mirostat TAU', + name: 'mirostatTau', + type: 'number', + description: + 'Controls the balance between coherence and diversity of the output. A lower value will result in more focused and coherent text. (Default: 5.0) Refer to docs for more details', + step: 0.1, + optional: true, + additionalParams: true + }, + { + label: 'Context Window Size', + name: 'numCtx', + type: 'number', + description: + 'Sets the size of the context window used to generate the next token. (Default: 2048) Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Number of GQA groups', + name: 'numGqa', + type: 'number', + description: + 'The number of GQA groups in the transformer layer. Required for some models, for example it is 8 for llama2:70b. Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Number of GPU', + name: 'numGpu', + type: 'number', + description: + 'The number of layers to send to the GPU(s). On macOS it defaults to 1 to enable metal support, 0 to disable. Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Number of Thread', + name: 'numThread', + type: 'number', + description: + 'Sets the number of threads to use during computation. By default, Ollama will detect this for optimal performance. It is recommended to set this value to the number of physical CPU cores your system has (as opposed to the logical number of cores). Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Repeat Last N', + name: 'repeatLastN', + type: 'number', + description: + 'Sets how far back for the model to look back to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx). Refer to docs for more details', + step: 1, + optional: true, + additionalParams: true + }, + { + label: 'Repeat Penalty', + name: 'repeatPenalty', + type: 'number', + description: + 'Sets how strongly to penalize repetitions. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. (Default: 1.1). Refer to docs for more details', + step: 0.1, + optional: true, + additionalParams: true + }, + { + label: 'Stop Sequence', + name: 'stop', + type: 'string', + rows: 4, + placeholder: 'AI assistant:', + description: + 'Sets the stop sequences to use. Use comma to seperate different sequences. Refer to docs for more details', + optional: true, + additionalParams: true + }, + { + label: 'Tail Free Sampling', + name: 'tfsZ', + type: 'number', + description: + 'Tail free sampling is used to reduce the impact of less probable tokens from the output. A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting. (Default: 1). Refer to docs for more details', + step: 0.1, + optional: true, + additionalParams: true + } + ] + } + + async init(nodeData: INodeData): Promise { + const temperature = nodeData.inputs?.temperature as string + const baseUrl = nodeData.inputs?.baseUrl as string + const modelName = nodeData.inputs?.modelName as string + const topP = nodeData.inputs?.topP as string + const topK = nodeData.inputs?.topK as string + const mirostat = nodeData.inputs?.mirostat as string + const mirostatEta = nodeData.inputs?.mirostatEta as string + const mirostatTau = nodeData.inputs?.mirostatTau as string + const numCtx = nodeData.inputs?.numCtx as string + const numGqa = nodeData.inputs?.numGqa as string + const numGpu = nodeData.inputs?.numGpu as string + const numThread = nodeData.inputs?.numThread as string + const repeatLastN = nodeData.inputs?.repeatLastN as string + const repeatPenalty = nodeData.inputs?.repeatPenalty as string + const stop = nodeData.inputs?.stop as string + const tfsZ = nodeData.inputs?.tfsZ as string + + const cache = nodeData.inputs?.cache as BaseCache + + const obj: OllamaInput & BaseLLMParams = { + baseUrl, + temperature: parseFloat(temperature), + model: modelName + } + + if (topP) obj.topP = parseFloat(topP) + if (topK) obj.topK = parseFloat(topK) + if (mirostat) obj.mirostat = parseFloat(mirostat) + if (mirostatEta) obj.mirostatEta = parseFloat(mirostatEta) + if (mirostatTau) obj.mirostatTau = parseFloat(mirostatTau) + if (numCtx) obj.numCtx = parseFloat(numCtx) + if (numGqa) obj.numGqa = parseFloat(numGqa) + if (numGpu) obj.numGpu = parseFloat(numGpu) + if (numThread) obj.numThread = parseFloat(numThread) + if (repeatLastN) obj.repeatLastN = parseFloat(repeatLastN) + if (repeatPenalty) obj.repeatPenalty = parseFloat(repeatPenalty) + if (tfsZ) obj.tfsZ = parseFloat(tfsZ) + if (stop) { + const stopSequences = stop.split(',') + obj.stop = stopSequences + } + if (cache) obj.cache = cache + + const model = new Ollama(obj) + return model + } +} + +module.exports = { nodeClass: Ollama_LLMs } diff --git a/packages/components/nodes/llms/Ollama/ollama.png b/packages/components/nodes/llms/Ollama/ollama.png new file mode 100644 index 00000000..8cd2cf1e Binary files /dev/null and b/packages/components/nodes/llms/Ollama/ollama.png differ diff --git a/packages/components/nodes/llms/OpenAI/OpenAI.ts b/packages/components/nodes/llms/OpenAI/OpenAI.ts index 2960ad2a..9109dd40 100644 --- a/packages/components/nodes/llms/OpenAI/OpenAI.ts +++ b/packages/components/nodes/llms/OpenAI/OpenAI.ts @@ -1,6 +1,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { OpenAI, OpenAIInput } from 'langchain/llms/openai' +import { BaseLLMParams } from 'langchain/llms/base' +import { BaseCache } from 'langchain/schema' class OpenAI_LLMs implements INode { label: string @@ -17,7 +19,7 @@ class OpenAI_LLMs implements INode { constructor() { this.label = 'OpenAI' this.name = 'openAI' - this.version = 2.0 + this.version = 3.0 this.type = 'OpenAI' this.icon = 'openai.png' this.category = 'LLMs' @@ -30,6 +32,12 @@ class OpenAI_LLMs implements INode { credentialNames: ['openAIApi'] } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model Name', name: 'modelName', @@ -149,7 +157,9 @@ class OpenAI_LLMs implements INode { const credentialData = await getCredentialData(nodeData.credential ?? '', options) const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData) - const obj: Partial & { openAIApiKey?: string } = { + const cache = nodeData.inputs?.cache as BaseCache + + const obj: Partial & BaseLLMParams & { openAIApiKey?: string } = { temperature: parseFloat(temperature), modelName, openAIApiKey, @@ -164,8 +174,9 @@ class OpenAI_LLMs implements INode { if (batchSize) obj.batchSize = parseInt(batchSize, 10) if (bestOf) obj.bestOf = parseInt(bestOf, 10) - let parsedBaseOptions: any | undefined = undefined + if (cache) obj.cache = cache + let parsedBaseOptions: any | undefined = undefined if (baseOptions) { try { parsedBaseOptions = typeof baseOptions === 'object' ? baseOptions : JSON.parse(baseOptions) diff --git a/packages/components/nodes/llms/Replicate/Replicate.ts b/packages/components/nodes/llms/Replicate/Replicate.ts index 22c6e93a..fd5373a1 100644 --- a/packages/components/nodes/llms/Replicate/Replicate.ts +++ b/packages/components/nodes/llms/Replicate/Replicate.ts @@ -1,6 +1,8 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' import { Replicate, ReplicateInput } from 'langchain/llms/replicate' +import { BaseCache } from 'langchain/schema' +import { BaseLLMParams } from 'langchain/llms/base' class Replicate_LLMs implements INode { label: string @@ -17,7 +19,7 @@ class Replicate_LLMs implements INode { constructor() { this.label = 'Replicate' this.name = 'replicate' - this.version = 1.0 + this.version = 2.0 this.type = 'Replicate' this.icon = 'replicate.svg' this.category = 'LLMs' @@ -30,6 +32,12 @@ class Replicate_LLMs implements INode { credentialNames: ['replicateApi'] } this.inputs = [ + { + label: 'Cache', + name: 'cache', + type: 'BaseCache', + optional: true + }, { label: 'Model', name: 'model', @@ -103,7 +111,9 @@ class Replicate_LLMs implements INode { const name = modelName.split(':')[0].split('/').pop() const org = modelName.split(':')[0].split('/')[0] - const obj: ReplicateInput = { + const cache = nodeData.inputs?.cache as BaseCache + + const obj: ReplicateInput & BaseLLMParams = { model: `${org}/${name}:${version}`, apiKey } @@ -120,6 +130,8 @@ class Replicate_LLMs implements INode { } if (Object.keys(inputs).length) obj.input = inputs + if (cache) obj.cache = cache + const model = new Replicate(obj) return model } diff --git a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts new file mode 100644 index 00000000..6b5fdf66 --- /dev/null +++ b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts @@ -0,0 +1,118 @@ +import { INode, INodeData, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { ICommonObject } from '../../../src' +import { BufferMemory, BufferMemoryInput } from 'langchain/memory' +import { UpstashRedisChatMessageHistory } from 'langchain/stores/message/upstash_redis' + +class UpstashRedisBackedChatMemory_Memory implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + credential: INodeParams + inputs: INodeParams[] + + constructor() { + this.label = 'Upstash Redis-Backed Chat Memory' + this.name = 'upstashRedisBackedChatMemory' + this.version = 1.0 + this.type = 'UpstashRedisBackedChatMemory' + this.icon = 'upstash.svg' + this.category = 'Memory' + this.description = 'Summarizes the conversation and stores the memory in Upstash Redis server' + this.baseClasses = [this.type, ...getBaseClasses(BufferMemory)] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + description: 'Configure password authentication on your upstash redis instance', + credentialNames: ['upstashRedisMemoryApi'] + } + this.inputs = [ + { + label: 'Upstash Redis REST URL', + name: 'baseURL', + type: 'string', + placeholder: 'https://.upstash.io' + }, + { + label: 'Session Id', + name: 'sessionId', + type: 'string', + description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId', + default: '', + additionalParams: true, + optional: true + }, + { + label: 'Session Timeouts', + name: 'sessionTTL', + type: 'number', + description: 'Omit this parameter to make sessions never expire', + additionalParams: true, + optional: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + return initalizeUpstashRedis(nodeData, options) + } + + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const redis = await initalizeUpstashRedis(nodeData, options) + const sessionId = nodeData.inputs?.sessionId as string + const chatId = options?.chatId as string + options.logger.info(`Clearing Upstash Redis memory session ${sessionId ? sessionId : chatId}`) + await redis.clear() + options.logger.info(`Successfully cleared Upstash Redis memory session ${sessionId ? sessionId : chatId}`) + } +} + +const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject): Promise => { + const baseURL = nodeData.inputs?.baseURL as string + const sessionId = nodeData.inputs?.sessionId as string + const sessionTTL = nodeData.inputs?.sessionTTL as string + const chatId = options?.chatId as string + + let isSessionIdUsingChatMessageId = false + if (!sessionId && chatId) isSessionIdUsingChatMessageId = true + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const upstashRestToken = getCredentialParam('upstashRestToken', credentialData, nodeData) + + const redisChatMessageHistory = new UpstashRedisChatMessageHistory({ + sessionId: sessionId ? sessionId : chatId, + sessionTTL: sessionTTL ? parseInt(sessionTTL, 10) : undefined, + config: { + url: baseURL, + token: upstashRestToken + } + }) + + const memory = new BufferMemoryExtended({ + chatHistory: redisChatMessageHistory, + isSessionIdUsingChatMessageId + }) + + return memory +} + +interface BufferMemoryExtendedInput { + isSessionIdUsingChatMessageId: boolean +} + +class BufferMemoryExtended extends BufferMemory { + isSessionIdUsingChatMessageId? = false + + constructor(fields: BufferMemoryInput & Partial) { + super(fields) + this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId + } +} + +module.exports = { nodeClass: UpstashRedisBackedChatMemory_Memory } diff --git a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/upstash.svg b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/upstash.svg new file mode 100644 index 00000000..a0fb96a7 --- /dev/null +++ b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/upstash.svg @@ -0,0 +1,12 @@ + + + upstash + + + + + + + + + diff --git a/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts b/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts index c9ec751d..83e1a857 100644 --- a/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts +++ b/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts @@ -53,7 +53,7 @@ class ChatPromptTemplate_Prompts implements INode { const humanMessagePrompt = nodeData.inputs?.humanMessagePrompt as string const promptValuesStr = nodeData.inputs?.promptValues as string - const prompt = ChatPromptTemplate.fromPromptMessages([ + const prompt = ChatPromptTemplate.fromMessages([ SystemMessagePromptTemplate.fromTemplate(systemMessagePrompt), HumanMessagePromptTemplate.fromTemplate(humanMessagePrompt) ]) diff --git a/packages/components/nodes/textsplitters/CharacterTextSplitter/CharacterTextSplitter.ts b/packages/components/nodes/textsplitters/CharacterTextSplitter/CharacterTextSplitter.ts index f9427d10..ed972ad6 100644 --- a/packages/components/nodes/textsplitters/CharacterTextSplitter/CharacterTextSplitter.ts +++ b/packages/components/nodes/textsplitters/CharacterTextSplitter/CharacterTextSplitter.ts @@ -23,12 +23,6 @@ class CharacterTextSplitter_TextSplitters implements INode { this.description = `splits only on one type of character (defaults to "\\n\\n").` this.baseClasses = [this.type, ...getBaseClasses(CharacterTextSplitter)] this.inputs = [ - { - label: 'Separator', - name: 'separator', - type: 'string', - optional: true - }, { label: 'Chunk Size', name: 'chunkSize', @@ -41,6 +35,14 @@ class CharacterTextSplitter_TextSplitters implements INode { name: 'chunkOverlap', type: 'number', optional: true + }, + { + label: 'Custom Separator', + name: 'separator', + type: 'string', + placeholder: `" "`, + description: 'Seperator to determine when to split the text, will override the default separator', + optional: true } ] } diff --git a/packages/components/nodes/textsplitters/RecursiveCharacterTextSplitter/RecursiveCharacterTextSplitter.ts b/packages/components/nodes/textsplitters/RecursiveCharacterTextSplitter/RecursiveCharacterTextSplitter.ts index dcca70ba..ec9095b6 100644 --- a/packages/components/nodes/textsplitters/RecursiveCharacterTextSplitter/RecursiveCharacterTextSplitter.ts +++ b/packages/components/nodes/textsplitters/RecursiveCharacterTextSplitter/RecursiveCharacterTextSplitter.ts @@ -16,7 +16,7 @@ class RecursiveCharacterTextSplitter_TextSplitters implements INode { constructor() { this.label = 'Recursive Character Text Splitter' this.name = 'recursiveCharacterTextSplitter' - this.version = 1.0 + this.version = 2.0 this.type = 'RecursiveCharacterTextSplitter' this.icon = 'textsplitter.svg' this.category = 'Text Splitters' @@ -35,6 +35,15 @@ class RecursiveCharacterTextSplitter_TextSplitters implements INode { name: 'chunkOverlap', type: 'number', optional: true + }, + { + label: 'Custom Separators', + name: 'separators', + type: 'string', + rows: 4, + description: 'Array of custom seperators to determine when to split the text, will override the default separators', + placeholder: `["|", "##", ">", "-"]`, + optional: true } ] } @@ -42,11 +51,19 @@ class RecursiveCharacterTextSplitter_TextSplitters implements INode { async init(nodeData: INodeData): Promise { const chunkSize = nodeData.inputs?.chunkSize as string const chunkOverlap = nodeData.inputs?.chunkOverlap as string + const separators = nodeData.inputs?.separators as string const obj = {} as RecursiveCharacterTextSplitterParams if (chunkSize) obj.chunkSize = parseInt(chunkSize, 10) if (chunkOverlap) obj.chunkOverlap = parseInt(chunkOverlap, 10) + if (separators) { + try { + obj.separators = JSON.parse(separators) + } catch (e) { + throw new Error(e) + } + } const splitter = new RecursiveCharacterTextSplitter(obj) diff --git a/packages/components/nodes/vectorstores/Elasticsearch/ElasticSearchBase.ts b/packages/components/nodes/vectorstores/Elasticsearch/ElasticSearchBase.ts new file mode 100644 index 00000000..59294b7e --- /dev/null +++ b/packages/components/nodes/vectorstores/Elasticsearch/ElasticSearchBase.ts @@ -0,0 +1,193 @@ +import { + getBaseClasses, + getCredentialData, + getCredentialParam, + ICommonObject, + INodeData, + INodeOutputsValue, + INodeParams +} from '../../../src' +import { Client, ClientOptions } from '@elastic/elasticsearch' +import { ElasticClientArgs, ElasticVectorSearch } from 'langchain/vectorstores/elasticsearch' +import { Embeddings } from 'langchain/embeddings/base' +import { VectorStore } from 'langchain/vectorstores/base' +import { Document } from 'langchain/document' + +export abstract class ElasticSearchBase { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + outputs: INodeOutputsValue[] + + protected constructor() { + this.type = 'Elasticsearch' + this.icon = 'elasticsearch.png' + this.category = 'Vector Stores' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + credentialNames: ['elasticsearchApi', 'elasticSearchUserPassword'] + } + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Index Name', + name: 'indexName', + placeholder: '', + type: 'string' + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + }, + { + label: 'Similarity', + name: 'similarity', + description: 'Similarity measure used in Elasticsearch.', + type: 'options', + default: 'l2_norm', + options: [ + { + label: 'l2_norm', + name: 'l2_norm' + }, + { + label: 'dot_product', + name: 'dot_product' + }, + { + label: 'cosine', + name: 'cosine' + } + ], + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'Elasticsearch Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Elasticsearch Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(ElasticVectorSearch)] + } + ] + } + + abstract constructVectorStore( + embeddings: Embeddings, + elasticSearchClientArgs: ElasticClientArgs, + docs: Document>[] | undefined + ): Promise + + async init(nodeData: INodeData, _: string, options: ICommonObject, docs: Document>[] | undefined): Promise { + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const endPoint = getCredentialParam('endpoint', credentialData, nodeData) + const cloudId = getCredentialParam('cloudId', credentialData, nodeData) + const indexName = nodeData.inputs?.indexName as string + const embeddings = nodeData.inputs?.embeddings as Embeddings + const topK = nodeData.inputs?.topK as string + const similarityMeasure = nodeData.inputs?.similarityMeasure as string + const k = topK ? parseFloat(topK) : 4 + const output = nodeData.outputs?.output as string + + const elasticSearchClientArgs = this.prepareClientArgs(endPoint, cloudId, credentialData, nodeData, similarityMeasure, indexName) + + const vectorStore = await this.constructVectorStore(embeddings, elasticSearchClientArgs, docs) + + if (output === 'retriever') { + return vectorStore.asRetriever(k) + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } + + protected prepareConnectionOptions( + endPoint: string | undefined, + cloudId: string | undefined, + credentialData: ICommonObject, + nodeData: INodeData + ) { + let elasticSearchClientOptions: ClientOptions = {} + if (endPoint) { + let apiKey = getCredentialParam('apiKey', credentialData, nodeData) + elasticSearchClientOptions = { + node: endPoint, + auth: { + apiKey: apiKey + } + } + } else if (cloudId) { + let username = getCredentialParam('username', credentialData, nodeData) + let password = getCredentialParam('password', credentialData, nodeData) + elasticSearchClientOptions = { + cloud: { + id: cloudId + }, + auth: { + username: username, + password: password + } + } + } + return elasticSearchClientOptions + } + + protected prepareClientArgs( + endPoint: string | undefined, + cloudId: string | undefined, + credentialData: ICommonObject, + nodeData: INodeData, + similarityMeasure: string, + indexName: string + ) { + let elasticSearchClientOptions = this.prepareConnectionOptions(endPoint, cloudId, credentialData, nodeData) + let vectorSearchOptions = {} + switch (similarityMeasure) { + case 'dot_product': + vectorSearchOptions = { + similarity: 'dot_product' + } + break + case 'cosine': + vectorSearchOptions = { + similarity: 'cosine' + } + break + default: + vectorSearchOptions = { + similarity: 'l2_norm' + } + } + const elasticSearchClientArgs: ElasticClientArgs = { + client: new Client(elasticSearchClientOptions), + indexName: indexName, + vectorSearchOptions: vectorSearchOptions + } + return elasticSearchClientArgs + } +} diff --git a/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch_Existing.ts b/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch_Existing.ts new file mode 100644 index 00000000..fcb1b2a5 --- /dev/null +++ b/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch_Existing.ts @@ -0,0 +1,31 @@ +import { ICommonObject, INode, INodeData } from '../../../src/Interface' +import { Embeddings } from 'langchain/embeddings/base' + +import { ElasticClientArgs, ElasticVectorSearch } from 'langchain/vectorstores/elasticsearch' +import { ElasticSearchBase } from './ElasticSearchBase' +import { VectorStore } from 'langchain/vectorstores/base' +import { Document } from 'langchain/document' + +class ElasicsearchExisting_VectorStores extends ElasticSearchBase implements INode { + constructor() { + super() + this.label = 'Elasticsearch Load Existing Index' + this.name = 'ElasticsearchIndex' + this.version = 1.0 + this.description = 'Load existing index from Elasticsearch (i.e: Document has been upserted)' + } + + async constructVectorStore( + embeddings: Embeddings, + elasticSearchClientArgs: ElasticClientArgs, + _: Document>[] | undefined + ): Promise { + return await ElasticVectorSearch.fromExistingIndex(embeddings, elasticSearchClientArgs) + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + return super.init(nodeData, _, options, undefined) + } +} + +module.exports = { nodeClass: ElasicsearchExisting_VectorStores } diff --git a/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch_Upsert.ts b/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch_Upsert.ts new file mode 100644 index 00000000..d4b79a5d --- /dev/null +++ b/packages/components/nodes/vectorstores/Elasticsearch/Elasticsearch_Upsert.ts @@ -0,0 +1,55 @@ +import { ICommonObject, INode, INodeData } from '../../../src/Interface' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' + +import { ElasticClientArgs, ElasticVectorSearch } from 'langchain/vectorstores/elasticsearch' +import { flatten } from 'lodash' +import { ElasticSearchBase } from './ElasticSearchBase' +import { VectorStore } from 'langchain/vectorstores/base' + +class ElasicsearchUpsert_VectorStores extends ElasticSearchBase implements INode { + constructor() { + super() + this.label = 'Elasticsearch Upsert Document' + this.name = 'ElasticsearchUpsert' + this.version = 1.0 + this.description = 'Upsert documents to Elasticsearch' + this.inputs.unshift({ + label: 'Document', + name: 'document', + type: 'Document', + list: true + }) + } + + async constructVectorStore( + embeddings: Embeddings, + elasticSearchClientArgs: ElasticClientArgs, + docs: Document>[] + ): Promise { + const vectorStore = new ElasticVectorSearch(embeddings, elasticSearchClientArgs) + await vectorStore.addDocuments(docs) + return vectorStore + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const docs = nodeData.inputs?.document as Document[] + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + // The following code is a workaround for a bug (Langchain Issue #1589) in the underlying library. + // Store does not support object in metadata and fail silently + finalDocs.forEach((d) => { + delete d.metadata.pdf + delete d.metadata.loc + }) + // end of workaround + return super.init(nodeData, _, options, flattenDocs) + } +} + +module.exports = { nodeClass: ElasicsearchUpsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/Elasticsearch/elasticsearch.png b/packages/components/nodes/vectorstores/Elasticsearch/elasticsearch.png new file mode 100644 index 00000000..fdb66863 Binary files /dev/null and b/packages/components/nodes/vectorstores/Elasticsearch/elasticsearch.png differ diff --git a/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts b/packages/components/nodes/vectorstores/Pinecone/Pinecone_Existing.ts similarity index 97% rename from packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts rename to packages/components/nodes/vectorstores/Pinecone/Pinecone_Existing.ts index 2369165d..e8536d8d 100644 --- a/packages/components/nodes/vectorstores/Pinecone_Existing/Pinecone_Existing.ts +++ b/packages/components/nodes/vectorstores/Pinecone/Pinecone_Existing.ts @@ -1,5 +1,5 @@ import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { PineconeClient } from '@pinecone-database/pinecone' +import { Pinecone } from '@pinecone-database/pinecone' import { PineconeLibArgs, PineconeStore } from 'langchain/vectorstores/pinecone' import { Embeddings } from 'langchain/embeddings/base' import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' @@ -95,8 +95,7 @@ class Pinecone_Existing_VectorStores implements INode { const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData) const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData) - const client = new PineconeClient() - await client.init({ + const client = new Pinecone({ apiKey: pineconeApiKey, environment: pineconeEnv }) diff --git a/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts b/packages/components/nodes/vectorstores/Pinecone/Pinecone_Upsert.ts similarity index 97% rename from packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts rename to packages/components/nodes/vectorstores/Pinecone/Pinecone_Upsert.ts index 3d2a6497..4a12f27b 100644 --- a/packages/components/nodes/vectorstores/Pinecone_Upsert/Pinecone_Upsert.ts +++ b/packages/components/nodes/vectorstores/Pinecone/Pinecone_Upsert.ts @@ -1,5 +1,5 @@ import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { PineconeClient } from '@pinecone-database/pinecone' +import { Pinecone } from '@pinecone-database/pinecone' import { PineconeLibArgs, PineconeStore } from 'langchain/vectorstores/pinecone' import { Embeddings } from 'langchain/embeddings/base' import { Document } from 'langchain/document' @@ -96,8 +96,7 @@ class PineconeUpsert_VectorStores implements INode { const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData) const pineconeEnv = getCredentialParam('pineconeEnv', credentialData, nodeData) - const client = new PineconeClient() - await client.init({ + const client = new Pinecone({ apiKey: pineconeApiKey, environment: pineconeEnv }) diff --git a/packages/components/nodes/vectorstores/Pinecone_Existing/pinecone.png b/packages/components/nodes/vectorstores/Pinecone/pinecone.png similarity index 100% rename from packages/components/nodes/vectorstores/Pinecone_Existing/pinecone.png rename to packages/components/nodes/vectorstores/Pinecone/pinecone.png diff --git a/packages/components/nodes/vectorstores/Pinecone_Upsert/pinecone.png b/packages/components/nodes/vectorstores/Pinecone_Upsert/pinecone.png deleted file mode 100644 index 1ae189fd..00000000 Binary files a/packages/components/nodes/vectorstores/Pinecone_Upsert/pinecone.png and /dev/null differ diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara_Existing.ts b/packages/components/nodes/vectorstores/Vectara/Vectara_Existing.ts index 122fcbd2..4448aa5c 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara_Existing.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara_Existing.ts @@ -32,7 +32,7 @@ class VectaraExisting_VectorStores implements INode { } this.inputs = [ { - label: 'Vectara Metadata Filter', + label: 'Metadata Filter', name: 'filter', description: 'Filter to apply to Vectara metadata. Refer to the documentation on how to use Vectara filters with Flowise.', @@ -105,7 +105,8 @@ class VectaraExisting_VectorStores implements INode { const vectaraArgs: VectaraLibArgs = { apiKey: apiKey, customerId: customerId, - corpusId: corpusId + corpusId: corpusId, + source: 'flowise' } const vectaraFilter: VectaraFilter = {} diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara_Upload.ts b/packages/components/nodes/vectorstores/Vectara/Vectara_Upload.ts index 56cfbcff..39104b9d 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara_Upload.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara_Upload.ts @@ -39,7 +39,7 @@ class VectaraUpload_VectorStores implements INode { type: 'file' }, { - label: 'Vectara Metadata Filter', + label: 'Metadata Filter', name: 'filter', description: 'Filter to apply to Vectara metadata. Refer to the documentation on how to use Vectara filters with Flowise.', @@ -113,7 +113,8 @@ class VectaraUpload_VectorStores implements INode { const vectaraArgs: VectaraLibArgs = { apiKey: apiKey, customerId: customerId, - corpusId: corpusId + corpusId: corpusId, + source: 'flowise' } const vectaraFilter: VectaraFilter = {} diff --git a/packages/components/nodes/vectorstores/Vectara/Vectara_Upsert.ts b/packages/components/nodes/vectorstores/Vectara/Vectara_Upsert.ts index 9918fff2..3e035a51 100644 --- a/packages/components/nodes/vectorstores/Vectara/Vectara_Upsert.ts +++ b/packages/components/nodes/vectorstores/Vectara/Vectara_Upsert.ts @@ -41,7 +41,7 @@ class VectaraUpsert_VectorStores implements INode { list: true }, { - label: 'Vectara Metadata Filter', + label: 'Metadata Filter', name: 'filter', description: 'Filter to apply to Vectara metadata. Refer to the documentation on how to use Vectara filters with Flowise.', @@ -116,7 +116,8 @@ class VectaraUpsert_VectorStores implements INode { const vectaraArgs: VectaraLibArgs = { apiKey: apiKey, customerId: customerId, - corpusId: corpusId + corpusId: corpusId, + source: 'flowise' } const vectaraFilter: VectaraFilter = {} diff --git a/packages/components/package.json b/packages/components/package.json index ba63cb92..262a49db 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "flowise-components", - "version": "1.3.7", + "version": "1.3.10", "description": "Flowiseai Components", "main": "dist/src/index", "types": "dist/src/index.d.ts", @@ -16,18 +16,23 @@ }, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { + "@aws-sdk/client-bedrock-runtime": "3.422.0", "@aws-sdk/client-dynamodb": "^3.360.0", + "@aws-sdk/client-s3": "^3.427.0", "@dqbd/tiktoken": "^1.0.7", + "@elastic/elasticsearch": "^8.9.0", "@getzep/zep-js": "^0.6.3", + "@gomomento/sdk": "^1.40.2", "@google-ai/generativelanguage": "^0.2.1", "@huggingface/inference": "^2.6.1", "@notionhq/client": "^2.2.8", "@opensearch-project/opensearch": "^1.2.0", - "@pinecone-database/pinecone": "^0.0.14", + "@pinecone-database/pinecone": "^1.1.1", "@qdrant/js-client-rest": "^1.2.2", "@supabase/supabase-js": "^2.29.0", "@types/js-yaml": "^4.0.5", "@types/jsdom": "^21.1.1", + "@upstash/redis": "^1.22.1", "@zilliz/milvus2-sdk-node": "^2.2.24", "apify-client": "^2.7.1", "axios": "^0.27.2", @@ -42,7 +47,8 @@ "google-auth-library": "^9.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.157", + "ioredis": "^5.3.2", + "langchain": "^0.0.165", "langfuse-langchain": "^1.0.14-alpha.0", "langsmith": "^0.0.32", "linkifyjs": "^4.1.1", @@ -53,6 +59,7 @@ "node-fetch": "^2.6.11", "node-html-markdown": "^1.3.0", "notion-to-md": "^3.1.1", + "object-hash": "^3.0.0", "pdf-parse": "^1.1.1", "pdfjs-dist": "^3.7.107", "pg": "^8.11.2", @@ -69,6 +76,7 @@ "devDependencies": { "@types/gulp": "4.0.9", "@types/node-fetch": "2.6.2", + "@types/object-hash": "^3.0.2", "@types/pg": "^8.10.2", "@types/ws": "^8.5.3", "gulp": "^4.0.2", diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index e883d056..d0694d6f 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -5,6 +5,7 @@ export type NodeParamsType = | 'asyncOptions' | 'options' + | 'multiOptions' | 'string' | 'number' | 'boolean' diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts index 10f9a214..37075342 100644 --- a/packages/components/src/handler.ts +++ b/packages/components/src/handler.ts @@ -151,6 +151,7 @@ export class CustomChainHandler extends BaseCallbackHandler { socketIOClientId = '' skipK = 0 // Skip streaming for first K numbers of handleLLMStart returnSourceDocuments = false + cachedResponse = true constructor(socketIO: Server, socketIOClientId: string, skipK?: number, returnSourceDocuments?: boolean) { super() @@ -161,6 +162,7 @@ export class CustomChainHandler extends BaseCallbackHandler { } handleLLMStart() { + this.cachedResponse = false if (this.skipK > 0) this.skipK -= 1 } @@ -178,9 +180,30 @@ export class CustomChainHandler extends BaseCallbackHandler { this.socketIO.to(this.socketIOClientId).emit('end') } - handleChainEnd(outputs: ChainValues): void | Promise { - if (this.returnSourceDocuments) { - this.socketIO.to(this.socketIOClientId).emit('sourceDocuments', outputs?.sourceDocuments) + handleChainEnd(outputs: ChainValues, _: string, parentRunId?: string): void | Promise { + /* + Langchain does not call handleLLMStart, handleLLMEnd, handleLLMNewToken when the chain is cached. + Callback Order is "Chain Start -> LLM Start --> LLM Token --> LLM End -> Chain End" for normal responses. + Callback Order is "Chain Start -> Chain End" for cached responses. + */ + if (this.cachedResponse && parentRunId === undefined) { + const cachedValue = outputs.text ?? outputs.response ?? outputs.output ?? outputs.output_text + //split at whitespace, and keep the whitespace. This is to preserve the original formatting. + const result = cachedValue.split(/(\s+)/) + result.forEach((token: string, index: number) => { + if (index === 0) { + this.socketIO.to(this.socketIOClientId).emit('start', token) + } + this.socketIO.to(this.socketIOClientId).emit('token', token) + }) + if (this.returnSourceDocuments) { + this.socketIO.to(this.socketIOClientId).emit('sourceDocuments', outputs?.sourceDocuments) + } + this.socketIO.to(this.socketIOClientId).emit('end') + } else { + if (this.returnSourceDocuments) { + this.socketIO.to(this.socketIOClientId).emit('sourceDocuments', outputs?.sourceDocuments) + } } } } @@ -215,23 +238,17 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO }) callbacks.push(tracer) } else if (provider === 'langFuse') { - const flushAt = analytic[provider].flushAt as string - const flushInterval = analytic[provider].flushInterval as string - const requestTimeout = analytic[provider].requestTimeout as string const release = analytic[provider].release as string const langFuseSecretKey = getCredentialParam('langFuseSecretKey', credentialData, nodeData) const langFusePublicKey = getCredentialParam('langFusePublicKey', credentialData, nodeData) const langFuseEndpoint = getCredentialParam('langFuseEndpoint', credentialData, nodeData) - const langFuseOptions: ICommonObject = { + const langFuseOptions: any = { secretKey: langFuseSecretKey, publicKey: langFusePublicKey, baseUrl: langFuseEndpoint ?? 'https://cloud.langfuse.com' } - if (flushAt) langFuseOptions.flushAt = parseInt(flushAt, 10) - if (flushInterval) langFuseOptions.flushInterval = parseInt(flushInterval, 10) - if (requestTimeout) langFuseOptions.requestTimeout = parseInt(requestTimeout, 10) if (release) langFuseOptions.release = release const handler = new CallbackHandler(langFuseOptions) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index 3ff57b06..8f33683c 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -533,7 +533,7 @@ export const mapChatHistory = (options: ICommonObject): ChatMessageHistory => { * @param {IMessage[]} chatHistory * @returns {string} */ -export const convertChatHistoryToText = (chatHistory: IMessage[]): string => { +export const convertChatHistoryToText = (chatHistory: IMessage[] = []): string => { return chatHistory .map((chatMessage) => { if (chatMessage.type === 'apiMessage') { diff --git a/packages/server/bin/.gitattributes b/packages/server/bin/.gitattributes new file mode 100644 index 00000000..473187f2 --- /dev/null +++ b/packages/server/bin/.gitattributes @@ -0,0 +1,2 @@ +dev eol=lf +run eol=lf \ No newline at end of file diff --git a/packages/server/marketplaces/chatflows/API Agent OpenAI.json b/packages/server/marketplaces/chatflows/API Agent OpenAI.json index 01e3d8f9..b2ff977b 100644 --- a/packages/server/marketplaces/chatflows/API Agent OpenAI.json +++ b/packages/server/marketplaces/chatflows/API Agent OpenAI.json @@ -89,7 +89,7 @@ "id": "chatOpenAI_1", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -201,7 +201,15 @@ "id": "chatOpenAI_1-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_1-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, @@ -392,7 +400,7 @@ "id": "chatOpenAI_2", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -504,7 +512,15 @@ "id": "chatOpenAI_2-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_2-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/API Agent.json b/packages/server/marketplaces/chatflows/API Agent.json index b9862add..c37aaff2 100644 --- a/packages/server/marketplaces/chatflows/API Agent.json +++ b/packages/server/marketplaces/chatflows/API Agent.json @@ -397,7 +397,7 @@ "id": "chatOpenAI_2", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -509,7 +509,15 @@ "id": "chatOpenAI_2-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_2-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, @@ -551,7 +559,7 @@ "id": "chatOpenAI_1", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -663,7 +671,15 @@ "id": "chatOpenAI_1-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_1-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, @@ -705,7 +721,7 @@ "id": "chatOpenAI_3", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -817,7 +833,15 @@ "id": "chatOpenAI_3-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_3-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/Antonym.json b/packages/server/marketplaces/chatflows/Antonym.json index 95d3c151..5f8ff7a8 100644 --- a/packages/server/marketplaces/chatflows/Antonym.json +++ b/packages/server/marketplaces/chatflows/Antonym.json @@ -169,14 +169,14 @@ "id": "chatOpenAI_0", "position": { "x": 1226.7977900193628, - "y": 48.01100655894436 + "y": -22.01100655894436 }, "type": "customNode", "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -288,7 +288,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, @@ -313,7 +321,7 @@ "selected": false, "positionAbsolute": { "x": 1226.7977900193628, - "y": 48.01100655894436 + "y": -22.01100655894436 }, "dragging": false }, diff --git a/packages/server/marketplaces/chatflows/AutoGPT.json b/packages/server/marketplaces/chatflows/AutoGPT.json index 53837151..4fb706ab 100644 --- a/packages/server/marketplaces/chatflows/AutoGPT.json +++ b/packages/server/marketplaces/chatflows/AutoGPT.json @@ -252,7 +252,7 @@ "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -364,7 +364,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/BabyAGI.json b/packages/server/marketplaces/chatflows/BabyAGI.json index c2897531..04410b82 100644 --- a/packages/server/marketplaces/chatflows/BabyAGI.json +++ b/packages/server/marketplaces/chatflows/BabyAGI.json @@ -78,7 +78,7 @@ "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -190,7 +190,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/CSV Agent.json b/packages/server/marketplaces/chatflows/CSV Agent.json index 1515fcad..37764a53 100644 --- a/packages/server/marketplaces/chatflows/CSV Agent.json +++ b/packages/server/marketplaces/chatflows/CSV Agent.json @@ -70,7 +70,7 @@ "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -182,7 +182,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/ChatGPTPlugin.json b/packages/server/marketplaces/chatflows/ChatGPTPlugin.json index 471853ba..1f00ff5f 100644 --- a/packages/server/marketplaces/chatflows/ChatGPTPlugin.json +++ b/packages/server/marketplaces/chatflows/ChatGPTPlugin.json @@ -215,7 +215,7 @@ "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -327,7 +327,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/Claude LLM.json b/packages/server/marketplaces/chatflows/Claude LLM.json index 243d2600..b7989815 100644 --- a/packages/server/marketplaces/chatflows/Claude LLM.json +++ b/packages/server/marketplaces/chatflows/Claude LLM.json @@ -141,14 +141,14 @@ "id": "chatAnthropic_0", "position": { "x": 800.5525382783799, - "y": -76.7988221837009 + "y": -130.7988221837009 }, "type": "customNode", "data": { "id": "chatAnthropic_0", "label": "ChatAnthropic", "name": "chatAnthropic", - "version": 1, + "version": 2, "type": "ChatAnthropic", "baseClasses": ["ChatAnthropic", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -258,7 +258,15 @@ "id": "chatAnthropic_0-input-topK-number" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatAnthropic_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "claude-2", "temperature": 0.9, @@ -280,7 +288,7 @@ "selected": false, "positionAbsolute": { "x": 800.5525382783799, - "y": -76.7988221837009 + "y": -130.7988221837009 }, "dragging": false }, diff --git a/packages/server/marketplaces/chatflows/Conversational Agent.json b/packages/server/marketplaces/chatflows/Conversational Agent.json index 55475b3e..d18f2ac0 100644 --- a/packages/server/marketplaces/chatflows/Conversational Agent.json +++ b/packages/server/marketplaces/chatflows/Conversational Agent.json @@ -157,7 +157,7 @@ "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -269,7 +269,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json index dcf344d1..7c5c38e2 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval Agent.json @@ -13,7 +13,7 @@ "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", - "version": 1, + "version": 2, "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], @@ -126,7 +126,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo-16k", "temperature": "0", diff --git a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json index bf27e443..8bbb904e 100644 --- a/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json +++ b/packages/server/marketplaces/chatflows/Conversational Retrieval QA Chain.json @@ -7,14 +7,14 @@ "id": "chatOpenAI_0", "position": { "x": 1184.1176114500388, - "y": -44.15535835370571 + "y": -74.15535835370571 }, "type": "customNode", "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -126,7 +126,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": "0", @@ -150,7 +158,7 @@ }, "positionAbsolute": { "x": 1184.1176114500388, - "y": -44.15535835370571 + "y": -74.15535835370571 }, "selected": false, "dragging": false @@ -418,7 +426,7 @@ "id": "textFile_0", "label": "Text File", "name": "textFile", - "version": 1, + "version": 2, "type": "Document", "baseClasses": ["Document"], "category": "Document Loaders", @@ -455,13 +463,29 @@ }, "outputAnchors": [ { - "id": "textFile_0-output-textFile-Document", - "name": "textFile", - "label": "Document", - "type": "Document" + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "textFile_0-output-document-Document", + "name": "document", + "label": "Document", + "type": "Document" + }, + { + "id": "textFile_0-output-text-string|json", + "name": "text", + "label": "Text", + "type": "string | json" + } + ], + "default": "document" } ], - "outputs": {}, + "outputs": { + "output": "document" + }, "selected": false }, "selected": false, diff --git a/packages/server/marketplaces/chatflows/Flowise Docs QnA.json b/packages/server/marketplaces/chatflows/Flowise Docs QnA.json index f8b274a1..2928d29d 100644 --- a/packages/server/marketplaces/chatflows/Flowise Docs QnA.json +++ b/packages/server/marketplaces/chatflows/Flowise Docs QnA.json @@ -386,7 +386,7 @@ "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -498,7 +498,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json b/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json index 6e159a28..e51e1ee0 100644 --- a/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json +++ b/packages/server/marketplaces/chatflows/HuggingFace LLM Chain.json @@ -148,14 +148,14 @@ "id": "huggingFaceInference_LLMs_0", "position": { "x": 498.8594464193537, - "y": -44.91050256311678 + "y": -94.91050256311678 }, "type": "customNode", "data": { "id": "huggingFaceInference_LLMs_0", "label": "HuggingFace Inference", "name": "huggingFaceInference_LLMs", - "version": 1, + "version": 2, "type": "HuggingFaceInference", "baseClasses": ["HuggingFaceInference", "LLM", "BaseLLM", "BaseLanguageModel"], "category": "LLMs", @@ -232,7 +232,15 @@ "id": "huggingFaceInference_LLMs_0-input-frequencyPenalty-number" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "huggingFaceInference_LLMs_0-input-cache-BaseCache" + } + ], "inputs": { "model": "tiiuae/falcon-7b-instruct", "endpoint": "", @@ -256,7 +264,7 @@ "selected": false, "positionAbsolute": { "x": 498.8594464193537, - "y": -44.91050256311678 + "y": -94.91050256311678 }, "dragging": false } diff --git a/packages/server/marketplaces/chatflows/Local QnA.json b/packages/server/marketplaces/chatflows/Local QnA.json index 9d9f5ec8..fb13ba21 100644 --- a/packages/server/marketplaces/chatflows/Local QnA.json +++ b/packages/server/marketplaces/chatflows/Local QnA.json @@ -265,14 +265,14 @@ "id": "chatLocalAI_0", "position": { "x": 1191.9512064167336, - "y": -44.05401001663306 + "y": -94.05401001663306 }, "type": "customNode", "data": { "id": "chatLocalAI_0", "label": "ChatLocalAI", "name": "chatLocalAI", - "version": 1, + "version": 2, "type": "ChatLocalAI", "baseClasses": ["ChatLocalAI", "BaseChatModel", "LLM", "BaseLLM", "BaseLanguageModel", "BaseLangChain"], "category": "Chat Models", @@ -325,7 +325,15 @@ "id": "chatLocalAI_0-input-timeout-number" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatLocalAI_0-input-cache-BaseCache" + } + ], "inputs": { "basePath": "http://localhost:8080/v1", "modelName": "ggml-gpt4all-j.bin", @@ -348,7 +356,7 @@ "selected": false, "positionAbsolute": { "x": 1191.9512064167336, - "y": -44.05401001663306 + "y": -94.05401001663306 }, "dragging": false }, @@ -402,13 +410,29 @@ }, "outputAnchors": [ { - "id": "textFile_0-output-textFile-Document", - "name": "textFile", - "label": "Document", - "type": "Document" + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "textFile_0-output-document-Document", + "name": "document", + "label": "Document", + "type": "Document" + }, + { + "id": "textFile_0-output-text-string|json", + "name": "text", + "label": "Text", + "type": "string | json" + } + ], + "default": "document" } ], - "outputs": {}, + "outputs": { + "output": "document" + }, "selected": false }, "selected": false, diff --git a/packages/server/marketplaces/chatflows/Long Term Memory.json b/packages/server/marketplaces/chatflows/Long Term Memory.json index 07669f82..6f22c00a 100644 --- a/packages/server/marketplaces/chatflows/Long Term Memory.json +++ b/packages/server/marketplaces/chatflows/Long Term Memory.json @@ -115,14 +115,14 @@ "id": "chatOpenAI_0", "position": { "x": 1554.3875781165111, - "y": -14.792508259787212 + "y": -74.792508259787212 }, "type": "customNode", "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -234,7 +234,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": "0", @@ -259,7 +267,7 @@ "selected": false, "positionAbsolute": { "x": 1554.3875781165111, - "y": -14.792508259787212 + "y": -74.792508259787212 }, "dragging": false }, diff --git a/packages/server/marketplaces/chatflows/MRKLAgent.json b/packages/server/marketplaces/chatflows/MRKLAgent.json index f851b0ed..697e4919 100644 --- a/packages/server/marketplaces/chatflows/MRKLAgent.json +++ b/packages/server/marketplaces/chatflows/MRKLAgent.json @@ -156,7 +156,7 @@ "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -268,7 +268,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/Metadata Filter Load.json b/packages/server/marketplaces/chatflows/Metadata Filter Load.json index b6ca91e3..43438d6b 100644 --- a/packages/server/marketplaces/chatflows/Metadata Filter Load.json +++ b/packages/server/marketplaces/chatflows/Metadata Filter Load.json @@ -113,14 +113,14 @@ "id": "chatOpenAI_0", "position": { "x": 1197.7264239788542, - "y": -16.177600120515933 + "y": -76.177600120515933 }, "type": "customNode", "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -232,7 +232,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": "0", @@ -257,7 +265,7 @@ "selected": false, "positionAbsolute": { "x": 1197.7264239788542, - "y": -16.177600120515933 + "y": -76.177600120515933 }, "dragging": false }, diff --git a/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json b/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json index e70b11f7..6e79301e 100644 --- a/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json +++ b/packages/server/marketplaces/chatflows/Metadata Filter Upsert.json @@ -109,13 +109,29 @@ }, "outputAnchors": [ { - "id": "textFile_0-output-textFile-Document", - "name": "textFile", - "label": "Document", - "type": "Document" + "name": "output", + "label": "Output", + "type": "options", + "options": [ + { + "id": "textFile_0-output-document-Document", + "name": "document", + "label": "Document", + "type": "Document" + }, + { + "id": "textFile_0-output-text-string|json", + "name": "text", + "label": "Text", + "type": "string | json" + } + ], + "default": "document" } ], - "outputs": {}, + "outputs": { + "output": "document" + }, "selected": false }, "selected": false, @@ -436,7 +452,7 @@ "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -548,7 +564,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/Multi Prompt Chain.json b/packages/server/marketplaces/chatflows/Multi Prompt Chain.json index cf86df5b..e1063dcf 100644 --- a/packages/server/marketplaces/chatflows/Multi Prompt Chain.json +++ b/packages/server/marketplaces/chatflows/Multi Prompt Chain.json @@ -278,7 +278,7 @@ "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -390,7 +390,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json b/packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json index f5604bf6..36240e39 100644 --- a/packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json +++ b/packages/server/marketplaces/chatflows/Multi Retrieval QA Chain.json @@ -679,7 +679,7 @@ "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -791,7 +791,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/Multiple VectorDB.json b/packages/server/marketplaces/chatflows/Multiple VectorDB.json index ac4643aa..b4dedfdd 100644 --- a/packages/server/marketplaces/chatflows/Multiple VectorDB.json +++ b/packages/server/marketplaces/chatflows/Multiple VectorDB.json @@ -321,14 +321,14 @@ "id": "openAI_2", "position": { "x": 520.8471510168988, - "y": -1282.1183473852964 + "y": -1362.1183473852964 }, "type": "customNode", "data": { "id": "openAI_2", "label": "OpenAI", "name": "openAI", - "version": 2, + "version": 3, "type": "OpenAI", "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], "category": "LLMs", @@ -436,7 +436,15 @@ "id": "openAI_2-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "openAI_2-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo-instruct", "temperature": 0.7, @@ -463,7 +471,7 @@ "selected": false, "positionAbsolute": { "x": 520.8471510168988, - "y": -1282.1183473852964 + "y": -1362.1183473852964 }, "dragging": false }, @@ -557,7 +565,7 @@ "id": "chromaExistingIndex_0", "position": { "x": 509.55198017578016, - "y": -732.42003311752 + "y": -782.42003311752 }, "type": "customNode", "data": { @@ -638,7 +646,7 @@ "selected": false, "positionAbsolute": { "x": 509.55198017578016, - "y": -732.42003311752 + "y": -782.42003311752 }, "dragging": false }, @@ -732,14 +740,14 @@ "id": "openAI_3", "position": { "x": 504.808358369027, - "y": -197.78194663790197 + "y": -257.78194663790197 }, "type": "customNode", "data": { "id": "openAI_3", "label": "OpenAI", "name": "openAI", - "version": 2, + "version": 3, "type": "OpenAI", "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], "category": "LLMs", @@ -847,7 +855,15 @@ "id": "openAI_3-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "openAI_3-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo-instruct", "temperature": 0.7, @@ -874,7 +890,7 @@ "selected": false, "positionAbsolute": { "x": 504.808358369027, - "y": -197.78194663790197 + "y": -257.78194663790197 }, "dragging": false }, @@ -993,14 +1009,14 @@ "id": "openAI_4", "position": { "x": 1619.5346765785587, - "y": 292.29615581180684 + "y": 352.29615581180684 }, "type": "customNode", "data": { "id": "openAI_4", "label": "OpenAI", "name": "openAI", - "version": 2, + "version": 3, "type": "OpenAI", "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], "category": "LLMs", @@ -1108,7 +1124,15 @@ "id": "openAI_4-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "openAI_4-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo-instruct", "temperature": 0.7, @@ -1135,7 +1159,7 @@ "selected": false, "positionAbsolute": { "x": 1619.5346765785587, - "y": 292.29615581180684 + "y": 352.29615581180684 }, "dragging": false } diff --git a/packages/server/marketplaces/chatflows/OpenAI Agent.json b/packages/server/marketplaces/chatflows/OpenAI Agent.json index 91d5d38c..9a98d29d 100644 --- a/packages/server/marketplaces/chatflows/OpenAI Agent.json +++ b/packages/server/marketplaces/chatflows/OpenAI Agent.json @@ -281,7 +281,7 @@ "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -393,7 +393,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, diff --git a/packages/server/marketplaces/chatflows/Prompt Chaining with VectorStore.json b/packages/server/marketplaces/chatflows/Prompt Chaining with VectorStore.json index 9d6838eb..2af61190 100644 --- a/packages/server/marketplaces/chatflows/Prompt Chaining with VectorStore.json +++ b/packages/server/marketplaces/chatflows/Prompt Chaining with VectorStore.json @@ -260,13 +260,13 @@ "id": "chatOpenAI_0", "position": { "x": 335.7621848973805, - "y": -651.7411273245009 + "y": -721.7411273245009 }, "type": "customNode", "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", - "version": 1, + "version": 2, "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"], @@ -385,7 +385,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo-16k", "temperature": 0.9, @@ -411,7 +419,7 @@ "dragging": false, "positionAbsolute": { "x": 335.7621848973805, - "y": -651.7411273245009 + "y": -721.7411273245009 } }, { @@ -420,13 +428,13 @@ "id": "chatOpenAI_1", "position": { "x": 1765.2801848172305, - "y": -667.9261054149061 + "y": -737.9261054149061 }, "type": "customNode", "data": { "id": "chatOpenAI_1", "label": "ChatOpenAI", - "version": 1, + "version": 2, "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"], @@ -545,7 +553,15 @@ "id": "chatOpenAI_1-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_1-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo-16k", "temperature": 0.9, @@ -571,7 +587,7 @@ "dragging": false, "positionAbsolute": { "x": 1765.2801848172305, - "y": -667.9261054149061 + "y": -737.9261054149061 } }, { diff --git a/packages/server/marketplaces/chatflows/Prompt Chaining.json b/packages/server/marketplaces/chatflows/Prompt Chaining.json index 5bce9905..77c238ad 100644 --- a/packages/server/marketplaces/chatflows/Prompt Chaining.json +++ b/packages/server/marketplaces/chatflows/Prompt Chaining.json @@ -289,14 +289,14 @@ "id": "openAI_1", "position": { "x": 791.6102007244282, - "y": -13.71386876566092 + "y": -83.71386876566092 }, "type": "customNode", "data": { "id": "openAI_1", "label": "OpenAI", "name": "openAI", - "version": 2, + "version": 3, "type": "OpenAI", "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], "category": "LLMs", @@ -404,7 +404,15 @@ "id": "openAI_1-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "openAI_1-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo-instruct", "temperature": 0.7, @@ -431,7 +439,7 @@ "selected": false, "positionAbsolute": { "x": 791.6102007244282, - "y": -13.71386876566092 + "y": -83.71386876566092 }, "dragging": false }, @@ -441,14 +449,14 @@ "id": "openAI_2", "position": { "x": 1571.148617508543, - "y": -20.372437481171687 + "y": -90.372437481171687 }, "type": "customNode", "data": { "id": "openAI_2", "label": "OpenAI", "name": "openAI", - "version": 2, + "version": 3, "type": "OpenAI", "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], "category": "LLMs", @@ -556,7 +564,15 @@ "id": "openAI_2-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "openAI_2-input-cache-BaseCache" + } + ], "default": "gpt-3.5-turbo-instruct", "inputs": { "modelName": "gpt-3.5-turbo-instruct", @@ -584,7 +600,7 @@ "selected": false, "positionAbsolute": { "x": 1571.148617508543, - "y": -20.372437481171687 + "y": -90.372437481171687 }, "dragging": false } diff --git a/packages/server/marketplaces/chatflows/Replicate LLM.json b/packages/server/marketplaces/chatflows/Replicate LLM.json index c5a0ac8f..0049214c 100644 --- a/packages/server/marketplaces/chatflows/Replicate LLM.json +++ b/packages/server/marketplaces/chatflows/Replicate LLM.json @@ -148,13 +148,13 @@ "id": "replicate_0", "position": { "x": 623.313978186024, - "y": -72.92788335022428 + "y": -142.92788335022428 }, "type": "customNode", "data": { "id": "replicate_0", "label": "Replicate", - "version": 1, + "version": 2, "name": "replicate", "type": "Replicate", "baseClasses": ["Replicate", "BaseChatModel", "LLM", "BaseLLM", "BaseLanguageModel", "Runnable"], @@ -226,7 +226,15 @@ "id": "replicate_0-input-additionalInputs-json" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "replicate_0-input-cache-BaseCache" + } + ], "inputs": { "model": "a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5", "temperature": 0.7, @@ -249,7 +257,7 @@ "selected": false, "positionAbsolute": { "x": 623.313978186024, - "y": -72.92788335022428 + "y": -142.92788335022428 }, "dragging": false } diff --git a/packages/server/marketplaces/chatflows/SQL DB Chain.json b/packages/server/marketplaces/chatflows/SQL DB Chain.json index 646db5d4..3b32efe0 100644 --- a/packages/server/marketplaces/chatflows/SQL DB Chain.json +++ b/packages/server/marketplaces/chatflows/SQL DB Chain.json @@ -13,7 +13,7 @@ "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", - "version": 1, + "version": 2, "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], @@ -126,7 +126,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": "0", diff --git a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json index 2c41a54f..57ff348a 100644 --- a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json +++ b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json @@ -64,14 +64,14 @@ "id": "chatOpenAI_0", "position": { "x": 754.8942497823595, - "y": -70.76607584232393 + "y": -140 }, "type": "customNode", "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -183,7 +183,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, @@ -208,7 +216,7 @@ "selected": false, "positionAbsolute": { "x": 754.8942497823595, - "y": -70.76607584232393 + "y": -140 }, "dragging": false }, diff --git a/packages/server/marketplaces/chatflows/Simple LLM Chain.json b/packages/server/marketplaces/chatflows/Simple LLM Chain.json index 21d5ab68..f3db04ef 100644 --- a/packages/server/marketplaces/chatflows/Simple LLM Chain.json +++ b/packages/server/marketplaces/chatflows/Simple LLM Chain.json @@ -148,14 +148,14 @@ "id": "openAI_0", "position": { "x": 513.3297923232442, - "y": -42.67554802812833 + "y": -112.67554802812833 }, "type": "customNode", "data": { "id": "openAI_0", "label": "OpenAI", "name": "openAI", - "version": 2, + "version": 3, "type": "OpenAI", "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], "category": "LLMs", @@ -263,7 +263,15 @@ "id": "openAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "openAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo-instruct", "temperature": 0.7, @@ -290,7 +298,7 @@ "selected": false, "positionAbsolute": { "x": 513.3297923232442, - "y": -42.67554802812833 + "y": -112.67554802812833 }, "dragging": false } diff --git a/packages/server/marketplaces/chatflows/Translator.json b/packages/server/marketplaces/chatflows/Translator.json index dc2ee6ba..b552aceb 100644 --- a/packages/server/marketplaces/chatflows/Translator.json +++ b/packages/server/marketplaces/chatflows/Translator.json @@ -157,14 +157,14 @@ "id": "chatOpenAI_0", "position": { "x": 436.97058562345904, - "y": 99.96180150605153 + "y": 29.96180150605153 }, "type": "customNode", "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -276,7 +276,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": "0", @@ -301,7 +309,7 @@ "selected": false, "positionAbsolute": { "x": 436.97058562345904, - "y": 99.96180150605153 + "y": 29.96180150605153 }, "dragging": false } diff --git a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json index 47cfef87..5fc540b0 100644 --- a/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json +++ b/packages/server/marketplaces/chatflows/Vectara LLM Chain Upload.json @@ -32,7 +32,7 @@ "id": "vectaraUpload_0-input-file-file" }, { - "label": "Vectara Metadata Filter", + "label": "Metadata Filter", "name": "filter", "description": "Filter to apply to Vectara metadata. Refer to the documentation on how to use Vectara filters with Flowise.", "type": "string", @@ -124,7 +124,7 @@ "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", - "version": 1, + "version": 2, "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel", "Runnable"], @@ -230,7 +230,15 @@ "id": "chatOpenAI_0-input-baseOptions-json" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": "0.5", diff --git a/packages/server/marketplaces/chatflows/WebBrowser.json b/packages/server/marketplaces/chatflows/WebBrowser.json index 95743f9f..b784f9ab 100644 --- a/packages/server/marketplaces/chatflows/WebBrowser.json +++ b/packages/server/marketplaces/chatflows/WebBrowser.json @@ -194,14 +194,14 @@ "id": "chatOpenAI_0", "position": { "x": 734.7477982032904, - "y": -400.9979556765114 + "y": -470.9979556765114 }, "type": "customNode", "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -313,7 +313,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, @@ -338,7 +346,7 @@ "selected": false, "positionAbsolute": { "x": 734.7477982032904, - "y": -400.9979556765114 + "y": -470.9979556765114 }, "dragging": false }, @@ -432,14 +440,14 @@ "id": "chatOpenAI_1", "position": { "x": 68.312124033115, - "y": -169.65476709991256 + "y": -239.65476709991256 }, "type": "customNode", "data": { "id": "chatOpenAI_1", "label": "ChatOpenAI", "name": "chatOpenAI", - "version": 1, + "version": 2, "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], "category": "Chat Models", @@ -551,7 +559,15 @@ "id": "chatOpenAI_1-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_1-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo", "temperature": 0.9, @@ -576,7 +592,7 @@ "selected": false, "positionAbsolute": { "x": 68.312124033115, - "y": -169.65476709991256 + "y": -239.65476709991256 }, "dragging": false } diff --git a/packages/server/marketplaces/chatflows/WebPage QnA.json b/packages/server/marketplaces/chatflows/WebPage QnA.json index 812f0bd5..da21cb2d 100644 --- a/packages/server/marketplaces/chatflows/WebPage QnA.json +++ b/packages/server/marketplaces/chatflows/WebPage QnA.json @@ -13,7 +13,7 @@ "data": { "id": "chatOpenAI_0", "label": "ChatOpenAI", - "version": 1, + "version": 2, "name": "chatOpenAI", "type": "ChatOpenAI", "baseClasses": ["ChatOpenAI", "BaseChatModel", "BaseLanguageModel"], @@ -126,7 +126,15 @@ "id": "chatOpenAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "chatOpenAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo-16k", "temperature": "0.9", @@ -425,7 +433,7 @@ "data": { "id": "cheerioWebScraper_0", "label": "Cheerio Web Scraper", - "version": 1, + "version": 1.1, "name": "cheerioWebScraper", "type": "Document", "baseClasses": ["Document"], @@ -469,6 +477,15 @@ "warning": "Retrieving all links might take long time, and all links will be upserted again if the flow's state changed (eg: different URL, chunk size, etc)", "id": "cheerioWebScraper_0-input-limit-number" }, + { + "label": "Selector (CSS)", + "name": "selector", + "type": "string", + "description": "Specify a CSS selector to select the content to be extracted", + "optional": true, + "additionalParams": true, + "id": "cheerioWebScraper_0-input-selector-string" + }, { "label": "Metadata", "name": "metadata", diff --git a/packages/server/marketplaces/chatflows/Zapier NLA.json b/packages/server/marketplaces/chatflows/Zapier NLA.json index 182b24ae..49527da2 100644 --- a/packages/server/marketplaces/chatflows/Zapier NLA.json +++ b/packages/server/marketplaces/chatflows/Zapier NLA.json @@ -115,7 +115,7 @@ "id": "openAI_0", "label": "OpenAI", "name": "openAI", - "version": 2, + "version": 3, "type": "OpenAI", "baseClasses": ["OpenAI", "BaseLLM", "BaseLanguageModel"], "category": "LLMs", @@ -223,7 +223,15 @@ "id": "openAI_0-input-basepath-string" } ], - "inputAnchors": [], + "inputAnchors": [ + { + "label": "Cache", + "name": "cache", + "type": "BaseCache", + "optional": true, + "id": "openAI_0-input-cache-BaseCache" + } + ], "inputs": { "modelName": "gpt-3.5-turbo-instruct", "temperature": 0.7, diff --git a/packages/server/package.json b/packages/server/package.json index ffd6b111..55a00755 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "flowise", - "version": "1.3.6", + "version": "1.3.8", "description": "Flowiseai Server", "main": "dist/index", "types": "dist/index.d.ts", diff --git a/packages/server/src/CachePool.ts b/packages/server/src/CachePool.ts new file mode 100644 index 00000000..b59789d2 --- /dev/null +++ b/packages/server/src/CachePool.ts @@ -0,0 +1,53 @@ +import { IActiveCache } from './Interface' + +/** + * This pool is to keep track of in-memory cache used for LLM and Embeddings + */ +export class CachePool { + activeLLMCache: IActiveCache = {} + activeEmbeddingCache: IActiveCache = {} + + /** + * Add to the llm cache pool + * @param {string} chatflowid + * @param {Map} value + */ + addLLMCache(chatflowid: string, value: Map) { + this.activeLLMCache[chatflowid] = value + } + + /** + * Add to the embedding cache pool + * @param {string} chatflowid + * @param {Map} value + */ + addEmbeddingCache(chatflowid: string, value: Map) { + this.activeEmbeddingCache[chatflowid] = value + } + + /** + * Get item from llm cache pool + * @param {string} chatflowid + */ + getLLMCache(chatflowid: string): Map | undefined { + return this.activeLLMCache[chatflowid] + } + + /** + * Get item from embedding cache pool + * @param {string} chatflowid + */ + getEmbeddingCache(chatflowid: string): Map | undefined { + return this.activeEmbeddingCache[chatflowid] + } +} + +let cachePoolInstance: CachePool | undefined + +export function getInstance(): CachePool { + if (cachePoolInstance === undefined) { + cachePoolInstance = new CachePool() + } + + return cachePoolInstance +} diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 120c07be..d6d24596 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -166,6 +166,10 @@ export interface IActiveChatflows { } } +export interface IActiveCache { + [key: string]: Map +} + export interface IOverrideConfig { node: string nodeId: string diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index c2f3cb38..7dc8cb8c 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -55,6 +55,7 @@ import { ChatMessage } from './database/entities/ChatMessage' import { Credential } from './database/entities/Credential' import { Tool } from './database/entities/Tool' import { ChatflowPool } from './ChatflowPool' +import { CachePool } from './CachePool' import { ICommonObject, INodeOptionsValue } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' @@ -62,6 +63,7 @@ export class App { app: express.Application nodesPool: NodesPool chatflowPool: ChatflowPool + cachePool: CachePool AppDataSource = getDataSource() constructor() { @@ -93,6 +95,9 @@ export class App { // Initialize Rate Limit const AllChatFlow: IChatFlow[] = await getAllChatFlow() await initializeRateLimiter(AllChatFlow) + + // Initialize cache pool + this.cachePool = new CachePool() }) .catch((err) => { logger.error('❌ [server]: Error during Data Source initialization:', err) @@ -994,8 +999,10 @@ export class App { incomingInput.question, incomingInput.history, chatId, + chatflowid, this.AppDataSource, - incomingInput?.overrideConfig + incomingInput?.overrideConfig, + this.cachePool ) const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index db13dd2d..749d8c55 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -35,6 +35,7 @@ import { ChatMessage } from '../database/entities/ChatMessage' import { Credential } from '../database/entities/Credential' import { Tool } from '../database/entities/Tool' import { DataSource } from 'typeorm' +import { CachePool } from '../CachePool' const QUESTION_VAR_PREFIX = 'question' const CHAT_HISTORY_VAR_PREFIX = 'chat_history' @@ -197,8 +198,10 @@ export const getEndingNode = (nodeDependencies: INodeDependencies, graph: INodeD * @param {IComponentNodes} componentNodes * @param {string} question * @param {string} chatId + * @param {string} chatflowid * @param {DataSource} appDataSource * @param {ICommonObject} overrideConfig + * @param {CachePool} cachePool */ export const buildLangchain = async ( startingNodeIds: string[], @@ -209,8 +212,10 @@ export const buildLangchain = async ( question: string, chatHistory: IMessage[], chatId: string, + chatflowid: string, appDataSource: DataSource, - overrideConfig?: ICommonObject + overrideConfig?: ICommonObject, + cachePool?: CachePool ) => { const flowNodes = cloneDeep(reactFlowNodes) @@ -245,9 +250,11 @@ export const buildLangchain = async ( logger.debug(`[server]: Initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData, question, { chatId, + chatflowid, appDataSource, databaseEntities, - logger + logger, + cachePool }) logger.debug(`[server]: Finished initializing ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) } catch (e: any) { @@ -477,6 +484,7 @@ export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig: */ export const isStartNodeDependOnInput = (startingNodes: IReactFlowNode[], nodes: IReactFlowNode[]): boolean => { for (const node of startingNodes) { + if (node.data.category === 'Cache') return true for (const inputName in node.data.inputs) { const inputVariables = getInputVariables(node.data.inputs[inputName]) if (inputVariables.length > 0) return true @@ -771,8 +779,8 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], component */ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNodeData: INodeData) => { const streamAvailableLLMs = { - 'Chat Models': ['azureChatOpenAI', 'chatOpenAI', 'chatAnthropic'], - LLMs: ['azureOpenAI', 'openAI'] + 'Chat Models': ['azureChatOpenAI', 'chatOpenAI', 'chatAnthropic', 'chatOllama'], + LLMs: ['azureOpenAI', 'openAI', 'ollama'] } let isChatOrLLMsExist = false diff --git a/packages/ui/package.json b/packages/ui/package.json index f0101d2c..41ad515f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "flowise-ui", - "version": "1.3.4", + "version": "1.3.6", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://flowiseai.com", "author": { diff --git a/packages/ui/src/ui-component/dialog/AnalyseFlowDialog.js b/packages/ui/src/ui-component/dialog/AnalyseFlowDialog.js index 2d9a7d91..dd6bb8ab 100644 --- a/packages/ui/src/ui-component/dialog/AnalyseFlowDialog.js +++ b/packages/ui/src/ui-component/dialog/AnalyseFlowDialog.js @@ -81,27 +81,6 @@ const analyticProviders = [ type: 'credential', credentialNames: ['langfuseApi'] }, - { - label: 'Flush At', - name: 'flushAt', - type: 'number', - optional: true, - description: 'Number of queued requests' - }, - { - label: 'Flush Interval', - name: 'flushInterval', - type: 'number', - optional: true, - description: 'Interval in ms to flush requests' - }, - { - label: 'Request Timeout', - name: 'requestTimeout', - type: 'number', - optional: true, - description: 'Timeout in ms for requests' - }, { label: 'Release', name: 'release', diff --git a/packages/ui/src/ui-component/dropdown/MultiDropdown.js b/packages/ui/src/ui-component/dropdown/MultiDropdown.js new file mode 100644 index 00000000..ff07b89f --- /dev/null +++ b/packages/ui/src/ui-component/dropdown/MultiDropdown.js @@ -0,0 +1,79 @@ +import { useState } from 'react' +import { useSelector } from 'react-redux' + +import { Popper, FormControl, TextField, Box, Typography } from '@mui/material' +import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete' +import { styled } from '@mui/material/styles' +import PropTypes from 'prop-types' + +const StyledPopper = styled(Popper)({ + boxShadow: '0px 8px 10px -5px rgb(0 0 0 / 20%), 0px 16px 24px 2px rgb(0 0 0 / 14%), 0px 6px 30px 5px rgb(0 0 0 / 12%)', + borderRadius: '10px', + [`& .${autocompleteClasses.listbox}`]: { + boxSizing: 'border-box', + '& ul': { + padding: 10, + margin: 10 + } + } +}) + +export const MultiDropdown = ({ name, value, options, onSelect, disabled = false, disableClearable = false }) => { + const customization = useSelector((state) => state.customization) + const findMatchingOptions = (options = [], internalValue) => { + let values = [] + if (internalValue && typeof internalValue === 'string') values = JSON.parse(internalValue) + else values = internalValue + return options.filter((option) => values.includes(option.name)) + } + const getDefaultOptionValue = () => [] + let [internalValue, setInternalValue] = useState(value ?? []) + + return ( + + { + let value = '' + if (selections.length) { + const selectionNames = [] + for (let i = 0; i < selections.length; i += 1) { + selectionNames.push(selections[i].name) + } + value = JSON.stringify(selectionNames) + } + setInternalValue(value) + onSelect(value) + }} + PopperComponent={StyledPopper} + renderInput={(params) => } + renderOption={(props, option) => ( + +
+ {option.label} + {option.description && ( + {option.description} + )} +
+
+ )} + /> +
+ ) +} + +MultiDropdown.propTypes = { + name: PropTypes.string, + value: PropTypes.string, + options: PropTypes.array, + onSelect: PropTypes.func, + disabled: PropTypes.bool, + disableClearable: PropTypes.bool +} diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index 324cc112..af7f4353 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -39,7 +39,20 @@ export const initNode = (nodeData, newNodeId) => { const incoming = nodeData.inputs ? nodeData.inputs.length : 0 const outgoing = 1 - const whitelistTypes = ['asyncOptions', 'options', 'string', 'number', 'boolean', 'password', 'json', 'code', 'date', 'file', 'folder'] + const whitelistTypes = [ + 'asyncOptions', + 'options', + 'multiOptions', + 'string', + 'number', + 'boolean', + 'password', + 'json', + 'code', + 'date', + 'file', + 'folder' + ] // Inputs for (let i = 0; i < incoming; i += 1) { diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 176df52f..3a13a3b5 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -11,6 +11,7 @@ import { IconArrowsMaximize, IconEdit, IconAlertTriangle } from '@tabler/icons' // project import import { Dropdown } from 'ui-component/dropdown/Dropdown' +import { MultiDropdown } from 'ui-component/dropdown/MultiDropdown' import { AsyncDropdown } from 'ui-component/dropdown/AsyncDropdown' import { Input } from 'ui-component/input/Input' import { File } from 'ui-component/file/File' @@ -308,6 +309,15 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'} /> )} + {inputParam.type === 'multiOptions' && ( + (data.inputs[inputParam.name] = newValue)} + value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'} + /> + )} {inputParam.type === 'asyncOptions' && ( <> {data.inputParams.length === 1 &&
}