UNPKG

@mastra/core

Version:
567 lines (380 loc) 25.1 kB
> Discover all available pages from the documentation index: https://mastra.ai/llms.txt # MongoDB vector store The `MongoDBVector` class provides vector search using [MongoDB Atlas Vector Search](https://www.mongodb.com/docs/atlas/atlas-vector-search/). It enables efficient similarity search and metadata filtering within your MongoDB collections. ## Installation **npm**: ```bash npm install @mastra/mongodb@latest ``` **pnpm**: ```bash pnpm add @mastra/mongodb@latest ``` **Yarn**: ```bash yarn add @mastra/mongodb@latest ``` **Bun**: ```bash bun add @mastra/mongodb@latest ``` ## Usage example ```typescript import { MongoDBVector } from '@mastra/mongodb' const store = new MongoDBVector({ id: 'mongodb-vector', uri: process.env.MONGODB_URI, dbName: process.env.MONGODB_DB_NAME, }) ``` ### Custom Embedding Field Path If you need to store embeddings in a nested field structure (e.g., to integrate with existing MongoDB collections), use the `embeddingFieldPath` option: ```typescript import { MongoDBVector } from '@mastra/mongodb' const store = new MongoDBVector({ id: 'mongodb-vector', uri: process.env.MONGODB_URI, dbName: process.env.MONGODB_DB_NAME, embeddingFieldPath: 'text.contentEmbedding', // Store embeddings at text.contentEmbedding }) ``` ## Constructor options **id** (`string`): Unique identifier for this vector store instance **uri** (`string`): MongoDB connection string **dbName** (`string`): Name of the MongoDB database to use **options** (`MongoClientOptions`): Optional MongoDB client options **embeddingFieldPath** (`string`): Path to the field that stores vector embeddings. Supports nested paths using dot notation (e.g., 'text.contentEmbedding'). (Default: `embedding`) ## Methods ### `connect()` Establishes connection to the MongoDB server. This is called automatically on first use, but can be called explicitly if needed. ```typescript await store.connect() ``` ### `createIndex()` Creates a new vector index (collection) in MongoDB. **indexName** (`string`): Name of the collection to create **dimension** (`number`): Vector dimension (must match your embedding model) **metric** (`'cosine' | 'euclidean' | 'dotproduct'`): Distance metric for similarity search (Default: `cosine`) **filterFields** (`string[]`): Metadata field names to declare as filter fields in the Atlas vectorSearch index (registered as metadata.\<field>). Queries that filter only on declared fields are pushed directly into $vectorSearch instead of pre-filtering candidate \_ids, avoiding the 16 MB BSON limit on large result sets. Filters that reference an undeclared field, or use an operator $vectorSearch does not support, fall back to the pre-filter automatically. **collectionName** (`string`): Store the vectors on an existing (operational) collection instead of a managed collection named after the index. The collection is never created or dropped by this store when set. Defaults to indexName. **searchIndexName** (`string`): Name for the Atlas vectorSearch index created on the collection. Defaults to ${indexName}\_vector\_index. **allowWrites** (`boolean`): Opt-in to write operations (upsert, updateVector, deleteVector, deleteVectors) on a bring-your-own collection. By default a BYO index is read-only: the store never modifies or deletes caller-owned operational documents. Ignored for managed collections, which are always writable. The policy is persisted with the index registration and survives restarts. (Default: `false`) ### `waitForIndexReady()` Waits for an index to become ready after creation. Useful when you need to ensure an index is ready before performing operations. **indexName** (`string`): Name of the index to wait for **timeoutMs** (`number`): Maximum time to wait in milliseconds (Default: `60000`) **checkIntervalMs** (`number`): Interval between status checks in milliseconds (Default: `2000`) ### `upsert()` Adds or updates vectors and their metadata in the collection. On a bring-your-own index, this requires `allowWrites: true` at `createIndex()` time because BYO collections are read-only by default. **indexName** (`string`): Name of the collection to insert into **vectors** (`number[][]`): Array of embedding vectors **metadata** (`Record<string, any>[]`): Metadata for each vector **ids** (`string[]`): Optional vector IDs (auto-generated if not provided) **documents** (`string[]`): Optional document text content to store alongside vectors ### `query()` Searches for similar vectors with optional metadata filtering. **indexName** (`string`): Name of the collection to search in **queryVector** (`number[]`): Query vector to find similar vectors for **topK** (`number`): Number of results to return (Default: `10`) **filter** (`Record<string, any>`): Metadata filters (applies to the metadata field) **documentFilter** (`Record<string, any>`): Filters on original document fields (not just metadata) **includeVector** (`boolean`): Whether to include vector data in results (Default: `false`) **numCandidates** (`number`): Number of candidates the HNSW graph considers before selecting top-K results. Higher values improve recall at the cost of latency. See: https\://www\.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/ (Default: `20 * topK (capped at 10000)`) **metadataMode** (`'field' | 'document'`): 'field' (default) projects the managed metadata/document fields, and filter fields are matched against the metadata subdocument. 'document' returns the full source document as metadata — use for bring-your-own operational collections whose documents have their own shape — and filter fields are matched against the \*\*root\*\* document (no metadata. prefix). The embedding field is omitted from metadata by default (to avoid payload bloat); set includeVector: true to retain it in metadata and also expose it as a top-level vector. (Default: `field`) ### `createSearchIndex()` Provisions an Atlas Search (BM25/full-text) index on the collection backing an index and records it as the text-search index that `textQuery()` and `hybridQuery()` will target. **Managed vs. bring-your-own collections:** - For a **managed** index (created without `collectionName`), `createIndex()` already provisions a _dynamic_ full-text index named `${collectionName}_search_index` (covering all string fields). `createSearchIndex()` is therefore only needed when you want a **field-restricted** mapping or a **custom index name**. - For a **bring-your-own** index (created with `collectionName`), `createIndex()` doesn't auto-create any full-text index. Enabling `textQuery()`/`hybridQuery()` on a caller-owned operational collection is opt-in. Call `createSearchIndex()` explicitly to provision the (billable) text index. Until you do, `textQuery()`/`hybridQuery()` throw a clear error rather than querying a non-existent index. Naming: - When `fields` is provided **without** an explicit `searchIndexName`, the field-mapped index is created under a **distinct** default name (`${collectionName}_${indexName}_search_fields_index`, unique per logical index) so it doesn't collide with a managed collection's auto-created dynamic index and get silently ignored. This distinct index is persisted as the text-search index, so `textQuery()`/`hybridQuery()` use the restricted mapping automatically. - When `searchIndexName` is provided, that exact name is used and persisted. `textQuery()`/`hybridQuery()` resolve the persisted name automatically. You can also override the name per call via their `searchIndexName` / `textSearchIndexName` parameters. **indexName** (`string`): Name of the Mastra index whose collection will have the search index **fields** (`string[]`): Field names to index for full-text search. Omit for dynamic mapping (all string fields). **searchIndexName** (`string`): Name for the Atlas Search index. When fields is provided and this is omitted, a distinct default name that is unique per logical index is used, so the field mapping is not shadowed by the auto-created dynamic index and two logical indexes on the same collection do not collide. (Default: ``${collectionName}_search_index (or ${collectionName}_${indexName}_search_fields_index when `fields` is given)``) **waitUntilReady** (`boolean`): When true, block until the provisioned full-text index reports READY before resolving. Defaults to false to avoid surprising latency; call waitForSearchIndexReady() explicitly if you prefer to await separately. (Default: `false`) ```typescript await store.createSearchIndex({ indexName: 'precedents', fields: ['note', 'description'], }) ``` The field-mapped index name includes the logical `indexName`, so two logical indexes on the same collection get distinct text indexes. Recreating the _same_ logical index with different `fields` still requires dropping the existing index first (`IndexAlreadyExists`). ### `waitForSearchIndexReady()` Waits for the full-text (BM25) search index of an index to become READY. `waitForIndexReady()` polls only the vectorSearch index; `createSearchIndex()` returns while the Atlas Search full-text index is still building, so an immediate `textQuery()`/`hybridQuery()` can intermittently fail. Call this (or pass `waitUntilReady: true` to `createSearchIndex()`) to block until the resolved text index reports READY. **indexName** (`string`): Logical name of the index whose text index to wait for **searchIndexName** (`string`): Override the resolved text-search index name **timeoutMs** (`number`): Maximum time to wait in milliseconds (Default: `60000`) **checkIntervalMs** (`number`): Interval between status checks in milliseconds (Default: `2000`) ```typescript await store.createSearchIndex({ indexName: 'precedents', fields: ['note'] }) await store.waitForSearchIndexReady({ indexName: 'precedents' }) ``` ### `textQuery()` Runs a full-text (BM25) search against an Atlas Search index. By default it targets the text-search index recorded for this index (set by `createSearchIndex()`, or the dynamic `${collectionName}_search_index` auto-created by `createIndex()`). Pass `searchIndexName` to target a specific index for this call. Metadata filters here (like `hybridQuery()`) are applied via a `$match` stage. For the vector branch of `hybridQuery()`, filters on fields not declared via `filterFields` at index creation are transparently materialised as candidate `_id`s (the same fallback `query()` uses), so undeclared-field filters don't error. **indexName** (`string`): Name of the Mastra index to search **query** (`string`): Full-text search query string **paths** (`string[]`): Field paths to search in (e.g., \["note", "description"]) **topK** (`number`): Number of results to return (Default: `10`) **filter** (`Record<string, any>`): Metadata filters (applies to the metadata field) **metadataMode** (`'field' | 'document'`): 'field' (default) projects the managed metadata/document fields. 'document' returns the full source document as metadata. (Default: `field`) **searchIndexName** (`string`): Override the resolved full-text search index name for this call. Defaults to the index persisted by createSearchIndex() / createIndex(). ```typescript const results = await store.textQuery({ indexName: 'precedents', query: 'shell company offshore', paths: ['note'], topK: 10, }) ``` ### `hybridQuery()` Runs a hybrid search that fuses vector similarity and full-text results using MongoDB's server-side `$rankFusion`. It requires MongoDB >= 8.0 and is generally available from 8.1. On 8.0.x, it may need a MongoDB support case to enable, and it runs where enabled, such as Atlas 8.0.x. A full-text search index must exist: it's auto-created for managed indexes, but for a bring-your-own collection you must call `createSearchIndex()` first (opt-in). **indexName** (`string`): Name of the Mastra index to search **queryVector** (`number[]`): Query vector for similarity search **query** (`string`): Full-text search query string **paths** (`string[]`): Field paths to search in for full-text (e.g., \["note", "description"]) **topK** (`number`): Number of results to return (Default: `10`) **filter** (`Record<string, any>`): Metadata filters (applies to both vector and text branches) **weights** (`{ vector?: number; text?: number }`): Relative weights for vector vs. text results in fusion (default: 1:1) **numCandidates** (`number`): Number of candidates for the vector search branch (Default: `20 * topK (capped at 10000)`) **metadataMode** (`'field' | 'document'`): 'field' (default) projects the managed metadata/document fields. 'document' returns the full source document as metadata. (Default: `field`) **textSearchIndexName** (`string`): Override the resolved full-text search index name for this call. Defaults to the index persisted by createSearchIndex() / createIndex(). ```typescript const results = await store.hybridQuery({ indexName: 'precedents', queryVector: embedding, query: 'shell company offshore', paths: ['note'], topK: 10, weights: { vector: 1, text: 1.5 }, // Favor text matches }) ``` `hybridQuery()` requires MongoDB >= 8.0 for the `$rankFusion` stage. The stage is generally available from 8.1. On 8.0.x, it may need a MongoDB support case to enable and runs where enabled, such as Atlas 8.0.x. If you're running an older version, or `$rankFusion` isn't enabled on your 8.0.x deployment, use `query()` and `textQuery()` separately and merge the results client-side. ### `describeIndex()` Returns information about the index (collection). **indexName** (`string`): Name of the collection to describe Returns: ```typescript interface IndexStats { dimension: number count: number metric: 'cosine' | 'euclidean' | 'dotproduct' } ``` ### `deleteIndex()` Deletes a vector index. Behavior depends on how the index was created: - **Managed index** (created without `collectionName`): drops the entire collection and all its data. - **Bring-your-own index** (created with `collectionName`): drops the Atlas vectorSearch index and, if one was provisioned via `createSearchIndex()`, the companion full-text search index. The caller's operational collection and its documents are preserved. This store never drops a collection it didn't create. The BYO classification is recorded durably when the index is created, so it's applied correctly even by a different process (e.g. an index created by a setup job and later deleted by a long-lived service). Always pass the **logical index name** (the `indexName` used at `createIndex`), not the physical collection name. **indexName** (`string`): Logical name of the index to delete ### `listIndexes()` Lists the **logical** Mastra index names (the `indexName` values passed to `createIndex`), not physical collection names. For a bring-your-own index whose data lives in an operational collection, the logical index name is returned instead of the physical collection name. The value can be passed straight back into `deleteIndex()` / `describeIndex()`. Managed indexes created before durable metadata was introduced are still discovered via their `${name}_vector_index` search index. The internal registry collection is never listed. Returns: `Promise<string[]>` ### `updateVector()` Update a single vector by ID or by metadata filter. Either `id` or `filter` must be provided, but not both. > **Bring-your-own collections are read-only by default.** `upsert()`, `updateVector()`, `deleteVector()`, and `deleteVectors()` throw a USER-category error on a BYO index unless it was created with `allowWrites: true`. See [Indexing an existing collection](#indexing-an-existing-collection). **indexName** (`string`): Name of the collection containing the vector **id** (`string`): ID of the vector entry to update (mutually exclusive with filter) **filter** (`Record<string, any>`): Metadata filter to identify vector(s) to update (mutually exclusive with id) **update** (`object`): Update data containing vector and/or metadata **update.vector** (`number[]`): New vector data to update **update.metadata** (`Record<string, any>`): New metadata to update ### `deleteVector()` Deletes a specific vector entry from an index by its ID. **indexName** (`string`): Name of the collection containing the vector **id** (`string`): ID of the vector entry to delete ### `deleteVectors()` Delete multiple vectors by IDs or by metadata filter. Either `ids` or `filter` must be provided, but not both. **indexName** (`string`): Name of the collection containing the vectors to delete **ids** (`string[]`): Array of vector IDs to delete (mutually exclusive with filter) **filter** (`Record<string, any>`): Metadata filter to identify vectors to delete (mutually exclusive with ids) ### `disconnect()` Closes the MongoDB client connection. Should be called when done using the store. ## Response types Query results are returned in this format: ```typescript interface QueryResult { id: string score: number metadata: Record<string, any> vector?: number[] // Only included if includeVector is true } ``` ## Error handling The store throws typed errors that can be caught: ```typescript try { await store.query({ indexName: 'my_collection', queryVector: queryVector, }) } catch (error) { // Handle specific error cases if (error.message.includes('Invalid collection name')) { console.error( 'Collection name must start with a letter or underscore and contain only valid characters.', ) } else if (error.message.includes('Collection not found')) { console.error('The specified collection does not exist') } else { console.error('Vector store error:', error.message) } } ``` ## Indexing an existing collection You can create a vector index on an existing operational collection instead of using a managed collection. This is useful when you want to add vector search capabilities to documents that already exist in your MongoDB database. ```typescript import { MongoDBVector } from '@mastra/mongodb' const store = new MongoDBVector({ id: 'mongodb-vector', uri: process.env.MONGODB_URI, dbName: process.env.MONGODB_DB_NAME, }) // Create a vector index on an existing 'transactions' collection await store.createIndex({ indexName: 'precedents', dimension: 1024, collectionName: 'transactions', // Use existing collection searchIndexName: 'txn_vec_idx', // Custom search index name }) // Wait for the index to be ready await store.waitForIndexReady({ indexName: 'precedents' }) // Query using document mode to get full source documents const hits = await store.query({ indexName: 'precedents', queryVector: embeddings, topK: 5, metadataMode: 'document', // Returns full document as metadata }) // hits[0].metadata now contains all fields from the source document console.log(hits[0].metadata.amount, hits[0].metadata.customField) // Full-text / hybrid search on a BYO collection is opt-in: provision the text index first. await store.createSearchIndex({ indexName: 'precedents', fields: ['note'] }) ``` **Important notes:** - The collection must already exist and contain documents with an `embedding` field (or the custom `embeddingFieldPath` you configured) - The collection is never created or dropped when using `collectionName` - **A BYO index is read-only by default.** `upsert()`, `updateVector()`, `deleteVector()`, and `deleteVectors()` throw a clear error rather than mutating caller-owned operational documents. To let the store write embeddings into (or delete documents from) your collection, opt in explicitly with `createIndex({ ..., allowWrites: true })`. The policy is persisted and survives restarts. Entries written by older versions without the flag are treated as read-only (fail closed). - Use `metadataMode: 'document'` when querying to retrieve the full source document as `metadata` - In `'document'` mode the embedding is omitted from `metadata` by default; pass `includeVector: true` to retain it (and also expose it as a top-level `vector`) - **Filtering in `'document'` mode operates on root document fields**, not a nested `metadata.` subdocument. `filter: { lane: 'fraud' }` matches the top-level `lane` field of your operational documents (in the default `'field'` mode, bare fields are rewritten to `metadata.<field>` for managed collections). Both the pushdown and `$match` fallback paths honor this. - **Native `ObjectId` `_id`s are supported.** Operational collections commonly key on `ObjectId`; query results coerce `_id` to a string (the `QueryResult.id` contract), and `deleteVector()`/`updateVector()`/`deleteVectors()` accept that string and match the underlying `ObjectId` document. Managed collections (string `_id`s) are unaffected. - Full-text and hybrid search on a BYO collection are **opt-in**: no full-text index is auto-created, so call `createSearchIndex()` before `textQuery()`/`hybridQuery()`. The full-text index builds asynchronously. Call `waitForSearchIndexReady()` (or pass `waitUntilReady: true`) before an immediate text/hybrid query. - `deleteIndex()` on a BYO index drops the vector index (and the text index if one was created) but **preserves** the collection and its documents ## Best practices - Index metadata fields used in filters for optimal query performance. - Use consistent field naming in metadata to avoid unexpected query results. - Regularly monitor index and collection statistics to ensure efficient search. - When indexing existing collections, ensure all documents have the required `embedding` field. ## Usage example ### Vector embeddings with `MongoDB` Embeddings are numeric vectors used by memory's `semanticRecall` to retrieve related messages by meaning (not keywords). > **Note:** MongoDB Atlas Vector Search is recommended for production use. For self-hosted deployments, Vector Search is available with [local Atlas deployments via the Atlas CLI](https://www.mongodb.com/docs/atlas/cli/current/atlas-cli-deploy-local/). This setup uses FastEmbed, a local embedding model, to generate vector embeddings. To use this, install `@mastra/fastembed`: **npm**: ```bash npm install @mastra/fastembed@latest ``` **pnpm**: ```bash pnpm add @mastra/fastembed@latest ``` **Yarn**: ```bash yarn add @mastra/fastembed@latest ``` **Bun**: ```bash bun add @mastra/fastembed@latest ``` Add the following to your agent: ```typescript import { Memory } from '@mastra/memory' import { Agent } from '@mastra/core/agent' import { MongoDBStore, MongoDBVector } from '@mastra/mongodb' import { fastembed } from '@mastra/fastembed' export const mongodbAgent = new Agent({ id: 'mongodb-agent', name: 'mongodb-agent', instructions: 'You are an AI agent with the ability to automatically recall memories from previous interactions.', model: 'openai/gpt-5.6-sol', memory: new Memory({ storage: new MongoDBStore({ id: 'mongodb-storage', uri: process.env.MONGODB_URI!, dbName: process.env.MONGODB_DB_NAME!, }), vector: new MongoDBVector({ id: 'mongodb-vector', uri: process.env.MONGODB_URI!, dbName: process.env.MONGODB_DB_NAME!, }), embedder: fastembed, options: { lastMessages: 10, semanticRecall: { topK: 3, messageRange: 2, }, generateTitle: true, // generates descriptive thread titles automatically }, }), }) ``` ### Vector embeddings with VoyageAI VoyageAI provides specialized embedding models optimized for retrieval tasks. VoyageAI is also integrated with MongoDB Atlas for multimodal embeddings. **npm**: ```bash npm install @mastra/voyageai@latest ``` **pnpm**: ```bash pnpm add @mastra/voyageai@latest ``` **Yarn**: ```bash yarn add @mastra/voyageai@latest ``` **Bun**: ```bash bun add @mastra/voyageai@latest ``` Basic usage example: ```typescript import { Memory } from '@mastra/memory' import { Agent } from '@mastra/core/agent' import { MongoDBStore, MongoDBVector } from '@mastra/mongodb' import { voyage } from '@mastra/voyageai' export const mongodbVoyageAgent = new Agent({ id: 'mongodb-voyage-agent', name: 'MongoDB VoyageAI Agent', instructions: 'You are an AI agent with semantic recall powered by VoyageAI and MongoDB.', model: 'openai/gpt-5.6-sol', memory: new Memory({ storage: new MongoDBStore({ id: 'mongodb-storage', uri: process.env.MONGODB_URI!, dbName: process.env.MONGODB_DB_NAME!, }), vector: new MongoDBVector({ id: 'mongodb-vector', uri: process.env.MONGODB_URI!, dbName: process.env.MONGODB_DB_NAME!, }), embedder: voyage, // VoyageAI's default model (voyage-3.5, 1024 dimensions) options: { lastMessages: 10, semanticRecall: { topK: 5, messageRange: 2, }, }, }), }) ``` For detailed VoyageAI embedding examples including specialized models, multimodal embeddings, and retrieval optimization, see the [VoyageAI embeddings documentation](https://mastra.ai/models/embeddings). ## Related - [Metadata Filters](https://mastra.ai/reference/rag/metadata-filters) - [VoyageAI Embeddings Documentation](https://mastra.ai/models/embeddings)