@guardaian/sdk
Version:
Zero-friction AI governance and monitoring SDK for Node.js applications
395 lines (343 loc) • 12.9 kB
JavaScript
/**
* 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
};