UNPKG

@guardaian/sdk

Version:

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

512 lines (439 loc) 17 kB
/** * Hugging Face Inference API Interceptor - Bulletproof Production Version * * CRITICAL GUARANTEES: * 1. Customer's Hugging Face 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. Supports all HF endpoints (textGeneration, chatCompletion, featureExtraction, textToImage) */ function patchHuggingFace(HfInference, guardaianClient) { // Early validation with error protection if (!HfInference || typeof HfInference !== 'function') { safeLog(guardaianClient, '⚠️ Invalid Hugging Face module - skipping patch (non-blocking)'); return; } // Prevent double-patching if (HfInference.__guardaianPatched) { safeLog(guardaianClient, '⚠️ Hugging Face already patched - skipping'); return; } try { safeLog(guardaianClient, '🔧 Patching Hugging Face Inference SDK with bulletproof protection...'); // Patch various HF endpoints patchTextGeneration(HfInference, guardaianClient); patchChatCompletion(HfInference, guardaianClient); patchFeatureExtraction(HfInference, guardaianClient); patchTextToImage(HfInference, guardaianClient); // Mark as patched HfInference.__guardaianPatched = true; safeLog(guardaianClient, '✅ Hugging Face Inference SDK patched successfully with failsafe protection'); } catch (patchError) { // Even patching errors should not break customer code safeLog(guardaianClient, `⚠️ Hugging Face patching failed (non-blocking): ${patchError.message}`); } } /** * Patch HF Text Generation endpoint with bulletproof error handling */ function patchTextGeneration(HfInference, guardaianClient) { try { const originalTextGeneration = HfInference.prototype.textGeneration; if (!originalTextGeneration) { safeLog(guardaianClient, '⚠️ HF textGeneration method not found - skipping patch'); return; } HfInference.prototype.textGeneration = async function(params, options) { const startTime = Date.now(); // CRITICAL: Always call original HF API first let response, originalError; try { response = await originalTextGeneration.call(this, params, options); } catch (error) { originalError = error; } // CRITICAL: Track usage in completely isolated context setImmediate(() => { try { trackHuggingFaceUsage(params, response, originalError, startTime, guardaianClient, 'textGeneration', options); } catch (trackingError) { safeLog(guardaianClient, `❌ HF textGeneration 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 HF textGeneration: ${patchError.message}`); } } /** * Patch HF Chat Completion endpoint with bulletproof error handling */ function patchChatCompletion(HfInference, guardaianClient) { try { const originalChatCompletion = HfInference.prototype.chatCompletion; if (!originalChatCompletion) { safeLog(guardaianClient, '⚠️ HF chatCompletion method not found - skipping patch'); return; } HfInference.prototype.chatCompletion = async function(params, options) { const startTime = Date.now(); // CRITICAL: Always call original HF API first let response, originalError; try { response = await originalChatCompletion.call(this, params, options); } catch (error) { originalError = error; } // CRITICAL: Track usage in completely isolated context setImmediate(() => { try { trackHuggingFaceUsage(params, response, originalError, startTime, guardaianClient, 'chatCompletion', options); } catch (trackingError) { safeLog(guardaianClient, `❌ HF chatCompletion 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 HF chatCompletion: ${patchError.message}`); } } /** * Patch HF Feature Extraction endpoint with bulletproof error handling */ function patchFeatureExtraction(HfInference, guardaianClient) { try { const originalFeatureExtraction = HfInference.prototype.featureExtraction; if (!originalFeatureExtraction) { safeLog(guardaianClient, '⚠️ HF featureExtraction method not found - skipping patch'); return; } HfInference.prototype.featureExtraction = async function(params, options) { const startTime = Date.now(); // CRITICAL: Always call original HF API first let response, originalError; try { response = await originalFeatureExtraction.call(this, params, options); } catch (error) { originalError = error; } // CRITICAL: Track usage in completely isolated context setImmediate(() => { try { trackHuggingFaceUsage(params, response, originalError, startTime, guardaianClient, 'featureExtraction', options); } catch (trackingError) { safeLog(guardaianClient, `❌ HF featureExtraction 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 HF featureExtraction: ${patchError.message}`); } } /** * Patch HF Text to Image endpoint with bulletproof error handling */ function patchTextToImage(HfInference, guardaianClient) { try { const originalTextToImage = HfInference.prototype.textToImage; if (!originalTextToImage) { safeLog(guardaianClient, '⚠️ HF textToImage method not found - skipping patch'); return; } HfInference.prototype.textToImage = async function(params, options) { const startTime = Date.now(); // CRITICAL: Always call original HF API first let response, originalError; try { response = await originalTextToImage.call(this, params, options); } catch (error) { originalError = error; } // CRITICAL: Track usage in completely isolated context setImmediate(() => { try { trackHuggingFaceUsage(params, response, originalError, startTime, guardaianClient, 'textToImage', options); } catch (trackingError) { safeLog(guardaianClient, `❌ HF textToImage 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 HF textToImage: ${patchError.message}`); } } /** * Track Hugging Face usage with complete error isolation */ async function trackHuggingFaceUsage(params, response, error, startTime, guardaianClient, operation, options) { try { const endTime = Date.now(); const duration = endTime - startTime; const model = params.model || options?.model || getDefaultModel(operation); if (response) { // Successful request tracking const usage = extractHuggingFaceUsage(params, response, operation); const cost = calculateHuggingFaceCost(model, usage, operation); // Fire-and-forget tracking call guardaianClient.track({ service: 'huggingface', model: model, operation: operation, inputTokens: usage.inputTokens || 0, outputTokens: usage.outputTokens || 0, totalTokens: (usage.inputTokens || 0) + (usage.outputTokens || 0), cost: cost, duration: duration, requestData: sanitizeHuggingFaceRequest(params, operation, options), responseData: sanitizeHuggingFaceResponse(response, operation), metadata: { success: true, operation: operation, responseType: Array.isArray(response) ? 'array' : typeof response } }).catch(trackingError => { safeLog(guardaianClient, `❌ HF track call failed (isolated): ${trackingError.message}`); }); safeLog(guardaianClient, `✅ Tracked HF ${operation}: ${model} (${(usage.inputTokens || 0) + (usage.outputTokens || 0)} tokens, $${cost.toFixed(6)})`); } else if (error) { // Failed request tracking guardaianClient.track({ service: 'huggingface', model: model, operation: operation, inputTokens: 0, outputTokens: 0, totalTokens: 0, cost: 0, duration: duration, requestData: sanitizeHuggingFaceRequest(params, operation, options), responseData: { error: true, status: error.status || 'unknown', message: error.message?.substring(0, 200) || 'Unknown error' }, metadata: { success: false, errorType: error.constructor.name, operation: operation } }).catch(trackingError => { safeLog(guardaianClient, `❌ HF error tracking failed (isolated): ${trackingError.message}`); }); safeLog(guardaianClient, `📊 Tracked HF ${operation} error: ${model} - ${error.message}`); } } catch (trackingError) { // Ultimate safety net safeLog(guardaianClient, `❌ HF usage tracking completely failed (isolated): ${trackingError.message}`); } } /** * Get default model for operation with error protection */ function getDefaultModel(operation) { try { const defaults = { 'textGeneration': 'gpt2', 'chatCompletion': 'microsoft/DialoGPT-medium', 'featureExtraction': 'sentence-transformers/all-MiniLM-L6-v2', 'textToImage': 'runwayml/stable-diffusion-v1-5' }; return defaults[operation] || 'unknown-model'; } catch (defaultError) { return 'unknown-model'; } } /** * Extract usage data from Hugging Face response with error protection */ function extractHuggingFaceUsage(params, response, operation) { try { switch (operation) { case 'textGeneration': const inputText = params.inputs || ''; const outputText = response.generated_text || ''; const cleanOutput = outputText.replace(inputText, ''); // Remove input from output return { inputTokens: estimateTokens(inputText), outputTokens: estimateTokens(cleanOutput) }; case 'chatCompletion': const usage = response.usage || {}; return { inputTokens: usage.prompt_tokens || estimateTokensFromMessages(params.messages || []), outputTokens: usage.completion_tokens || estimateTokens(response.choices?.[0]?.message?.content || '') }; case 'featureExtraction': // Embeddings only have input tokens const inputTextEmbed = params.inputs || ''; return { inputTokens: estimateTokens(inputTextEmbed), outputTokens: 0 }; case 'textToImage': // Image generation based on prompt const inputTextImage = params.inputs || ''; return { inputTokens: estimateTokens(inputTextImage), outputTokens: 0 }; default: return { inputTokens: 0, outputTokens: 0 }; } } catch (extractError) { return { inputTokens: 0, outputTokens: 0 }; } } /** * Estimate tokens from text with error protection */ function estimateTokens(text) { try { if (!text || typeof text !== 'string') return 0; return Math.ceil(text.length / 4); // Rough estimate: 1 token ≈ 4 characters } catch (estimateError) { return 0; } } /** * Estimate tokens from messages array with error protection */ function estimateTokensFromMessages(messages) { try { return messages.reduce((total, message) => { const content = typeof message.content === 'string' ? message.content : JSON.stringify(message.content); return total + estimateTokens(content) + 4; // +4 for role and formatting tokens }, 0); } catch (estimateError) { return 0; } } /** * Calculate cost for Hugging Face API calls with error protection */ function calculateHuggingFaceCost(model, usage, operation) { try { // Hugging Face Inference API pricing varies by model and compute time // Many models are free but rate-limited, enterprise pricing varies const baseRates = { 'textGeneration': { input: 0.0001, output: 0.0002 }, 'chatCompletion': { input: 0.0001, output: 0.0002 }, 'featureExtraction': { input: 0.0001, output: 0 }, 'textToImage': { input: 0.001, output: 0 } // Higher cost for image generation }; // Model-specific pricing adjustments const modelMultipliers = { 'gpt2': 0.5, 'microsoft/DialoGPT-medium': 0.8, 'sentence-transformers/all-MiniLM-L6-v2': 0.3, 'runwayml/stable-diffusion-v1-5': 2.0, 'meta-llama/Llama-2-7b-chat-hf': 1.2, 'mistralai/Mistral-7B-Instruct-v0.1': 1.0, 'HuggingFaceH4/zephyr-7b-beta': 1.0 }; const rates = baseRates[operation] || baseRates['textGeneration']; const multiplier = modelMultipliers[model] || 1.0; const inputCost = (usage.inputTokens || 0) * rates.input * multiplier / 1000; const outputCost = (usage.outputTokens || 0) * rates.output * multiplier / 1000; return Math.round((inputCost + outputCost) * 10000) / 10000; } catch (costError) { safeLog(null, `❌ HF cost calculation error: ${costError.message}`); return 0; } } /** * Sanitize Hugging Face request data with privacy protection */ function sanitizeHuggingFaceRequest(params, operation, options) { try { const sanitized = { model: params.model || options?.model || getDefaultModel(operation), operation: operation }; switch (operation) { case 'textGeneration': sanitized.inputs = params.inputs ? params.inputs.substring(0, 200) + (params.inputs.length > 200 ? '...' : '') : undefined; sanitized.parameters = params.parameters; break; case 'chatCompletion': sanitized.messages = params.messages ? params.messages.map(msg => ({ role: msg.role, content: typeof msg.content === 'string' ? msg.content.substring(0, 100) + (msg.content.length > 100 ? '...' : '') : '[complex_content]' })) : []; sanitized.temperature = params.temperature; sanitized.max_tokens = params.max_tokens; sanitized.top_p = params.top_p; break; case 'featureExtraction': sanitized.inputs = params.inputs ? params.inputs.substring(0, 200) + (params.inputs.length > 200 ? '...' : '') : undefined; break; case 'textToImage': sanitized.inputs = params.inputs ? params.inputs.substring(0, 200) + (params.inputs.length > 200 ? '...' : '') : undefined; sanitized.parameters = params.parameters; break; } sanitized.options = options; return sanitized; } catch (sanitizeError) { return { error: 'sanitization_failed' }; } } /** * Sanitize Hugging Face response data with privacy protection */ function sanitizeHuggingFaceResponse(response, operation) { try { const sanitized = { operation: operation, response_type: Array.isArray(response) ? 'array' : typeof response }; switch (operation) { case 'textGeneration': sanitized.generated_text_length = response.generated_text?.length || 0; break; case 'chatCompletion': sanitized.finish_reason = response.choices?.[0]?.finish_reason; sanitized.choices_count = response.choices?.length || 0; sanitized.response_id = response.id; break; case 'featureExtraction': sanitized.embeddings_count = Array.isArray(response) ? response.length : 1; sanitized.dimensions = Array.isArray(response) && response[0] ? response[0].length : 0; break; case 'textToImage': sanitized.image_generated = !!response; break; } 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 = { patchHuggingFace };