mastra-azure-ai-search
v0.1.0
Published
Azure AI Search vector store provider for Mastra
Maintainers
Readme
mastra-azure-ai-search
Azure AI Search vector store provider for Mastra. This package provides vector storage and similarity search capabilities using Azure AI Search's vector search features.
Installation
npm install mastra-azure-ai-search
# or
pnpm add mastra-azure-ai-search
# or
yarn add mastra-azure-ai-searchCommunity Demo
A public demo using this package is available at:
- https://github.com/valdepeace/mastra-azure-aisearch-demo
Use it as a reference for end-to-end setup and usage with Azure AI Search.
Upstream Context
This package tracks the Azure AI Search vector store work proposed for Mastra in mastra-ai/mastra#10146.
Publishing
This package is intended to be published independently from the Mastra monorepo.
pnpm install --ignore-workspace
pnpm build
pnpm publish --access publicPrerequisites
Before using this package, you'll need:
- Azure AI Search service: Create an Azure AI Search service in your Azure subscription
- API Key or Azure credentials: Get your API key from the Azure portal or use Azure authentication
- Service endpoint: The URL of your Azure AI Search service (e.g.,
https://your-service.search.windows.net)
Configuration
Basic Setup with API Key
import { AzureAISearchVector } from 'mastra-azure-ai-search';
const azureVector = new AzureAISearchVector({
id: 'azure-search-vectors',
endpoint: 'https://your-service.search.windows.net',
credential: 'your-api-key'
});Setup with Azure Credentials
import { AzureAISearchVector } from 'mastra-azure-ai-search';
import { DefaultAzureCredential } from '@azure/identity';
const azureVector = new AzureAISearchVector({
id: 'azure-search-vectors',
endpoint: 'https://your-service.search.windows.net',
credential: new DefaultAzureCredential()
});Advanced Client Configuration
Use clientOptions to customize the SearchClient behavior with retry policies, custom headers, or proxy configurations:
import { AzureAISearchVector } from 'mastra-azure-ai-search';
const azureVector = new AzureAISearchVector({
id: 'azure-search-custom',
endpoint: 'https://your-service.search.windows.net',
credential: 'your-api-key',
clientOptions: {
// Add custom policies (e.g., for proxy, logging, etc.)
additionalPolicies: [
{
position: 'perCall',
policy: {
name: 'CustomHeadersPolicy',
async sendRequest(request, next) {
// Add custom headers
request.headers.set('X-Custom-Header', 'my-value');
return next(request);
}
}
}
],
// Configure retry behavior
retryOptions: {
maxRetries: 3,
retryDelayInMs: 1000
}
}
});Example: Using with a Proxy
import { AzureAISearchVector } from 'mastra-azure-ai-search';
import type { PipelinePolicy } from '@azure/core-rest-pipeline';
// Custom proxy policy
const createProxyPolicy = (config: {
proxyUrl: string;
token: string;
}): PipelinePolicy => ({
name: 'ProxyPolicy',
async sendRequest(request, next) {
// Rewrite URL to proxy
const originalUrl = new URL(request.url);
request.url = `${config.proxyUrl}${originalUrl.pathname}${originalUrl.search}`;
// Add proxy authentication
request.headers.set('Authorization', `Bearer ${config.token}`);
return next(request);
}
});
const azureVector = new AzureAISearchVector({
id: 'azure-search-proxy',
endpoint: 'https://your-service.search.windows.net',
credential: 'dummy-key', // Not used with proxy
clientOptions: {
additionalPolicies: [{
position: 'perCall',
policy: createProxyPolicy({
proxyUrl: 'https://my-proxy.example.com',
token: process.env.PROXY_TOKEN!
})
}]
}
});Integration with Mastra Memory System
import { openai } from '@ai-sdk/openai';
import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { AzureAISearchVector } from 'mastra-azure-ai-search';
// Setup Azure AI Search vector store
const azureVector = new AzureAISearchVector({
id: 'azure-memory-store',
endpoint: process.env.AZURE_AI_SEARCH_ENDPOINT!,
credential: process.env.AZURE_AI_SEARCH_CREDENTIAL!,
});
// Configure Memory with Azure AI Search
const memory = new Memory({
vector: azureVector,
options: {
lastMessages: 15,
semanticRecall: {
topK: 5,
messageRange: 3,
},
},
embedder: openai.embedding('text-embedding-3-small'),
});
// Create agent with advanced memory
const agent = new Agent({
id: 'azure-assistant',
name: 'Azure-Powered Assistant',
instructions: 'You are an assistant with advanced memory capabilities powered by Azure AI Search.',
model: openai('gpt-4o'),
memory,
});Basic Vector Store Setup
import { Mastra } from '@mastra/core';
import { AzureAISearchVector } from 'mastra-azure-ai-search';
const azureVector = new AzureAISearchVector({
id: 'azure-search',
endpoint: process.env.AZURE_AI_SEARCH_ENDPOINT!,
credential: process.env.AZURE_AI_SEARCH_CREDENTIAL!
});
const mastra = new Mastra({
vectors: {
'azure-search': azureVector
}
});Usage Examples
Creating an Index
// Create a new vector index
await azureVector.createIndex({
indexName: 'products',
dimension: 1536, // Vector dimension (e.g., for OpenAI embeddings)
metric: 'cosine' // Similarity metric: 'cosine', 'euclidean', or 'dotproduct'
});Add explicit fields when you need Azure AI Search to filter on values that also live in Mastra metadata:
await azureVector.createIndex({
indexName: 'products',
dimension: 1536,
additionalFields: [
{ name: 'category', type: 'Edm.String', filterable: true },
{ name: 'price', type: 'Edm.Double', filterable: true },
{ name: 'inStock', type: 'Edm.Boolean', filterable: true },
],
});Mastra Memory passes metadataIndexes when creating semantic recall indexes. This adapter maps those metadata indexes to filterable Azure AI Search string fields:
await azureVector.createIndex({
indexName: 'memory_messages',
dimension: 1536,
metadataIndexes: ['thread_id', 'resource_id'],
});Inserting Vectors
// Insert vectors with metadata
const vectorIds = await azureVector.upsert({
indexName: 'products',
vectors: [
[0.1, 0.2, 0.3 /* ...1536 dimensions */], // Vector 1
[0.4, 0.5, 0.6 /* ...1536 dimensions */], // Vector 2
],
metadata: [
{
category: 'electronics',
brand: 'Apple',
price: 999,
content: 'iPhone 15 Pro Max with advanced camera system'
},
{
category: 'electronics',
brand: 'Samsung',
price: 899,
content: 'Galaxy S24 Ultra with S Pen and AI features'
}
],
ids: ['iphone-15-pro', 'galaxy-s24-ultra'] // Optional: provide custom IDs
});
console.log('Inserted vector IDs:', vectorIds);The full metadata object is stored in the metadata JSON string. Values with keys that match explicit Azure index fields are also written to those fields so they can be filtered by Azure AI Search.
Searching Vectors
Basic Vector Search
const results = await azureVector.query({
indexName: 'products',
queryVector: [0.1, 0.2, 0.3 /* ...1536 dimensions */],
topK: 5, // Return top 5 similar results
includeVector: false // Set to true if you want the vectors in results
});
console.log('Search results:', results);
// Output: [{ id: 'iphone-15-pro', score: 0.95, metadata: {...}, document: '...' }, ...]Filtered Vector Search
Filters only work on fields that are present in the Azure AI Search index.
// Using structured filter syntax
const results = await azureVector.query({
indexName: 'products',
queryVector: [0.1, 0.2, 0.3 /* ...1536 dimensions */],
topK: 10,
filter: {
and: [
{ eq: { category: 'electronics' } },
{ gt: { price: 500 } },
{ contains: { content: 'camera' } }
]
}
});Mastra Memory uses flat metadata filters for semantic recall:
const results = await azureVector.query({
indexName: 'memory_messages',
queryVector: [0.1, 0.2, 0.3 /* ...1536 dimensions */],
topK: 5,
filter: {
resource_id: 'resource-123',
},
});Advanced Filtering Examples
// Complex filter with OR conditions
const complexFilter = {
and: [
{
or: [
{ eq: { brand: 'Apple' } },
{ eq: { brand: 'Samsung' } }
]
},
{
and: [
{ ge: { price: 500 } },
{ le: { price: 1500 } }
]
},
{
not: {
contains: { content: 'refurbished' }
}
}
]
};
const results = await azureVector.query({
indexName: 'products',
queryVector: queryEmbedding,
filter: complexFilter,
topK: 20
});Using Raw OData Filters
// Using raw OData filter syntax for advanced scenarios
const results = await azureVector.query({
indexName: 'products',
queryVector: queryEmbedding,
filter: {
$filter: "category eq 'electronics' and price lt 1000 and search.ismatch('smartphone', 'content')"
},
topK: 5
});Advanced Features
Azure AI Search for Mastra includes the following advanced capabilities.
Semantic Search
Significantly improves result relevance using advanced language models:
// Basic semantic search
const results = await azureVector.query({
indexName: 'my-index',
queryVector: [0.1, 0.2 /* ...more dimensions */],
topK: 10,
useSemanticSearch: true,
semanticOptions: {
configurationName: 'my-config',
semanticQuery: 'What is artificial intelligence?',
answers: true,
captions: true,
maxWaitTime: 5000
}
});Multi-Vector Hybrid Search
Combines multiple vectors with different weights for more sophisticated searches:
// Multi-vector search with text vectorization
const results = await azureVector.query({
indexName: 'my-index',
queryVector: manualVector,
topK: 10,
textVectorization: {
text: 'machine learning algorithms',
fields: ['content_vector', 'title_vector']
}
});Advanced Vector Search Options
const results = await azureVector.query({
indexName: 'my-index',
queryVector: [0.1, 0.2 /* ...more dimensions */],
topK: 10,
exhaustiveSearch: true, // Exact k-NN search for precision
weight: 2.0, // Relative weight in hybrid searches
oversampling: 3, // Only with compressed vectors
queryType: 'full', // 'simple' | 'full' | 'semantic'
filterMode: 'preFilter' // 'preFilter' | 'postFilter'
});Document Search with Automatic Answers
const results = await azureVector.query({
indexName: 'knowledge-base',
queryVector: await embed('What are the benefits of AI?'),
topK: 5,
useSemanticSearch: true,
semanticOptions: {
configurationName: 'default',
answers: true, // Extract direct answers
captions: true // Generate passage summaries
}
});
// Results will include:
// - result.metadata['@search.captions']: Automatic summaries
// - result.metadata['@search.rerankerScore']: Semantic scoreFlexible Schema Support
Azure AI Search supports completely flexible schemas with customizable vector fields and advanced configurations.
Advanced Index Creation
// Create index with custom vector field and additional fields
await azureVector.createIndex({
indexName: 'my-flexible-index',
dimension: 512,
vectorField: 'custom_embedding', // Custom vector field name
additionalFields: [
{
name: 'title',
type: 'Edm.String',
searchable: true,
filterable: true
},
{
name: 'tags',
type: 'Collection(Edm.String)',
searchable: true,
filterable: true,
facetable: true
}
],
hnswParameters: {
m: 16, // Connections per layer
efConstruction: 800, // Construction time accuracy
efSearch: 500 // Query time accuracy
},
semanticConfig: {
name: 'semantic-config',
prioritizedFields: {
titleField: { fieldName: 'title' },
prioritizedContentFields: [{ fieldName: 'content' }],
prioritizedKeywordsFields: [{ fieldName: 'tags' }]
}
}
});Dynamic Vector Field Detection
The implementation automatically detects vector fields in existing indexes:
// Works with any existing index regardless of vector field name
const results = await azureVector.query({
indexName: 'legacy-index', // May use 'vector', 'embedding', etc.
queryVector: [0.1, 0.2 /* ...more dimensions */],
topK: 5
});
// Automatically detects and uses the correct vector fieldFeature Comparison
| Feature | Pinecone | Qdrant | Azure AI Search |
|---------|----------|--------|---------------------|
| Basic vector search | ✅ | ✅ | ✅ |
| Metadata/payload filters | ✅ | ✅ | ✅ |
| Hybrid lexical + vector search | ✅ | ✅ | ✅ |
| Semantic reranking (platform-provided) | ❌ | ❌ | ✅ |
| Automatic vectorization (platform-provided) | ✅ (integrated embedding indexes) | ❌ | ✅ (integrated vectorization) |
| Exact search mode | Not explicitly exposed as query option | ✅ (exact: true) | ✅ (exhaustive: true) |
| Multiple vector fields per record | Limited (single dense + single sparse index design) | ✅ (named vectors) | ✅ (multi-vector fields) |
| HNSW query tuning | Not exposed as HNSW params | ✅ (hnsw_ef, collection config) | ✅ (algorithm/profile configuration) |
Comparison notes:
- This table is based on vendor documentation and public APIs as of February 2026.
- "Platform-provided" means available directly in the vendor platform, not via external rerankers or custom pipelines.
Updating Vectors
// Update vector and/or metadata
await azureVector.updateVector({
indexName: 'products',
id: 'iphone-15-pro',
update: {
vector: [0.2, 0.3, 0.4 /* ...more dimensions */], // New vector
metadata: {
category: 'electronics',
brand: 'Apple',
price: 899, // Updated price
content: 'iPhone 15 Pro Max - Now with better price!'
}
}
});Managing Indexes
// List all indexes
const indexes = await azureVector.listIndexes();
console.log('Available indexes:', indexes);
// Get index information
const indexInfo = await azureVector.describeIndex({ indexName: 'products' });
console.log('Index stats:', indexInfo);
// Output: { dimension: 1536, count: 1000, metric: 'cosine' }
// Delete an index
await azureVector.deleteIndex({ indexName: 'products' });Deleting Vectors
// Delete specific vector
await azureVector.deleteVector({
indexName: 'products',
id: 'iphone-15-pro'
});Filter Syntax
Azure AI Search uses OData syntax for filtering. This package supports both structured filter objects and raw OData strings.
Structured Filter Syntax
| Operation | Description | Example |
|-----------|-------------|---------|
| eq | Equals | { eq: { category: 'electronics' } } |
| ne | Not equals | { ne: { status: 'discontinued' } } |
| gt | Greater than | { gt: { price: 100 } } |
| ge | Greater than or equal | { ge: { rating: 4.0 } } |
| lt | Less than | { lt: { price: 1000 } } |
| le | Less than or equal | { le: { discount: 50 } } |
| contains | String contains | { contains: { description: 'wireless' } } |
| startsWith | String starts with | { startsWith: { name: 'iPhone' } } |
| endsWith | String ends with | { endsWith: { model: 'Pro' } } |
| and | Logical AND | { and: [filter1, filter2] } |
| or | Logical OR | { or: [filter1, filter2] } |
| not | Logical NOT | { not: filter } |
Raw OData Filter
For advanced scenarios, you can use raw OData syntax:
const filter = {
$filter: "category eq 'electronics' and price lt 1000 and geo.distance(location, geography'POINT(-122.131577 47.678581)') le 10"
};Error Handling
The package uses Mastra's error handling system. All errors are wrapped in MastraError objects with appropriate categorization:
import { MastraError } from '@mastra/core/error';
try {
await azureVector.createIndex({
indexName: 'test',
dimension: 1536,
metric: 'cosine'
});
} catch (error) {
if (error instanceof MastraError) {
console.error('Mastra Error:', error.id);
console.error('Details:', error.details);
}
}Supported Metrics
- cosine: Cosine similarity (default, recommended for most use cases)
- euclidean: Euclidean distance
- dotproduct: Dot product similarity
Limitations and Considerations
Azure AI Search Limitations
- Maximum vector dimensions: 3072 per field
- Maximum document size: 16 MB
- Query limits: Rate limits apply based on your pricing tier
- Index limits: Number of indexes varies by pricing tier
Performance Considerations
- Batch operations: Use batch upsert for better performance when inserting multiple vectors
- Index warming: First queries might be slower on cold indexes
- Field selection: Only select necessary fields in queries to improve performance
- Filter optimization: Structure filters for optimal performance (equality filters first)
Best Practices
- Index naming: Use descriptive names following Azure naming conventions
- Metadata design: Keep metadata flat when possible for better filtering performance
- Vector dimensions: Ensure all vectors have the same dimension within an index
- Connection pooling: Reuse the same AzureAISearchVector instance across your application
- Error handling: Always wrap operations in try-catch blocks
Environment Variables
For production use, store sensitive configuration in environment variables:
# .env file
AZURE_AI_SEARCH_ENDPOINT=https://your-service.search.windows.net
AZURE_AI_SEARCH_CREDENTIAL=your-api-key// Configuration
const azureVector = new AzureAISearchVector({
id: 'azure-search',
endpoint: process.env.AZURE_AI_SEARCH_ENDPOINT!,
credential: process.env.AZURE_AI_SEARCH_CREDENTIAL!
});TypeScript Support
This package is written in TypeScript:
import type {
AzureAISearchVector,
AzureAISearchVectorFilter,
AzureAISearchVectorOptions
} from 'mastra-azure-ai-search';
// Type-safe filter construction
const filter: AzureAISearchVectorFilter = {
and: [
{ eq: { category: 'electronics' } },
{ gt: { price: 100 } }
]
};Testing
This package includes tests for Azure AI Search integration with Mastra Memory.
Test Types
# Run all tests
pnpm test
# Unit tests only
pnpm test:unit
# Integration tests (requires Azure credentials)
pnpm test:integrationMemory Integration Testing
Use this package with Mastra Memory in your application-level tests:
- Unit Tests: Mock the vector store where your app does not need Azure behavior
- Scenario Tests: Add application-level tests in your Mastra app for realistic memory operations
- Real Integration: Validate with @mastra/memory in your application when Azure credentials are available
Running Memory Tests
# Run this package's unit tests
pnpm test:unit
# Run Azure AI Search integration tests when credentials are configured
pnpm test:integrationMemory Test Environment
For memory integration tests, set these environment variables:
# Required for all memory tests
AZURE_AI_SEARCH_ENDPOINT=https://your-service.search.windows.net
AZURE_AI_SEARCH_CREDENTIAL=your-admin-api-key
# Required for real memory integration tests
OPENAI_API_KEY=your-openai-api-keyContributing
This package is part of the Mastra framework. For contributions:
- Open an issue or discussion describing the change before large refactors
- Ensure all tests pass:
pnpm test - Add tests for new functionality, especially memory-related features
- Test memory integration in your Mastra app when changing memory-related behavior
- Update documentation as needed
License
Apache-2.0.
Support
- Documentation: Mastra Docs
- Discord: Mastra Community
- GitHub Issues: Use the issue tracker for this package repository once published
