paitient-secure-model
Version:
PaiTIENT - HIPAA/SOC2 compliant secure model hosting SDK
350 lines (307 loc) • 11.6 kB
JavaScript
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 };
;