Best Practices

Production-ready patterns and optimization techniques for the PromptCompose SDK

SDK Best Practices

This guide covers best practices, optimization techniques, and recommended patterns for using the PromptCompose SDK effectively in production environments.

SDK Configuration Best Practices

Environment-Based Configuration

Set up different configurations for different environments:

// config/promptcompose.ts
interface PromptComposeConfig {
  apiKey: string;
  projectId: string;
  debug: boolean;
  apiUrl?: string;
}

function getConfig(): PromptComposeConfig {
  const env = process.env.NODE_ENV || 'development';
  
  const configs = {
    production: {
      apiKey: process.env.PROMPT_COMPOSE_API_KEY!,
      projectId: process.env.PROMPT_COMPOSE_PROJECT_ID!,
      debug: false,
      apiUrl: 'https://api.promptcompose.io/api/v1'
    },
    staging: {
      apiKey: process.env.STAGING_PROMPT_COMPOSE_API_KEY!,
      projectId: process.env.STAGING_PROMPT_COMPOSE_PROJECT_ID!,
      debug: true,
      apiUrl: 'https://staging-api.promptcompose.io/api/v1'
    },
    development: {
      apiKey: process.env.DEV_PROMPT_COMPOSE_API_KEY!,
      projectId: process.env.DEV_PROMPT_COMPOSE_PROJECT_ID!,
      debug: true,
      apiUrl: 'http://localhost:8080/api/v1'
    }
  };
  
  return configs[env] || configs.development;
}

export const config = getConfig();

Singleton Pattern for SDK Instance

class PromptComposeService {
  private static instance: PromptCompose;
  private static initialized = false;
  
  static async getInstance(): Promise<PromptCompose> {
    if (!this.instance) {
      const config = getConfig();
      this.instance = new PromptCompose(
        config.apiKey,
        config.projectId,
        { debug: config.debug }
      );
      
      // Set custom API URL if provided
      if (config.apiUrl) {
        process.env.PROMPT_COMPOSE_API_URL = config.apiUrl;
      }
    }
    
    if (!this.initialized) {
      await this.instance.init();
      this.initialized = true;
    }
    
    return this.instance;
  }
}

// Usage
const promptCompose = await PromptComposeService.getInstance();

Performance Optimization

Intelligent Caching

interface CacheEntry {
  content: string;
  timestamp: number;
  variant?: string;
  test?: string;
}

class PromptCache {
  private cache = new Map<string, CacheEntry>();
  private readonly cacheTimeout = 5 * 60 * 1000; // 5 minutes
  
  getCacheKey(promptId: string, config?: any, variables?: any): string {
    const configStr = config ? JSON.stringify(config) : '';
    const variablesStr = variables ? JSON.stringify(variables) : '';
    return `${promptId}-${configStr}-${variablesStr}`;
  }
  
  get(key: string): CacheEntry | null {
    const entry = this.cache.get(key);
    if (!entry) return null;
    
    if (Date.now() - entry.timestamp > this.cacheTimeout) {
      this.cache.delete(key);
      return null;
    }
    
    return entry;
  }
  
  set(key: string, content: string, metadata?: any): void {
    this.cache.set(key, {
      content,
      timestamp: Date.now(),
      variant: metadata?.variant?.name,
      test: metadata?.abTest?.name
    });
  }
  
  clear(): void {
    this.cache.clear();
  }
  
  // Clean expired entries
  cleanup(): void {
    const now = Date.now();
    for (const [key, entry] of this.cache.entries()) {
      if (now - entry.timestamp > this.cacheTimeout) {
        this.cache.delete(key);
      }
    }
  }
}

// Usage with SDK
class CachedPromptService {
  private promptCompose: PromptCompose;
  private cache = new PromptCache();

  constructor(promptCompose: PromptCompose) {
    this.promptCompose = promptCompose;
    
    // Clean up cache every 10 minutes
    setInterval(() => this.cache.cleanup(), 10 * 60 * 1000);
  }

