Skip to content

Commit d53ed48

Browse files
authored
Merge pull request FlowiseAI#786 from FlowiseAI/feature/ZepVS
Feature/ZepVS
2 parents a6d3e11 + 1431143 commit d53ed48

File tree

6 files changed

+372
-20
lines changed

6 files changed

+372
-20
lines changed

packages/components/nodes/memory/ZepMemory/ZepMemory.ts

+2-18
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ class ZepMemory_Memory implements INode {
120120
zep.loadMemoryVariables = async (values) => {
121121
let data = await tmpFunc.bind(zep, values)()
122122
if (autoSummary && zep.returnMessages && data[zep.memoryKey] && data[zep.memoryKey].length) {
123-
const memory = await zep.zepClient.getMemory(zep.sessionId, parseInt(k, 10) ?? 10)
123+
const zepClient = await zep.zepClientPromise
124+
const memory = await zepClient.memory.getMemory(zep.sessionId, parseInt(k, 10) ?? 10)
124125
if (memory?.summary) {
125126
let summary = autoSummaryTemplate.replace(/{summary}/g, memory.summary.content)
126127
// eslint-disable-next-line no-console
@@ -190,23 +191,6 @@ class ZepMemoryExtended extends ZepMemory {
190191
super(fields)
191192
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
192193
}
193-
194-
async clear(): Promise<void> {
195-
// Only clear when sessionId is using chatId
196-
// If sessionId is specified, clearing and inserting again will error because the sessionId has been soft deleted
197-
// If using chatId, it will not be a problem because the sessionId will always be the new chatId
198-
if (this.isSessionIdUsingChatMessageId) {
199-
try {
200-
await this.zepClient.deleteMemory(this.sessionId)
201-
} catch (error) {
202-
console.error('Error deleting session: ', error)
203-
}
204-
205-
// Clear the superclass's chat history
206-
await super.clear()
207-
}
208-
await this.chatHistory.clear()
209-
}
210194
}
211195

212196
module.exports = { nodeClass: ZepMemory_Memory }
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface'
2+
import { ZepVectorStore, IZepConfig } from 'langchain/vectorstores/zep'
3+
import { Embeddings } from 'langchain/embeddings/base'
4+
import { Document } from 'langchain/document'
5+
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
6+
import { IDocument, ZepClient } from '@getzep/zep-js'
7+
8+
class Zep_Existing_VectorStores implements INode {
9+
label: string
10+
name: string
11+
version: number
12+
description: string
13+
type: string
14+
icon: string
15+
category: string
16+
baseClasses: string[]
17+
inputs: INodeParams[]
18+
credential: INodeParams
19+
outputs: INodeOutputsValue[]
20+
21+
constructor() {
22+
this.label = 'Zep Load Existing Index'
23+
this.name = 'zepExistingIndex'
24+
this.version = 1.0
25+
this.type = 'Zep'
26+
this.icon = 'zep.png'
27+
this.category = 'Vector Stores'
28+
this.description = 'Load existing index from Zep (i.e: Document has been upserted)'
29+
this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
30+
this.credential = {
31+
label: 'Connect Credential',
32+
name: 'credential',
33+
type: 'credential',
34+
optional: true,
35+
description: 'Configure JWT authentication on your Zep instance (Optional)',
36+
credentialNames: ['zepMemoryApi']
37+
}
38+
this.inputs = [
39+
{
40+
label: 'Embeddings',
41+
name: 'embeddings',
42+
type: 'Embeddings'
43+
},
44+
{
45+
label: 'Base URL',
46+
name: 'baseURL',
47+
type: 'string',
48+
default: 'http://127.0.0.1:8000'
49+
},
50+
{
51+
label: 'Zep Collection',
52+
name: 'zepCollection',
53+
type: 'string',
54+
placeholder: 'my-first-collection'
55+
},
56+
{
57+
label: 'Zep Metadata Filter',
58+
name: 'zepMetadataFilter',
59+
type: 'json',
60+
optional: true,
61+
additionalParams: true
62+
},
63+
{
64+
label: 'Embedding Dimension',
65+
name: 'dimension',
66+
type: 'number',
67+
default: 1536,
68+
additionalParams: true
69+
},
70+
{
71+
label: 'Top K',
72+
name: 'topK',
73+
description: 'Number of top results to fetch. Default to 4',
74+
placeholder: '4',
75+
type: 'number',
76+
additionalParams: true,
77+
optional: true
78+
}
79+
]
80+
this.outputs = [
81+
{
82+
label: 'Pinecone Retriever',
83+
name: 'retriever',
84+
baseClasses: this.baseClasses
85+
},
86+
{
87+
label: 'Pinecone Vector Store',
88+
name: 'vectorStore',
89+
baseClasses: [this.type, ...getBaseClasses(ZepVectorStore)]
90+
}
91+
]
92+
}
93+
94+
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
95+
const baseURL = nodeData.inputs?.baseURL as string
96+
const zepCollection = nodeData.inputs?.zepCollection as string
97+
const zepMetadataFilter = nodeData.inputs?.zepMetadataFilter
98+
const dimension = nodeData.inputs?.dimension as number
99+
const embeddings = nodeData.inputs?.embeddings as Embeddings
100+
const output = nodeData.outputs?.output as string
101+
const topK = nodeData.inputs?.topK as string
102+
const k = topK ? parseFloat(topK) : 4
103+
104+
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
105+
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
106+
107+
const zepConfig: IZepConfig & Partial<ZepFilter> = {
108+
apiUrl: baseURL,
109+
collectionName: zepCollection,
110+
embeddingDimensions: dimension,
111+
isAutoEmbedded: false
112+
}
113+
if (apiKey) zepConfig.apiKey = apiKey
114+
if (zepMetadataFilter) {
115+
const metadatafilter = typeof zepMetadataFilter === 'object' ? zepMetadataFilter : JSON.parse(zepMetadataFilter)
116+
zepConfig.filter = metadatafilter
117+
}
118+
119+
const vectorStore = await ZepExistingVS.fromExistingIndex(embeddings, zepConfig)
120+
121+
if (output === 'retriever') {
122+
const retriever = vectorStore.asRetriever(k)
123+
return retriever
124+
} else if (output === 'vectorStore') {
125+
;(vectorStore as any).k = k
126+
return vectorStore
127+
}
128+
return vectorStore
129+
}
130+
}
131+
132+
interface ZepFilter {
133+
filter: Record<string, any>
134+
}
135+
136+
function zepDocsToDocumentsAndScore(results: IDocument[]): [Document, number][] {
137+
return results.map((d) => [
138+
new Document({
139+
pageContent: d.content,
140+
metadata: d.metadata
141+
}),
142+
d.score ? d.score : 0
143+
])
144+
}
145+
146+
function assignMetadata(value: string | Record<string, unknown> | object | undefined): Record<string, unknown> | undefined {
147+
if (typeof value === 'object' && value !== null) {
148+
return value as Record<string, unknown>
149+
}
150+
if (value !== undefined) {
151+
console.warn('Metadata filters must be an object, Record, or undefined.')
152+
}
153+
return undefined
154+
}
155+
156+
class ZepExistingVS extends ZepVectorStore {
157+
filter?: Record<string, any>
158+
args?: IZepConfig & Partial<ZepFilter>
159+
160+
constructor(embeddings: Embeddings, args: IZepConfig & Partial<ZepFilter>) {
161+
super(embeddings, args)
162+
this.filter = args.filter
163+
this.args = args
164+
}
165+
166+
async initalizeCollection(args: IZepConfig & Partial<ZepFilter>) {
167+
this.client = await ZepClient.init(args.apiUrl, args.apiKey)
168+
try {
169+
this.collection = await this.client.document.getCollection(args.collectionName)
170+
} catch (err) {
171+
if (err instanceof Error) {
172+
if (err.name === 'NotFoundError') {
173+
await this.createNewCollection(args)
174+
} else {
175+
throw err
176+
}
177+
}
178+
}
179+
}
180+
181+
async createNewCollection(args: IZepConfig & Partial<ZepFilter>) {
182+
if (!args.embeddingDimensions) {
183+
throw new Error(
184+
`Collection ${args.collectionName} not found. You can create a new Collection by providing embeddingDimensions.`
185+
)
186+
}
187+
188+
this.collection = await this.client.document.addCollection({
189+
name: args.collectionName,
190+
description: args.description,
191+
metadata: args.metadata,
192+
embeddingDimensions: args.embeddingDimensions,
193+
isAutoEmbedded: false
194+
})
195+
}
196+
197+
async similaritySearchVectorWithScore(
198+
query: number[],
199+
k: number,
200+
filter?: Record<string, unknown> | undefined
201+
): Promise<[Document, number][]> {
202+
if (filter && this.filter) {
203+
throw new Error('cannot provide both `filter` and `this.filter`')
204+
}
205+
const _filters = filter ?? this.filter
206+
const ANDFilters = []
207+
for (const filterKey in _filters) {
208+
let filterVal = _filters[filterKey]
209+
if (typeof filterVal === 'string') filterVal = `"${filterVal}"`
210+
ANDFilters.push({ jsonpath: `$[*] ? (@.${filterKey} == ${filterVal})` })
211+
}
212+
const newfilter = {
213+
where: { and: ANDFilters }
214+
}
215+
await this.initalizeCollection(this.args!).catch((err) => {
216+
console.error('Error initializing collection:', err)
217+
throw err
218+
})
219+
const results = await this.collection.search(
220+
{
221+
embedding: new Float32Array(query),
222+
metadata: assignMetadata(newfilter)
223+
},
224+
k
225+
)
226+
return zepDocsToDocumentsAndScore(results)
227+
}
228+
229+
static async fromExistingIndex(embeddings: Embeddings, dbConfig: IZepConfig & Partial<ZepFilter>): Promise<ZepVectorStore> {
230+
const instance = new this(embeddings, dbConfig)
231+
return instance
232+
}
233+
}
234+
235+
module.exports = { nodeClass: Zep_Existing_VectorStores }

0 commit comments

Comments
 (0)