UNPKG

@guardaian/sdk

Version:

Zero-friction AI governance and monitoring SDK for Node.js applications

395 lines (343 loc) 12.9 kB
/** * Azure OpenAI API Interceptor - Bulletproof Production Version * * CRITICAL GUARANTEES: * 1. Customer's Azure OpenAI calls ALWAYS work, even if GuardAIan is down * 2. Zero performance impact on customer's AI operations * 3. All tracking errors are isolated and non-blocking * 4. Transparent Azure endpoint detection and tracking */ function patchAzureOpenAI(OpenAI, guardaianClient) { // Early validation with error protection if (!OpenAI || typeof OpenAI !== 'function') { safeLog(guardaianClient, '⚠️ Invalid OpenAI module for Azure - skipping patch (non-blocking)'); return; } // Prevent double-patching if (OpenAI.__guardaianAzurePatched) { safeLog(guardaianClient, '⚠️ Azure OpenAI already patched - skipping'); return; } try { safeLog(guardaianClient, '🔧 Patching Azure OpenAI SDK with bulletproof protection...'); // Patch constructor to detect Azure configuration patchAzureConstructor(OpenAI, guardaianClient); // Patch chat completions for Azure patchAzureChatCompletions(OpenAI, guardaianClient); // Mark as patched OpenAI.__guardaianAzurePatched = true; safeLog(guardaianClient, '✅ Azure OpenAI SDK patched successfully with failsafe protection'); } catch (patchError) { // Even patching errors should not break customer code safeLog(guardaianClient, `⚠️ Azure OpenAI patching failed (non-blocking): ${patchError.message}`); } } /** * Patch OpenAI constructor to detect Azure configuration with error protection */ function patchAzureConstructor(OpenAI, guardaianClient) { try { const OriginalOpenAI = OpenAI; // Create a wrapper that detects Azure configuration function AzureAwareOpenAI(config = {}) { let instance; try { // CRITICAL: Always call original constructor first instance = new OriginalOpenAI(config); } catch (constructorError) { // Re-throw original constructor errors - don't interfere throw constructorError; } // CRITICAL: Azure detection in isolated context setImmediate(() => { try { detectAzureConfiguration(instance, config, guardaianClient); } catch (detectionError) { // Detection errors are isolated from customer code safeLog(guardaianClient, `❌ Azure detection error (isolated): ${detectionError.message}`); } }); // CRITICAL: Always return original instance unchanged return instance; } // Copy static properties and methods safely try { Object.setPrototypeOf(AzureAwareOpenAI, OriginalOpenAI); Object.assign(AzureAwareOpenAI, OriginalOpenAI); } catch (copyError) { safeLog(guardaianClient, `❌ Azure constructor setup error: ${copyError.message}`); } } catch (wrapperError) { safeLog(guardaianClient, `❌ Failed to wrap Azure constructor: ${wrapperError.message}`); } } /** * Detect Azure configuration with error protection */ function detectAzureConfiguration(instance, config, guardaianClient) { try { // Detect Azure OpenAI endpoint if (config.baseURL && ( config.baseURL.includes('openai.azure.com') || config.baseURL.includes('.azure.com') || config.azureADTokenProvider // Azure-specific auth )) { instance._isAzureOpenAI = true; instance._azureEndpoint = config.baseURL; instance._azureDeployment = extractAzureDeployment(config.baseURL); instance._azureApiVersion = config.defaultQuery?.['api-version'] || '2024-02-01'; safeLog(guardaianClient, `🔍 Detected Azure OpenAI configuration: ${instance._azureDeployment}`); } } catch (detectionError) { safeLog(guardaianClient, `❌ Azure configuration detection failed: ${detectionError.message}`); } } /** * Patch Azure OpenAI Chat Completions with bulletproof error handling */ function patchAzureChatCompletions(OpenAI, guardaianClient) { try { const originalCreate = OpenAI.prototype.chat?.completions?.create; if (!originalCreate) { safeLog(guardaianClient, '⚠️ OpenAI chat.completions.create not found - skipping Azure patch'); return; } OpenAI.prototype.chat.completions.create = async function(params, options) { const startTime = Date.now(); // CRITICAL: Always call original OpenAI API first // Customer's AI call must NEVER depend on GuardAIan let response, originalError; try { // Execute customer's actual Azure OpenAI API call response = await originalCreate.call(this, params, options); } catch (error) { // Store the original error to re-throw later originalError = error; } // CRITICAL: Track usage in completely isolated context setImmediate(() => { try { trackAzureUsage(this, params, response, originalError, startTime, guardaianClient); } catch (trackingError) { // Tracking errors are completely isolated from customer code safeLog(guardaianClient, `❌ Azure tracking error (isolated): ${trackingError.message}`); } }); // CRITICAL: Always return original result or throw original error if (originalError) { throw originalError; } return response; }; } catch (patchError) { safeLog(guardaianClient, `❌ Failed to patch Azure chat completions: ${patchError.message}`); } } /** * Track Azure usage with complete error isolation */ async function trackAzureUsage(instance, params, response, error, startTime, guardaianClient) { try { const endTime = Date.now(); const duration = endTime - startTime; // Detect Azure vs regular OpenAI const isAzure = instance._isAzureOpenAI; const deployment = instance._azureDeployment; const service = isAzure ? 'azure-openai' : 'openai'; if (response) { // Successful request tracking const usage = response.usage || {}; const model = isAzure ? deployment : params.model; const cost = calculateAzureCost(model, usage, isAzure); // Fire-and-forget tracking call guardaianClient.track({ service: service, model: model || 'unknown', operation: 'chat.completions.create', inputTokens: usage.prompt_tokens || 0, outputTokens: usage.completion_tokens || 0, totalTokens: usage.total_tokens || 0, cost: cost, duration: duration, requestData: sanitizeAzureRequest(params, instance, isAzure), responseData: sanitizeAzureResponse(response, instance, isAzure), metadata: { success: true, isAzure: isAzure, deployment: isAzure ? deployment : undefined, apiVersion: isAzure ? instance._azureApiVersion : undefined } }).catch(trackingError => { // Even the track() call errors are isolated safeLog(guardaianClient, `❌ Azure track call failed (isolated): ${trackingError.message}`); }); safeLog(guardaianClient, `✅ Tracked ${service} call: ${model} (${usage.total_tokens || 0} tokens, $${cost.toFixed(6)})`); } else if (error) { // Failed request tracking guardaianClient.track({ service: service, model: isAzure ? deployment : params.model, operation: 'chat.completions.create', inputTokens: 0, outputTokens: 0, totalTokens: 0, cost: 0, duration: duration, requestData: sanitizeAzureRequest(params, instance, isAzure), responseData: { error: true, status: error.status || 'unknown', type: error.type || 'api_error', message: error.message?.substring(0, 200) || 'Unknown error', isAzure: isAzure }, metadata: { success: false, errorType: error.constructor.name, isAzure: isAzure } }).catch(trackingError => { safeLog(guardaianClient, `❌ Azure error tracking failed (isolated): ${trackingError.message}`); }); safeLog(guardaianClient, `📊 Tracked ${service} error: ${isAzure ? deployment : params.model} - ${error.message}`); } } catch (trackingError) { // Ultimate safety net safeLog(guardaianClient, `❌ Azure usage tracking completely failed (isolated): ${trackingError.message}`); } } /** * Extract Azure deployment name from endpoint URL with error protection */ function extractAzureDeployment(baseURL) { try { if (!baseURL) return 'unknown-deployment'; // Azure OpenAI URL format: https://{resource}.openai.azure.com/openai/deployments/{deployment}/... const match = baseURL.match(/\/deployments\/([^\/]+)/); if (match) { return match[1]; } // Fallback: extract from subdomain const subdomain = baseURL.split('.')[0].split('//')[1]; return subdomain || 'unknown-deployment'; } catch (extractError) { return 'unknown-deployment'; } } /** * Calculate cost for Azure OpenAI calls with error protection */ function calculateAzureCost(model, usage, isAzure) { try { // Azure pricing (may vary by region, using standard rates) const azurePricing = { 'gpt-4': { input: 0.03, output: 0.06 }, 'gpt-4-32k': { input: 0.06, output: 0.12 }, 'gpt-4-turbo': { input: 0.01, output: 0.03 }, 'gpt-4o': { input: 0.005, output: 0.015 }, 'gpt-35-turbo': { input: 0.0015, output: 0.002 }, 'gpt-35-turbo-16k': { input: 0.003, output: 0.004 }, 'text-embedding-ada-002': { input: 0.0001, output: 0 }, 'text-embedding-3-small': { input: 0.00002, output: 0 }, 'text-embedding-3-large': { input: 0.00013, output: 0 } }; // Map common deployment names to models const deploymentToModel = { 'gpt-4': 'gpt-4', 'gpt-4-32k': 'gpt-4-32k', 'gpt-4-turbo': 'gpt-4-turbo', 'gpt-4o': 'gpt-4o', 'gpt-35-turbo': 'gpt-35-turbo', 'gpt-35-turbo-16k': 'gpt-35-turbo-16k', 'text-embedding-ada-002': 'text-embedding-ada-002' }; // Resolve model name let resolvedModel = model; if (isAzure && deploymentToModel[model]) { resolvedModel = deploymentToModel[model]; } // Get pricing const modelPricing = azurePricing[resolvedModel] || azurePricing['gpt-4']; const inputCost = (usage.prompt_tokens || 0) * modelPricing.input / 1000; const outputCost = (usage.completion_tokens || 0) * modelPricing.output / 1000; return Math.round((inputCost + outputCost) * 10000) / 10000; } catch (costError) { safeLog(null, `❌ Azure cost calculation error: ${costError.message}`); return 0; } } /** * Sanitize Azure request data with privacy protection */ function sanitizeAzureRequest(params, instance, isAzure) { try { const sanitized = { model: params.model, temperature: params.temperature, max_tokens: params.max_tokens, top_p: params.top_p, deployment: isAzure ? instance._azureDeployment : undefined, endpoint: isAzure ? instance._azureEndpoint : undefined, api_version: isAzure ? instance._azureApiVersion : undefined }; // Handle messages safely if (params.messages && Array.isArray(params.messages)) { sanitized.messages = params.messages.slice(0, 10).map(msg => ({ role: msg.role, content: typeof msg.content === 'string' ? msg.content.substring(0, 200) + (msg.content.length > 200 ? '...' : '') : '[complex_content]' })); } return sanitized; } catch (sanitizeError) { return { error: 'sanitization_failed' }; } } /** * Sanitize Azure response data with privacy protection */ function sanitizeAzureResponse(response, instance, isAzure) { try { const sanitized = { id: response.id, object: response.object, created: response.created, model: response.model, usage: response.usage }; // Add Azure-specific metadata if (isAzure) { sanitized.azure_metadata = { deployment: instance._azureDeployment, api_version: instance._azureApiVersion }; } // Handle choices without exposing content if (response.choices) { sanitized.choices = response.choices.length; if (response.choices[0]) { sanitized.finish_reason = response.choices[0].finish_reason; sanitized.has_content = !!(response.choices[0].message?.content); } } return sanitized; } catch (sanitizeError) { return { error: 'sanitization_failed' }; } } /** * Safe logging that never crashes */ function safeLog(guardaianClient, message) { try { if (guardaianClient?.options?.debug) { console.log(`🛡️ GuardAIan: ${message}`); } } catch (logError) { // Even logging can fail - do nothing } } module.exports = { patchAzureOpenAI };