  async resolvePrompt(promptId: string, config?: any, variables?: any) {
    const cacheKey = this.cache.getCacheKey(promptId, config, variables);
    const cached = this.cache.get(cacheKey);

    if (cached) {
      return { content: cached.content, fromCache: true };
    }

    const result = await this.promptCompose.resolvePrompt(promptId, config, variables);
    this.cache.set(cacheKey, result.content, { variant: result.variant, abTest: result.abTest });

    return result;
  }
}

Batch Operations

class BatchPromptResolver {
  private promptCompose: PromptCompose;
  private batchSize = 10;
  private batchTimeout = 100; // ms
  private pendingRequests: Array<{
    promptId: string;
    config?: any;
    variables?: any;
    resolve: (result: any) => void;
    reject: (error: any) => void;
  }> = [];
  private batchTimer?: NodeJS.Timeout;
  
  constructor(promptCompose: PromptCompose) {
    this.promptCompose = promptCompose;
  }
  
  async resolve(promptId: string, config?: any, variables?: any): Promise<any> {
    return new Promise((resolve, reject) => {
      this.pendingRequests.push({
        promptId,
        config,
        variables,
        resolve,
        reject
      });
      
      this.scheduleBatch();
    });
  }
  
  private scheduleBatch(): void {
    if (this.batchTimer) return;
    
    this.batchTimer = setTimeout(async () => {
      await this.processBatch();
    }, this.batchTimeout);
    
    if (this.pendingRequests.length >= this.batchSize) {
      clearTimeout(this.batchTimer);
      this.batchTimer = undefined;
      await this.processBatch();
    }
  }
  
  private async processBatch(): Promise<void> {
    const requests = this.pendingRequests.splice(0, this.batchSize);
    this.batchTimer = undefined;
    
    const promises = requests.map(async (request) => {
      try {
        const result = await this.promptCompose.resolvePrompt(
          request.promptId,
          request.config,
          request.variables
        );
        request.resolve(result);
      } catch (error) {
        request.reject(error);
      }
    });
    
    await Promise.all(promises);
    
    // Schedule next batch if there are more requests
    if (this.pendingRequests.length > 0) {
      this.scheduleBatch();
    }
  }
}

Error Handling Best Practices

Graceful Degradation

class ResilientPromptService {
  private promptCompose: PromptCompose;
  private fallbackContent = new Map<string, string>();
  private retryConfig = {
    maxRetries: 3,
    backoffMultiplier: 2,
    initialDelay: 1000
  };
  
  constructor(promptCompose: PromptCompose) {
    this.promptCompose = promptCompose;
  }
  
  setFallback(promptId: string, content: string): void {
    this.fallbackContent.set(promptId, content);
  }
  
  async resolveWithFallback(
    promptId: string,
    config?: any,
    variables?: any
  ): Promise<string> {
    try {
      const result = await this.resolveWithRetry(promptId, config, variables);
      return result.content;
    } catch (error) {
      console.error('Prompt resolution failed:', error.message);
      
      // Use fallback content if available
      const fallback = this.fallbackContent.get(promptId);
      if (fallback) {
        console.log('Using fallback content for prompt:', promptId);
        return this.interpolateFallback(fallback, variables || {});
      }
      
      // Last resort generic fallback
      return 'We apologize, but the requested content is temporarily unavailable.';
    }
  }
  
  private async resolveWithRetry(
    promptId: string,
    config?: any,
    variables?: any,
    attempt = 1
  ): Promise<any> {
    try {
      return await this.promptCompose.resolvePrompt(promptId, config, variables);
    } catch (error) {
      if (attempt >= this.retryConfig.maxRetries || error instanceof ValidationError) {
        throw error;
      }
      
      const delay = this.retryConfig.initialDelay * 
                   Math.pow(this.retryConfig.backoffMultiplier, attempt - 1);
      
      console.warn(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
      
      return this.resolveWithRetry(promptId, config, variables, attempt + 1);
    }
  }
  
  private interpolateFallback(content: string, variables: Record<string, any>): string {
    return content.replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (_, key) => {
      return variables[key] !== undefined ? String(variables[key]) : `{{${key}}}`;
    });
  }
}

