Skip to main content
Glama
assistant.js7.5 kB
import { Anthropic } from '@anthropic-ai/sdk'; import OpenAI from 'openai'; import { logger } from '../utils/logger.js'; export class MetabaseAIAssistant { constructor(config) { this.metabaseClient = config.metabaseClient; this.aiProvider = config.aiProvider || 'anthropic'; if (this.aiProvider === 'anthropic') { this.ai = new Anthropic({ apiKey: config.anthropicApiKey }); } else { this.ai = new OpenAI({ apiKey: config.openaiApiKey }); } } async analyzeRequest(userRequest) { const prompt = ` Analyze the following user request for Metabase operations. Determine what type of operation is needed and extract relevant parameters. User Request: "${userRequest}" Respond with a JSON object containing: - operation_type: (model|question|sql|metric|dashboard|segment) - action: (create|update|query|analyze) - parameters: relevant extracted parameters - suggested_approach: brief description of recommended approach `; const response = await this.getAIResponse(prompt); return JSON.parse(response); } async generateSQL(description, schema) { const prompt = ` Generate SQL query based on the following description: "${description}" Available schema: ${JSON.stringify(schema, null, 2)} Requirements: - Use proper SQL syntax - Include appropriate JOINs if needed - Add meaningful aliases - Consider performance optimization Return only the SQL query without explanation. `; return await this.getAIResponse(prompt); } async suggestVisualization(data, questionType) { const prompt = ` Based on the following data structure and question type, suggest the best visualization: Question Type: ${questionType} Data Sample: ${JSON.stringify(data.slice(0, 3), null, 2)} Respond with: - visualization_type: (table|bar|line|pie|number|scatter|map) - settings: visualization settings object - reasoning: brief explanation `; const response = await this.getAIResponse(prompt); return JSON.parse(response); } async createModel(description, databaseId) { logger.info(`Creating model for: ${description}`); // Get database schema const tables = await this.metabaseClient.getDatabaseTables(databaseId); // Generate SQL for the model const sql = await this.generateSQL(description, tables); // Create the model const model = await this.metabaseClient.createModel({ name: this.generateName(description, 'Model'), description, database_id: databaseId, dataset_query: { database: databaseId, type: 'native', native: { query: sql } } }); logger.info(`Model created: ${model.id}`); return model; } async createQuestion(description, databaseId, collectionId) { logger.info(`Creating question for: ${description}`); // Get database schema const tables = await this.metabaseClient.getDatabaseTables(databaseId); // Generate SQL const sql = await this.generateSQL(description, tables); // Execute query to get sample data const result = await this.metabaseClient.executeNativeQuery(databaseId, sql + ' LIMIT 10'); // Suggest visualization const vizSuggestion = await this.suggestVisualization(result.data.rows, description); // Create question const question = await this.metabaseClient.createSQLQuestion( this.generateName(description, 'Question'), description, databaseId, sql, collectionId ); // Update with visualization settings if (vizSuggestion.visualization_type !== 'table') { await this.metabaseClient.updateQuestion(question.id, { display: vizSuggestion.visualization_type, visualization_settings: vizSuggestion.settings }); } logger.info(`Question created: ${question.id}`); return question; } async createMetric(description, tableId) { const prompt = ` Create a metric definition based on: "${description}" Provide: - name: metric name - description: detailed description - aggregation: (count|sum|avg|min|max|distinct) - field: field to aggregate (if applicable) - filter: filter conditions (if any) `; const metricDef = JSON.parse(await this.getAIResponse(prompt)); const metric = await this.metabaseClient.createMetric({ name: metricDef.name, description: metricDef.description, table_id: tableId, definition: { aggregation: [metricDef.aggregation, metricDef.field].filter(Boolean), filter: metricDef.filter } }); logger.info(`Metric created: ${metric.id}`); return metric; } async createDashboard(description, questions = []) { logger.info(`Creating dashboard: ${description}`); // Create dashboard const dashboard = await this.metabaseClient.createDashboard({ name: this.generateName(description, 'Dashboard'), description }); // Suggest layout for cards const layoutPrompt = ` Suggest a dashboard layout for ${questions.length} cards. Provide a grid layout (12 columns wide) with: - card positions (row, col) - card sizes (sizeX, sizeY) Return as JSON array of layout objects. `; const layout = JSON.parse(await this.getAIResponse(layoutPrompt)); // Add questions to dashboard for (let i = 0; i < questions.length; i++) { await this.metabaseClient.addCardToDashboard( dashboard.id, questions[i].id, layout[i] || { row: Math.floor(i / 3) * 4, col: (i % 3) * 4, sizeX: 4, sizeY: 4 } ); } logger.info(`Dashboard created: ${dashboard.id}`); return dashboard; } async optimizeQuery(sql) { const prompt = ` Optimize the following SQL query for better performance: ${sql} Provide: 1. Optimized query 2. List of optimizations applied 3. Expected performance improvements Return as JSON with: optimized_sql, optimizations[], improvements `; const response = await this.getAIResponse(prompt); return JSON.parse(response); } async explainQuery(sql) { const prompt = ` Explain the following SQL query in simple terms: ${sql} Provide: 1. What the query does 2. Tables and relationships used 3. Any potential issues or improvements `; return await this.getAIResponse(prompt); } // Helper methods async getAIResponse(prompt) { try { if (this.aiProvider === 'anthropic') { const response = await this.ai.messages.create({ model: 'claude-3-sonnet-20240229', max_tokens: 4000, messages: [{ role: 'user', content: prompt }] }); return response.content[0].text; } else { const response = await this.ai.chat.completions.create({ model: 'gpt-4-turbo-preview', messages: [{ role: 'user', content: prompt }], response_format: { type: 'text' } }); return response.choices[0].message.content; } } catch (error) { logger.error('AI response error:', error); throw error; } } generateName(description, type) { const words = description.split(' ').slice(0, 5).join(' '); return `${words} - ${type} (AI Generated)`; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/enessari/metabase-ai-assistant'

If you have feedback or need assistance with the MCP directory API, please join our Discord server