From 76ac97fa5051d33fe6e302aa72936831bbb04b63 Mon Sep 17 00:00:00 2001 From: paulpaliychuk Date: Mon, 19 Feb 2024 22:11:07 -0500 Subject: [PATCH] wip --- .../memory/ZepMemoryCloud/ZepMemoryCloud.ts | 172 +++++++++++++ .../nodes/memory/ZepMemoryCloud/zep.svg | 19 ++ .../nodes/vectorstores/ZepCloud/ZepCloud.ts | 234 ++++++++++++++++++ .../nodes/vectorstores/ZepCloud/zep.svg | 19 ++ packages/components/package.json | 1 + 5 files changed, 445 insertions(+) create mode 100644 packages/components/nodes/memory/ZepMemoryCloud/ZepMemoryCloud.ts create mode 100644 packages/components/nodes/memory/ZepMemoryCloud/zep.svg create mode 100644 packages/components/nodes/vectorstores/ZepCloud/ZepCloud.ts create mode 100644 packages/components/nodes/vectorstores/ZepCloud/zep.svg diff --git a/packages/components/nodes/memory/ZepMemoryCloud/ZepMemoryCloud.ts b/packages/components/nodes/memory/ZepMemoryCloud/ZepMemoryCloud.ts new file mode 100644 index 00000000..336fbc4d --- /dev/null +++ b/packages/components/nodes/memory/ZepMemoryCloud/ZepMemoryCloud.ts @@ -0,0 +1,172 @@ +import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' +import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { ZepMemory, ZepMemoryInput } from '@getzep/zep-cloud/langchain' + +import { ICommonObject } from '../../../src' +import { InputValues, MemoryVariables, OutputValues } from 'langchain/memory' +import { BaseMessage } from 'langchain/schema' + +class ZepMemoryCloud_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 = 'Zep Memory - Cloud' + this.name = 'ZepMemory (Cloud)' + this.version = 2.0 + this.type = 'ZepMemory' + this.icon = 'zep.svg' + this.category = 'Memory' + this.description = 'Summarizes the conversation and stores the memory in zep server' + this.baseClasses = [this.type, ...getBaseClasses(ZepMemory)] + 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: 'Session Id', + name: 'sessionId', + type: 'string', + description: + 'If not specified, a random id will be used. Learn more', + default: '', + additionalParams: true, + optional: true + }, + { + label: 'Memory Type', + name: 'memoryType', + type: 'string', + default: 'perpetual', + description: 'Zep Memory Type, can be perpetual or message_window', + additionalParams: true + }, + { + label: 'AI Prefix', + name: 'aiPrefix', + type: 'string', + default: 'ai', + additionalParams: true + }, + { + label: 'Human Prefix', + name: 'humanPrefix', + type: 'string', + default: 'human', + additionalParams: true + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history', + additionalParams: true + }, + { + label: 'Output Key', + name: 'outputKey', + type: 'string', + default: 'text', + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + return await initalizeZep(nodeData, options) + } +} + +const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promise => { + const aiPrefix = nodeData.inputs?.aiPrefix as string + const humanPrefix = nodeData.inputs?.humanPrefix as string + const memoryKey = nodeData.inputs?.memoryKey as string + const memoryType = nodeData.inputs?.memoryType as 'perpetual' | 'message_window' + const sessionId = nodeData.inputs?.sessionId as string + + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + + const obj: ZepMemoryInput & ZepMemoryExtendedInput = { + apiKey, + baseURL: 'https://api.development.getzep.com', + aiPrefix, + humanPrefix, + memoryKey, + sessionId, + memoryType: memoryType + } + + return new ZepMemoryExtended(obj) +} + +interface ZepMemoryExtendedInput { + memoryType?: 'perpetual' | 'message_window' +} + +class ZepMemoryExtended extends ZepMemory implements MemoryMethods { + memoryType: 'perpetual' | 'message_window' + + constructor(fields: ZepMemoryInput & ZepMemoryExtendedInput) { + super(fields) + this.memoryType = fields.memoryType ?? 'perpetual' + } + + async loadMemoryVariables(values: InputValues, overrideSessionId = ''): Promise { + if (overrideSessionId) { + this.sessionId = overrideSessionId + } + return super.loadMemoryVariables({ ...values, memoryType: this.memoryType }) + } + + async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideSessionId = ''): Promise { + if (overrideSessionId) { + this.sessionId = overrideSessionId + } + return super.saveContext(inputValues, outputValues) + } + + async clear(overrideSessionId = ''): Promise { + if (overrideSessionId) { + this.sessionId = overrideSessionId + } + return super.clear() + } + + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { + const id = overrideSessionId ? overrideSessionId : this.sessionId + const memoryVariables = await this.loadMemoryVariables({}, id) + const baseMessages = memoryVariables[this.memoryKey] + return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + } + + async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise { + const id = overrideSessionId ? overrideSessionId : this.sessionId + const input = msgArray.find((msg) => msg.type === 'userMessage') + const output = msgArray.find((msg) => msg.type === 'apiMessage') + const inputValues = { ['input']: input?.text } + const outputValues = { output: output?.text } + + await this.saveContext(inputValues, outputValues, id) + } + + async clearChatMessages(overrideSessionId = ''): Promise { + const id = overrideSessionId ? overrideSessionId : this.sessionId + await this.clear(id) + } +} + +module.exports = { nodeClass: ZepMemoryCloud_Memory } diff --git a/packages/components/nodes/memory/ZepMemoryCloud/zep.svg b/packages/components/nodes/memory/ZepMemoryCloud/zep.svg new file mode 100644 index 00000000..6cbbaad2 --- /dev/null +++ b/packages/components/nodes/memory/ZepMemoryCloud/zep.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/components/nodes/vectorstores/ZepCloud/ZepCloud.ts b/packages/components/nodes/vectorstores/ZepCloud/ZepCloud.ts new file mode 100644 index 00000000..10847bb8 --- /dev/null +++ b/packages/components/nodes/vectorstores/ZepCloud/ZepCloud.ts @@ -0,0 +1,234 @@ +import { flatten } from 'lodash' +import { IDocument, ZepClient } from '@getzep/zep-cloud' +import { IZepConfig, ZepVectorStore } from '@getzep/zep-cloud/langchain' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' +import { addMMRInputParams, resolveVectorStoreOrRetriever } from '../VectorStoreUtils' +import { FakeEmbeddings } from 'langchain/embeddings/fake' +class Zep_CloudVectorStores implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + badge: string + baseClasses: string[] + inputs: INodeParams[] + credential: INodeParams + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Zep Collection - Cloud' + this.name = 'zepCloud' + this.version = 2.0 + this.type = 'Zep' + this.icon = 'zep.svg' + this.category = 'Vector Stores' + this.description = + 'Upsert embedded data and perform similarity or mmr search upon query using Zep, a fast and scalable building block for LLM apps' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.badge = 'NEW' + this.credential = { + label: 'Connect Credential', + name: 'credential', + type: 'credential', + optional: false, + description: 'Configure JWT authentication on your Zep instance (Optional)', + credentialNames: ['zepMemoryApi'] + } + this.inputs = [ + { + label: 'Document', + name: 'document', + type: 'Document', + list: true, + optional: true + }, + { + label: 'Zep Collection', + name: 'zepCollection', + type: 'string', + placeholder: 'my-first-collection' + }, + { + label: 'Zep Metadata Filter', + name: 'zepMetadataFilter', + type: 'json', + optional: true, + 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 + } + ] + addMMRInputParams(this.inputs) + this.outputs = [ + { + label: 'Zep Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Zep Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(ZepVectorStore)] + } + ] + } + + //@ts-ignore + vectorStoreMethods = { + async upsert(nodeData: INodeData, options: ICommonObject): Promise { + const zepCollection = nodeData.inputs?.zepCollection as string + const docs = nodeData.inputs?.document as Document[] + 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) { + if (flattenDocs[i] && flattenDocs[i].pageContent) { + finalDocs.push(new Document(flattenDocs[i])) + } + } + const client = await ZepClient.init(apiKey, 'https://api.development.getzep.com') + const zepConfig = { + apiKey: apiKey, + collectionName: zepCollection, + client + } + try { + await ZepVectorStore.fromDocuments(finalDocs, new FakeEmbeddings(), zepConfig) + } catch (e) { + throw new Error(e) + } + } + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const zepCollection = nodeData.inputs?.zepCollection as string + const zepMetadataFilter = nodeData.inputs?.zepMetadataFilter + const credentialData = await getCredentialData(nodeData.credential ?? '', options) + const apiKey = getCredentialParam('apiKey', credentialData, nodeData) + + const zepConfig: IZepConfig & Partial = { + apiUrl: 'https://api.development.getzep.com', + apiKey, + collectionName: zepCollection + } + if (zepMetadataFilter) { + zepConfig.filter = typeof zepMetadataFilter === 'object' ? zepMetadataFilter : JSON.parse(zepMetadataFilter) + } + const client = await ZepClient.init(zepConfig.apiKey, zepConfig.apiUrl) + zepConfig.client = client + const vectorStore = await ZepExistingVS.init(zepConfig) + return resolveVectorStoreOrRetriever(nodeData, 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 initializeCollection(args: IZepConfig & Partial) { + this.client = await ZepClient.init(args.apiKey, args.apiUrl) + 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) { + this.collection = await this.client.document.addCollection({ + name: args.collectionName, + description: args.description, + metadata: args.metadata + }) + } + + 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.initializeCollection(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 { + console.log('fromExistingIndex') + const instance = new this(embeddings, dbConfig) + return instance + } +} + +module.exports = { nodeClass: Zep_CloudVectorStores } diff --git a/packages/components/nodes/vectorstores/ZepCloud/zep.svg b/packages/components/nodes/vectorstores/ZepCloud/zep.svg new file mode 100644 index 00000000..6cbbaad2 --- /dev/null +++ b/packages/components/nodes/vectorstores/ZepCloud/zep.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/components/package.json b/packages/components/package.json index b53945bd..0f1c70a8 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -23,6 +23,7 @@ "@dqbd/tiktoken": "^1.0.7", "@elastic/elasticsearch": "^8.9.0", "@getzep/zep-js": "^0.9.0", + "@getzep/zep-cloud": "file:/Users/paulpaliychuk/job/zep-js/getzep-zep-js-v2.0.0-rc.37.tgz", "@gomomento/sdk": "^1.51.1", "@gomomento/sdk-core": "^1.51.1", "@google-ai/generativelanguage": "^0.2.1",