UNPKG

@guardaian/sdk

Version:

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

407 lines (346 loc) 13.1 kB
/** * OpenAI API Interceptor - Bulletproof Production Version * * CRITICAL GUARANTEES: * 1. Customer's OpenAI calls ALWAYS work, even if GuardAIan is completely down * 2. Zero performance impact on customer's AI operations * 3. All tracking errors are isolated and logged separately * 4. Transparent operation - customer never knows we're there */ function patchOpenAI(OpenAI, guardaianClient) { // Early validation with error protection if (!OpenAI || typeof OpenAI !== 'function') { safeLog(guardaianClient, '⚠️ Invalid OpenAI module - skipping patch (non-blocking)'); return; } // Prevent double-patching if (OpenAI.__guardaianPatched) { safeLog(guardaianClient, '⚠️ OpenAI already patched - skipping'); return; } try { safeLog(guardaianClient, '🔧 Patching OpenAI SDK with bulletproof protection...'); // Patch chat completions with complete error isolation patchChatCompletions(OpenAI, guardaianClient); // Patch embeddings patchEmbeddings(OpenAI, guardaianClient); // Patch images (DALL-E) patchImages(OpenAI, guardaianClient); // Mark as patched OpenAI.__guardaianPatched = true; safeLog(guardaianClient, '✅ OpenAI SDK patched successfully with failsafe protection'); } catch (patchError) { // Even patching errors should not break customer code safeLog(guardaianClient, `⚠️ OpenAI patching failed (non-blocking): ${patchError.message}`); } } /** * Patch OpenAI Chat Completions with bulletproof error handling */ function patchChatCompletions(OpenAI, guardaianClient) { try { const originalCreate = OpenAI.prototype.chat?.completions?.create; if (!originalCreate) { safeLog(guardaianClient, '⚠️ OpenAI chat.completions.create not found - skipping 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 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 // This happens asynchronously and never affects the customer's response setImmediate(() => { try { trackOpenAIUsage(params, response, originalError, startTime, guardaianClient, 'chat.completions.create'); } catch (trackingError) { // Tracking errors are completely isolated from customer code safeLog(guardaianClient, `❌ Chat completion tracking error (isolated): ${trackingError.message}`); } }); // CRITICAL: Always return original result or throw original error // Customer gets exactly what they would get without GuardAIan if (originalError) { throw originalError; } return response; }; } catch (patchError) { safeLog(guardaianClient, `❌ Failed to patch chat completions: ${patchError.message}`); } } /** * Patch OpenAI Embeddings */ function patchEmbeddings(OpenAI, guardaianClient) { try { const originalCreate = OpenAI.prototype.embeddings?.create; if (!originalCreate) { safeLog(guardaianClient, '⚠️ OpenAI embeddings.create not found - skipping patch'); return; } OpenAI.prototype.embeddings.create = async function(params, options) { const startTime = Date.now(); // CRITICAL: Customer's API call always happens first let response, originalError; try { response = await originalCreate.call(this, params, options); } catch (error) { originalError = error; } // Isolated tracking setImmediate(() => { try { trackOpenAIUsage(params, response, originalError, startTime, guardaianClient, 'embeddings.create'); } catch (trackingError) { safeLog(guardaianClient, `❌ Embeddings tracking error (isolated): ${trackingError.message}`); } }); // Return original result if (originalError) throw originalError; return response; }; } catch (patchError) { safeLog(guardaianClient, `❌ Failed to patch embeddings: ${patchError.message}`); } } /** * Patch OpenAI Images (DALL-E) */ function patchImages(OpenAI, guardaianClient) { try { const originalGenerate = OpenAI.prototype.images?.generate; if (!originalGenerate) { safeLog(guardaianClient, '⚠️ OpenAI images.generate not found - skipping patch'); return; } OpenAI.prototype.images.generate = async function(params, options) { const startTime = Date.now(); // CRITICAL: Customer's API call always happens first let response, originalError; try { response = await originalGenerate.call(this, params, options); } catch (error) { originalError = error; } // Isolated tracking setImmediate(() => { try { trackOpenAIUsage(params, response, originalError, startTime, guardaianClient, 'images.generate'); } catch (trackingError) { safeLog(guardaianClient, `❌ Images tracking error (isolated): ${trackingError.message}`); } }); // Return original result if (originalError) throw originalError; return response; }; } catch (patchError) { safeLog(guardaianClient, `❌ Failed to patch images: ${patchError.message}`); } } /** * Track OpenAI usage with complete error isolation */ async function trackOpenAIUsage(params, response, error, startTime, guardaianClient, operation) { try { const endTime = Date.now(); const duration = endTime - startTime; if (response) { // Successful request tracking const usage = response.usage || {}; const cost = calculateOpenAICost(params.model, usage, operation); // Fire-and-forget tracking call guardaianClient.track({ service: 'openai', model: params.model || 'unknown', operation: operation, inputTokens: usage.prompt_tokens || 0, outputTokens: usage.completion_tokens || 0, totalTokens: usage.total_tokens || 0, cost: cost, duration: duration, requestData: sanitizeRequestData(params), responseData: sanitizeResponseData(response), metadata: { success: true, hasUsage: !!response.usage } }).catch(trackingError => { // Even the track() call errors are isolated safeLog(guardaianClient, `❌ Track call failed (isolated): ${trackingError.message}`); }); safeLog(guardaianClient, `✅ Tracked OpenAI ${operation}: ${params.model} (${usage.total_tokens || 0} tokens, $${cost.toFixed(6)})`); } else if (error) { // Failed request tracking (for monitoring) guardaianClient.track({ service: 'openai', model: params.model || 'unknown', operation: operation, inputTokens: 0, outputTokens: 0, totalTokens: 0, cost: 0, duration: duration, requestData: sanitizeRequestData(params), responseData: { error: true, status: error.status || 'unknown', type: error.type || 'api_error', message: error.message?.substring(0, 200) || 'Unknown error' }, metadata: { success: false, errorType: error.constructor.name } }).catch(trackingError => { // Tracking errors for failed requests are also isolated safeLog(guardaianClient, `❌ Error tracking failed (isolated): ${trackingError.message}`); }); safeLog(guardaianClient, `📊 Tracked OpenAI error: ${params.model} - ${error.message}`); } } catch (trackingError) { // Ultimate safety net - even this function can fail safely safeLog(guardaianClient, `❌ Usage tracking completely failed (isolated): ${trackingError.message}`); } } /** * Calculate OpenAI costs with error protection */ function calculateOpenAICost(model, usage, operation) { try { // Latest OpenAI pricing (as of 2024) const pricing = { 'gpt-4': { input: 0.03 / 1000, output: 0.06 / 1000 }, 'gpt-4-turbo': { input: 0.01 / 1000, output: 0.03 / 1000 }, 'gpt-4-turbo-preview': { input: 0.01 / 1000, output: 0.03 / 1000 }, 'gpt-4o': { input: 0.005 / 1000, output: 0.015 / 1000 }, 'gpt-4o-mini': { input: 0.00015 / 1000, output: 0.0006 / 1000 }, 'gpt-3.5-turbo': { input: 0.0015 / 1000, output: 0.002 / 1000 }, 'gpt-3.5-turbo-instruct': { input: 0.0015 / 1000, output: 0.002 / 1000 }, // Embeddings (input only) 'text-embedding-ada-002': { input: 0.0001 / 1000, output: 0 }, 'text-embedding-3-small': { input: 0.00002 / 1000, output: 0 }, 'text-embedding-3-large': { input: 0.00013 / 1000, output: 0 }, // Images (per image) 'dall-e-3': { input: 0.04, output: 0 }, // $0.04 per image (1024x1024) 'dall-e-2': { input: 0.02, output: 0 } // $0.02 per image (1024x1024) }; // Handle image operations if (operation === 'images.generate') { const basePrice = pricing[model]?.input || pricing['dall-e-2'].input; const imageCount = usage.n || 1; return basePrice * imageCount; } // Handle text operations const modelPricing = pricing[model] || pricing['gpt-3.5-turbo']; // Default fallback const inputCost = (usage.prompt_tokens || 0) * modelPricing.input; const outputCost = (usage.completion_tokens || 0) * modelPricing.output; return Math.round((inputCost + outputCost) * 10000) / 10000; // Round to 4 decimal places } catch (costError) { safeLog(null, `❌ Cost calculation error: ${costError.message}`); return 0; } } /** * Sanitize request data with privacy protection */ function sanitizeRequestData(params) { try { if (!params) return {}; const sanitized = { model: params.model, temperature: params.temperature, max_tokens: params.max_tokens, top_p: params.top_p, frequency_penalty: params.frequency_penalty, presence_penalty: params.presence_penalty, n: params.n, stream: params.stream, stop: Array.isArray(params.stop) ? params.stop.length : params.stop ? 1 : 0 }; // Handle messages (chat completions) 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]' })); } // Handle prompt (legacy completions) if (params.prompt) { sanitized.prompt = typeof params.prompt === 'string' ? params.prompt.substring(0, 200) + (params.prompt.length > 200 ? '...' : '') : '[complex_prompt]'; } // Handle input (embeddings) if (params.input) { if (typeof params.input === 'string') { sanitized.input = params.input.substring(0, 200) + (params.input.length > 200 ? '...' : ''); } else if (Array.isArray(params.input)) { sanitized.input = `[${params.input.length} items]`; } } return sanitized; } catch (sanitizeError) { return { error: 'sanitization_failed' }; } } /** * Sanitize response data with privacy protection */ function sanitizeResponseData(response) { try { if (!response) return {}; const sanitized = { id: response.id, object: response.object, created: response.created, model: response.model, usage: response.usage }; // Handle choices (chat/completions) if (response.choices) { sanitized.choices = response.choices.length; if (response.choices[0]) { sanitized.finish_reason = response.choices[0].finish_reason; // Don't include actual content for privacy sanitized.has_content = !!(response.choices[0].message?.content || response.choices[0].text); } } // Handle embeddings if (response.data && Array.isArray(response.data)) { sanitized.embeddings_count = response.data.length; if (response.data[0]?.embedding) { sanitized.embedding_dimensions = response.data[0].embedding.length; } } 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 to prevent infinite loops } } module.exports = { patchOpenAI };