@mastra/mongodb 1.18.10-alpha.0 → 1.19.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-rag-vector-databases.md +23 -0
- package/dist/docs/references/reference-vectors-mongodb.md +90 -5
- package/dist/index.cjs +233 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +233 -64
- package/dist/index.js.map +1 -1
- package/dist/vector/index.d.ts +89 -5
- package/dist/vector/index.d.ts.map +1 -1
- package/package.json +6 -5
package/dist/index.cjs
CHANGED
|
@@ -10,7 +10,7 @@ let _mastra_core_agent = require("@mastra/core/agent");
|
|
|
10
10
|
let _mastra_core_evals = require("@mastra/core/evals");
|
|
11
11
|
let _mastra_core_storage_domains_skills = require("@mastra/core/storage/domains/skills");
|
|
12
12
|
//#region package.json
|
|
13
|
-
var version = "1.
|
|
13
|
+
var version = "1.19.0-alpha.0";
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/vector/filter.ts
|
|
16
16
|
/**
|
|
@@ -82,6 +82,10 @@ var MongoDBFilterTranslator = class extends _mastra_core_vector_filter.BaseFilte
|
|
|
82
82
|
};
|
|
83
83
|
//#endregion
|
|
84
84
|
//#region src/vector/index.ts
|
|
85
|
+
/** Name an index's embedding mode for a conflict message. */
|
|
86
|
+
function describeEmbedding(config) {
|
|
87
|
+
return config ? `autoEmbed (path "${config.path}", model "${config.model}")` : "client-side vectors";
|
|
88
|
+
}
|
|
85
89
|
var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector {
|
|
86
90
|
client;
|
|
87
91
|
db;
|
|
@@ -186,7 +190,8 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
186
190
|
searchIndexName: persisted.searchIndexName ?? `${indexName}_vector_index`,
|
|
187
191
|
isByo,
|
|
188
192
|
allowWrites: isByo ? persisted.allowWrites ?? false : true,
|
|
189
|
-
textSearchIndexName: persisted.textSearchIndexName
|
|
193
|
+
textSearchIndexName: persisted.textSearchIndexName,
|
|
194
|
+
autoEmbed: persisted.autoEmbed
|
|
190
195
|
};
|
|
191
196
|
this.indexTargets.set(indexName, target);
|
|
192
197
|
return {
|
|
@@ -289,9 +294,17 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
289
294
|
* Persist (upsert) a logical-index target so it survives a process restart. Idempotent
|
|
290
295
|
* createIndex refreshes it; a partial update (e.g. only the text-search-index name) merges
|
|
291
296
|
* into the existing entry.
|
|
297
|
+
*
|
|
298
|
+
* `claim` narrows the upsert to entries the caller considers compatible. Because the filter
|
|
299
|
+
* still pins `_id`, an incompatible entry matches nothing, the upsert attempts an insert, and
|
|
300
|
+
* the duplicate `_id` surfaces as a duplicate-key error (code 11000) instead of overwriting
|
|
301
|
+
* the entry someone else registered. Callers that only refresh part of an entry omit it.
|
|
292
302
|
*/
|
|
293
|
-
async writeRegistryEntry(indexName, entry) {
|
|
294
|
-
await this.db.collection(MongoDBVector.REGISTRY_COLLECTION).updateOne({
|
|
303
|
+
async writeRegistryEntry(indexName, entry, claim) {
|
|
304
|
+
await this.db.collection(MongoDBVector.REGISTRY_COLLECTION).updateOne({
|
|
305
|
+
_id: indexName,
|
|
306
|
+
...claim
|
|
307
|
+
}, { $set: {
|
|
295
308
|
indexName,
|
|
296
309
|
...entry
|
|
297
310
|
} }, { upsert: true });
|
|
@@ -325,6 +338,31 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
325
338
|
text: `hybridQuery uses $rankFusion, which requires MongoDB >= 8.0 (found ${this.rankFusionSupport.version}). On 8.0.x it may need a MongoDB support case to enable; upgrade to >= 8.1, or run query() and textQuery() separately and fuse client-side.`
|
|
326
339
|
});
|
|
327
340
|
}
|
|
341
|
+
/** Validates a text upsert: `documents` is present, and `metadata`/`ids` line up with it. */
|
|
342
|
+
validateTextUpsertInput(indexName, documents, vectors, metadata, ids) {
|
|
343
|
+
const fail = (errorType, message, details = {}) => {
|
|
344
|
+
throw new _mastra_core_error.MastraError({
|
|
345
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "UPSERT", errorType),
|
|
346
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
347
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
348
|
+
text: message,
|
|
349
|
+
details: {
|
|
350
|
+
indexName,
|
|
351
|
+
...details
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
};
|
|
355
|
+
if (vectors?.length) fail("INVALID_ARGS", `Index "${indexName}" was created with autoEmbed, so MongoDB generates the embeddings: pass documents only. The supplied vectors would be stored in a field no index reads.`);
|
|
356
|
+
if (!documents || documents.length === 0) fail("EMPTY_DOCUMENTS", "documents array cannot be empty when upserting into an autoEmbed index");
|
|
357
|
+
if (metadata && metadata.length > 0 && metadata.length !== documents.length) fail("METADATA_LENGTH_MISMATCH", "Metadata array length must match documents array length", {
|
|
358
|
+
documentsLength: documents.length,
|
|
359
|
+
metadataLength: metadata.length
|
|
360
|
+
});
|
|
361
|
+
if (ids && ids.length !== documents.length) fail("IDS_LENGTH_MISMATCH", "IDs array length must match documents array length", {
|
|
362
|
+
documentsLength: documents.length,
|
|
363
|
+
idsLength: ids.length
|
|
364
|
+
});
|
|
365
|
+
}
|
|
328
366
|
/**
|
|
329
367
|
* Builds the projection pipeline stage(s) for query results based on metadata mode and
|
|
330
368
|
* whether to include vectors. Shared across query, textQuery, and hybridQuery to keep
|
|
@@ -351,7 +389,7 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
351
389
|
* @param scoreMeta - metadata field holding the relevance score for the search stage in use
|
|
352
390
|
* (`vectorSearchScore` for $vectorSearch, `searchScore` for $search, `score` for $rankFusion).
|
|
353
391
|
*/
|
|
354
|
-
buildProjection(metadataMode, includeVector, scoreMeta) {
|
|
392
|
+
buildProjection(metadataMode, includeVector, scoreMeta, documentPath = this.documentFieldName) {
|
|
355
393
|
const score = { $meta: scoreMeta };
|
|
356
394
|
if (metadataMode === "document") {
|
|
357
395
|
const project = {
|
|
@@ -367,7 +405,7 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
367
405
|
_id: 1,
|
|
368
406
|
score,
|
|
369
407
|
metadata: `$${this.metadataFieldName}`,
|
|
370
|
-
document: `$${
|
|
408
|
+
document: `$${documentPath}`,
|
|
371
409
|
...includeVector && { vector: `$${this.embeddingFieldName}` }
|
|
372
410
|
} }];
|
|
373
411
|
}
|
|
@@ -404,10 +442,14 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
404
442
|
* queryable. Skipping that step on a real Atlas cluster may cause
|
|
405
443
|
* "index not found" or "index not ready" errors on subsequent operations.
|
|
406
444
|
*/
|
|
407
|
-
async createIndex(
|
|
445
|
+
async createIndex(params) {
|
|
446
|
+
const { indexName, dimension, metric = "cosine", filterFields, collectionName, searchIndexName, allowWrites, autoEmbed } = params;
|
|
408
447
|
let mongoMetric;
|
|
409
448
|
try {
|
|
410
|
-
if (
|
|
449
|
+
if (autoEmbed) {
|
|
450
|
+
if (dimension !== void 0) throw new Error("dimension cannot be combined with autoEmbed. The embedding model determines the vector size; use autoEmbed.numDimensions to override it.");
|
|
451
|
+
if (params.metric !== void 0) throw new Error("metric cannot be combined with autoEmbed. metric configures a client-side vector field; use autoEmbed.similarity instead.");
|
|
452
|
+
} else if (dimension === void 0 || !Number.isInteger(dimension) || dimension <= 0) throw new Error("Dimension must be a positive integer");
|
|
411
453
|
mongoMetric = this.mongoMetricMap[metric];
|
|
412
454
|
if (!mongoMetric) throw new Error(`Invalid metric: "${metric}". Must be one of: cosine, euclidean, dotproduct`);
|
|
413
455
|
} catch (error) {
|
|
@@ -417,7 +459,7 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
417
459
|
category: _mastra_core_error.ErrorCategory.USER,
|
|
418
460
|
details: {
|
|
419
461
|
indexName,
|
|
420
|
-
dimension,
|
|
462
|
+
...dimension !== void 0 ? { dimension } : {},
|
|
421
463
|
metric
|
|
422
464
|
}
|
|
423
465
|
}, error);
|
|
@@ -457,40 +499,87 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
457
499
|
text: `searchIndexName "${targetSearchIndex}" collides with the full-text search index name for collection "${targetCollection}". Choose a different vector searchIndexName.`
|
|
458
500
|
});
|
|
459
501
|
const textSearchIndexName = effectiveIsByo ? existingEntry?.textSearchIndexName : existingEntry?.textSearchIndexName ?? defaultTextSearchIndex;
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
502
|
+
const resolvedAutoEmbed = autoEmbed ? {
|
|
503
|
+
...autoEmbed,
|
|
504
|
+
path: autoEmbed.path ?? this.documentFieldName
|
|
505
|
+
} : void 0;
|
|
506
|
+
const existingAutoEmbed = existingEntry?.autoEmbed;
|
|
507
|
+
const embeddingChanged = Boolean(existingAutoEmbed) !== Boolean(resolvedAutoEmbed) || existingAutoEmbed && resolvedAutoEmbed && (existingAutoEmbed.path !== resolvedAutoEmbed.path || existingAutoEmbed.model !== resolvedAutoEmbed.model);
|
|
508
|
+
if (existingEntry && embeddingChanged) throw new _mastra_core_error.MastraError({
|
|
509
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "CREATE_INDEX", "CONFLICT"),
|
|
510
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
511
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
512
|
+
details: { indexName },
|
|
513
|
+
text: `Index "${indexName}" is already registered with ${describeEmbedding(existingAutoEmbed)}, but this createIndex call resolves to ${describeEmbedding(resolvedAutoEmbed)}. Call deleteIndex({ indexName: "${indexName}" }) first to rebuild it.`
|
|
468
514
|
});
|
|
515
|
+
const registryClaim = {
|
|
516
|
+
collectionName: targetCollection,
|
|
517
|
+
...resolvedAutoEmbed ? {
|
|
518
|
+
"autoEmbed.path": resolvedAutoEmbed.path,
|
|
519
|
+
"autoEmbed.model": resolvedAutoEmbed.model
|
|
520
|
+
} : { autoEmbed: null }
|
|
521
|
+
};
|
|
522
|
+
try {
|
|
523
|
+
await this.writeRegistryEntry(indexName, {
|
|
524
|
+
collectionName: targetCollection,
|
|
525
|
+
searchIndexName: targetSearchIndex,
|
|
526
|
+
isByo: effectiveIsByo,
|
|
527
|
+
allowWrites: effectiveWritable,
|
|
528
|
+
textSearchIndexName,
|
|
529
|
+
dimension,
|
|
530
|
+
...resolvedAutoEmbed ? {} : { metric },
|
|
531
|
+
autoEmbed: resolvedAutoEmbed
|
|
532
|
+
}, registryClaim);
|
|
533
|
+
} catch (error) {
|
|
534
|
+
if (error?.code !== 11e3) throw error;
|
|
535
|
+
const winner = await this.readRegistryEntry(indexName);
|
|
536
|
+
throw new _mastra_core_error.MastraError({
|
|
537
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "CREATE_INDEX", "CONFLICT"),
|
|
538
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
539
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
540
|
+
details: { indexName },
|
|
541
|
+
text: winner ? `Index "${indexName}" was registered concurrently against collection "${winner.collectionName}" with ${describeEmbedding(winner.autoEmbed)}, but this createIndex call resolves to collection "${targetCollection}" with ${describeEmbedding(resolvedAutoEmbed)}. Call deleteIndex({ indexName: "${indexName}" }) first to rebuild it.` : `Index "${indexName}" was registered concurrently by another process and removed again before this call could read it. Retry createIndex.`
|
|
542
|
+
});
|
|
543
|
+
}
|
|
469
544
|
this.indexTargets.set(indexName, {
|
|
470
545
|
collectionName: targetCollection,
|
|
471
546
|
searchIndexName: targetSearchIndex,
|
|
472
547
|
isByo: effectiveIsByo,
|
|
473
548
|
allowWrites: effectiveWritable,
|
|
474
|
-
textSearchIndexName
|
|
549
|
+
textSearchIndexName,
|
|
550
|
+
autoEmbed: resolvedAutoEmbed
|
|
475
551
|
});
|
|
476
552
|
const embeddingField = this.embeddingFieldName;
|
|
477
553
|
const numDimensions = dimension;
|
|
478
554
|
const declaredMetadataPaths = this.buildDeclaredMetadataPaths(filterFields);
|
|
555
|
+
const autoEmbedPath = resolvedAutoEmbed?.path;
|
|
556
|
+
let searchField;
|
|
557
|
+
if (autoEmbed) {
|
|
558
|
+
const { model, path: _path, ...indexOptions } = autoEmbed;
|
|
559
|
+
searchField = {
|
|
560
|
+
type: "autoEmbed",
|
|
561
|
+
modality: "text",
|
|
562
|
+
path: autoEmbedPath,
|
|
563
|
+
model,
|
|
564
|
+
...indexOptions
|
|
565
|
+
};
|
|
566
|
+
} else searchField = {
|
|
567
|
+
type: "vector",
|
|
568
|
+
path: embeddingField,
|
|
569
|
+
numDimensions,
|
|
570
|
+
similarity: mongoMetric
|
|
571
|
+
};
|
|
572
|
+
const documentIsEmbedded = autoEmbedPath === this.documentFieldName;
|
|
479
573
|
const fields = [
|
|
480
|
-
|
|
481
|
-
type: "vector",
|
|
482
|
-
path: embeddingField,
|
|
483
|
-
numDimensions,
|
|
484
|
-
similarity: mongoMetric
|
|
485
|
-
},
|
|
574
|
+
searchField,
|
|
486
575
|
{
|
|
487
576
|
type: "filter",
|
|
488
577
|
path: "_id"
|
|
489
578
|
},
|
|
490
|
-
{
|
|
579
|
+
...documentIsEmbedded ? [] : [{
|
|
491
580
|
type: "filter",
|
|
492
581
|
path: this.documentFieldName
|
|
493
|
-
},
|
|
582
|
+
}],
|
|
494
583
|
...declaredMetadataPaths.map((path) => ({
|
|
495
584
|
type: "filter",
|
|
496
585
|
path
|
|
@@ -500,7 +589,7 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
500
589
|
definition: { fields },
|
|
501
590
|
name: indexNameInternal,
|
|
502
591
|
type: "vectorSearch"
|
|
503
|
-
})) this.declaredFilterPaths.set(indexName,
|
|
592
|
+
})) this.declaredFilterPaths.set(indexName, new Set(documentIsEmbedded ? declaredMetadataPaths : [this.documentFieldName, ...declaredMetadataPaths]));
|
|
504
593
|
if (!effectiveIsByo) await this.createSearchIndexIgnoringExisting(collection, {
|
|
505
594
|
definition: { mappings: { dynamic: true } },
|
|
506
595
|
name: textSearchIndexName ?? defaultTextSearchIndex,
|
|
@@ -607,7 +696,7 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
607
696
|
*/
|
|
608
697
|
async textQuery(params) {
|
|
609
698
|
const { indexName, query, paths, topK = 10, filter, metadataMode = "field", searchIndexName } = params;
|
|
610
|
-
const { collectionName } = await this.resolveIndexTarget(indexName);
|
|
699
|
+
const { collectionName, autoEmbed } = await this.resolveIndexTarget(indexName);
|
|
611
700
|
const textIndex = searchIndexName ?? await this.resolveTextSearchIndexName(indexName);
|
|
612
701
|
try {
|
|
613
702
|
const collection = await this.getCollection(collectionName, true);
|
|
@@ -622,7 +711,7 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
622
711
|
} },
|
|
623
712
|
...Object.keys(metadataFilter).length ? [{ $match: metadataFilter }] : [],
|
|
624
713
|
{ $limit: Math.min(1e4, topK) },
|
|
625
|
-
...this.buildProjection(metadataMode, false, "searchScore")
|
|
714
|
+
...this.buildProjection(metadataMode, false, "searchScore", autoEmbed?.path)
|
|
626
715
|
];
|
|
627
716
|
return (await collection.aggregate(pipeline).toArray()).map((r) => ({
|
|
628
717
|
id: this.idToString(r._id),
|
|
@@ -645,9 +734,37 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
645
734
|
* need a MongoDB support case to enable, and runs where enabled, e.g. Atlas 8.0.x).
|
|
646
735
|
*/
|
|
647
736
|
async hybridQuery(params) {
|
|
648
|
-
const { indexName, queryVector, query, paths, topK = 10, filter, weights, numCandidates, metadataMode = "field", textSearchIndexName } = params;
|
|
737
|
+
const { indexName, queryVector, queryText, model, query, paths, topK = 10, filter, weights, numCandidates, metadataMode = "field", textSearchIndexName } = params;
|
|
738
|
+
if (model && !queryText) throw new _mastra_core_error.MastraError({
|
|
739
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "HYBRID_QUERY", "INVALID_ARGS"),
|
|
740
|
+
text: "model only applies to queryText, which MongoDB embeds server-side for the vector branch.",
|
|
741
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
742
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
743
|
+
details: { indexName }
|
|
744
|
+
});
|
|
745
|
+
if (queryVector && queryText) throw new _mastra_core_error.MastraError({
|
|
746
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "HYBRID_QUERY", "MUTUALLY_EXCLUSIVE"),
|
|
747
|
+
text: "queryVector and queryText are mutually exclusive: the vector branch takes one or the other.",
|
|
748
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
749
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
750
|
+
details: { indexName }
|
|
751
|
+
});
|
|
752
|
+
if (!queryVector && !queryText) throw new _mastra_core_error.MastraError({
|
|
753
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "HYBRID_QUERY", "MISSING_VECTOR"),
|
|
754
|
+
text: "The vector branch needs queryVector (or queryText on an autoEmbed index).",
|
|
755
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
756
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
757
|
+
details: { indexName }
|
|
758
|
+
});
|
|
649
759
|
await this.assertRankFusionSupported();
|
|
650
|
-
const { collectionName, searchIndexName } = await this.resolveIndexTarget(indexName);
|
|
760
|
+
const { collectionName, searchIndexName, autoEmbed } = await this.resolveIndexTarget(indexName);
|
|
761
|
+
if (queryText && !autoEmbed) throw new _mastra_core_error.MastraError({
|
|
762
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "HYBRID_QUERY", "NOT_AUTO_EMBED"),
|
|
763
|
+
text: `Index "${indexName}" was not created with autoEmbed, so MongoDB cannot embed a query string for it. Pass queryVector instead.`,
|
|
764
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
765
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
766
|
+
details: { indexName }
|
|
767
|
+
});
|
|
651
768
|
const textIndex = textSearchIndexName ?? await this.resolveTextSearchIndexName(indexName);
|
|
652
769
|
try {
|
|
653
770
|
const collection = await this.getCollection(collectionName, true);
|
|
@@ -657,8 +774,9 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
657
774
|
const candidates = Math.min(1e4, Math.max(perBranch, numCandidates ?? topK * 20));
|
|
658
775
|
const vectorSearch = {
|
|
659
776
|
index: searchIndexName,
|
|
660
|
-
path: this.embeddingFieldName,
|
|
661
|
-
queryVector,
|
|
777
|
+
path: autoEmbed ? autoEmbed.path : this.embeddingFieldName,
|
|
778
|
+
...queryText ? { query: { text: queryText } } : { queryVector },
|
|
779
|
+
...model ? { model } : {},
|
|
662
780
|
numCandidates: candidates,
|
|
663
781
|
limit: perBranch
|
|
664
782
|
};
|
|
@@ -700,7 +818,7 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
700
818
|
...combination ? { combination } : {}
|
|
701
819
|
} },
|
|
702
820
|
{ $limit: Math.min(1e4, topK) },
|
|
703
|
-
...this.buildProjection(metadataMode, false, "score")
|
|
821
|
+
...this.buildProjection(metadataMode, false, "score", autoEmbed?.path)
|
|
704
822
|
];
|
|
705
823
|
return (await collection.aggregate(pipeline).toArray()).map((r) => ({
|
|
706
824
|
id: this.idToString(r._id),
|
|
@@ -774,17 +892,24 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
774
892
|
* @returns The IDs of the upserted vectors in input order.
|
|
775
893
|
*/
|
|
776
894
|
async upsert({ indexName, vectors, metadata, ids, documents }) {
|
|
777
|
-
|
|
778
|
-
(
|
|
895
|
+
const { collectionName, autoEmbed } = await this.resolveIndexTarget(indexName);
|
|
896
|
+
if (autoEmbed) this.validateTextUpsertInput(indexName, documents, vectors, metadata, ids);
|
|
897
|
+
else {
|
|
898
|
+
(0, _mastra_core_vector.validateUpsertInput)("MONGODB", vectors, metadata, ids);
|
|
899
|
+
(0, _mastra_core_vector.validateVectorValues)("MONGODB", vectors);
|
|
900
|
+
}
|
|
779
901
|
await this.assertWritable(indexName, "UPSERT");
|
|
780
902
|
if (metadata) this.assertNoCircularReferences(metadata, "UPSERT");
|
|
781
903
|
try {
|
|
782
|
-
const { collectionName } = await this.resolveIndexTarget(indexName);
|
|
783
904
|
const collection = await this.getCollection(collectionName);
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
905
|
+
if (!autoEmbed) {
|
|
906
|
+
const stats = await this.describeIndex({ indexName });
|
|
907
|
+
await this.validateVectorDimensions(vectors, stats.dimension);
|
|
908
|
+
}
|
|
909
|
+
const rowCount = (autoEmbed ? documents : vectors)?.length ?? 0;
|
|
910
|
+
const generatedIds = ids || Array.from({ length: rowCount }, () => (0, _lukeed_uuid.v4)());
|
|
911
|
+
const operations = Array.from({ length: rowCount }, (_unused, idx) => {
|
|
912
|
+
const vector = vectors?.[idx];
|
|
788
913
|
const id = generatedIds[idx];
|
|
789
914
|
const meta = metadata?.[idx] || {};
|
|
790
915
|
const doc = documents?.[idx];
|
|
@@ -793,10 +918,10 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
793
918
|
return acc;
|
|
794
919
|
}, {});
|
|
795
920
|
const updateDoc = {
|
|
796
|
-
[this.embeddingFieldName]: vector,
|
|
921
|
+
...autoEmbed ? {} : { [this.embeddingFieldName]: vector },
|
|
797
922
|
[this.metadataFieldName]: normalizedMeta
|
|
798
923
|
};
|
|
799
|
-
if (doc !== void 0) updateDoc[this.documentFieldName] = doc;
|
|
924
|
+
if (doc !== void 0) updateDoc[autoEmbed ? autoEmbed.path : this.documentFieldName] = doc;
|
|
800
925
|
const idMatch = this.buildIdMatch(id);
|
|
801
926
|
return { updateOne: {
|
|
802
927
|
filter: { _id: idMatch },
|
|
@@ -834,42 +959,78 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
834
959
|
* recall at the cost of latency. Defaults to 20 * topK, capped at 10000.
|
|
835
960
|
* @returns Array of results ordered by descending similarity score.
|
|
836
961
|
*/
|
|
837
|
-
async query({ indexName, queryVector, topK = 10, filter, includeVector = false, documentFilter, numCandidates, metadataMode = "field" }) {
|
|
838
|
-
if (!
|
|
962
|
+
async query({ indexName, queryVector, queryText, model, topK = 10, filter, includeVector = false, documentFilter, numCandidates, metadataMode = "field" }) {
|
|
963
|
+
if (model && !queryText) throw new _mastra_core_error.MastraError({
|
|
964
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "QUERY", "INVALID_ARGS"),
|
|
965
|
+
text: "model only applies to queryText, which MongoDB embeds server-side. A precomputed queryVector has already been embedded.",
|
|
966
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
967
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
968
|
+
details: { indexName }
|
|
969
|
+
});
|
|
970
|
+
if (queryVector && queryText) throw new _mastra_core_error.MastraError({
|
|
971
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "QUERY", "MUTUALLY_EXCLUSIVE"),
|
|
972
|
+
text: "queryVector and queryText are mutually exclusive: pass a vector you embedded yourself, or a string for MongoDB to embed server-side.",
|
|
973
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
974
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
975
|
+
details: { indexName }
|
|
976
|
+
});
|
|
977
|
+
if (!queryVector && !queryText) throw new _mastra_core_error.MastraError({
|
|
839
978
|
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "QUERY", "MISSING_VECTOR"),
|
|
840
|
-
text: "queryVector is required for MongoDB queries. Metadata-only queries are not supported by this vector store.",
|
|
979
|
+
text: "queryVector (or queryText on an autoEmbed index) is required for MongoDB queries. Metadata-only queries are not supported by this vector store.",
|
|
980
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
981
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
982
|
+
details: { indexName }
|
|
983
|
+
});
|
|
984
|
+
const { collectionName, searchIndexName, autoEmbed, isByo } = await this.resolveIndexTarget(indexName);
|
|
985
|
+
if (queryText && !autoEmbed) throw new _mastra_core_error.MastraError({
|
|
986
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "QUERY", "NOT_AUTO_EMBED"),
|
|
987
|
+
text: `Index "${indexName}" was not created with autoEmbed, so MongoDB cannot embed a query string for it. Pass queryVector instead, or use textQuery() for full-text search.`,
|
|
988
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
989
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
990
|
+
details: { indexName }
|
|
991
|
+
});
|
|
992
|
+
if (includeVector && autoEmbed) throw new _mastra_core_error.MastraError({
|
|
993
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "QUERY", "INVALID_ARGS"),
|
|
994
|
+
text: `includeVector is not supported on autoEmbed index "${indexName}": MongoDB keeps the generated embeddings in its own internal database, so they are not on the returned documents.`,
|
|
841
995
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
842
996
|
category: _mastra_core_error.ErrorCategory.USER,
|
|
843
997
|
details: { indexName }
|
|
844
998
|
});
|
|
845
999
|
try {
|
|
846
|
-
const { collectionName, searchIndexName } = await this.resolveIndexTarget(indexName);
|
|
847
1000
|
const collection = await this.getCollection(collectionName, true);
|
|
848
1001
|
const indexNameInternal = searchIndexName;
|
|
849
1002
|
const metadataFilter = this.transformMetadataFilter(this.transformFilter(filter), metadataMode);
|
|
850
1003
|
const hasMetadataFilter = Object.keys(metadataFilter).length > 0;
|
|
851
1004
|
const vectorSearch = {
|
|
852
1005
|
index: indexNameInternal,
|
|
853
|
-
queryVector,
|
|
854
|
-
|
|
1006
|
+
...queryText ? { query: { text: queryText } } : { queryVector },
|
|
1007
|
+
...model ? { model } : {},
|
|
1008
|
+
path: autoEmbed ? autoEmbed.path : this.embeddingFieldName,
|
|
855
1009
|
numCandidates: Math.min(1e4, Math.max(topK, numCandidates ?? topK * 20)),
|
|
856
1010
|
limit: Math.min(1e4, topK)
|
|
857
1011
|
};
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1012
|
+
const documentTextField = autoEmbed ? autoEmbed.path : this.documentFieldName;
|
|
1013
|
+
const documentFilterExpr = documentFilter ? { [documentTextField]: documentFilter } : void 0;
|
|
1014
|
+
const verifyDocumentPath = Boolean(documentFilterExpr) && !autoEmbed && isByo;
|
|
1015
|
+
if (hasMetadataFilter || documentFilterExpr) {
|
|
1016
|
+
let declaredPaths = /* @__PURE__ */ new Set();
|
|
1017
|
+
if (hasMetadataFilter || verifyDocumentPath) try {
|
|
861
1018
|
declaredPaths = await this.getDeclaredFilterPaths(indexName);
|
|
862
1019
|
} catch {
|
|
863
1020
|
declaredPaths = /* @__PURE__ */ new Set();
|
|
864
1021
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1022
|
+
const pushMetadata = !hasMetadataFilter || this.canPushDownFilter(metadataFilter, declaredPaths);
|
|
1023
|
+
const pushDocument = !documentFilterExpr || !autoEmbed && (!verifyDocumentPath || declaredPaths.has(documentTextField));
|
|
1024
|
+
const pushed = [...hasMetadataFilter && pushMetadata ? [metadataFilter] : [], ...documentFilterExpr && pushDocument ? [documentFilterExpr] : []];
|
|
1025
|
+
if (!pushMetadata || !pushDocument) {
|
|
1026
|
+
const preFilter = [...hasMetadataFilter && !pushMetadata ? [metadataFilter] : [], ...documentFilterExpr && !pushDocument ? [documentFilterExpr] : []];
|
|
1027
|
+
const candidateIds = await collection.aggregate([{ $match: preFilter.length > 1 ? { $and: preFilter } : preFilter[0] }, { $project: { _id: 1 } }]).map((doc) => doc._id).toArray();
|
|
868
1028
|
if (candidateIds.length === 0) return [];
|
|
869
|
-
|
|
1029
|
+
pushed.unshift({ _id: { $in: candidateIds } });
|
|
870
1030
|
}
|
|
871
|
-
|
|
872
|
-
|
|
1031
|
+
vectorSearch.filter = pushed.length > 1 ? { $and: pushed } : pushed[0];
|
|
1032
|
+
}
|
|
1033
|
+
const pipeline = [{ $vectorSearch: vectorSearch }, ...this.buildProjection(metadataMode, includeVector, "vectorSearchScore", autoEmbed?.path)];
|
|
873
1034
|
return (await collection.aggregate(pipeline).toArray()).map((result) => ({
|
|
874
1035
|
id: this.idToString(result._id),
|
|
875
1036
|
score: result.score,
|
|
@@ -937,10 +1098,11 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
937
1098
|
*/
|
|
938
1099
|
async describeIndex({ indexName }) {
|
|
939
1100
|
try {
|
|
940
|
-
const { collectionName, searchIndexName } = await this.resolveIndexTarget(indexName);
|
|
1101
|
+
const { collectionName, searchIndexName, autoEmbed } = await this.resolveIndexTarget(indexName);
|
|
941
1102
|
const collection = await this.getCollection(collectionName, true);
|
|
1103
|
+
const embeddedFieldName = autoEmbed ? autoEmbed.path : this.embeddingFieldName;
|
|
942
1104
|
const count = await collection.countDocuments({
|
|
943
|
-
[
|
|
1105
|
+
[embeddedFieldName]: { $exists: true },
|
|
944
1106
|
_id: { $ne: "__index_metadata__" }
|
|
945
1107
|
});
|
|
946
1108
|
const indexNameInternal = searchIndexName;
|
|
@@ -952,7 +1114,7 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
952
1114
|
details: { indexName },
|
|
953
1115
|
text: `Atlas Search index "${indexNameInternal}" does not exist on collection "${indexName}". The collection may predate Mastra or the index may have been dropped externally.`
|
|
954
1116
|
});
|
|
955
|
-
const vectorField = indexData.latestDefinition?.fields?.find((f) => f.type === "vector");
|
|
1117
|
+
const vectorField = indexData.latestDefinition?.fields?.find((f) => f.type === "vector" || f.type === "autoEmbed");
|
|
956
1118
|
if (!vectorField) throw new _mastra_core_error.MastraError({
|
|
957
1119
|
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "DESCRIBE_INDEX", "INVALID"),
|
|
958
1120
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -961,9 +1123,9 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
961
1123
|
text: `Atlas Search index "${indexNameInternal}" exists but has no vector field. The index may have been created outside of Mastra or without vector configuration.`
|
|
962
1124
|
});
|
|
963
1125
|
return {
|
|
964
|
-
dimension: vectorField.numDimensions,
|
|
1126
|
+
dimension: vectorField.type === "autoEmbed" ? vectorField.numDimensions ?? 1024 : vectorField.numDimensions,
|
|
965
1127
|
count,
|
|
966
|
-
metric: {
|
|
1128
|
+
metric: vectorField.type === "autoEmbed" && vectorField.similarity === void 0 ? void 0 : {
|
|
967
1129
|
cosine: "cosine",
|
|
968
1130
|
euclidean: "euclidean",
|
|
969
1131
|
dotProduct: "dotproduct"
|
|
@@ -1046,9 +1208,16 @@ var MongoDBVector = class MongoDBVector extends _mastra_core_vector.MastraVector
|
|
|
1046
1208
|
if (update.metadata) this.assertNoCircularReferences(update.metadata, "UPDATE_VECTOR");
|
|
1047
1209
|
try {
|
|
1048
1210
|
if (!update.vector && !update.metadata) throw new Error("No updates provided");
|
|
1049
|
-
const { collectionName } = await this.resolveIndexTarget(indexName);
|
|
1211
|
+
const { collectionName, autoEmbed } = await this.resolveIndexTarget(indexName);
|
|
1050
1212
|
const collection = await this.getCollection(collectionName, true);
|
|
1051
1213
|
const updateDoc = {};
|
|
1214
|
+
if (update.vector && autoEmbed) throw new _mastra_core_error.MastraError({
|
|
1215
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("MONGODB", "UPDATE_VECTOR", "INVALID_ARGS"),
|
|
1216
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
1217
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
1218
|
+
details: { indexName },
|
|
1219
|
+
text: `Index "${indexName}" was created with autoEmbed, so MongoDB owns the embeddings and a vector cannot be written directly. Upsert the document with new text instead.`
|
|
1220
|
+
});
|
|
1052
1221
|
if (update.vector) {
|
|
1053
1222
|
const stats = await this.describeIndex({ indexName });
|
|
1054
1223
|
await this.validateVectorDimensions([update.vector], stats.dimension);
|