Skip to content

Commit 3d87228

Browse files
Pash10gShireenMissinetroy
authoredMar 17, 2025··
feat(MongoDB Atlas Vector Store Node): Add Mongo db vector store Node (#12924)
Co-authored-by: Shireen Missi <94372015+ShireenMissi@users.noreply.github.com> Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <netroy@users.noreply.github.com> Co-authored-by: Shireen Missi <shireen@n8n.io>
1 parent 39208dc commit 3d87228

File tree

5 files changed

+314
-11
lines changed

5 files changed

+314
-11
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import { MongoDBAtlasVectorSearch } from '@langchain/mongodb';
2+
import { MongoClient } from 'mongodb';
3+
import { type ILoadOptionsFunctions, NodeOperationError, type INodeProperties } from 'n8n-workflow';
4+
5+
import { metadataFilterField } from '@utils/sharedFields';
6+
7+
import { createVectorStoreNode } from '../shared/createVectorStoreNode/createVectorStoreNode';
8+
9+
const mongoCollectionRLC: INodeProperties = {
10+
displayName: 'MongoDB Collection',
11+
name: 'mongoCollection',
12+
type: 'resourceLocator',
13+
default: { mode: 'list', value: '' },
14+
required: true,
15+
modes: [
16+
{
17+
displayName: 'From List',
18+
name: 'list',
19+
type: 'list',
20+
typeOptions: {
21+
searchListMethod: 'mongoCollectionSearch', // Method to fetch collections
22+
},
23+
},
24+
{
25+
displayName: 'Name',
26+
name: 'name',
27+
type: 'string',
28+
placeholder: 'e.g. my_collection',
29+
},
30+
],
31+
};
32+
33+
const vectorIndexName: INodeProperties = {
34+
displayName: 'Vector Index Name',
35+
name: 'vectorIndexName',
36+
type: 'string',
37+
default: '',
38+
description: 'The name of the vector index',
39+
required: true,
40+
};
41+
42+
const embeddingField: INodeProperties = {
43+
displayName: 'Embedding',
44+
name: 'embedding',
45+
type: 'string',
46+
default: 'embedding',
47+
description: 'The field with the embedding array',
48+
required: true,
49+
};
50+
51+
const metadataField: INodeProperties = {
52+
displayName: 'Metadata Field',
53+
name: 'metadata_field',
54+
type: 'string',
55+
default: 'text',
56+
description: 'The text field of the raw data',
57+
required: true,
58+
};
59+
60+
const sharedFields: INodeProperties[] = [
61+
mongoCollectionRLC,
62+
embeddingField,
63+
metadataField,
64+
vectorIndexName,
65+
];
66+
67+
const mongoNamespaceField: INodeProperties = {
68+
displayName: 'Namespace',
69+
name: 'namespace',
70+
type: 'string',
71+
description: 'Logical partition for documents. Uses metadata.namespace field for filtering.',
72+
default: '',
73+
};
74+
75+
const retrieveFields: INodeProperties[] = [
76+
{
77+
displayName: 'Options',
78+
name: 'options',
79+
type: 'collection',
80+
placeholder: 'Add Option',
81+
default: {},
82+
options: [mongoNamespaceField, metadataFilterField],
83+
},
84+
];
85+
86+
const insertFields: INodeProperties[] = [
87+
{
88+
displayName: 'Options',
89+
name: 'options',
90+
type: 'collection',
91+
placeholder: 'Add Option',
92+
default: {},
93+
options: [
94+
{
95+
displayName: 'Clear Namespace',
96+
name: 'clearNamespace',
97+
type: 'boolean',
98+
default: false,
99+
description: 'Whether to clear documents in the namespace before inserting new data',
100+
},
101+
mongoNamespaceField,
102+
],
103+
},
104+
];
105+
106+
let mongoClient: MongoClient | null = null;
107+
108+
async function getMongoClient(context: any) {
109+
if (!mongoClient) {
110+
const credentials = await context.getCredentials('mongoDb');
111+
mongoClient = new MongoClient(credentials.connectionString as string, {
112+
appName: 'devrel.integration.n8n_vector_integ',
113+
});
114+
await mongoClient.connect();
115+
}
116+
return mongoClient;
117+
}
118+
119+
async function mongoClientAndDatabase(context: any) {
120+
const client = await getMongoClient(context);
121+
const credentials = await context.getCredentials('mongoDb');
122+
const db = client.db(credentials.database as string);
123+
return { client, db };
124+
}
125+
126+
async function mongoCollectionSearch(this: ILoadOptionsFunctions) {
127+
const { db } = await mongoClientAndDatabase(this);
128+
try {
129+
const collections = await db.listCollections().toArray();
130+
const results = collections.map((collection) => ({
131+
name: collection.name,
132+
value: collection.name,
133+
}));
134+
135+
return { results };
136+
} catch (error) {
137+
throw new NodeOperationError(this.getNode(), `Error: ${error.message}`);
138+
}
139+
}
140+
export class VectorStoreMongoDBAtlas extends createVectorStoreNode({
141+
meta: {
142+
displayName: 'MongoDB Atlas Vector Store',
143+
name: 'vectorStoreMongoDBAtlas',
144+
description: 'Work with your data in MongoDB Atlas Vector Store',
145+
icon: { light: 'file:mongodb.svg', dark: 'file:mongodb.dark.svg' },
146+
docsUrl:
147+
'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.vectorstoremongodbatlas/',
148+
credentials: [
149+
{
150+
name: 'mongoDb',
151+
required: true,
152+
},
153+
],
154+
operationModes: ['load', 'insert', 'retrieve', 'update', 'retrieve-as-tool'],
155+
},
156+
methods: { listSearch: { mongoCollectionSearch } },
157+
retrieveFields,
158+
loadFields: retrieveFields,
159+
insertFields,
160+
sharedFields,
161+
async getVectorStoreClient(context, _filter, embeddings, itemIndex) {
162+
try {
163+
const { db } = await mongoClientAndDatabase(context);
164+
try {
165+
const collectionName = context.getNodeParameter('mongoCollection', itemIndex, '', {
166+
extractValue: true,
167+
}) as string;
168+
169+
const mongoVectorIndexName = context.getNodeParameter('vectorIndexName', itemIndex, '', {
170+
extractValue: true,
171+
}) as string;
172+
173+
const embeddingFieldName = context.getNodeParameter('embedding', itemIndex, '', {
174+
extractValue: true,
175+
}) as string;
176+
177+
const metadataFieldName = context.getNodeParameter('metadata_field', itemIndex, '', {
178+
extractValue: true,
179+
}) as string;
180+
181+
const collection = db.collection(collectionName);
182+
183+
// test index exists
184+
const indexes = await collection.listSearchIndexes().toArray();
185+
186+
const indexExists = indexes.some((index) => index.name === mongoVectorIndexName);
187+
188+
if (!indexExists) {
189+
throw new NodeOperationError(
190+
context.getNode(),
191+
`Index ${mongoVectorIndexName} not found`,
192+
{
193+
itemIndex,
194+
description: 'Please check that the index exists in your collection',
195+
},
196+
);
197+
}
198+
199+
return new MongoDBAtlasVectorSearch(embeddings, {
200+
collection,
201+
indexName: mongoVectorIndexName, // Default index name
202+
textKey: metadataFieldName, // Field containing raw text
203+
embeddingKey: embeddingFieldName, // Field containing embeddings
204+
});
205+
} catch (error) {
206+
throw new NodeOperationError(context.getNode(), `Error: ${error.message}`, {
207+
itemIndex,
208+
description: 'Please check your MongoDB Atlas connection details',
209+
});
210+
} finally {
211+
// Don't close the client here to maintain connection pooling
212+
}
213+
} catch (error) {
214+
throw new NodeOperationError(context.getNode(), `Error: ${error.message}`, {
215+
itemIndex,
216+
description: 'Please check your MongoDB Atlas connection details',
217+
});
218+
}
219+
},
220+
async populateVectorStore(context, embeddings, documents, itemIndex) {
221+
try {
222+
const { db } = await mongoClientAndDatabase(context);
223+
try {
224+
const mongoCollectionName = context.getNodeParameter('mongoCollection', itemIndex, '', {
225+
extractValue: true,
226+
}) as string;
227+
const embeddingFieldName = context.getNodeParameter('embedding', itemIndex, '', {
228+
extractValue: true,
229+
}) as string;
230+
231+
const metadataFieldName = context.getNodeParameter('metadata_field', itemIndex, '', {
232+
extractValue: true,
233+
}) as string;
234+
235+
const mongoDBAtlasVectorIndex = context.getNodeParameter('vectorIndexName', itemIndex, '', {
236+
extractValue: true,
237+
}) as string;
238+
239+
// Check if collection exists
240+
const collections = await db.listCollections({ name: mongoCollectionName }).toArray();
241+
if (collections.length === 0) {
242+
await db.createCollection(mongoCollectionName);
243+
}
244+
const collection = db.collection(mongoCollectionName);
245+
await MongoDBAtlasVectorSearch.fromDocuments(documents, embeddings, {
246+
collection,
247+
indexName: mongoDBAtlasVectorIndex, // Default index name
248+
textKey: metadataFieldName, // Field containing raw text
249+
embeddingKey: embeddingFieldName, // Field containing embeddings
250+
});
251+
} catch (error) {
252+
throw new NodeOperationError(context.getNode(), `Error: ${error.message}`, {
253+
itemIndex,
254+
description: 'Please check your MongoDB Atlas connection details',
255+
});
256+
} finally {
257+
// Don't close the client here to maintain connection pooling
258+
}
259+
} catch (error) {
260+
throw new NodeOperationError(context.getNode(), `Error: ${error.message}`, {
261+
itemIndex,
262+
description: 'Please check your MongoDB Atlas connection details',
263+
});
264+
}
265+
},
266+
}) {}
Loading

‎packages/@n8n/nodes-langchain/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
"dist/nodes/vector_store/VectorStoreInMemory/VectorStoreInMemory.node.js",
110110
"dist/nodes/vector_store/VectorStoreInMemoryInsert/VectorStoreInMemoryInsert.node.js",
111111
"dist/nodes/vector_store/VectorStoreInMemoryLoad/VectorStoreInMemoryLoad.node.js",
112+
"dist/nodes/vector_store/VectorStoreMongoDBAtlas/VectorStoreMongoDBAtlas.node.js",
112113
"dist/nodes/vector_store/VectorStorePGVector/VectorStorePGVector.node.js",
113114
"dist/nodes/vector_store/VectorStorePinecone/VectorStorePinecone.node.js",
114115
"dist/nodes/vector_store/VectorStorePineconeInsert/VectorStorePineconeInsert.node.js",
@@ -129,8 +130,8 @@
129130
"@types/json-schema": "^7.0.15",
130131
"@types/mime-types": "^2.1.0",
131132
"@types/pg": "^8.11.6",
132-
"@types/temp": "^0.9.1",
133133
"@types/sanitize-html": "^2.11.0",
134+
"@types/temp": "^0.9.1",
134135
"n8n-core": "workspace:*"
135136
},
136137
"dependencies": {
@@ -150,6 +151,7 @@
150151
"@langchain/google-vertexai": "0.1.8",
151152
"@langchain/groq": "0.1.3",
152153
"@langchain/mistralai": "0.2.0",
154+
"@langchain/mongodb": "^0.1.0",
153155
"@langchain/ollama": "0.1.4",
154156
"@langchain/openai": "0.3.17",
155157
"@langchain/pinecone": "0.1.3",
@@ -178,6 +180,7 @@
178180
"lodash": "catalog:",
179181
"mammoth": "1.7.2",
180182
"mime-types": "2.1.35",
183+
"mongodb": "6.11.0",
181184
"n8n-nodes-base": "workspace:*",
182185
"n8n-workflow": "workspace:*",
183186
"openai": "4.78.1",

‎pnpm-lock.yaml

+38-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.