UNPKG

paitient-secure-model

Version:

PaiTIENT - HIPAA/SOC2 compliant secure model hosting SDK

350 lines (307 loc) 11.6 kB
'use strict'; const fetch = require('node-fetch'); const AWS = require('aws-sdk'); /** * SecureModelClient for PaiTIENT's HIPAA/SOC2 compliant model service * Provides a secure interface to deploy, manage, and interact with medical AI models * in compliance with HIPAA and SOC2 standards. */ class SecureModelClient { /** * Create a new SecureModelClient * @param {Object} options - Client options * @param {string} options.apiKey - API key for authentication * @param {string} options.clientId - Client ID for tracking and monitoring * @param {string} [options.endpoint] - API endpoint (defaults to production) * @param {string} [options.region] - AWS region (defaults to us-west-2) * @param {Object} [options.awsConfig] - AWS configuration overrides */ constructor(options = {}) { if (!options.apiKey) { throw new Error('API key is required for authentication'); } if (!options.clientId) { throw new Error('Client ID is required for tracking deployments'); } this.apiKey = options.apiKey; this.clientId = options.clientId; this.endpoint = options.endpoint || 'https://api.paitient.ai/v1'; this.region = options.region || 'us-west-2'; // Configure AWS if credentials are provided if (options.awsConfig) { AWS.config.update({ region: this.region, ...options.awsConfig }); } else { AWS.config.update({ region: this.region }); } // Initialize AWS services this.lambda = new AWS.Lambda(); this.cloudformation = new AWS.CloudFormation(); this.ecs = new AWS.ECS(); // Set request timeout this.timeout = options.timeout || 30000; // 30 seconds default // Configure retry settings this.maxRetries = options.maxRetries || 3; this.retryDelay = options.retryDelay || 1000; // 1 second console.log(`SecureModelClient initialized with endpoint: ${this.endpoint}`); } /** * Make an API request with automatic retries * @private * @param {string} method - HTTP method (GET, POST, etc.) * @param {string} path - API path * @param {Object} [data] - Request data for POST/PUT * @returns {Promise<Object>} - API response */ async _request(method, path, data = null) { let lastError; for (let attempt = 0; attempt < this.maxRetries; attempt++) { try { const url = `${this.endpoint}${path.startsWith('/') ? path : '/' + path}`; const options = { method, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}`, 'X-Client-ID': this.clientId, 'User-Agent': 'SecureModelClient/1.0.0' }, timeout: this.timeout }; if (data && (method === 'POST' || method === 'PUT')) { options.body = JSON.stringify(data); } console.log(`[SecureModelClient] ${method} ${url} - Attempt ${attempt + 1}/${this.maxRetries}`); const response = await fetch(url, options); // Parse response const contentType = response.headers.get('content-type'); const isJson = contentType && contentType.includes('application/json'); const body = isJson ? await response.json() : await response.text(); // Handle error responses if (!response.ok) { const errorMessage = isJson && body.message ? body.message : `API request failed with status ${response.status}`; throw new Error(errorMessage); } return body; } catch (error) { lastError = error; console.error(`[SecureModelClient] Request failed (attempt ${attempt + 1}/${this.maxRetries}):`, error.message); // Wait before retrying (except on last attempt) if (attempt < this.maxRetries - 1) { const delay = this.retryDelay * Math.pow(2, attempt); // Exponential backoff await new Promise(resolve => setTimeout(resolve, delay)); } } } throw lastError || new Error('Request failed after maximum retries'); } /** * Deploy a secure model * @param {Object} options - Deployment options * @param {string} [options.modelName] - Model identifier to deploy (default: secure-llm-default) * @param {string} [options.tier] - Deployment tier (standard, professional, enterprise) * @param {string} [options.region] - AWS region for deployment * @param {Object} [options.metadata] - Additional metadata for the deployment * @returns {Promise<Object>} Deployment result with deploymentId and status */ async deploy(options = {}) { console.log('[SecureModelClient] Deploying model with options:', options); try { // Prepare deployment request const deploymentData = { clientId: this.clientId, modelName: options.modelName || 'secure-llm-default', tier: options.tier || 'standard', region: options.region || this.region, metadata: options.metadata || {}, timestamp: Date.now() }; // Call the deployment API const result = await this._request('POST', '/model-deployment/deploy', deploymentData); console.log('[SecureModelClient] Deployment initiated:', result); return result; } catch (error) { console.error('[SecureModelClient] Deployment failed:', error); // Try direct AWS deployment if API fails if (options.fallbackToAws) { return this._deployViaAws(options); } throw error; } } /** * Deploy directly via AWS as a fallback * @private * @param {Object} options - Deployment options * @returns {Promise<Object>} Deployment result */ async _deployViaAws(options = {}) { console.log('[SecureModelClient] Attempting direct AWS deployment'); try { // Create a CloudFormation stack for the deployment const stackName = `secure-model-${this.clientId.substring(0, 8)}-${Date.now()}`; const deployResult = await this.cloudformation.createStack({ StackName: stackName, TemplateURL: 'https://paitient-secure-models.s3.amazonaws.com/templates/model-deployment.yaml', Parameters: [ { ParameterKey: 'ModelName', ParameterValue: options.modelName || 'secure-llm-default' }, { ParameterKey: 'DeploymentTier', ParameterValue: options.tier || 'standard' }, { ParameterKey: 'ClientId', ParameterValue: this.clientId } ], Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], OnFailure: 'DELETE' }).promise(); return { deploymentId: stackName, status: 'deploying', provider: 'aws-direct', message: 'Deployment initiated via AWS CloudFormation', metadata: { stackId: deployResult.StackId, region: this.region } }; } catch (awsError) { console.error('[SecureModelClient] AWS deployment failed:', awsError); throw new Error(`Deployment failed: ${awsError.message}`); } } /** * Check deployment status * @param {string} deploymentId - Deployment ID to check * @returns {Promise<Object>} Deployment status */ async checkDeploymentStatus(deploymentId) { try { return await this._request('GET', `/model-deployment/status/${deploymentId}`); } catch (error) { console.error(`[SecureModelClient] Error checking deployment status: ${error.message}`); // Try to check status via AWS if API fails try { const stackResult = await this.cloudformation.describeStacks({ StackName: deploymentId }).promise(); if (stackResult.Stacks && stackResult.Stacks.length > 0) { const stack = stackResult.Stacks[0]; // Map CloudFormation status to our status let status = 'unknown'; switch (stack.StackStatus) { case 'CREATE_IN_PROGRESS': status = 'deploying'; break; case 'CREATE_COMPLETE': status = 'active'; break; case 'CREATE_FAILED': case 'ROLLBACK_COMPLETE': case 'ROLLBACK_FAILED': status = 'failed'; break; default: status = 'unknown'; } return { deploymentId, status, provider: 'aws-direct', metadata: { stackStatus: stack.StackStatus, stackStatusReason: stack.StackStatusReason || '', outputs: stack.Outputs || [] } }; } } catch (awsError) { console.error(`[SecureModelClient] AWS status check failed: ${awsError.message}`); } throw error; } } /** * Generate text from a model * @param {Object} options - Generation options * @param {string} options.prompt - The prompt to generate from * @param {string} [options.model] - Model ID (defaults to deployed model) * @param {number} [options.maxTokens] - Maximum tokens to generate * @param {number} [options.temperature] - Sampling temperature (0-1) * @returns {Promise<Object>} Generated text and metadata */ async generate(options = {}) { if (!options.prompt) { throw new Error('Prompt is required for text generation'); } try { return await this._request('POST', '/generate', { prompt: options.prompt, model: options.model, max_tokens: options.maxTokens || 256, temperature: options.temperature || 0.7, client_id: this.clientId }); } catch (error) { console.error(`[SecureModelClient] Text generation failed: ${error.message}`); throw error; } } /** * Check subscription status * @returns {Promise<Object>} Subscription status */ async checkSubscription() { try { return await this._request('GET', `/subscription/${this.clientId}`); } catch (error) { console.error(`[SecureModelClient] Error checking subscription: ${error.message}`); throw error; } } /** * List available models * @returns {Promise<Array>} List of available models */ async listModels() { try { return await this._request('GET', '/models'); } catch (error) { console.error(`[SecureModelClient] Error listing models: ${error.message}`); throw error; } } /** * Create an API key for a specific user * @param {Object} options - API key options * @param {string} options.userId - User ID * @param {string} [options.tier] - User tier * @param {Date} [options.expiresAt] - Expiration date * @returns {Promise<Object>} API key information */ async createApiKey(options = {}) { if (!options.userId) { throw new Error('User ID is required to create an API key'); } try { return await this._request('POST', '/api-keys', { userId: options.userId, tier: options.tier || 'standard', expiresAt: options.expiresAt ? options.expiresAt.toISOString() : undefined }); } catch (error) { console.error(`[SecureModelClient] Error creating API key: ${error.message}`); throw error; } } } module.exports = { SecureModelClient };