Circuit Breaker Pattern

class PromptServiceCircuitBreaker {
  private promptCompose: PromptCompose;
  private failureThreshold = 5;
  private recoveryTimeout = 60000; // 1 minute
  private failureCount = 0;
  private lastFailureTime = 0;
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
  
  constructor(promptCompose: PromptCompose) {
    this.promptCompose = promptCompose;
  }
  
  async resolve(promptId: string, config?: any, variables?: any): Promise<any> {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
        this.state = 'HALF_OPEN';
        this.failureCount = 0;
      } else {
        throw new Error('Service temporarily unavailable (circuit breaker open)');
      }
    }
    
    try {
      const result = await this.promptCompose.resolvePrompt(promptId, config, variables);
      
      if (this.state === 'HALF_OPEN') {
        this.state = 'CLOSED';
        this.failureCount = 0;
      }
      
      return result;
    } catch (error) {
      this.failureCount++;
      this.lastFailureTime = Date.now();
      
      if (this.failureCount >= this.failureThreshold) {
        this.state = 'OPEN';
        console.warn('Circuit breaker opened due to repeated failures');
      }
      
      throw error;
    }
  }
}

A/B Testing Best Practices

Session Management

class SessionManager {
  private static sessions = new Map<string, string>();
  
  static getSessionId(userId: string, context?: any): string {
    const key = `${userId}-${context?.deviceType || 'unknown'}`;
    
    if (!this.sessions.has(key)) {
      const sessionId = this.generateSessionId(userId, context);
      this.sessions.set(key, sessionId);
    }
    
    return this.sessions.get(key)!;
  }
  
  private static generateSessionId(userId: string, context?: any): string {
    const timestamp = Date.now();
    const deviceType = context?.deviceType || 'web';
    const hash = this.simpleHash(`${userId}-${timestamp}-${deviceType}`);
    return `${userId}-${deviceType}-${hash}`;
  }
  
  private static simpleHash(str: string): string {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash; // Convert to 32-bit integer
    }
    return Math.abs(hash).toString(16).substr(0, 8);
  }
}

Conversion Tracking

class ConversionTracker {
  private promptCompose: PromptCompose;
  private pendingConversions = new Map<string, any>();
  private conversionWindow = 24 * 60 * 60 * 1000; // 24 hours
  
  constructor(promptCompose: PromptCompose) {
    this.promptCompose = promptCompose;
    
    // Cleanup expired conversions every hour
    setInterval(() => this.cleanup(), 60 * 60 * 1000);
  }
  
  trackPromptServed(
    promptId: string,
    userId: string,
    result: any,
    context?: any
  ): void {
    if (result.variant && result.abTest) {
      const key = `${userId}-${promptId}`;
      this.pendingConversions.set(key, {
        testId: result.abTest.publicId,
        variantId: result.variant.publicId,
        userId,
        promptId,
        servedAt: Date.now(),
        context
      });
    }
  }
  
  async trackConversion(
    promptId: string,
    userId: string,
    status: 'success' | 'fail' | 'timeout' | 'error',
    metadata?: any
  ): Promise<void> {
    const key = `${userId}-${promptId}`;
    const conversion = this.pendingConversions.get(key);
    
    if (!conversion) {
      console.warn('No pending conversion found for:', key);
      return;
    }
    
    try {
      await this.promptCompose.reportABResult(conversion.testId, {
        variantId: conversion.variantId,
        status,
        sessionId: SessionManager.getSessionId(userId, conversion.context)
      });
      
      console.log(`Conversion reported: ${status} for variant ${conversion.variantId}`);
      
      // Track analytics
      this.trackAnalytics(conversion, status, metadata);
      
      // Remove from pending
      this.pendingConversions.delete(key);
    } catch (error) {
      console.error('Failed to report conversion:', error.message);
    }
  }
  
