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..0918a08977 --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.ts @@ -0,0 +1,266 @@ +import { MongoDBAtlasVectorSearch } from '@langchain/mongodb'; +import { MongoClient } from 'mongodb'; +import { type ILoadOptionsFunctions, NodeOperationError, type INodeProperties } from 'n8n-workflow'; + +import { metadataFilterField } from '@utils/sharedFields'; + +import { createVectorStoreNode } from '../shared/createVectorStoreNode/createVectorStoreNode'; + +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', + }, + ], +}; + +const vectorIndexName: INodeProperties = { + displayName: 'Vector Index Name', + name: 'vectorIndexName', + type: 'string', + default: '', + description: 'The name of the vector index', + required: true, +}; + +const embeddingField: INodeProperties = { + displayName: 'Embedding', + name: 'embedding', + type: 'string', + default: 'embedding', + description: 'The field with the embedding array', + required: true, +}; + +const metadataField: INodeProperties = { + displayName: 'Metadata Field', + name: 'metadata_field', + type: 'string', + default: 'text', + description: 'The text field of the raw data', + required: true, +}; + +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, + ], + }, +]; + +let mongoClient: MongoClient | null = null; + +async function getMongoClient(context: any) { + if (!mongoClient) { + const credentials = await context.getCredentials('mongoDb'); + mongoClient = new MongoClient(credentials.connectionString as string, { + appName: 'devrel.integration.n8n_vector_integ', + }); + await mongoClient.connect(); + } + return mongoClient; +} + +async function mongoClientAndDatabase(context: any) { + const client = await getMongoClient(context); + const credentials = await context.getCredentials('mongoDb'); + const db = client.db(credentials.database as string); + return { client, db }; +} + +async function mongoCollectionSearch(this: ILoadOptionsFunctions) { + const { db } = await mongoClientAndDatabase(this); + try { + const collections = await db.listCollections().toArray(); + const results = collections.map((collection) => ({ + name: collection.name, + value: collection.name, + })); + + return { results }; + } catch (error) { + throw new NodeOperationError(this.getNode(), `Error: ${error.message}`); + } +} +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 { db } = await mongoClientAndDatabase(context); + try { + const collectionName = context.getNodeParameter('mongoCollection', itemIndex, '', { + extractValue: true, + }) as string; + + const mongoVectorIndexName = context.getNodeParameter('vectorIndexName', itemIndex, '', { + extractValue: true, + }) as string; + + const embeddingFieldName = context.getNodeParameter('embedding', itemIndex, '', { + extractValue: true, + }) as string; + + const metadataFieldName = context.getNodeParameter('metadata_field', itemIndex, '', { + extractValue: true, + }) as string; + + const collection = db.collection(collectionName); + + // test index exists + const indexes = await collection.listSearchIndexes().toArray(); + + const indexExists = indexes.some((index) => index.name === mongoVectorIndexName); + + if (!indexExists) { + throw new NodeOperationError( + context.getNode(), + `Index ${mongoVectorIndexName} not found`, + { + itemIndex, + description: 'Please check that the index exists in your collection', + }, + ); + } + + return new MongoDBAtlasVectorSearch(embeddings, { + collection, + indexName: mongoVectorIndexName, // Default index name + textKey: metadataFieldName, // Field containing raw text + embeddingKey: embeddingFieldName, // Field containing embeddings + }); + } catch (error) { + throw new NodeOperationError(context.getNode(), `Error: ${error.message}`, { + itemIndex, + description: 'Please check your MongoDB Atlas connection details', + }); + } finally { + // Don't close the client here to maintain connection pooling + } + } 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 { db } = await mongoClientAndDatabase(context); + try { + const mongoCollectionName = context.getNodeParameter('mongoCollection', itemIndex, '', { + extractValue: true, + }) as string; + const embeddingFieldName = context.getNodeParameter('embedding', itemIndex, '', { + extractValue: true, + }) as string; + + const metadataFieldName = context.getNodeParameter('metadata_field', itemIndex, '', { + extractValue: true, + }) as string; + + const mongoDBAtlasVectorIndex = context.getNodeParameter('vectorIndexName', itemIndex, '', { + extractValue: true, + }) as string; + + // Check if collection exists + const collections = await db.listCollections({ name: mongoCollectionName }).toArray(); + if (collections.length === 0) { + await db.createCollection(mongoCollectionName); + } + const collection = db.collection(mongoCollectionName); + await MongoDBAtlasVectorSearch.fromDocuments(documents, embeddings, { + collection, + indexName: mongoDBAtlasVectorIndex, // Default index name + textKey: metadataFieldName, // Field containing raw text + embeddingKey: embeddingFieldName, // Field containing embeddings + }); + } catch (error) { + throw new NodeOperationError(context.getNode(), `Error: ${error.message}`, { + itemIndex, + description: 'Please check your MongoDB Atlas connection details', + }); + } finally { + // Don't close the client here to maintain connection pooling + } + } 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/package.json b/packages/@n8n/nodes-langchain/package.json index 51a6672356..3418ee7fa3 100644 --- a/packages/@n8n/nodes-langchain/package.json +++ b/packages/@n8n/nodes-langchain/package.json @@ -109,6 +109,7 @@ "dist/nodes/vector_store/VectorStoreInMemory/VectorStoreInMemory.node.js", "dist/nodes/vector_store/VectorStoreInMemoryInsert/VectorStoreInMemoryInsert.node.js", "dist/nodes/vector_store/VectorStoreInMemoryLoad/VectorStoreInMemoryLoad.node.js", + "dist/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.js", "dist/nodes/vector_store/VectorStorePGVector/VectorStorePGVector.node.js", "dist/nodes/vector_store/VectorStorePinecone/VectorStorePinecone.node.js", "dist/nodes/vector_store/VectorStorePineconeInsert/VectorStorePineconeInsert.node.js", @@ -129,8 +130,8 @@ "@types/json-schema": "^7.0.15", "@types/mime-types": "^2.1.0", "@types/pg": "^8.11.6", - "@types/temp": "^0.9.1", "@types/sanitize-html": "^2.11.0", + "@types/temp": "^0.9.1", "n8n-core": "workspace:*" }, "dependencies": { @@ -150,6 +151,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", @@ -178,6 +180,7 @@ "lodash": "catalog:", "mammoth": "1.7.2", "mime-types": "2.1.35", + "mongodb": "6.11.0", "n8n-nodes-base": "workspace:*", "n8n-workflow": "workspace:*", "openai": "4.78.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0aa23bcbcc..4f38521b90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -521,7 +521,7 @@ importers: version: 0.3.2(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(encoding@0.1.13) '@langchain/community': specifier: 0.3.24 - version: 0.3.24(6476879575b309260030c283813e748d) + version: 0.3.24(1725dd003b6ba0539bce135b7f30abed) '@langchain/core': specifier: 'catalog:' version: 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)) @@ -537,6 +537,9 @@ importers: '@langchain/mistralai': specifier: 0.2.0 version: 0.2.0(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))) + '@langchain/mongodb': + specifier: ^0.1.0 + version: 0.1.0(@aws-sdk/credential-providers@3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0)))(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.8.3) '@langchain/ollama': specifier: 0.1.4 version: 0.1.4(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))) @@ -563,7 +566,7 @@ importers: version: link:../json-schema-to-zod '@n8n/typeorm': specifier: 0.3.20-12 - version: 0.3.20-12(@sentry/node@8.52.1)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.12)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.8.2)) + version: 0.3.20-12(@sentry/node@8.52.1)(ioredis@5.3.2)(mongodb@6.11.0(@aws-sdk/credential-providers@3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.8.3))(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.12)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.8.2)) '@n8n/typescript-config': specifier: workspace:* version: link:../typescript-config @@ -621,6 +624,9 @@ importers: mime-types: specifier: 2.1.35 version: 2.1.35 + mongodb: + specifier: 6.11.0 + version: 6.11.0(@aws-sdk/credential-providers@3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.8.3) n8n-nodes-base: specifier: workspace:* version: link:../../nodes-base @@ -4378,6 +4384,12 @@ packages: peerDependencies: '@langchain/core': '>=0.3.7 <0.4.0' + '@langchain/mongodb@0.1.0': + resolution: {integrity: sha512-5yO6aNMkdtxlJBjR8LFuvgDgnM/sbAhYe5AkN8VznPkpEoI6Pq4zjvl8gB3YTVpzdrp38HT5Z40VEwNEDHpwIw==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': '>=0.2.21 <0.4.0' + '@langchain/ollama@0.1.4': resolution: {integrity: sha512-olHPViUurGcmOI3IbhIGK/EJ7QxDlZru4j98V269PiEFTIVlciRULltgI/t3voHYTdvB8R+HV8pMo/Y3UVzvzA==} engines: {node: '>=18'} @@ -7042,6 +7054,7 @@ packages: bson@6.10.0: resolution: {integrity: sha512-ROchNosXMJD2cbQGm84KoP7vOGPO6/bOAW0veMMbzhXLqoZptcaYRVLitwvuhwhjjpU1qP4YZRWLhgETdgqUQw==} engines: {node: '>=16.20.1'} + deprecated: a critical bug affecting only useBigInt64=true deserialization usage is fixed in bson@6.10.3 buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -16501,7 +16514,7 @@ snapshots: - aws-crt - encoding - '@langchain/community@0.3.24(6476879575b309260030c283813e748d)': + '@langchain/community@0.3.24(1725dd003b6ba0539bce135b7f30abed)': dependencies: '@browserbasehq/stagehand': 1.9.0(@playwright/test@1.49.1)(deepmerge@4.3.1)(dotenv@16.4.5)(encoding@0.1.13)(openai@4.78.1(encoding@0.1.13)(zod@3.24.1))(zod@3.24.1) '@ibm-cloud/watsonx-ai': 1.1.2 @@ -16555,6 +16568,7 @@ snapshots: jsonwebtoken: 9.0.2 lodash: 4.17.21 mammoth: 1.7.2 + mongodb: 6.11.0(@aws-sdk/credential-providers@3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.8.3) mysql2: 3.11.0 pdf-parse: 1.1.1 pg: 8.12.0 @@ -16649,6 +16663,19 @@ snapshots: zod: 3.24.1 zod-to-json-schema: 3.23.3(zod@3.24.1) + '@langchain/mongodb@0.1.0(@aws-sdk/credential-providers@3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0)))(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.8.3)': + dependencies: + '@langchain/core': 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)) + mongodb: 6.11.0(@aws-sdk/credential-providers@3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.8.3) + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + '@langchain/ollama@0.1.4(@langchain/core@0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)))': dependencies: '@langchain/core': 0.3.30(openai@4.78.1(encoding@0.1.13)(zod@3.24.1)) @@ -16845,7 +16872,7 @@ snapshots: esprima-next: 5.8.4 recast: 0.22.0 - '@n8n/typeorm@0.3.20-12(@sentry/node@8.52.1)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.12)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.8.2))': + '@n8n/typeorm@0.3.20-12(@sentry/node@8.52.1)(ioredis@5.3.2)(mongodb@6.11.0(@aws-sdk/credential-providers@3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.8.3))(mssql@10.0.2)(mysql2@3.11.0)(pg@8.12.0)(redis@4.6.12)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.8.2))': dependencies: '@n8n/p-retry': 6.2.0-2 '@sqltools/formatter': 1.2.5 @@ -16867,6 +16894,7 @@ snapshots: optionalDependencies: '@sentry/node': 8.52.1 ioredis: 5.3.2 + mongodb: 6.11.0(@aws-sdk/credential-providers@3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0)))(gcp-metadata@5.3.0(encoding@0.1.13))(socks@2.8.3) mssql: 10.0.2 mysql2: 3.11.0 pg: 8.12.0 @@ -21539,7 +21567,7 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) is-core-module: 2.13.1 resolve: 1.22.8 transitivePeerDependencies: @@ -21564,7 +21592,7 @@ snapshots: eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) optionalDependencies: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.8.2) eslint: 8.57.0 @@ -21584,7 +21612,7 @@ snapshots: array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 @@ -22362,7 +22390,7 @@ snapshots: array-parallel: 0.1.3 array-series: 0.1.5 cross-spawn: 4.0.2 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -25234,7 +25262,7 @@ snapshots: pdf-parse@1.1.1: dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) node-ensure: 0.0.0 transitivePeerDependencies: - supports-color @@ -26089,7 +26117,7 @@ snapshots: rhea@1.0.24: dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color