From d98854d85e13b8c3cbda5a0b766d97512c0e7807 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 5 Jun 2023 11:02:22 +0100 Subject: [PATCH] add faiss --- .../Faiss_Existing/Faiss_Existing.ts | 70 +++++++++++++++ .../vectorstores/Faiss_Existing/faiss.svg | 10 +++ .../vectorstores/Faiss_Upsert/Faiss_Upsert.ts | 85 +++++++++++++++++++ .../nodes/vectorstores/Faiss_Upsert/faiss.svg | 10 +++ packages/components/package.json | 1 + packages/server/src/index.ts | 4 +- packages/server/src/utils/index.ts | 30 ++++++- 7 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts create mode 100644 packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg create mode 100644 packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts create mode 100644 packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg diff --git a/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts b/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts new file mode 100644 index 00000000000..8916a734fd5 --- /dev/null +++ b/packages/components/nodes/vectorstores/Faiss_Existing/Faiss_Existing.ts @@ -0,0 +1,70 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { FaissStore } from 'langchain/vectorstores/faiss' +import { Embeddings } from 'langchain/embeddings/base' +import { getBaseClasses } from '../../../src/utils' + +class Faiss_Existing_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Faiss Load Existing Index' + this.name = 'faissExistingIndex' + this.type = 'Faiss' + this.icon = 'faiss.svg' + this.category = 'Vector Stores' + this.description = 'Load existing index from Faiss (i.e: Document has been upserted)' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Base Path to load', + name: 'basePath', + description: 'Path to load faiss.index file', + placeholder: `C:\\Users\\User\\Desktop`, + type: 'string' + } + ] + this.outputs = [ + { + label: 'Faiss Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Faiss Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(FaissStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const embeddings = nodeData.inputs?.embeddings as Embeddings + const basePath = nodeData.inputs?.basePath as string + const output = nodeData.outputs?.output as string + + const vectorStore = await FaissStore.load(basePath, embeddings) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever() + return retriever + } else if (output === 'vectorStore') { + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: Faiss_Existing_VectorStores } diff --git a/packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg b/packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg new file mode 100644 index 00000000000..5fbe98322bd --- /dev/null +++ b/packages/components/nodes/vectorstores/Faiss_Existing/faiss.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts new file mode 100644 index 00000000000..2db6a038421 --- /dev/null +++ b/packages/components/nodes/vectorstores/Faiss_Upsert/Faiss_Upsert.ts @@ -0,0 +1,85 @@ +import { INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' +import { Embeddings } from 'langchain/embeddings/base' +import { Document } from 'langchain/document' +import { getBaseClasses } from '../../../src/utils' +import { FaissStore } from 'langchain/vectorstores/faiss' + +class FaissUpsert_VectorStores implements INode { + label: string + name: string + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + outputs: INodeOutputsValue[] + + constructor() { + this.label = 'Faiss Upsert Document' + this.name = 'faissUpsert' + this.type = 'Faiss' + this.icon = 'faiss.svg' + this.category = 'Vector Stores' + this.description = 'Upsert documents to Faiss' + this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever'] + this.inputs = [ + { + label: 'Document', + name: 'document', + type: 'Document', + list: true + }, + { + label: 'Embeddings', + name: 'embeddings', + type: 'Embeddings' + }, + { + label: 'Base Path to store', + name: 'basePath', + description: 'Path to store faiss.index file', + placeholder: `C:\\Users\\User\\Desktop`, + type: 'string' + } + ] + this.outputs = [ + { + label: 'Faiss Retriever', + name: 'retriever', + baseClasses: this.baseClasses + }, + { + label: 'Faiss Vector Store', + name: 'vectorStore', + baseClasses: [this.type, ...getBaseClasses(FaissStore)] + } + ] + } + + async init(nodeData: INodeData): Promise { + const docs = nodeData.inputs?.document as Document[] + const embeddings = nodeData.inputs?.embeddings as Embeddings + const output = nodeData.outputs?.output as string + const basePath = nodeData.inputs?.basePath as string + + const flattenDocs = docs && docs.length ? docs.flat() : [] + const finalDocs = [] + for (let i = 0; i < flattenDocs.length; i += 1) { + finalDocs.push(new Document(flattenDocs[i])) + } + + const vectorStore = await FaissStore.fromDocuments(finalDocs, embeddings) + await vectorStore.save(basePath) + + if (output === 'retriever') { + const retriever = vectorStore.asRetriever() + return retriever + } else if (output === 'vectorStore') { + return vectorStore + } + return vectorStore + } +} + +module.exports = { nodeClass: FaissUpsert_VectorStores } diff --git a/packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg b/packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg new file mode 100644 index 00000000000..5fbe98322bd --- /dev/null +++ b/packages/components/nodes/vectorstores/Faiss_Upsert/faiss.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index 762af1232c8..161792b2d5e 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -28,6 +28,7 @@ "d3-dsv": "2", "dotenv": "^16.0.0", "express": "^4.17.3", + "faiss-node": "^0.2.1", "form-data": "^4.0.0", "graphql": "^16.6.0", "langchain": "^0.0.84", diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 548c27e9dba..0e030734a34 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -34,7 +34,8 @@ import { findAvailableConfigs, isSameOverrideConfig, replaceAllAPIKeys, - isFlowValidForStream + isFlowValidForStream, + isVectorStoreFaiss } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -634,6 +635,7 @@ export class App { const nodeModule = await import(nodeInstanceFilePath) const nodeInstance = new nodeModule.nodeClass() + isStreamValid = isStreamValid && !isVectorStoreFaiss(nodeToExecuteData) const result = isStreamValid ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatHistory: incomingInput.history, diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 982a82d0f35..18473c51140 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -14,7 +14,7 @@ import { INodeData, IOverrideConfig } from '../Interface' -import { cloneDeep, get } from 'lodash' +import { cloneDeep, get, omit, merge } from 'lodash' import { ICommonObject, getInputVariables } from 'flowise-components' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' @@ -317,6 +317,25 @@ export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowN return returnVal } +/** + * Temporarily disable streaming if vectorStore is Faiss + * @param {INodeData} flowNodeData + * @returns {boolean} + */ +export const isVectorStoreFaiss = (flowNodeData: INodeData) => { + if (flowNodeData.inputs && flowNodeData.inputs.vectorStoreRetriever) { + const vectorStoreRetriever = flowNodeData.inputs.vectorStoreRetriever + if (typeof vectorStoreRetriever === 'string' && vectorStoreRetriever.includes('faiss')) return true + if ( + typeof vectorStoreRetriever === 'object' && + vectorStoreRetriever.vectorStore && + vectorStoreRetriever.vectorStore.constructor.name === 'FaissStore' + ) + return true + } + return false +} + /** * Loop through each inputs and resolve variable if neccessary * @param {INodeData} reactFlowNodeData @@ -325,7 +344,12 @@ export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowN * @returns {INodeData} */ export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[], question: string): INodeData => { - const flowNodeData = cloneDeep(reactFlowNodeData) + let flowNodeData = cloneDeep(reactFlowNodeData) + if (reactFlowNodeData.instance && isVectorStoreFaiss(reactFlowNodeData)) { + // omit and merge because cloneDeep of instance gives "Illegal invocation" Exception + const flowNodeDataWithoutInstance = cloneDeep(omit(reactFlowNodeData, ['instance'])) + flowNodeData = merge(flowNodeDataWithoutInstance, { instance: reactFlowNodeData.instance }) + } const types = 'inputs' const getParamValues = (paramsObj: ICommonObject) => { @@ -633,5 +657,5 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod } } - return isChatOrLLMsExist && endingNodeData.category === 'Chains' + return isChatOrLLMsExist && endingNodeData.category === 'Chains' && !isVectorStoreFaiss(endingNodeData) }