UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

552 lines (551 loc) • 21.8 kB
/** * Model Configuration System * * Replaces hardcoded model-specific logic with configurable, runtime-updateable configurations. * This addresses GitHub Copilot review comment about making model-specific logic configuration-based. */ import { logger } from "../utils/logger.js"; /** * Model name constants - extracted from hardcoded values for better maintainability * These constants can be overridden by environment variables */ export const MODEL_NAMES = { // Google AI Models GOOGLE_AI: { FAST: "gemini-2.5-flash", BALANCED: "gemini-2.5-pro", QUALITY: "gemini-2.5-pro", }, // Google Vertex Models GOOGLE_VERTEX: { FAST: "gemini-2.5-flash", BALANCED: "gemini-2.5-pro", QUALITY: "gemini-2.5-pro", }, // OpenAI Models OPENAI: { FAST: "gpt-4o-mini", BALANCED: "gpt-4o", QUALITY: "gpt-4o", }, // Anthropic Models ANTHROPIC: { FAST: "claude-3-haiku-20240307", BALANCED: "claude-3-sonnet-20240229", QUALITY: "claude-3-5-sonnet-20241022", }, // Vertex AI Models (legacy alias) VERTEX: { FAST: "gemini-2.5-flash", BALANCED: "gemini-2.5-pro", QUALITY: "gemini-2.5-pro", }, // AWS Bedrock Models BEDROCK: { FAST: "anthropic.claude-3-haiku-20240307-v1:0", BALANCED: "anthropic.claude-3-sonnet-20240229-v1:0", QUALITY: "anthropic.claude-3-opus-20240229-v1:0", }, // Azure OpenAI Models AZURE: { FAST: "gpt-4o-mini", BALANCED: "gpt-4o", QUALITY: "gpt-4o", }, // Ollama Models OLLAMA: { FAST: "llama3.2:latest", BALANCED: "llama3.1:8b", QUALITY: "llama3.1:70b", }, // HuggingFace Models — router-served ids. The previous three // (DialoGPT-medium / -large, Llama-2-7b-chat-hf) are absent from the // router catalogue and answer 400 on every request. HUGGINGFACE: { FAST: "meta-llama/Llama-3.1-8B-Instruct", BALANCED: "Qwen/Qwen2.5-72B-Instruct", QUALITY: "deepseek-ai/DeepSeek-V3", }, // Mistral Models MISTRAL: { FAST: "mistral-small-latest", BALANCED: "mistral-medium-latest", QUALITY: "mistral-large-latest", }, }; /** * Model configuration manager */ export class ModelConfigurationManager { static instance; configurations = new Map(); configSource = "default"; lastUpdated = Date.now(); constructor() { this.loadDefaultConfigurations(); } static getInstance() { if (!ModelConfigurationManager.instance) { ModelConfigurationManager.instance = new ModelConfigurationManager(); } return ModelConfigurationManager.instance; } /** * Create Google AI provider configuration */ createGoogleAIConfig() { return { provider: "google-ai", models: { fast: this.getConfigValue("GOOGLE_AI_FAST_MODEL", MODEL_NAMES.GOOGLE_AI.FAST), balanced: this.getConfigValue("GOOGLE_AI_BALANCED_MODEL", MODEL_NAMES.GOOGLE_AI.BALANCED), quality: this.getConfigValue("GOOGLE_AI_QUALITY_MODEL", MODEL_NAMES.GOOGLE_AI.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.GOOGLE_AI_DEFAULT_INPUT_COST, 0.000075), output: this.parseFloat(process.env.GOOGLE_AI_DEFAULT_OUTPUT_COST, 0.0003), }, requiredEnvVars: ["GOOGLE_AI_API_KEY"], performance: { speed: this.parseInt(process.env.GOOGLE_AI_SPEED_RATING, 3), quality: this.parseInt(process.env.GOOGLE_AI_QUALITY_RATING, 3), cost: this.parseInt(process.env.GOOGLE_AI_COST_RATING, 3), }, modelBehavior: { maxTokensIssues: this.getConfigArray("GOOGLE_AI_MAX_TOKENS_ISSUES", [ MODEL_NAMES.GOOGLE_AI.FAST, MODEL_NAMES.GOOGLE_AI.BALANCED, ]), }, }; } /** * Create Google Vertex AI provider configuration */ createVertexConfig() { return { provider: "google-vertex", models: { fast: this.getConfigValue("GOOGLE_VERTEX_FAST_MODEL", MODEL_NAMES.GOOGLE_VERTEX.FAST), balanced: this.getConfigValue("GOOGLE_VERTEX_BALANCED_MODEL", MODEL_NAMES.GOOGLE_VERTEX.BALANCED), quality: this.getConfigValue("GOOGLE_VERTEX_QUALITY_MODEL", MODEL_NAMES.GOOGLE_VERTEX.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.GOOGLE_VERTEX_DEFAULT_INPUT_COST, 0.000075), output: this.parseFloat(process.env.GOOGLE_VERTEX_DEFAULT_OUTPUT_COST, 0.0003), }, requiredEnvVars: ["GOOGLE_VERTEX_PROJECT_ID", "GOOGLE_VERTEX_LOCATION"], performance: { speed: this.parseInt(process.env.GOOGLE_VERTEX_SPEED_RATING, 3), quality: this.parseInt(process.env.GOOGLE_VERTEX_QUALITY_RATING, 3), cost: this.parseInt(process.env.GOOGLE_VERTEX_COST_RATING, 3), }, modelBehavior: { maxTokensIssues: this.getConfigArray("GOOGLE_VERTEX_MAX_TOKENS_ISSUES", [MODEL_NAMES.GOOGLE_VERTEX.FAST, MODEL_NAMES.GOOGLE_VERTEX.BALANCED]), }, }; } /** * Create OpenAI provider configuration */ createOpenAIConfig() { return { provider: "openai", models: { fast: this.getConfigValue("OPENAI_FAST_MODEL", MODEL_NAMES.OPENAI.FAST), balanced: this.getConfigValue("OPENAI_BALANCED_MODEL", MODEL_NAMES.OPENAI.BALANCED), quality: this.getConfigValue("OPENAI_QUALITY_MODEL", MODEL_NAMES.OPENAI.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.OPENAI_DEFAULT_INPUT_COST, 0.00015), output: this.parseFloat(process.env.OPENAI_DEFAULT_OUTPUT_COST, 0.0006), }, requiredEnvVars: ["OPENAI_API_KEY"], performance: { speed: this.parseInt(process.env.OPENAI_SPEED_RATING, 2), quality: this.parseInt(process.env.OPENAI_QUALITY_RATING, 3), cost: this.parseInt(process.env.OPENAI_COST_RATING, 2), }, modelBehavior: { maxTokensIssues: this.getConfigArray("OPENAI_MAX_TOKENS_ISSUES", []), specialHandling: this.getConfigObject("OPENAI_SPECIAL_HANDLING", {}), }, }; } /** * Create all provider configurations - centralized approach */ createAllProviderConfigurations() { return { "google-ai": this.createGoogleAIConfig(), "google-vertex": this.createVertexConfig(), openai: this.createOpenAIConfig(), anthropic: this.createAnthropicConfig(), vertex: this.createVertexAlternativeConfig(), bedrock: this.createBedrockConfig(), azure: this.createAzureConfig(), ollama: this.createOllamaConfig(), huggingface: this.createHuggingFaceConfig(), mistral: this.createMistralConfig(), }; } /** * Create Anthropic provider configuration */ createAnthropicConfig() { return { provider: "anthropic", models: { fast: this.getConfigValue("ANTHROPIC_FAST_MODEL", MODEL_NAMES.ANTHROPIC.FAST), balanced: this.getConfigValue("ANTHROPIC_BALANCED_MODEL", MODEL_NAMES.ANTHROPIC.BALANCED), quality: this.getConfigValue("ANTHROPIC_QUALITY_MODEL", MODEL_NAMES.ANTHROPIC.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.ANTHROPIC_DEFAULT_INPUT_COST, 0.0015), output: this.parseFloat(process.env.ANTHROPIC_DEFAULT_OUTPUT_COST, 0.0075), }, requiredEnvVars: ["ANTHROPIC_API_KEY"], performance: { speed: this.parseInt(process.env.ANTHROPIC_SPEED_RATING, 3), quality: this.parseInt(process.env.ANTHROPIC_QUALITY_RATING, 4), cost: this.parseInt(process.env.ANTHROPIC_COST_RATING, 2), }, modelBehavior: { maxTokensIssues: this.getConfigArray("ANTHROPIC_MAX_TOKENS_ISSUES", []), }, }; } /** * Create Vertex alternative provider configuration */ createVertexAlternativeConfig() { return { provider: "vertex", models: { fast: this.getConfigValue("VERTEX_FAST_MODEL", MODEL_NAMES.VERTEX.FAST), balanced: this.getConfigValue("VERTEX_BALANCED_MODEL", MODEL_NAMES.VERTEX.BALANCED), quality: this.getConfigValue("VERTEX_QUALITY_MODEL", MODEL_NAMES.VERTEX.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.VERTEX_DEFAULT_INPUT_COST, 0.000075), output: this.parseFloat(process.env.VERTEX_DEFAULT_OUTPUT_COST, 0.0003), }, requiredEnvVars: ["GOOGLE_VERTEX_PROJECT_ID", "GOOGLE_VERTEX_LOCATION"], performance: { speed: this.parseInt(process.env.VERTEX_SPEED_RATING, 3), quality: this.parseInt(process.env.VERTEX_QUALITY_RATING, 4), cost: this.parseInt(process.env.VERTEX_COST_RATING, 3), }, modelBehavior: { maxTokensIssues: this.getConfigArray("VERTEX_MAX_TOKENS_ISSUES", []), }, }; } /** * Create Bedrock provider configuration */ createBedrockConfig() { return { provider: "bedrock", models: { fast: this.getConfigValue("BEDROCK_FAST_MODEL", MODEL_NAMES.BEDROCK.FAST), balanced: this.getConfigValue("BEDROCK_BALANCED_MODEL", MODEL_NAMES.BEDROCK.BALANCED), quality: this.getConfigValue("BEDROCK_QUALITY_MODEL", MODEL_NAMES.BEDROCK.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.BEDROCK_DEFAULT_INPUT_COST, 0.0015), output: this.parseFloat(process.env.BEDROCK_DEFAULT_OUTPUT_COST, 0.0075), }, requiredEnvVars: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"], performance: { speed: this.parseInt(process.env.BEDROCK_SPEED_RATING, 3), quality: this.parseInt(process.env.BEDROCK_QUALITY_RATING, 4), cost: this.parseInt(process.env.BEDROCK_COST_RATING, 3), }, modelBehavior: { maxTokensIssues: this.getConfigArray("BEDROCK_MAX_TOKENS_ISSUES", []), }, }; } /** * Create Azure provider configuration */ createAzureConfig() { return { provider: "azure", models: { fast: this.getConfigValue("AZURE_FAST_MODEL", MODEL_NAMES.AZURE.FAST), balanced: this.getConfigValue("AZURE_BALANCED_MODEL", MODEL_NAMES.AZURE.BALANCED), quality: this.getConfigValue("AZURE_QUALITY_MODEL", MODEL_NAMES.AZURE.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.AZURE_DEFAULT_INPUT_COST, 0.00015), output: this.parseFloat(process.env.AZURE_DEFAULT_OUTPUT_COST, 0.0006), }, requiredEnvVars: ["AZURE_API_KEY", "AZURE_ENDPOINT"], performance: { speed: this.parseInt(process.env.AZURE_SPEED_RATING, 2), quality: this.parseInt(process.env.AZURE_QUALITY_RATING, 3), cost: this.parseInt(process.env.AZURE_COST_RATING, 2), }, modelBehavior: { maxTokensIssues: this.getConfigArray("AZURE_MAX_TOKENS_ISSUES", []), }, }; } /** * Create Ollama provider configuration */ createOllamaConfig() { return { provider: "ollama", models: { fast: this.getConfigValue("OLLAMA_FAST_MODEL", MODEL_NAMES.OLLAMA.FAST), balanced: this.getConfigValue("OLLAMA_BALANCED_MODEL", MODEL_NAMES.OLLAMA.BALANCED), quality: this.getConfigValue("OLLAMA_QUALITY_MODEL", MODEL_NAMES.OLLAMA.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.OLLAMA_DEFAULT_INPUT_COST, 0), output: this.parseFloat(process.env.OLLAMA_DEFAULT_OUTPUT_COST, 0), }, requiredEnvVars: [], performance: { speed: this.parseInt(process.env.OLLAMA_SPEED_RATING, 4), quality: this.parseInt(process.env.OLLAMA_QUALITY_RATING, 2), cost: this.parseInt(process.env.OLLAMA_COST_RATING, 5), }, modelBehavior: { maxTokensIssues: this.getConfigArray("OLLAMA_MAX_TOKENS_ISSUES", [ MODEL_NAMES.OLLAMA.FAST, MODEL_NAMES.OLLAMA.BALANCED, MODEL_NAMES.OLLAMA.QUALITY, ]), toolCapableModels: this.getConfigArray("OLLAMA_TOOL_CAPABLE_MODELS", [ "llama3.1", "llama3.2", "mistral", "hermes3", "qwen2.5", "codellama", "dolphin", "openchat", "solar", ]), specialHandling: this.getConfigObject("OLLAMA_SPECIAL_HANDLING", { baseUrl: this.getConfigValue("OLLAMA_BASE_URL", "http://localhost:11434"), }), }, }; } /** * Create HuggingFace provider configuration */ createHuggingFaceConfig() { return { provider: "huggingface", models: { fast: this.getConfigValue("HUGGINGFACE_FAST_MODEL", MODEL_NAMES.HUGGINGFACE.FAST), balanced: this.getConfigValue("HUGGINGFACE_BALANCED_MODEL", MODEL_NAMES.HUGGINGFACE.BALANCED), quality: this.getConfigValue("HUGGINGFACE_QUALITY_MODEL", MODEL_NAMES.HUGGINGFACE.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.HUGGINGFACE_DEFAULT_INPUT_COST, 0.0002), output: this.parseFloat(process.env.HUGGINGFACE_DEFAULT_OUTPUT_COST, 0.0008), }, requiredEnvVars: ["HUGGINGFACE_API_KEY"], performance: { speed: this.parseInt(process.env.HUGGINGFACE_SPEED_RATING, 3), quality: this.parseInt(process.env.HUGGINGFACE_QUALITY_RATING, 3), cost: this.parseInt(process.env.HUGGINGFACE_COST_RATING, 4), }, modelBehavior: { maxTokensIssues: this.getConfigArray("HUGGINGFACE_MAX_TOKENS_ISSUES", []), }, }; } /** * Create Mistral provider configuration */ createMistralConfig() { return { provider: "mistral", models: { fast: this.getConfigValue("MISTRAL_FAST_MODEL", MODEL_NAMES.MISTRAL.FAST), balanced: this.getConfigValue("MISTRAL_BALANCED_MODEL", MODEL_NAMES.MISTRAL.BALANCED), quality: this.getConfigValue("MISTRAL_QUALITY_MODEL", MODEL_NAMES.MISTRAL.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.MISTRAL_DEFAULT_INPUT_COST, 0.0001), output: this.parseFloat(process.env.MISTRAL_DEFAULT_OUTPUT_COST, 0.0003), }, requiredEnvVars: ["MISTRAL_API_KEY"], performance: { speed: this.parseInt(process.env.MISTRAL_SPEED_RATING, 3), quality: this.parseInt(process.env.MISTRAL_QUALITY_RATING, 3), cost: this.parseInt(process.env.MISTRAL_COST_RATING, 4), }, modelBehavior: { maxTokensIssues: this.getConfigArray("MISTRAL_MAX_TOKENS_ISSUES", []), }, }; } /** * Load default configurations (replaces hardcoded values) */ loadDefaultConfigurations() { // Load all provider configurations using centralized method const defaultConfigs = this.createAllProviderConfigurations(); // Load configurations for (const [provider, config] of Object.entries(defaultConfigs)) { this.configurations.set(provider, config); } logger.debug(`Loaded ${this.configurations.size} provider configurations from ${this.configSource}`); } /** * Helper method to get configuration value with fallback and validation */ getConfigValue(envVar, defaultValue) { const value = process.env[envVar]; if (value && !this.isValidConfigValue(value)) { logger.warn(`Environment variable ${envVar} has an invalid value: "${value}". Falling back to default value.`); return defaultValue; } return value || defaultValue; } /** * Validate configuration values for security and correctness */ isValidConfigValue(value) { // Basic validation rules for security-sensitive configuration values // Check for potentially dangerous characters (script injection prevention) const dangerousChars = /[<>;"'`${}]/; if (dangerousChars.test(value)) { return false; } // Check for excessively long values (DoS prevention) if (value.length > 500) { return false; } // Check for null bytes (security) if (value.includes("\0")) { return false; } // Check for control characters except newlines and tabs // Using String.fromCharCode to avoid ESLint control character error const controlChars = new RegExp(`[${String.fromCharCode(0x00)}-${String.fromCharCode(0x08)}${String.fromCharCode(0x0b)}${String.fromCharCode(0x0c)}${String.fromCharCode(0x0e)}-${String.fromCharCode(0x1f)}${String.fromCharCode(0x7f)}]`); if (controlChars.test(value)) { return false; } return true; } /** * Helper method to get configuration array with fallback * Parses comma-separated environment variable values */ getConfigArray(envVar, defaultValue) { const envValue = process.env[envVar]; if (!envValue) { return defaultValue; } return envValue .split(",") .map((item) => item.trim()) .filter(Boolean); } /** * Helper method to parse float with fallback */ parseFloat(value, defaultValue) { if (!value) { return defaultValue; } const parsed = Number.parseFloat(value); return Number.isNaN(parsed) ? defaultValue : parsed; } /** * Helper method to parse int with fallback */ parseInt(value, defaultValue) { if (!value) { return defaultValue; } const parsed = Number.parseInt(value, 10); return Number.isNaN(parsed) ? defaultValue : parsed; } /** * Helper method to get configuration object with fallback * Parses JSON environment variable values */ getConfigObject(envVar, defaultValue) { const envValue = process.env[envVar]; if (!envValue) { return defaultValue; } try { const parsed = JSON.parse(envValue); return typeof parsed === "object" && parsed !== null ? parsed : defaultValue; } catch { logger.warn(`Invalid JSON in environment variable ${envVar}, using default`); return defaultValue; } } /** * Get provider configuration */ getProviderConfiguration(provider) { return this.configurations.get(provider) || null; } /** * Get all provider configurations */ getAllConfigurations() { return new Map(this.configurations); } /** * Get model for specific tier and provider */ getModelForTier(provider, tier) { const config = this.getProviderConfiguration(provider); return config?.models[tier] || null; } /** * Get cost information for provider and model */ getCostInfo(provider, model) { const config = this.getProviderConfiguration(provider); if (!config) { return null; } // If specific model config exists, use it; otherwise use default if (model && config.modelConfigs?.[model]) { return config.modelConfigs[model].cost; } return config.defaultCost; } /** * Check if provider is available (has required environment variables) */ isProviderAvailable(provider) { const config = this.getProviderConfiguration(provider); if (!config) { return false; } if (config.requiredEnvVars.length === 0) { return true; // No requirements (e.g., Ollama) } return config.requiredEnvVars.some((envVar) => Boolean(process.env[envVar])); } /** * Get available providers */ getAvailableProviders() { return Array.from(this.configurations.values()).filter((config) => this.isProviderAvailable(config.provider)); } } /** * Global instance accessor */ export const modelConfig = ModelConfigurationManager.getInstance();