Back to Mastra

Database-Specific Configurations

examples/basics/rag/database-specific-config/README.md

2025-12-1811.0 KB
Original Source

Database-Specific Configurations

This example demonstrates how to use database-specific configurations with vector query tools to optimize performance and leverage unique features of different vector stores.

Multi-Environment Setup

Use different configurations for different environments:

TypeScript

typescript
import { createVectorQueryTool } from '@mastra/rag';
import { RequestContext } from '@mastra/core/request-context';
import { ModelRouterEmbeddingModel } from '@mastra/core/llm';

// Base configuration
const createSearchTool = (environment: 'dev' | 'staging' | 'prod') => {
  return createVectorQueryTool({
    vectorStoreName: 'pinecone',
    indexName: 'documents',
    model: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
    databaseConfig: {
      pinecone: {
        namespace: environment,
      },
    },
  });
};

// Create environment-specific tools
const devSearchTool = createSearchTool('dev');
const prodSearchTool = createSearchTool('prod');

// Or use runtime override
const dynamicSearchTool = createVectorQueryTool({
  vectorStoreName: 'pinecone',
  indexName: 'documents',
  model: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
});

// Switch environment at runtime
const switchEnvironment = async (environment: string, query: string) => {
  const requestContext = new RequestContext();
  requestContext.set('databaseConfig', {
    pinecone: {
      namespace: environment,
    },
  });

  return await dynamicSearchTool.execute({
    context: { queryText: query },
    mastra,
    requestContext,
  });
};

JavaScript

javascript
import { createVectorQueryTool } from '@mastra/rag';
import { RequestContext } from '@mastra/core/request-context';

// Base configuration
const createSearchTool = environment => {
  return createVectorQueryTool({
    vectorStoreName: 'pinecone',
    indexName: 'documents',
    model: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
    databaseConfig: {
      pinecone: {
        namespace: environment,
      },
    },
  });
};

// Create environment-specific tools
const devSearchTool = createSearchTool('dev');
const prodSearchTool = createSearchTool('prod');

// Or use runtime override
const dynamicSearchTool = createVectorQueryTool({
  vectorStoreName: 'pinecone',
  indexName: 'documents',
  model: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
});

// Switch environment at runtime
const switchEnvironment = async (environment, query) => {
  const requestContext = new RequestContext();
  requestContext.set('databaseConfig', {
    pinecone: {
      namespace: environment,
    },
  });

  return await dynamicSearchTool.execute({
    context: { queryText: query },
    mastra,
    requestContext,
  });
};

Performance Optimization with pgVector

Optimize search performance for different use cases:

High Accuracy

typescript
import { ModelRouterEmbeddingModel } from '@mastra/core/llm';

// High accuracy configuration - slower but more precise
const highAccuracyTool = createVectorQueryTool({
  vectorStoreName: 'postgres',
  indexName: 'embeddings',
  model: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
  databaseConfig: {
    pgvector: {
      ef: 400, // High accuracy for HNSW
      probes: 20, // High recall for IVFFlat
      minScore: 0.85, // High quality threshold
    },
  },
});

// Use for critical searches where accuracy is paramount
const criticalSearch = async (query: string) => {
  return await highAccuracyTool.execute({
    context: {
      queryText: query,
      topK: 5, // Fewer, higher quality results
    },
    mastra,
  });
};

High Speed

typescript
import { ModelRouterEmbeddingModel } from '@mastra/core/llm';

// High speed configuration - faster but less precise
const highSpeedTool = createVectorQueryTool({
  vectorStoreName: 'postgres',
  indexName: 'embeddings',
  model: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
  databaseConfig: {
    pgvector: {
      ef: 50, // Lower accuracy for speed
      probes: 3, // Lower recall for speed
      minScore: 0.6, // Lower quality threshold
    },
  },
});

// Use for real-time applications
const realtimeSearch = async (query: string) => {
  return await highSpeedTool.execute({
    context: {
      queryText: query,
      topK: 10, // More results to compensate for lower precision
    },
    mastra,
  });
};

Balanced

typescript
import { ModelRouterEmbeddingModel } from '@mastra/core/llm';

// Balanced configuration - good compromise
const balancedTool = createVectorQueryTool({
  vectorStoreName: 'postgres',
  indexName: 'embeddings',
  model: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
  databaseConfig: {
    pgvector: {
      ef: 150, // Moderate accuracy
      probes: 8, // Moderate recall
      minScore: 0.7, // Moderate quality threshold
    },
  },
});

// Adjust parameters based on load
const adaptiveSearch = async (query: string, isHighLoad: boolean) => {
  const requestContext = new RequestContext();

  if (isHighLoad) {
    // Reduce quality for speed during high load
    requestContext.set('databaseConfig', {
      pgvector: {
        ef: 75,
        probes: 5,
        minScore: 0.65,
      },
    });
  }

  return await balancedTool.execute({
    context: { queryText: query },
    mastra,
    requestContext,
  });
};

Multi-Tenant Application with Pinecone

Implement tenant isolation using Pinecone namespaces:

typescript
import { ModelRouterEmbeddingModel } from '@mastra/core/llm';

interface Tenant {
  id: string;
  name: string;
  namespace: string;
}

