diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index f2a47852..0c05563a 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -120,7 +120,8 @@ class ZepMemory_Memory implements INode { zep.loadMemoryVariables = async (values) => { let data = await tmpFunc.bind(zep, values)() if (autoSummary && zep.returnMessages && data[zep.memoryKey] && data[zep.memoryKey].length) { - const memory = await zep.zepClient.getMemory(zep.sessionId, parseInt(k, 10) ?? 10) + const zepClient = await zep.zepClientPromise + const memory = await zepClient.memory.getMemory(zep.sessionId, parseInt(k, 10) ?? 10) if (memory?.summary) { let summary = autoSummaryTemplate.replace(/{summary}/g, memory.summary.content) // eslint-disable-next-line no-console @@ -190,23 +191,6 @@ class ZepMemoryExtended extends ZepMemory { super(fields) this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId } - - async clear(): Promise { - // Only clear when sessionId is using chatId - // If sessionId is specified, clearing and inserting again will error because the sessionId has been soft deleted - // If using chatId, it will not be a problem because the sessionId will always be the new chatId - if (this.isSessionIdUsingChatMessageId) { - try { - await this.zepClient.deleteMemory(this.sessionId) - } catch (error) { - console.error('Error deleting session: ', error) - } - - // Clear the superclass's chat history - await super.clear() - } - await this.chatHistory.clear() - } } module.exports = { nodeClass: ZepMemory_Memory } diff --git a/packages/components/nodes/memory/ZepMemory/zep.png b/packages/components/nodes/memory/ZepMemory/zep.png index 293be6f6..2fdb2382 100644 Binary files a/packages/components/nodes/memory/ZepMemory/zep.png and b/packages/components/nodes/memory/ZepMemory/zep.png differ diff --git a/packages/components/nodes/vectorstores/Zep/Zep_Existing.ts b/packages/components/nodes/vectorstores/Zep/Zep_Existing.ts new file mode 100644 index 00000000..a2c2261f --- /dev/null +++ b/packages/components/nodes/vectorstores/Zep/Zep_Existing.ts @@ -0,0 +1,235 @@ +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ZepVectorStore, IZepConfig } from 'langchain/vectorstores/zep' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { IDocument, ZepClient } from '@getzep/zep-js' + +class Zep_Existing_VectorStores implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Zep Load Existing Index' + this.name = 'zepExistingIndex' + this.version = 1.0 + this.type = 'Zep' + this.icon = 'zep.png' + this.category = 'Vector Stores' + this.description = 'Load existing index from Zep (i.e: Document has been upserted)' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + description: 'Configure JWT authentication on your Zep instance (Optional)', + credentialNames: ['zepMemoryApi'] + } + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Base URL', + name: 'baseURL', + type: 'string', + default: 'http://127.0.0.1:8000' + }, + { + label: 'Zep Collection', + name: 'zepCollection', + type: 'string', + placeholder: 'my-first-collection' + }, + { + label: 'Zep Metadata Filter', + name: 'zepMetadataFilter', + type: 'json', + optional: true, + additionalParams: true + }, + { + label: 'Embedding Dimension', + name: 'dimension', + type: 'number', + default: 1536, + additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'Pinecone Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Pinecone Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(ZepVectorStore)] + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const baseURL = nodeData.inputs?.baseURL as string + const zepCollection = nodeData.inputs?.zepCollection as string + const zepMetadataFilter = nodeData.inputs?.zepMetadataFilter + const dimension = nodeData.inputs?.dimension as number + const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string + const topK = nodeData.inputs?.topK as string + const k = topK ? parseFloat(topK) : 4 + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + + const zepConfig: IZepConfig & Partial = { + apiUrl: baseURL, + collectionName: zepCollection, + embeddingDimensions: dimension, + isAutoEmbedded: false + } + if (apiKey) zepConfig.apiKey = apiKey + if (zepMetadataFilter) { + const metadatafilter = typeof zepMetadataFilter === 'object' ? zepMetadataFilter : JSON.parse(zepMetadataFilter) + zepConfig.filter = metadatafilter + } + + const vectorStore = await ZepExistingVS.fromExistingIndex(embeddings, zepConfig) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +interface ZepFilter { + filter: Record +} + +function zepDocsToDocumentsAndScore(results: IDocument[]): [Document, number][] { + return results.map((d) => [ + new Document({ + pageContent: d.content, + metadata: d.metadata + }), + d.score ? d.score : 0 + ]) +} + +function assignMetadata(value: string | Record | object | undefined): Record | undefined { + if (typeof value === 'object' && value !== null) { + return value as Record + } + if (value !== undefined) { + console.warn('Metadata filters must be an object, Record, or undefined.') + } + return undefined +} + +class ZepExistingVS extends ZepVectorStore { + filter?: Record + args?: IZepConfig & Partial + + constructor(embeddings: Embeddings, args: IZepConfig & Partial) { + super(embeddings, args) + this.filter = args.filter + this.args = args + } + + async initalizeCollection(args: IZepConfig & Partial) { + this.client = await ZepClient.init(args.apiUrl, args.apiKey) + try { + this.collection = await this.client.document.getCollection(args.collectionName) + } catch (err) { + if (err instanceof Error) { + if (err.name === 'NotFoundError') { + await this.createNewCollection(args) + } else { + throw err + } + } + } + } + + async createNewCollection(args: IZepConfig & Partial) { + if (!args.embeddingDimensions) { + throw new Error( + `Collection ${args.collectionName} not found. You can create a new Collection by providing embeddingDimensions.` + ) + } + + this.collection = await this.client.document.addCollection({ + name: args.collectionName, + description: args.description, + metadata: args.metadata, + embeddingDimensions: args.embeddingDimensions, + isAutoEmbedded: false + }) + } + + async similaritySearchVectorWithScore( + query: number[], + k: number, + filter?: Record | undefined + ): Promise<[Document, number][]> { + if (filter && this.filter) { + throw new Error('cannot provide both `filter` and `this.filter`') + } + const _filters = filter ?? this.filter + const ANDFilters = [] + for (const filterKey in _filters) { + let filterVal = _filters[filterKey] + if (typeof filterVal === 'string') filterVal = `"${filterVal}"` + ANDFilters.push({ jsonpath: `$[*] ? (@.${filterKey} == ${filterVal})` }) + } + const newfilter = { + where: { and: ANDFilters } + } + await this.initalizeCollection(this.args!).catch((err) => { + console.error('Error initializing collection:', err) + throw err + }) + const results = await this.collection.search( + { + embedding: new Float32Array(query), + metadata: assignMetadata(newfilter) + }, + k + ) + return zepDocsToDocumentsAndScore(results) + } + + static async fromExistingIndex(embeddings: Embeddings, dbConfig: IZepConfig & Partial): Promise { + const instance = new this(embeddings, dbConfig) + return instance + } +} + +module.exports = { nodeClass: Zep_Existing_VectorStores } diff --git a/packages/components/nodes/vectorstores/Zep/Zep_Upsert.ts b/packages/components/nodes/vectorstores/Zep/Zep_Upsert.ts new file mode 100644 index 00000000..0f976d2b --- /dev/null +++ b/packages/components/nodes/vectorstores/Zep/Zep_Upsert.ts @@ -0,0 +1,133 @@ +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { ZepVectorStore, IZepConfig } from 'langchain/vectorstores/zep' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { flatten } from 'lodash' + +class Zep_Upsert_VectorStores implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Zep Upsert Document' + this.name = 'zepUpsert' + this.version = 1.0 + this.type = 'Zep' + this.icon = 'zep.png' + this.category = 'Vector Stores' + this.description = 'Upsert documents to Zep' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: true, + description: 'Configure JWT authentication on your Zep instance (Optional)', + credentialNames: ['zepMemoryApi'] + } + this.inputs = [ + { + label: 'Document', + name: 'document', + type: 'Document', + list: true + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Base URL', + name: 'baseURL', + type: 'string', + default: 'http://127.0.0.1:8000' + }, + { + label: 'Zep Collection', + name: 'zepCollection', + type: 'string', + placeholder: 'my-first-collection' + }, + { + label: 'Embedding Dimension', + name: 'dimension', + type: 'number', + default: 1536, + additionalParams: true + }, + { + label: 'Top K', + name: 'topK', + description: 'Number of top results to fetch. Default to 4', + placeholder: '4', + type: 'number', + additionalParams: true, + optional: true + } + ] + this.outputs = [ + { + label: 'Zep Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Zep Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(ZepVectorStore)] + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const baseURL = nodeData.inputs?.baseURL as string + const zepCollection = nodeData.inputs?.zepCollection as string + const dimension = (nodeData.inputs?.dimension as number) ?? 1536 + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const topK = nodeData.inputs?.topK as string + const k = topK ? parseFloat(topK) : 4 + const output = nodeData.outputs?.output as string + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + + const flattenDocs = docs && docs.length ? flatten(docs) : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + const zepConfig: IZepConfig = { + apiUrl: baseURL, + collectionName: zepCollection, + embeddingDimensions: dimension, + isAutoEmbedded: false + } + if (apiKey) zepConfig.apiKey = apiKey + + const vectorStore = await ZepVectorStore.fromDocuments(finalDocs, embeddings, zepConfig) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever(k) + return retriever + } else if (output === 'vectorStore') { + ;(vectorStore as any).k = k + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: Zep_Upsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/Zep/zep.png b/packages/components/nodes/vectorstores/Zep/zep.png new file mode 100644 index 00000000..2fdb2382 Binary files /dev/null and b/packages/components/nodes/vectorstores/Zep/zep.png differ diff --git a/packages/components/package.json b/packages/components/package.json index da4d0971..2192dba8 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -18,7 +18,7 @@ "dependencies": { "@aws-sdk/client-dynamodb": "^3.360.0", "@dqbd/tiktoken": "^1.0.7", - "@getzep/zep-js": "^0.4.1", + "@getzep/zep-js": "^0.6.3", "@huggingface/inference": "^2.6.1", "@notionhq/client": "^2.2.8", "@opensearch-project/opensearch": "^1.2.0", @@ -40,7 +40,7 @@ "google-auth-library": "^9.0.0", "graphql": "^16.6.0", "html-to-text": "^9.0.5", - "langchain": "^0.0.126", + "langchain": "^0.0.128", "linkifyjs": "^4.1.1", "mammoth": "^1.5.1", "moment": "^2.29.3",