  private trackAnalytics(conversion: any, status: string, metadata?: any): void {
    // Integrate with your analytics service
    console.log('Analytics event:', {
      event: 'prompt_conversion',
      testId: conversion.testId,
      variantId: conversion.variantId,
      userId: conversion.userId,
      promptId: conversion.promptId,
      status,
      timeToConversion: Date.now() - conversion.servedAt,
      metadata
    });
  }
  
  private cleanup(): void {
    const now = Date.now();
    for (const [key, conversion] of this.pendingConversions.entries()) {
      if (now - conversion.servedAt > this.conversionWindow) {
        this.pendingConversions.delete(key);
      }
    }
  }
}

Security Best Practices

Secure Variable Handling

class SecureVariableManager {
  private sensitiveFields = ['password', 'token', 'apiKey', 'secret', 'ssn', 'creditCard'];
  
  sanitizeVariables(variables: Record<string, any>): Record<string, any> {
    const sanitized = { ...variables };
    
    for (const key in sanitized) {
      // Remove sensitive fields
      if (this.isSensitiveField(key)) {
        delete sanitized[key];
        console.warn(`Removed sensitive field: ${key}`);
      }
      
      // Sanitize string values
      if (typeof sanitized[key] === 'string') {
        sanitized[key] = this.sanitizeString(sanitized[key]);
      }
    }
    
    return sanitized;
  }
  
  private isSensitiveField(fieldName: string): boolean {
    const lowerField = fieldName.toLowerCase();
    return this.sensitiveFields.some(sensitive => 
      lowerField.includes(sensitive.toLowerCase())
    );
  }
  
  private sanitizeString(value: string): string {
    // Remove potential XSS payloads
    return value
      .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
      .replace(/javascript:/gi, '')
      .replace(/on\w+\s*=/gi, '')
      .trim();
  }
}

Rate Limiting

class RateLimiter {
  private requests = new Map<string, number[]>();
  private maxRequests = 100; // per minute
  private windowSize = 60 * 1000; // 1 minute
  
  canMakeRequest(identifier: string): boolean {
    const now = Date.now();
    const userRequests = this.requests.get(identifier) || [];
    
    // Remove old requests outside the window
    const validRequests = userRequests.filter(
      timestamp => now - timestamp < this.windowSize
    );
    
    if (validRequests.length >= this.maxRequests) {
      return false;
    }
    
    validRequests.push(now);
    this.requests.set(identifier, validRequests);
    return true;
  }
  
  getRemainingRequests(identifier: string): number {
    const userRequests = this.requests.get(identifier) || [];
    const now = Date.now();
    const validRequests = userRequests.filter(
      timestamp => now - timestamp < this.windowSize
    );
    
    return Math.max(0, this.maxRequests - validRequests.length);
  }
}

Monitoring and Logging

Structured Logging

class PromptLogger {
  private logger: any; // Your logging service
  
  constructor(logger: any) {
    this.logger = logger;
  }
  
  logPromptResolution(
    promptId: string,
    result: any,
    duration: number,
    userId?: string,
    context?: any
  ): void {
    this.logger.info('prompt_resolved', {
      promptId,
      source: result.source,
      variant: result.variant?.name,
      test: result.abTest?.name,
      duration,
      userId,
      context,
      timestamp: new Date().toISOString()
    });
  }
  
  logError(
    operation: string,
    error: Error,
    promptId?: string,
    userId?: string
  ): void {
    this.logger.error('prompt_error', {
      operation,
      error: error.message,
      stack: error.stack,
      promptId,
      userId,
      timestamp: new Date().toISOString()
    });
  }
  
  logConversion(
    testId: string,
    variantId: string,
    status: string,
    userId: string,
    metadata?: any
  ): void {
    this.logger.info('ab_test_conversion', {
      testId,
      variantId,
      status,
      userId,
      metadata,
      timestamp: new Date().toISOString()
    });
  }
}

Health Monitoring

class PromptServiceHealthMonitor {
  private promptCompose: PromptCompose;
  private healthCheckInterval = 30000; // 30 seconds
  private healthStatus = {
    isHealthy: true,
    lastCheck: Date.now(),
    consecutiveFailures: 0,
    averageResponseTime: 0
  };
  
