From e5e24450c94ee9591cc9d94017f6c47136bed9ad Mon Sep 17 00:00:00 2001 From: Nirmal Arya Date: Sat, 31 May 2025 13:58:55 -0400 Subject: [PATCH] fix: Robust BayerMGA provider implementation with better error handling Key improvements: - Switch from getOpenAILikeModel to createOpenAI for better control - Comprehensive error handling and validation - Better base URL normalization and configuration - Enhanced logging for debugging inference issues - Proper header configuration for API requests - Detailed error messages for troubleshooting This should resolve the inference execution issues while maintaining model listing functionality. --- app/lib/modules/llm/providers/bayer-mga.ts | 126 +++++++++++++++++---- 1 file changed, 102 insertions(+), 24 deletions(-) diff --git a/app/lib/modules/llm/providers/bayer-mga.ts b/app/lib/modules/llm/providers/bayer-mga.ts index dc5ec06f..a72dae22 100644 --- a/app/lib/modules/llm/providers/bayer-mga.ts +++ b/app/lib/modules/llm/providers/bayer-mga.ts @@ -3,6 +3,7 @@ import type { ModelInfo } from '~/lib/modules/llm/types'; import type { IProviderSetting } from '~/types/model'; import type { LanguageModelV1 } from 'ai'; import { createScopedLogger } from '~/utils/logger'; +import { createOpenAI } from '@ai-sdk/openai'; const logger = createScopedLogger('BayerMGAProvider'); @@ -18,44 +19,108 @@ export default class BayerMGAProvider extends BaseProvider { staticModels: ModelInfo[] = []; + /** + * Normalizes a base URL by removing trailing slashes + * to prevent double-slash issues in path construction + */ + private normalizeBaseUrl(url: string): string { + return url.endsWith('/') ? url.slice(0, -1) : url; + } + + /** + * Validates API configuration and returns normalized values + * or throws descriptive errors for troubleshooting + */ + private validateApiConfig(options: { + apiKeys?: Record; + providerSettings?: IProviderSetting; + serverEnv: Record; + operation: string; + }): { baseUrl: string; apiKey: string } { + const { apiKeys, providerSettings, serverEnv, operation } = options; + + const { baseUrl, apiKey } = this.getProviderBaseUrlAndKey({ + apiKeys, + providerSettings, + serverEnv, + defaultBaseUrlKey: 'BAYER_MGA_API_BASE_URL', + defaultApiTokenKey: 'BAYER_MGA_API_KEY', + }); + + if (!baseUrl) { + const error = `Missing base URL configuration for ${this.name} provider (${operation})`; + logger.error(error); + throw new Error(error); + } + + if (!apiKey) { + const error = `Missing API key configuration for ${this.name} provider (${operation})`; + logger.error(error); + throw new Error(error); + } + + return { + baseUrl: this.normalizeBaseUrl(baseUrl), + apiKey, + }; + } + async getDynamicModels( apiKeys?: Record, settings?: IProviderSetting, serverEnv: Record = {}, ): Promise { try { - const { baseUrl, apiKey } = this.getProviderBaseUrlAndKey({ - apiKeys, - providerSettings: settings, - serverEnv, - defaultBaseUrlKey: 'BAYER_MGA_API_BASE_URL', - defaultApiTokenKey: 'BAYER_MGA_API_KEY', - }); - - if (!baseUrl || !apiKey) { - logger.warn('Missing baseUrl or apiKey configuration for Bayer MGA provider'); + // Validate configuration + let baseUrl, apiKey; + try { + const config = this.validateApiConfig({ + apiKeys, + providerSettings: settings, + serverEnv, + operation: 'getDynamicModels', + }); + baseUrl = config.baseUrl; + apiKey = config.apiKey; + } catch (error) { + logger.warn(`Configuration validation failed: ${error instanceof Error ? error.message : String(error)}`); return []; } + // Construct models URL with query parameters const modelsUrl = `${baseUrl}/models?include_hidden_models=false&include_aliases=true`; logger.info(`Fetching models from ${modelsUrl}`); + // Make API request with proper headers const response = await fetch(modelsUrl, { headers: { Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', }, }); + // Handle HTTP errors if (!response.ok) { const errorText = await response.text(); - logger.error(`Failed to fetch Bayer MGA models: ${response.status} ${errorText}`); + logger.error(`Failed to fetch Bayer MGA models: ${response.status} ${response.statusText}`); + logger.error(`Error details: ${errorText}`); return []; } - const res = (await response.json()) as any; + // Parse response + const responseText = await response.text(); + let res; + try { + res = JSON.parse(responseText); + } catch (e) { + logger.error(`Invalid JSON response from Bayer MGA API: ${responseText.substring(0, 200)}...`); + return []; + } + // Validate response structure if (!res.data || !Array.isArray(res.data)) { logger.error(`Invalid response format from Bayer MGA API: missing data array`); + logger.debug(`Response: ${JSON.stringify(res).substring(0, 500)}...`); return []; } @@ -73,6 +138,7 @@ export default class BayerMGAProvider extends BaseProvider { return models; } catch (error) { logger.error(`Error fetching Bayer MGA models: ${error instanceof Error ? error.message : String(error)}`); + logger.error(error instanceof Error && error.stack ? error.stack : 'No stack trace available'); return []; } } @@ -86,27 +152,39 @@ export default class BayerMGAProvider extends BaseProvider { try { const { model, serverEnv, apiKeys, providerSettings } = options; - const { baseUrl, apiKey } = this.getProviderBaseUrlAndKey({ + // Validate configuration + const { baseUrl, apiKey } = this.validateApiConfig({ apiKeys, providerSettings: providerSettings?.[this.name], serverEnv: serverEnv as any, - defaultBaseUrlKey: 'BAYER_MGA_API_BASE_URL', - defaultApiTokenKey: 'BAYER_MGA_API_KEY', + operation: 'getModelInstance', }); - if (!baseUrl || !apiKey) { - throw new Error(`Missing configuration for ${this.name} provider`); - } + // Log the model instance creation attempt + logger.info(`Creating model instance for ${model} using Bayer MGA API at ${baseUrl}`); - // Ensure baseUrl doesn't have trailing slash since the SDK will append paths - const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; - - logger.info(`Creating model instance for ${model} using Bayer MGA API at ${normalizedBaseUrl}`); + // Create the OpenAI-compatible client with the correct base URL + // The SDK will append /chat/completions to this URL + const openai = createOpenAI({ + baseURL: baseUrl, + apiKey, + // Add custom headers if needed for this provider + headers: { + 'Content-Type': 'application/json', + }, + }); - // The OpenAI SDK will automatically append /chat/completions to the baseURL - return getOpenAILikeModel(normalizedBaseUrl, apiKey, model); + // Return the model instance + return openai(model); } catch (error) { + // Log detailed error information logger.error(`Error creating Bayer MGA model instance: ${error instanceof Error ? error.message : String(error)}`); + logger.error(error instanceof Error && error.stack ? error.stack : 'No stack trace available'); + + // Rethrow the error with a more descriptive message + if (error instanceof Error) { + throw new Error(`Bayer MGA provider error: ${error.message}`); + } throw error; } }