class MultiTenantSearchService {
  private searchTool: RagTool;

  constructor() {
    this.searchTool = createVectorQueryTool({
      vectorStoreName: 'pinecone',
      indexName: 'shared-documents',
      model: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
    });
  }

  async searchForTenant(tenant: Tenant, query: string) {
    const requestContext = new RequestContext();

    // Isolate search to tenant's namespace
    requestContext.set('databaseConfig', {
      pinecone: {
        namespace: tenant.namespace,
      },
    });

    const results = await this.searchTool.execute({
      context: {
        queryText: query,
        topK: 10,
      },
      mastra,
      requestContext,
    });

    // Add tenant context to results
    return {
      tenant: tenant.name,
      query,
      results: results.relevantContext,
      sources: results.sources,
    };
  }

  async bulkSearchForTenants(tenants: Tenant[], query: string) {
    const promises = tenants.map(tenant => this.searchForTenant(tenant, query));

    return await Promise.all(promises);
  }
}

// Usage
const searchService = new MultiTenantSearchService();

const tenants = [
  { id: '1', name: 'Company A', namespace: 'company-a' },
  { id: '2', name: 'Company B', namespace: 'company-b' },
];

const results = await searchService.searchForTenant(tenants[0], 'product documentation');

Hybrid Search with Pinecone

Combine semantic and keyword search:

typescript
import { ModelRouterEmbeddingModel } from '@mastra/core/llm';

const hybridSearchTool = createVectorQueryTool({
  vectorStoreName: 'pinecone',
  indexName: 'documents',
  model: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
  databaseConfig: {
    pinecone: {
      namespace: 'production',
      sparseVector: {
        // Example sparse vector for keyword "API"
        indices: [1, 5, 10, 15],
        values: [0.8, 0.6, 0.4, 0.2],
      },
    },
  },
});

// Helper function to generate sparse vectors for keywords
const generateSparseVector = (keywords: string[]) => {
  // This is a simplified example - in practice, you'd use
  // a proper sparse encoding method like BM25
  const indices: number[] = [];
  const values: number[] = [];

  keywords.forEach((keyword, i) => {
    const hash = keyword.split('').reduce((a, b) => {
      a = (a << 5) - a + b.charCodeAt(0);
      return a & a;
    }, 0);

    indices.push(Math.abs(hash) % 1000);
    values.push(1.0 / (i + 1)); // Decrease weight for later keywords
  });

  return { indices, values };
};

const hybridSearch = async (query: string, keywords: string[]) => {
  const requestContext = new RequestContext();

  if (keywords.length > 0) {
    const sparseVector = generateSparseVector(keywords);
    requestContext.set('databaseConfig', {
      pinecone: {
        namespace: 'production',
        sparseVector,
      },
    });
  }

  return await hybridSearchTool.execute({
    context: { queryText: query },
    mastra,
    requestContext,
  });
};

// Usage
const results = await hybridSearch('How to use the REST API', ['API', 'REST', 'documentation']);

Implement progressive search quality:

typescript
const createQualityGatedSearch = () => {
  const baseConfig = {
    vectorStoreName: 'postgres',
    indexName: 'embeddings',
    model: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
  };

  return {
    // High quality search first
    highQuality: createVectorQueryTool({
      ...baseConfig,
      databaseConfig: {
        pgvector: {
          minScore: 0.85,
          ef: 200,
          probes: 15,
        },
      },
    }),

    // Medium quality fallback
    mediumQuality: createVectorQueryTool({
      ...baseConfig,
      databaseConfig: {
        pgvector: {
          minScore: 0.7,
          ef: 150,
          probes: 10,
        },
      },
    }),

    // Low quality last resort
    lowQuality: createVectorQueryTool({
      ...baseConfig,
      databaseConfig: {
        pgvector: {
          minScore: 0.5,
          ef: 100,
          probes: 5,
        },
      },
    }),
  };
};

const progressiveSearch = async (query: string, minResults: number = 3) => {
  const tools = createQualityGatedSearch();

  // Try high quality first
  let results = await tools.highQuality.execute({
    context: { queryText: query },
    mastra,
  });

  if (results.sources.length >= minResults) {
    return { quality: 'high', ...results };
  }

  // Fallback to medium quality
  results = await tools.mediumQuality.execute({
    context: { queryText: query },
    mastra,
  });

  if (results.sources.length >= minResults) {
    return { quality: 'medium', ...results };
  }

  // Last resort: low quality
  results = await tools.lowQuality.execute({
    context: { queryText: query },
    mastra,
  });

  return { quality: 'low', ...results };
};

// Usage
const results = await progressiveSearch('complex technical query', 5);
console.log(`Found ${results.sources.length} results with ${results.quality} quality`);

Key Takeaways

  1. Environment Isolation: Use namespaces to separate data by environment or tenant
  2. Performance Tuning: Adjust ef/probes parameters based on your accuracy vs speed requirements
  3. Quality Control: Use minScore to filter out low-quality matches
  4. Runtime Flexibility: Override configurations dynamically based on context
  5. Progressive Quality: Implement fallback strategies for different quality levels

This approach allows you to optimize vector search for your specific use case while maintaining flexibility and performance.