diff --git a/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.ts b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.ts new file mode 100644 index 0000000000..6c3efa96cf --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.ts @@ -0,0 +1,180 @@ +import { MongoDBAtlasVectorSearch } from '@langchain/mongodb'; +import { MongoClient } from 'mongodb'; +import { NodeOperationError, type INodeProperties } from 'n8n-workflow'; + +import { metadataFilterField } from '@utils/sharedFields'; +import { + mongoCollectionRLC, + embeddingField, + metadataField, + vectorIndexName, +} from '../shared/descriptions'; +import { mongoCollectionSearch } from '../shared/methods/listSearch'; + +import { createVectorStoreNode } from '../shared/createVectorStoreNode'; + +const sharedFields: INodeProperties[] = [ + mongoCollectionRLC, + embeddingField, + metadataField, + vectorIndexName, +]; + +const mongoNamespaceField: INodeProperties = { + displayName: 'Namespace', + name: 'namespace', + type: 'string', + description: 'Logical partition for documents. Uses metadata.namespace field for filtering.', + default: '', +}; + +const retrieveFields: INodeProperties[] = [ + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [mongoNamespaceField, metadataFilterField], + }, +]; + +const insertFields: INodeProperties[] = [ + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Clear Namespace', + name: 'clearNamespace', + type: 'boolean', + default: false, + description: 'Whether to clear documents in the namespace before inserting new data', + }, + mongoNamespaceField, + ], + }, +]; + +export class VectorStoreMongoDBAtlas extends createVectorStoreNode({ + meta: { + displayName: 'MongoDB Atlas Vector Store', + name: 'vectorStoreMongoDBAtlas', + description: 'Work with your data in MongoDB Atlas Vector Store', + icon: { light: 'file:mongodb.svg', dark: 'file:mongodb.dark.svg' }, + docsUrl: + 'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.vectorstoremongodbatlas/', + credentials: [ + { + name: 'mongoDb', + required: true, + }, + ], + operationModes: ['load', 'insert', 'retrieve', 'update', 'retrieve-as-tool'], + }, + methods: { listSearch: { mongoCollectionSearch } }, + retrieveFields, + loadFields: retrieveFields, + insertFields, + sharedFields, + async getVectorStoreClient(context, filter, embeddings, itemIndex) { + try { + const collectionName = context.getNodeParameter('mongoCollection', itemIndex, '', { + extractValue: true, + }) as string; + + const vectorIndexName = context.getNodeParameter('vectorIndexName', itemIndex, '', { + extractValue: true, + }) as string; + + const embeddingField = context.getNodeParameter('embedding', itemIndex, '', { + extractValue: true, + }) as string; + + const metadataField = context.getNodeParameter('metadata_field', itemIndex, '', { + extractValue: true, + }) as string; + + const credentials = await context.getCredentials('mongoDb'); + const client = new MongoClient(credentials.connectionString as string, { + appName: 'devrel.content.n8n_vector_integ', // Added appName + }); + await client.connect(); + const collection = client.db(credentials.database as string).collection(collectionName); + + // test index exists + const indexes = await collection.listSearchIndexes().toArray(); + + const indexExists = indexes.some((index) => index.name === vectorIndexName); + + if (!indexExists) { + throw new NodeOperationError(context.getNode(), `Index ${vectorIndexName} not found`, { + itemIndex, + description: 'Please check that the index exists in your collection', + }); + } + + return new MongoDBAtlasVectorSearch(embeddings, { + collection, + indexName: vectorIndexName, // Default index name + textKey: metadataField, // Field containing raw text + embeddingKey: embeddingField, // Field containing embeddings + }); + } catch (error) { + throw new NodeOperationError(context.getNode(), `Error: ${error.message}`, { + itemIndex, + description: 'Please check your MongoDB Atlas connection details', + }); + } + }, + async populateVectorStore(context, embeddings, documents, itemIndex) { + try { + const collectionName = context.getNodeParameter('mongoCollection', itemIndex, '', { + extractValue: true, + }) as string; + const embeddingField = context.getNodeParameter('embedding', itemIndex, '', { + extractValue: true, + }) as string; + + const metadataField = context.getNodeParameter('metadata_field', itemIndex, '', { + extractValue: true, + }) as string; + + const vectorIndexName = context.getNodeParameter('vectorIndexName', itemIndex, '', { + extractValue: true, + }) as string; + + const credentials = await context.getCredentials('mongoDb'); + + const client = new MongoClient(credentials.connectionString as string, { + appName: 'devrel.content.n8n_vector_integ', // Added appName + }); + await client.connect(); + + const db = client.db(credentials.database as string); + + // Check if collection exists + const collections = await db.listCollections({ name: collectionName }).toArray(); + if (collections.length === 0) { + await db.createCollection(collectionName); + } + const collection = db.collection(collectionName); + await MongoDBAtlasVectorSearch.fromDocuments(documents, embeddings, { + collection, + indexName: vectorIndexName, // Default index name + textKey: metadataField, // Field containing raw text + embeddingKey: embeddingField, // Field containing embeddings + }); + + await client.close(); + } catch (error) { + throw new NodeOperationError(context.getNode(), `Error: ${error.message}`, { + itemIndex, + description: 'Please check your MongoDB Atlas connection details', + }); + } + }, +}) {} diff --git a/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/mongodb.dark.svg b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/mongodb.dark.svg new file mode 100644 index 0000000000..3ccdc84421 --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/mongodb.dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/mongodb.svg b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/mongodb.svg new file mode 100644 index 0000000000..8da45829e2 --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/mongodb.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/@n8n/nodes-langchain/nodes/vector_store/shared/descriptions.ts b/packages/@n8n/nodes-langchain/nodes/vector_store/shared/descriptions.ts index 483e73c1fe..c86c2df655 100644 --- a/packages/@n8n/nodes-langchain/nodes/vector_store/shared/descriptions.ts +++ b/packages/@n8n/nodes-langchain/nodes/vector_store/shared/descriptions.ts @@ -68,3 +68,54 @@ export const qdrantCollectionRLC: INodeProperties = { }, ], }; + +export const mongoCollectionRLC: INodeProperties = { + displayName: 'MongoDB Collection', + name: 'mongoCollection', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + modes: [ + { + displayName: 'From List', + name: 'list', + type: 'list', + typeOptions: { + searchListMethod: 'mongoCollectionSearch', // Method to fetch collections + }, + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + placeholder: 'e.g. my_collection', + }, + ], +}; + +export const vectorIndexName: INodeProperties = { + displayName: 'Vector Index Name', + name: 'vectorIndexName', + type: 'string', + default: 'vector_index', + description: 'The name of the vector index', + required: true, +}; + +export const embeddingField: INodeProperties = { + displayName: 'Embedding', + name: 'embedding', + type: 'string', + default: 'embedding', + description: 'The field with the embedding array', + required: true, +}; + +export const metadataField: INodeProperties = { + displayName: 'Metadata Field', + name: 'metadata_field', + type: 'string', + default: 'text', + description: 'The text field of the raw data', + required: true, +}; diff --git a/packages/@n8n/nodes-langchain/nodes/vector_store/shared/methods/listSearch.ts b/packages/@n8n/nodes-langchain/nodes/vector_store/shared/methods/listSearch.ts index 278d879f90..4616205845 100644 --- a/packages/@n8n/nodes-langchain/nodes/vector_store/shared/methods/listSearch.ts +++ b/packages/@n8n/nodes-langchain/nodes/vector_store/shared/methods/listSearch.ts @@ -1,6 +1,7 @@ import { Pinecone } from '@pinecone-database/pinecone'; import { QdrantClient } from '@qdrant/js-client-rest'; import { ApplicationError, type IDataObject, type ILoadOptionsFunctions } from 'n8n-workflow'; +import { MongoClient } from 'mongodb'; export async function pineconeIndexSearch(this: ILoadOptionsFunctions) { const credentials = await this.getCredentials('pineconeApi'); @@ -67,3 +68,25 @@ export async function qdrantCollectionsSearch(this: ILoadOptionsFunctions) { return { results }; } + +export async function mongoCollectionSearch(this: ILoadOptionsFunctions) { + const credentials = await this.getCredentials('mongoDb'); + + const client = new MongoClient(credentials.connectionString as string, { + appName: 'devrel.content.n8n_vector_integ', + }); + await client.connect(); + + try { + const db = client.db(credentials.database as string); + const collections = await db.listCollections().toArray(); + const results = collections.map((collection) => ({ + name: collection.name, + value: collection.name, + })); + + return { results }; + } finally { + await client.close(); + } +} diff --git a/packages/@n8n/nodes-langchain/package.json b/packages/@n8n/nodes-langchain/package.json index ffdd31a7b9..8e1d3b2c3f 100644 --- a/packages/@n8n/nodes-langchain/package.json +++ b/packages/@n8n/nodes-langchain/package.json @@ -150,6 +150,7 @@ "@langchain/google-vertexai": "0.1.8", "@langchain/groq": "0.1.3", "@langchain/mistralai": "0.2.0", + "@langchain/mongodb": "^0.1.0", "@langchain/ollama": "0.1.4", "@langchain/openai": "0.3.17", "@langchain/pinecone": "0.1.3",