mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 15:00:57 +03:00
Feature/Add bullmq redis for message queue processing (#3568)
* add bullmq redis for message queue processing * Update pnpm-lock.yaml * update queue manager * remove singleton patterns, add redis to cache pool * add bull board ui * update rate limit handler * update redis configuration * Merge add rate limit redis prefix * update rate limit queue events * update preview loader to queue * refractor namings to constants * update env variable for queue * update worker shutdown gracefully
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
||||
} from '../../../src/Interface'
|
||||
import { AgentExecutor } from '../../../src/agents'
|
||||
import { addImagesToMessages, llmSupportsVision } from '../../../src/multiModalUtils'
|
||||
import { checkInputs, Moderation } from '../../moderation/Moderation'
|
||||
import { checkInputs, Moderation, streamResponse } from '../../moderation/Moderation'
|
||||
import { formatResponse } from '../../outputparsers/OutputParserHelpers'
|
||||
|
||||
const DEFAULT_PREFIX = `Assistant is a large language model trained by OpenAI.
|
||||
@@ -124,10 +124,9 @@ class ConversationalAgent_Agents implements INode {
|
||||
input = await checkInputs(moderations, input)
|
||||
} catch (e) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
// if (options.shouldStreamResponse) {
|
||||
// streamResponse(options.sseStreamer, options.chatId, e.message)
|
||||
// }
|
||||
//streamResponse(options.socketIO && options.socketIOClientId, e.message, options.socketIO, options.socketIOClientId)
|
||||
if (options.shouldStreamResponse) {
|
||||
streamResponse(sseStreamer, chatId, e.message)
|
||||
}
|
||||
return formatResponse(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,17 +27,17 @@ class InMemoryCache implements INode {
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const memoryMap = options.cachePool.getLLMCache(options.chatflowid) ?? new Map()
|
||||
const memoryMap = (await options.cachePool.getLLMCache(options.chatflowid)) ?? new Map()
|
||||
const inMemCache = new InMemoryCacheExtended(memoryMap)
|
||||
|
||||
inMemCache.lookup = async (prompt: string, llmKey: string): Promise<any | null> => {
|
||||
const memory = options.cachePool.getLLMCache(options.chatflowid) ?? inMemCache.cache
|
||||
const memory = (await 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<void> => {
|
||||
inMemCache.cache.set(getCacheKey(prompt, llmKey), value)
|
||||
options.cachePool.addLLMCache(options.chatflowid, inMemCache.cache)
|
||||
await options.cachePool.addLLMCache(options.chatflowid, inMemCache.cache)
|
||||
}
|
||||
return inMemCache
|
||||
}
|
||||
|
||||
@@ -43,11 +43,11 @@ class InMemoryEmbeddingCache implements INode {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const namespace = nodeData.inputs?.namespace as string
|
||||
const underlyingEmbeddings = nodeData.inputs?.embeddings as Embeddings
|
||||
const memoryMap = options.cachePool.getEmbeddingCache(options.chatflowid) ?? {}
|
||||
const memoryMap = (await options.cachePool.getEmbeddingCache(options.chatflowid)) ?? {}
|
||||
const inMemCache = new InMemoryEmbeddingCacheExtended(memoryMap)
|
||||
|
||||
inMemCache.mget = async (keys: string[]) => {
|
||||
const memory = options.cachePool.getEmbeddingCache(options.chatflowid) ?? inMemCache.store
|
||||
const memory = (await options.cachePool.getEmbeddingCache(options.chatflowid)) ?? inMemCache.store
|
||||
return keys.map((key) => memory[key])
|
||||
}
|
||||
|
||||
@@ -55,14 +55,14 @@ class InMemoryEmbeddingCache implements INode {
|
||||
for (const [key, value] of keyValuePairs) {
|
||||
inMemCache.store[key] = value
|
||||
}
|
||||
options.cachePool.addEmbeddingCache(options.chatflowid, inMemCache.store)
|
||||
await options.cachePool.addEmbeddingCache(options.chatflowid, inMemCache.store)
|
||||
}
|
||||
|
||||
inMemCache.mdelete = async (keys: string[]): Promise<void> => {
|
||||
for (const key of keys) {
|
||||
delete inMemCache.store[key]
|
||||
}
|
||||
options.cachePool.addEmbeddingCache(options.chatflowid, inMemCache.store)
|
||||
await options.cachePool.addEmbeddingCache(options.chatflowid, inMemCache.store)
|
||||
}
|
||||
|
||||
return CacheBackedEmbeddings.fromBytesStore(underlyingEmbeddings, inMemCache, {
|
||||
|
||||
+53
-62
@@ -1,47 +1,10 @@
|
||||
import { Redis, RedisOptions } from 'ioredis'
|
||||
import { isEqual } from 'lodash'
|
||||
import { Redis } from 'ioredis'
|
||||
import hash from 'object-hash'
|
||||
import { RedisCache as LangchainRedisCache } from '@langchain/community/caches/ioredis'
|
||||
import { StoredGeneration, mapStoredMessageToChatMessage } from '@langchain/core/messages'
|
||||
import { Generation, ChatGeneration } from '@langchain/core/outputs'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam, ICommonObject, INode, INodeData, INodeParams } from '../../../src'
|
||||
|
||||
let redisClientSingleton: Redis
|
||||
let redisClientOption: RedisOptions
|
||||
let redisClientUrl: string
|
||||
|
||||
const getRedisClientbyOption = (option: RedisOptions) => {
|
||||
if (!redisClientSingleton) {
|
||||
// if client doesn't exists
|
||||
redisClientSingleton = new Redis(option)
|
||||
redisClientOption = option
|
||||
return redisClientSingleton
|
||||
} else if (redisClientSingleton && !isEqual(option, redisClientOption)) {
|
||||
// if client exists but option changed
|
||||
redisClientSingleton.quit()
|
||||
redisClientSingleton = new Redis(option)
|
||||
redisClientOption = option
|
||||
return redisClientSingleton
|
||||
}
|
||||
return redisClientSingleton
|
||||
}
|
||||
|
||||
const getRedisClientbyUrl = (url: string) => {
|
||||
if (!redisClientSingleton) {
|
||||
// if client doesn't exists
|
||||
redisClientSingleton = new Redis(url)
|
||||
redisClientUrl = url
|
||||
return redisClientSingleton
|
||||
} else if (redisClientSingleton && url !== redisClientUrl) {
|
||||
// if client exists but option changed
|
||||
redisClientSingleton.quit()
|
||||
redisClientSingleton = new Redis(url)
|
||||
redisClientUrl = url
|
||||
return redisClientSingleton
|
||||
}
|
||||
return redisClientSingleton
|
||||
}
|
||||
|
||||
class RedisCache implements INode {
|
||||
label: string
|
||||
name: string
|
||||
@@ -85,33 +48,19 @@ class RedisCache implements INode {
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const ttl = nodeData.inputs?.ttl as string
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData)
|
||||
|
||||
let client: Redis
|
||||
if (!redisUrl || redisUrl === '') {
|
||||
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 sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData)
|
||||
|
||||
const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}
|
||||
|
||||
client = getRedisClientbyOption({
|
||||
port: portStr ? parseInt(portStr) : 6379,
|
||||
host,
|
||||
username,
|
||||
password,
|
||||
...tlsOptions
|
||||
})
|
||||
} else {
|
||||
client = getRedisClientbyUrl(redisUrl)
|
||||
}
|
||||
|
||||
let client = await getRedisClient(nodeData, options)
|
||||
const redisClient = new LangchainRedisCache(client)
|
||||
|
||||
redisClient.lookup = async (prompt: string, llmKey: string) => {
|
||||
try {
|
||||
const pingResp = await client.ping()
|
||||
if (pingResp !== 'PONG') {
|
||||
client = await getRedisClient(nodeData, options)
|
||||
}
|
||||
} catch (error) {
|
||||
client = await getRedisClient(nodeData, options)
|
||||
}
|
||||
|
||||
let idx = 0
|
||||
let key = getCacheKey(prompt, llmKey, String(idx))
|
||||
let value = await client.get(key)
|
||||
@@ -125,10 +74,21 @@ class RedisCache implements INode {
|
||||
value = await client.get(key)
|
||||
}
|
||||
|
||||
client.quit()
|
||||
|
||||
return generations.length > 0 ? generations : null
|
||||
}
|
||||
|
||||
redisClient.update = async (prompt: string, llmKey: string, value: Generation[]) => {
|
||||
try {
|
||||
const pingResp = await client.ping()
|
||||
if (pingResp !== 'PONG') {
|
||||
client = await getRedisClient(nodeData, options)
|
||||
}
|
||||
} catch (error) {
|
||||
client = await getRedisClient(nodeData, options)
|
||||
}
|
||||
|
||||
for (let i = 0; i < value.length; i += 1) {
|
||||
const key = getCacheKey(prompt, llmKey, String(i))
|
||||
if (ttl) {
|
||||
@@ -137,12 +97,43 @@ class RedisCache implements INode {
|
||||
await client.set(key, JSON.stringify(serializeGeneration(value[i])))
|
||||
}
|
||||
}
|
||||
|
||||
client.quit()
|
||||
}
|
||||
|
||||
client.quit()
|
||||
|
||||
return redisClient
|
||||
}
|
||||
}
|
||||
const getRedisClient = async (nodeData: INodeData, options: ICommonObject) => {
|
||||
let client: Redis
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData)
|
||||
|
||||
if (!redisUrl || redisUrl === '') {
|
||||
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 sslEnabled = getCredentialParam('redisCacheSslEnabled', credentialData, nodeData)
|
||||
|
||||
const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}
|
||||
|
||||
client = new Redis({
|
||||
port: portStr ? parseInt(portStr) : 6379,
|
||||
host,
|
||||
username,
|
||||
password,
|
||||
...tlsOptions
|
||||
})
|
||||
} else {
|
||||
client = new Redis(redisUrl)
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
const getCacheKey = (...strings: string[]): string => hash(strings.join('_'))
|
||||
const deserializeStoredGeneration = (storedGeneration: StoredGeneration) => {
|
||||
if (storedGeneration.message !== undefined) {
|
||||
|
||||
+143
-44
@@ -1,45 +1,11 @@
|
||||
import { Redis, RedisOptions } from 'ioredis'
|
||||
import { isEqual } from 'lodash'
|
||||
import { Redis } from 'ioredis'
|
||||
import { RedisByteStore } from '@langchain/community/storage/ioredis'
|
||||
import { Embeddings } from '@langchain/core/embeddings'
|
||||
import { CacheBackedEmbeddings } from 'langchain/embeddings/cache_backed'
|
||||
import { Embeddings, EmbeddingsInterface } from '@langchain/core/embeddings'
|
||||
import { CacheBackedEmbeddingsFields } from 'langchain/embeddings/cache_backed'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam, ICommonObject, INode, INodeData, INodeParams } from '../../../src'
|
||||
|
||||
let redisClientSingleton: Redis
|
||||
let redisClientOption: RedisOptions
|
||||
let redisClientUrl: string
|
||||
|
||||
const getRedisClientbyOption = (option: RedisOptions) => {
|
||||
if (!redisClientSingleton) {
|
||||
// if client doesn't exists
|
||||
redisClientSingleton = new Redis(option)
|
||||
redisClientOption = option
|
||||
return redisClientSingleton
|
||||
} else if (redisClientSingleton && !isEqual(option, redisClientOption)) {
|
||||
// if client exists but option changed
|
||||
redisClientSingleton.quit()
|
||||
redisClientSingleton = new Redis(option)
|
||||
redisClientOption = option
|
||||
return redisClientSingleton
|
||||
}
|
||||
return redisClientSingleton
|
||||
}
|
||||
|
||||
const getRedisClientbyUrl = (url: string) => {
|
||||
if (!redisClientSingleton) {
|
||||
// if client doesn't exists
|
||||
redisClientSingleton = new Redis(url)
|
||||
redisClientUrl = url
|
||||
return redisClientSingleton
|
||||
} else if (redisClientSingleton && url !== redisClientUrl) {
|
||||
// if client exists but option changed
|
||||
redisClientSingleton.quit()
|
||||
redisClientSingleton = new Redis(url)
|
||||
redisClientUrl = url
|
||||
return redisClientSingleton
|
||||
}
|
||||
return redisClientSingleton
|
||||
}
|
||||
import { BaseStore } from '@langchain/core/stores'
|
||||
import { insecureHash } from '@langchain/core/utils/hash'
|
||||
import { Document } from '@langchain/core/documents'
|
||||
|
||||
class RedisEmbeddingsCache implements INode {
|
||||
label: string
|
||||
@@ -112,7 +78,7 @@ class RedisEmbeddingsCache implements INode {
|
||||
|
||||
const tlsOptions = sslEnabled === true ? { tls: { rejectUnauthorized: false } } : {}
|
||||
|
||||
client = getRedisClientbyOption({
|
||||
client = new Redis({
|
||||
port: portStr ? parseInt(portStr) : 6379,
|
||||
host,
|
||||
username,
|
||||
@@ -120,7 +86,7 @@ class RedisEmbeddingsCache implements INode {
|
||||
...tlsOptions
|
||||
})
|
||||
} else {
|
||||
client = getRedisClientbyUrl(redisUrl)
|
||||
client = new Redis(redisUrl)
|
||||
}
|
||||
|
||||
ttl ??= '3600'
|
||||
@@ -130,10 +96,143 @@ class RedisEmbeddingsCache implements INode {
|
||||
ttl: ttlNumber
|
||||
})
|
||||
|
||||
return CacheBackedEmbeddings.fromBytesStore(underlyingEmbeddings, redisStore, {
|
||||
namespace: namespace
|
||||
const store = CacheBackedEmbeddings.fromBytesStore(underlyingEmbeddings, redisStore, {
|
||||
namespace: namespace,
|
||||
redisClient: client
|
||||
})
|
||||
|
||||
return store
|
||||
}
|
||||
}
|
||||
|
||||
class CacheBackedEmbeddings extends Embeddings {
|
||||
protected underlyingEmbeddings: EmbeddingsInterface
|
||||
|
||||
protected documentEmbeddingStore: BaseStore<string, number[]>
|
||||
|
||||
protected redisClient?: Redis
|
||||
|
||||
constructor(fields: CacheBackedEmbeddingsFields & { redisClient?: Redis }) {
|
||||
super(fields)
|
||||
this.underlyingEmbeddings = fields.underlyingEmbeddings
|
||||
this.documentEmbeddingStore = fields.documentEmbeddingStore
|
||||
this.redisClient = fields.redisClient
|
||||
}
|
||||
|
||||
async embedQuery(document: string): Promise<number[]> {
|
||||
const res = this.underlyingEmbeddings.embedQuery(document)
|
||||
this.redisClient?.quit()
|
||||
return res
|
||||
}
|
||||
|
||||
async embedDocuments(documents: string[]): Promise<number[][]> {
|
||||
const vectors = await this.documentEmbeddingStore.mget(documents)
|
||||
const missingIndicies = []
|
||||
const missingDocuments = []
|
||||
for (let i = 0; i < vectors.length; i += 1) {
|
||||
if (vectors[i] === undefined) {
|
||||
missingIndicies.push(i)
|
||||
missingDocuments.push(documents[i])
|
||||
}
|
||||
}
|
||||
if (missingDocuments.length) {
|
||||
const missingVectors = await this.underlyingEmbeddings.embedDocuments(missingDocuments)
|
||||
const keyValuePairs: [string, number[]][] = missingDocuments.map((document, i) => [document, missingVectors[i]])
|
||||
await this.documentEmbeddingStore.mset(keyValuePairs)
|
||||
for (let i = 0; i < missingIndicies.length; i += 1) {
|
||||
vectors[missingIndicies[i]] = missingVectors[i]
|
||||
}
|
||||
}
|
||||
this.redisClient?.quit()
|
||||
return vectors as number[][]
|
||||
}
|
||||
|
||||
static fromBytesStore(
|
||||
underlyingEmbeddings: EmbeddingsInterface,
|
||||
documentEmbeddingStore: BaseStore<string, Uint8Array>,
|
||||
options?: {
|
||||
namespace?: string
|
||||
redisClient?: Redis
|
||||
}
|
||||
) {
|
||||
const encoder = new TextEncoder()
|
||||
const decoder = new TextDecoder()
|
||||
const encoderBackedStore = new EncoderBackedStore<string, number[], Uint8Array>({
|
||||
store: documentEmbeddingStore,
|
||||
keyEncoder: (key) => (options?.namespace ?? '') + insecureHash(key),
|
||||
valueSerializer: (value) => encoder.encode(JSON.stringify(value)),
|
||||
valueDeserializer: (serializedValue) => JSON.parse(decoder.decode(serializedValue))
|
||||
})
|
||||
return new this({
|
||||
underlyingEmbeddings,
|
||||
documentEmbeddingStore: encoderBackedStore,
|
||||
redisClient: options?.redisClient
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class EncoderBackedStore<K, V, SerializedType = any> extends BaseStore<K, V> {
|
||||
lc_namespace = ['langchain', 'storage']
|
||||
|
||||
store: BaseStore<string, SerializedType>
|
||||
|
||||
keyEncoder: (key: K) => string
|
||||
|
||||
valueSerializer: (value: V) => SerializedType
|
||||
|
||||
valueDeserializer: (value: SerializedType) => V
|
||||
|
||||
constructor(fields: {
|
||||
store: BaseStore<string, SerializedType>
|
||||
keyEncoder: (key: K) => string
|
||||
valueSerializer: (value: V) => SerializedType
|
||||
valueDeserializer: (value: SerializedType) => V
|
||||
}) {
|
||||
super(fields)
|
||||
this.store = fields.store
|
||||
this.keyEncoder = fields.keyEncoder
|
||||
this.valueSerializer = fields.valueSerializer
|
||||
this.valueDeserializer = fields.valueDeserializer
|
||||
}
|
||||
|
||||
async mget(keys: K[]): Promise<(V | undefined)[]> {
|
||||
const encodedKeys = keys.map(this.keyEncoder)
|
||||
const values = await this.store.mget(encodedKeys)
|
||||
return values.map((value) => {
|
||||
if (value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return this.valueDeserializer(value)
|
||||
})
|
||||
}
|
||||
|
||||
async mset(keyValuePairs: [K, V][]): Promise<void> {
|
||||
const encodedPairs: [string, SerializedType][] = keyValuePairs.map(([key, value]) => [
|
||||
this.keyEncoder(key),
|
||||
this.valueSerializer(value)
|
||||
])
|
||||
return this.store.mset(encodedPairs)
|
||||
}
|
||||
|
||||
async mdelete(keys: K[]): Promise<void> {
|
||||
const encodedKeys = keys.map(this.keyEncoder)
|
||||
return this.store.mdelete(encodedKeys)
|
||||
}
|
||||
|
||||
async *yieldKeys(prefix?: string | undefined): AsyncGenerator<string | K> {
|
||||
yield* this.store.yieldKeys(prefix)
|
||||
}
|
||||
}
|
||||
|
||||
export function createDocumentStoreFromByteStore(store: BaseStore<string, Uint8Array>) {
|
||||
const encoder = new TextEncoder()
|
||||
const decoder = new TextDecoder()
|
||||
return new EncoderBackedStore({
|
||||
store,
|
||||
keyEncoder: (key: string) => key,
|
||||
valueSerializer: (doc: Document) => encoder.encode(JSON.stringify({ pageContent: doc.pageContent, metadata: doc.metadata })),
|
||||
valueDeserializer: (bytes: Uint8Array) => new Document(JSON.parse(decoder.decode(bytes)))
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: RedisEmbeddingsCache }
|
||||
|
||||
@@ -180,7 +180,6 @@ class SqlDatabaseChain_Chains implements INode {
|
||||
if (shouldStreamResponse) {
|
||||
streamResponse(sseStreamer, chatId, e.message)
|
||||
}
|
||||
// streamResponse(options.socketIO && options.socketIOClientId, e.message, options.socketIO, options.socketIOClientId)
|
||||
return formatResponse(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-21
@@ -1,5 +1,4 @@
|
||||
import { Redis, RedisConfigNodejs } from '@upstash/redis'
|
||||
import { isEqual } from 'lodash'
|
||||
import { Redis } from '@upstash/redis'
|
||||
import { BufferMemory, BufferMemoryInput } from 'langchain/memory'
|
||||
import { UpstashRedisChatMessageHistory } from '@langchain/community/stores/message/upstash_redis'
|
||||
import { mapStoredMessageToChatMessage, AIMessage, HumanMessage, StoredMessage, BaseMessage } from '@langchain/core/messages'
|
||||
@@ -13,24 +12,6 @@ import {
|
||||
} from '../../../src/utils'
|
||||
import { ICommonObject } from '../../../src/Interface'
|
||||
|
||||
let redisClientSingleton: Redis
|
||||
let redisClientOption: RedisConfigNodejs
|
||||
|
||||
const getRedisClientbyOption = (option: RedisConfigNodejs) => {
|
||||
if (!redisClientSingleton) {
|
||||
// if client doesn't exists
|
||||
redisClientSingleton = new Redis(option)
|
||||
redisClientOption = option
|
||||
return redisClientSingleton
|
||||
} else if (redisClientSingleton && !isEqual(option, redisClientOption)) {
|
||||
// if client exists but option changed
|
||||
redisClientSingleton = new Redis(option)
|
||||
redisClientOption = option
|
||||
return redisClientSingleton
|
||||
}
|
||||
return redisClientSingleton
|
||||
}
|
||||
|
||||
class UpstashRedisBackedChatMemory_Memory implements INode {
|
||||
label: string
|
||||
name: string
|
||||
@@ -109,7 +90,7 @@ const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const upstashRestToken = getCredentialParam('upstashRestToken', credentialData, nodeData)
|
||||
|
||||
const client = getRedisClientbyOption({
|
||||
const client = new Redis({
|
||||
url: baseURL,
|
||||
token: upstashRestToken
|
||||
})
|
||||
|
||||
@@ -138,7 +138,14 @@ class Elasticsearch_VectorStores implements INode {
|
||||
})
|
||||
// end of workaround
|
||||
|
||||
const elasticSearchClientArgs = prepareClientArgs(endPoint, cloudId, credentialData, nodeData, similarityMeasure, indexName)
|
||||
const { elasticClient, elasticSearchClientArgs } = prepareClientArgs(
|
||||
endPoint,
|
||||
cloudId,
|
||||
credentialData,
|
||||
nodeData,
|
||||
similarityMeasure,
|
||||
indexName
|
||||
)
|
||||
const vectorStore = new ElasticVectorSearch(embeddings, elasticSearchClientArgs)
|
||||
|
||||
try {
|
||||
@@ -155,9 +162,11 @@ class Elasticsearch_VectorStores implements INode {
|
||||
vectorStoreName: indexName
|
||||
}
|
||||
})
|
||||
await elasticClient.close()
|
||||
return res
|
||||
} else {
|
||||
await vectorStore.addDocuments(finalDocs)
|
||||
await elasticClient.close()
|
||||
return { numAdded: finalDocs.length, addedDocs: finalDocs }
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -174,7 +183,14 @@ class Elasticsearch_VectorStores implements INode {
|
||||
const endPoint = getCredentialParam('endpoint', credentialData, nodeData)
|
||||
const cloudId = getCredentialParam('cloudId', credentialData, nodeData)
|
||||
|
||||
const elasticSearchClientArgs = prepareClientArgs(endPoint, cloudId, credentialData, nodeData, similarityMeasure, indexName)
|
||||
const { elasticClient, elasticSearchClientArgs } = prepareClientArgs(
|
||||
endPoint,
|
||||
cloudId,
|
||||
credentialData,
|
||||
nodeData,
|
||||
similarityMeasure,
|
||||
indexName
|
||||
)
|
||||
const vectorStore = new ElasticVectorSearch(embeddings, elasticSearchClientArgs)
|
||||
|
||||
try {
|
||||
@@ -186,8 +202,10 @@ class Elasticsearch_VectorStores implements INode {
|
||||
|
||||
await vectorStore.delete({ ids: keys })
|
||||
await recordManager.deleteKeys(keys)
|
||||
await elasticClient.close()
|
||||
} else {
|
||||
await vectorStore.delete({ ids })
|
||||
await elasticClient.close()
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e)
|
||||
@@ -206,8 +224,22 @@ class Elasticsearch_VectorStores implements INode {
|
||||
const k = topK ? parseFloat(topK) : 4
|
||||
const output = nodeData.outputs?.output as string
|
||||
|
||||
const elasticSearchClientArgs = prepareClientArgs(endPoint, cloudId, credentialData, nodeData, similarityMeasure, indexName)
|
||||
const { elasticClient, elasticSearchClientArgs } = prepareClientArgs(
|
||||
endPoint,
|
||||
cloudId,
|
||||
credentialData,
|
||||
nodeData,
|
||||
similarityMeasure,
|
||||
indexName
|
||||
)
|
||||
const vectorStore = await ElasticVectorSearch.fromExistingIndex(embeddings, elasticSearchClientArgs)
|
||||
const originalSimilaritySearchVectorWithScore = vectorStore.similaritySearchVectorWithScore
|
||||
|
||||
vectorStore.similaritySearchVectorWithScore = async (query: number[], k: number, filter?: any) => {
|
||||
const results = await originalSimilaritySearchVectorWithScore.call(vectorStore, query, k, filter)
|
||||
await elasticClient.close()
|
||||
return results
|
||||
}
|
||||
|
||||
if (output === 'retriever') {
|
||||
return vectorStore.asRetriever(k)
|
||||
@@ -289,12 +321,17 @@ const prepareClientArgs = (
|
||||
similarity: 'l2_norm'
|
||||
}
|
||||
}
|
||||
|
||||
const elasticClient = new Client(elasticSearchClientOptions)
|
||||
const elasticSearchClientArgs: ElasticClientArgs = {
|
||||
client: new Client(elasticSearchClientOptions),
|
||||
client: elasticClient,
|
||||
indexName: indexName,
|
||||
vectorSearchOptions: vectorSearchOptions
|
||||
}
|
||||
return elasticSearchClientArgs
|
||||
return {
|
||||
elasticClient,
|
||||
elasticSearchClientArgs
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: Elasticsearch_VectorStores }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { flatten, isEqual } from 'lodash'
|
||||
import { Pinecone, PineconeConfiguration } from '@pinecone-database/pinecone'
|
||||
import { flatten } from 'lodash'
|
||||
import { Pinecone } from '@pinecone-database/pinecone'
|
||||
import { PineconeStoreParams, PineconeStore } from '@langchain/pinecone'
|
||||
import { Embeddings } from '@langchain/core/embeddings'
|
||||
import { Document } from '@langchain/core/documents'
|
||||
@@ -9,23 +9,6 @@ import { FLOWISE_CHATID, getBaseClasses, getCredentialData, getCredentialParam }
|
||||
import { addMMRInputParams, howToUseFileUpload, resolveVectorStoreOrRetriever } from '../VectorStoreUtils'
|
||||
import { index } from '../../../src/indexing'
|
||||
|
||||
let pineconeClientSingleton: Pinecone
|
||||
let pineconeClientOption: PineconeConfiguration
|
||||
|
||||
const getPineconeClient = (option: PineconeConfiguration) => {
|
||||
if (!pineconeClientSingleton) {
|
||||
// if client doesn't exists
|
||||
pineconeClientSingleton = new Pinecone(option)
|
||||
pineconeClientOption = option
|
||||
return pineconeClientSingleton
|
||||
} else if (pineconeClientSingleton && !isEqual(option, pineconeClientOption)) {
|
||||
// if client exists but option changed
|
||||
pineconeClientSingleton = new Pinecone(option)
|
||||
return pineconeClientSingleton
|
||||
}
|
||||
return pineconeClientSingleton
|
||||
}
|
||||
|
||||
class Pinecone_VectorStores implements INode {
|
||||
label: string
|
||||
name: string
|
||||
@@ -155,7 +138,7 @@ class Pinecone_VectorStores implements INode {
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
|
||||
|
||||
const client = getPineconeClient({ apiKey: pineconeApiKey })
|
||||
const client = new Pinecone({ apiKey: pineconeApiKey })
|
||||
|
||||
const pineconeIndex = client.Index(_index)
|
||||
|
||||
@@ -211,7 +194,7 @@ class Pinecone_VectorStores implements INode {
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
|
||||
|
||||
const client = getPineconeClient({ apiKey: pineconeApiKey })
|
||||
const client = new Pinecone({ apiKey: pineconeApiKey })
|
||||
|
||||
const pineconeIndex = client.Index(_index)
|
||||
|
||||
@@ -253,7 +236,7 @@ class Pinecone_VectorStores implements INode {
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const pineconeApiKey = getCredentialParam('pineconeApiKey', credentialData, nodeData)
|
||||
|
||||
const client = getPineconeClient({ apiKey: pineconeApiKey })
|
||||
const client = new Pinecone({ apiKey: pineconeApiKey })
|
||||
|
||||
const pineconeIndex = client.Index(index)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { howToUseFileUpload } from '../VectorStoreUtils'
|
||||
import { VectorStore } from '@langchain/core/vectorstores'
|
||||
import { VectorStoreDriver } from './driver/Base'
|
||||
import { TypeORMDriver } from './driver/TypeORM'
|
||||
import { PGVectorDriver } from './driver/PGVector'
|
||||
// import { PGVectorDriver } from './driver/PGVector'
|
||||
import { getContentColumnName, getDatabase, getHost, getPort, getTableName } from './utils'
|
||||
|
||||
const serverCredentialsExists = !!process.env.POSTGRES_VECTORSTORE_USER && !!process.env.POSTGRES_VECTORSTORE_PASSWORD
|
||||
@@ -91,7 +91,7 @@ class Postgres_VectorStores implements INode {
|
||||
additionalParams: true,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
/*{
|
||||
label: 'Driver',
|
||||
name: 'driver',
|
||||
type: 'options',
|
||||
@@ -109,7 +109,7 @@ class Postgres_VectorStores implements INode {
|
||||
],
|
||||
optional: true,
|
||||
additionalParams: true
|
||||
},
|
||||
},*/
|
||||
{
|
||||
label: 'Distance Strategy',
|
||||
name: 'distanceStrategy',
|
||||
@@ -300,14 +300,15 @@ class Postgres_VectorStores implements INode {
|
||||
}
|
||||
|
||||
static getDriverFromConfig(nodeData: INodeData, options: ICommonObject): VectorStoreDriver {
|
||||
switch (nodeData.inputs?.driver) {
|
||||
/*switch (nodeData.inputs?.driver) {
|
||||
case 'typeorm':
|
||||
return new TypeORMDriver(nodeData, options)
|
||||
case 'pgvector':
|
||||
return new PGVectorDriver(nodeData, options)
|
||||
default:
|
||||
return new TypeORMDriver(nodeData, options)
|
||||
}
|
||||
}*/
|
||||
return new TypeORMDriver(nodeData, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/*
|
||||
* Temporary disabled due to increasing open connections without releasing them
|
||||
* Use TypeORM instead
|
||||
|
||||
import { VectorStoreDriver } from './Base'
|
||||
import { FLOWISE_CHATID } from '../../../../src'
|
||||
import { DistanceStrategy, PGVectorStore, PGVectorStoreArgs } from '@langchain/community/vectorstores/pgvector'
|
||||
@@ -120,3 +124,4 @@ export class PGVectorDriver extends VectorStoreDriver {
|
||||
return instance
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -51,7 +51,9 @@ export class TypeORMDriver extends VectorStoreDriver {
|
||||
}
|
||||
|
||||
async instanciate(metadataFilters?: any) {
|
||||
return this.adaptInstance(await TypeORMVectorStore.fromDataSource(this.getEmbeddings(), await this.getArgs()), metadataFilters)
|
||||
// @ts-ignore
|
||||
const instance = new TypeORMVectorStore(this.getEmbeddings(), await this.getArgs())
|
||||
return this.adaptInstance(instance, metadataFilters)
|
||||
}
|
||||
|
||||
async fromDocuments(documents: Document[]) {
|
||||
@@ -77,7 +79,8 @@ export class TypeORMDriver extends VectorStoreDriver {
|
||||
[ERROR]: uncaughtException: Illegal invocation TypeError: Illegal invocation at Socket.ref (node:net:1524:18) at Connection.ref (.../node_modules/pg/lib/connection.js:183:17) at Client.ref (.../node_modules/pg/lib/client.js:591:21) at BoundPool._pulseQueue (/node_modules/pg-pool/index.js:148:28) at .../node_modules/pg-pool/index.js:184:37 at process.processTicksAndRejections (node:internal/process/task_queues:77:11)
|
||||
*/
|
||||
instance.similaritySearchVectorWithScore = async (query: number[], k: number, filter?: any) => {
|
||||
return await TypeORMDriver.similaritySearchVectorWithScore(
|
||||
await instance.appDataSource.initialize()
|
||||
const res = await TypeORMDriver.similaritySearchVectorWithScore(
|
||||
query,
|
||||
k,
|
||||
tableName,
|
||||
@@ -85,6 +88,8 @@ export class TypeORMDriver extends VectorStoreDriver {
|
||||
filter ?? metadataFilters,
|
||||
this.computedOperatorString
|
||||
)
|
||||
await instance.appDataSource.destroy()
|
||||
return res
|
||||
}
|
||||
|
||||
instance.delete = async (params: { ids: string[] }): Promise<void> => {
|
||||
@@ -92,9 +97,12 @@ export class TypeORMDriver extends VectorStoreDriver {
|
||||
|
||||
if (ids?.length) {
|
||||
try {
|
||||
await instance.appDataSource.initialize()
|
||||
instance.appDataSource.getRepository(instance.documentEntity).delete(ids)
|
||||
} catch (e) {
|
||||
console.error('Failed to delete')
|
||||
} finally {
|
||||
await instance.appDataSource.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +110,10 @@ export class TypeORMDriver extends VectorStoreDriver {
|
||||
const baseAddVectorsFn = instance.addVectors.bind(instance)
|
||||
|
||||
instance.addVectors = async (vectors, documents) => {
|
||||
return baseAddVectorsFn(vectors, this.sanitizeDocuments(documents))
|
||||
await instance.appDataSource.initialize()
|
||||
const res = baseAddVectorsFn(vectors, this.sanitizeDocuments(documents))
|
||||
await instance.appDataSource.destroy()
|
||||
return res
|
||||
}
|
||||
|
||||
return instance
|
||||
|
||||
@@ -1,32 +1,11 @@
|
||||
import { flatten, isEqual } from 'lodash'
|
||||
import { createClient, SearchOptions, RedisClientOptions } from 'redis'
|
||||
import { flatten } from 'lodash'
|
||||
import { createClient, SearchOptions } from 'redis'
|
||||
import { Embeddings } from '@langchain/core/embeddings'
|
||||
import { RedisVectorStore, RedisVectorStoreConfig } from '@langchain/community/vectorstores/redis'
|
||||
import { Document } from '@langchain/core/documents'
|
||||
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams, IndexingResult } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { escapeAllStrings, escapeSpecialChars, unEscapeSpecialChars } from './utils'
|
||||
|
||||
let redisClientSingleton: ReturnType<typeof createClient>
|
||||
let redisClientOption: RedisClientOptions
|
||||
|
||||
const getRedisClient = async (option: RedisClientOptions) => {
|
||||
if (!redisClientSingleton) {
|
||||
// if client doesn't exists
|
||||
redisClientSingleton = createClient(option)
|
||||
await redisClientSingleton.connect()
|
||||
redisClientOption = option
|
||||
return redisClientSingleton
|
||||
} else if (redisClientSingleton && !isEqual(option, redisClientOption)) {
|
||||
// if client exists but option changed
|
||||
redisClientSingleton.quit()
|
||||
redisClientSingleton = createClient(option)
|
||||
await redisClientSingleton.connect()
|
||||
redisClientOption = option
|
||||
return redisClientSingleton
|
||||
}
|
||||
return redisClientSingleton
|
||||
}
|
||||
import { escapeSpecialChars, unEscapeSpecialChars } from './utils'
|
||||
|
||||
class Redis_VectorStores implements INode {
|
||||
label: string
|
||||
@@ -163,13 +142,13 @@ class Redis_VectorStores implements INode {
|
||||
for (let i = 0; i < flattenDocs.length; i += 1) {
|
||||
if (flattenDocs[i] && flattenDocs[i].pageContent) {
|
||||
const document = new Document(flattenDocs[i])
|
||||
escapeAllStrings(document.metadata)
|
||||
finalDocs.push(document)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const redisClient = await getRedisClient({ url: redisUrl })
|
||||
const redisClient = createClient({ url: redisUrl })
|
||||
await redisClient.connect()
|
||||
|
||||
const storeConfig: RedisVectorStoreConfig = {
|
||||
redisClient: redisClient,
|
||||
@@ -203,6 +182,8 @@ class Redis_VectorStores implements INode {
|
||||
)
|
||||
}
|
||||
|
||||
await redisClient.quit()
|
||||
|
||||
return { numAdded: finalDocs.length, addedDocs: finalDocs }
|
||||
} catch (e) {
|
||||
throw new Error(e)
|
||||
@@ -231,7 +212,7 @@ class Redis_VectorStores implements INode {
|
||||
redisUrl = 'redis://' + username + ':' + password + '@' + host + ':' + portStr
|
||||
}
|
||||
|
||||
const redisClient = await getRedisClient({ url: redisUrl })
|
||||
const redisClient = createClient({ url: redisUrl })
|
||||
|
||||
const storeConfig: RedisVectorStoreConfig = {
|
||||
redisClient: redisClient,
|
||||
@@ -246,7 +227,19 @@ class Redis_VectorStores implements INode {
|
||||
|
||||
// Avoid Illegal invocation error
|
||||
vectorStore.similaritySearchVectorWithScore = async (query: number[], k: number, filter?: any) => {
|
||||
return await similaritySearchVectorWithScore(query, k, indexName, metadataKey, vectorKey, contentKey, redisClient, filter)
|
||||
await redisClient.connect()
|
||||
const results = await similaritySearchVectorWithScore(
|
||||
query,
|
||||
k,
|
||||
indexName,
|
||||
metadataKey,
|
||||
vectorKey,
|
||||
contentKey,
|
||||
redisClient,
|
||||
filter
|
||||
)
|
||||
await redisClient.quit()
|
||||
return results
|
||||
}
|
||||
|
||||
if (output === 'retriever') {
|
||||
|
||||
@@ -125,7 +125,6 @@
|
||||
"redis": "^4.6.7",
|
||||
"replicate": "^0.31.1",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"socket.io": "^4.6.1",
|
||||
"srt-parser-2": "^1.2.3",
|
||||
"typeorm": "^0.3.6",
|
||||
"weaviate-ts-client": "^1.1.0",
|
||||
|
||||
@@ -406,12 +406,9 @@ export interface IStateWithMessages extends ICommonObject {
|
||||
}
|
||||
|
||||
export interface IServerSideEventStreamer {
|
||||
streamEvent(chatId: string, data: string): void
|
||||
streamStartEvent(chatId: string, data: any): void
|
||||
|
||||
streamTokenEvent(chatId: string, data: string): void
|
||||
streamCustomEvent(chatId: string, eventType: string, data: any): void
|
||||
|
||||
streamSourceDocumentsEvent(chatId: string, data: any): void
|
||||
streamUsedToolsEvent(chatId: string, data: any): void
|
||||
streamFileAnnotationsEvent(chatId: string, data: any): void
|
||||
|
||||
Reference in New Issue
Block a user