  constructor(promptCompose: PromptCompose) {
    this.promptCompose = promptCompose;
    this.startHealthChecks();
  }
  
  private startHealthChecks(): void {
    setInterval(async () => {
      await this.performHealthCheck();
    }, this.healthCheckInterval);
  }
  
  private async performHealthCheck(): Promise<void> {
    const startTime = Date.now();
    
    try {
      await this.promptCompose.init();
      
      const responseTime = Date.now() - startTime;
      this.updateHealthStatus(true, responseTime);
      
      console.log('Health check passed:', {
        responseTime,
        status: 'healthy'
      });
    } catch (error) {
      this.updateHealthStatus(false, Date.now() - startTime);
      
      console.error('Health check failed:', {
        error: error.message,
        consecutiveFailures: this.healthStatus.consecutiveFailures
      });
    }
  }
  
  private updateHealthStatus(success: boolean, responseTime: number): void {
    this.healthStatus.lastCheck = Date.now();
    
    if (success) {
      this.healthStatus.isHealthy = true;
      this.healthStatus.consecutiveFailures = 0;
      this.healthStatus.averageResponseTime = 
        (this.healthStatus.averageResponseTime * 0.9) + (responseTime * 0.1);
    } else {
      this.healthStatus.consecutiveFailures++;
      if (this.healthStatus.consecutiveFailures >= 3) {
        this.healthStatus.isHealthy = false;
      }
    }
  }
  
  getHealthStatus(): any {
    return { ...this.healthStatus };
  }
}

Production Deployment

Environment Configuration

// Production-ready configuration
const productionConfig = {
  promptCompose: new PromptCompose(
    process.env.PROMPT_COMPOSE_API_KEY!,
    process.env.PROMPT_COMPOSE_PROJECT_ID!,
    {
      debug: false,
      timeout: 30000,
      retries: 3
    }
  ),
  cache: {
    enabled: true,
    timeout: 5 * 60 * 1000, // 5 minutes
    maxSize: 1000
  },
  rateLimit: {
    maxRequests: 1000,
    windowSize: 60 * 1000 // 1 minute
  },
  fallback: {
    enabled: true,
    defaultMessage: 'Content is temporarily unavailable.'
  }
};

Docker Configuration

# Dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

# Environment variables
ENV NODE_ENV=production
ENV PROMPT_COMPOSE_API_KEY=""
ENV PROMPT_COMPOSE_PROJECT_ID=""

EXPOSE 3000

CMD ["node", "dist/server.js"]

Kubernetes Deployment

# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: promptcompose-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: promptcompose-app
  template:
    metadata:
      labels:
        app: promptcompose-app
    spec:
      containers:
      - name: app
        image: your-app:latest
        env:
        - name: PROMPT_COMPOSE_API_KEY
          valueFrom:
            secretKeyRef:
              name: promptcompose-secrets
              key: api-key
        - name: PROMPT_COMPOSE_PROJECT_ID
          valueFrom:
            secretKeyRef:
              name: promptcompose-secrets
              key: project-id
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 512Mi

Summary

Key best practices for PromptCompose SDK:

Configuration

  • ✅ Use environment-based configuration
  • ✅ Implement singleton pattern for SDK instances
  • ✅ Centralize API URL and credential management

Performance

  • ✅ Implement intelligent caching strategies
  • ✅ Use batch operations for multiple requests
  • ✅ Optimize variable processing

Error Handling

  • ✅ Implement graceful degradation with fallbacks
  • ✅ Use circuit breaker pattern for resilience
  • ✅ Implement retry logic with exponential backoff

A/B Testing

  • ✅ Use consistent session IDs for user experience
  • ✅ Implement comprehensive conversion tracking
  • ✅ Track analytics for data-driven decisions

Security

  • ✅ Sanitize variables to prevent XSS
  • ✅ Implement rate limiting
  • ✅ Remove sensitive data from variables

Monitoring

  • ✅ Use structured logging for observability
  • ✅ Implement health monitoring
  • ✅ Track performance metrics

These practices ensure your PromptCompose integration is robust, secure, and performant in production environments.