UNPKG

ai-patterns

Version:

Production-ready TypeScript patterns to build solid and robust AI applications. Retry logic, circuit breakers, rate limiting, human-in-the-loop escalation, prompt versioning, response validation, context window management, and more—all with complete type

275 lines 10.6 kB
"use strict"; /** * Cost Tracking Pattern * * Monitors and controls AI spending in real-time, preventing budget overruns * and optimizing costs across your application. * * @example * ```typescript * const result = await costTracking({ * execute: async () => { * const { text, usage } = await generateText({ * model: openai('gpt-4-turbo'), * prompt: longPrompt * }); * return { value: text, tokens: usage.totalTokens }; * }, * costPerToken: ModelCost.GPT4_TURBO, * monthlyBudget: 500, * dailyLimit: 50, * onBudgetWarning: (spent, limit) => { * console.warn(`Budget at 80%: $${spent}/$${limit}`); * }, * tags: { feature: 'chatbot', userId: 'user-123' } * }); * ``` */ Object.defineProperty(exports, "__esModule", { value: true }); exports.InMemoryCostStorage = void 0; exports.costTracking = costTracking; exports.createCostTracker = createCostTracker; const common_1 = require("../types/common"); const errors_1 = require("../types/errors"); const storage_1 = require("../common/storage"); /** * In-memory cost storage implementation using GlobalStorage */ class InMemoryCostStorage { constructor() { this.namespace = storage_1.StorageNamespace.COST_TRACKING; this.initialized = false; this.storage = storage_1.GlobalStorage.getInstance(); } async ensureInitialized() { if (this.initialized) return; const now = Date.now(); const periods = ["monthly", "daily", "hourly"]; const durations = { monthly: 30 * 24 * 60 * 60 * 1000, daily: 24 * 60 * 60 * 1000, hourly: 60 * 60 * 1000, }; for (const period of periods) { const existing = await this.storage.get(this.namespace, period); if (!existing) { await this.storage.set(this.namespace, period, { spent: 0, periodStart: now, periodDuration: durations[period], }); } } this.initialized = true; } async getSpent(period) { await this.ensureInitialized(); const track = await this.storage.get(this.namespace, period); if (!track) return 0; const now = Date.now(); // Reset if period has elapsed if (now - track.periodStart > track.periodDuration) { track.spent = 0; track.periodStart = now; await this.storage.set(this.namespace, period, track); } return track.spent; } async addSpent(period, amount) { await this.ensureInitialized(); const track = await this.storage.get(this.namespace, period); if (!track) return; const now = Date.now(); // Reset if period has elapsed if (now - track.periodStart > track.periodDuration) { track.spent = 0; track.periodStart = now; } track.spent += amount; await this.storage.set(this.namespace, period, track); } async resetSpent(period) { await this.ensureInitialized(); const track = await this.storage.get(this.namespace, period); if (!track) return; track.spent = 0; track.periodStart = Date.now(); await this.storage.set(this.namespace, period, track); } } exports.InMemoryCostStorage = InMemoryCostStorage; /** * Default storage instance (lazy initialization) */ let defaultStorage = null; function getDefaultStorage() { if (!defaultStorage) { defaultStorage = new InMemoryCostStorage(); } return defaultStorage; } /** * Check if budget would be exceeded */ async function checkBudgetLimits(cost, config, storage) { const { monthlyBudget, dailyLimit, hourlyLimit, budget, logger = common_1.defaultLogger } = config; // Check monthly budget const monthlyMax = budget?.monthly ?? monthlyBudget; if (monthlyMax !== undefined) { const monthlySpent = await storage.getSpent("monthly"); const newMonthlyTotal = monthlySpent + cost; if (newMonthlyTotal > monthlyMax) { logger.error(`Monthly budget exceeded: $${newMonthlyTotal.toFixed(2)} > $${monthlyMax.toFixed(2)}`); if (config.onBudgetExceeded) { await config.onBudgetExceeded(newMonthlyTotal, monthlyMax); } throw new errors_1.PatternError(`Monthly budget exceeded: $${newMonthlyTotal.toFixed(2)} > $${monthlyMax.toFixed(2)}`, errors_1.ErrorCode.BUDGET_EXCEEDED, undefined, { period: "monthly", spent: newMonthlyTotal, limit: monthlyMax }); } } // Check daily limit const dailyMax = budget?.daily ?? dailyLimit; if (dailyMax !== undefined) { const dailySpent = await storage.getSpent("daily"); const newDailyTotal = dailySpent + cost; if (newDailyTotal > dailyMax) { logger.error(`Daily budget exceeded: $${newDailyTotal.toFixed(2)} > $${dailyMax.toFixed(2)}`); if (config.onBudgetExceeded) { await config.onBudgetExceeded(newDailyTotal, dailyMax); } throw new errors_1.PatternError(`Daily budget exceeded: $${newDailyTotal.toFixed(2)} > $${dailyMax.toFixed(2)}`, errors_1.ErrorCode.BUDGET_EXCEEDED, undefined, { period: "daily", spent: newDailyTotal, limit: dailyMax }); } } // Check hourly limit const hourlyMax = budget?.hourly ?? hourlyLimit; if (hourlyMax !== undefined) { const hourlySpent = await storage.getSpent("hourly"); const newHourlyTotal = hourlySpent + cost; if (newHourlyTotal > hourlyMax) { logger.error(`Hourly budget exceeded: $${newHourlyTotal.toFixed(2)} > $${hourlyMax.toFixed(2)}`); if (config.onBudgetExceeded) { await config.onBudgetExceeded(newHourlyTotal, hourlyMax); } throw new errors_1.PatternError(`Hourly budget exceeded: $${newHourlyTotal.toFixed(2)} > $${hourlyMax.toFixed(2)}`, errors_1.ErrorCode.BUDGET_EXCEEDED, undefined, { period: "hourly", spent: newHourlyTotal, limit: hourlyMax }); } } } /** * Trigger alerts based on spending thresholds */ async function triggerAlerts(cost, config, storage) { const { monthlyBudget, budget, alerts, logger = common_1.defaultLogger } = config; // Process custom alerts if (alerts && alerts.length > 0) { const monthlyMax = budget?.monthly ?? monthlyBudget; if (monthlyMax !== undefined) { const monthlySpent = await storage.getSpent("monthly"); const newMonthlyTotal = monthlySpent + cost; const percentage = newMonthlyTotal / monthlyMax; for (const alert of alerts) { if (percentage >= alert.threshold && monthlySpent / monthlyMax < alert.threshold) { logger.warn(`Budget alert: ${(alert.threshold * 100).toFixed(0)}% threshold reached - $${newMonthlyTotal.toFixed(2)}/$${monthlyMax.toFixed(2)}`); await alert.action(newMonthlyTotal, monthlyMax); } } } } // Default warning at 80% if (config.onBudgetWarning) { const monthlyMax = budget?.monthly ?? monthlyBudget; if (monthlyMax !== undefined) { const monthlySpent = await storage.getSpent("monthly"); const newMonthlyTotal = monthlySpent + cost; const percentage = newMonthlyTotal / monthlyMax; if (percentage >= 0.8 && monthlySpent / monthlyMax < 0.8) { logger.warn(`Budget warning: 80% threshold reached - $${newMonthlyTotal.toFixed(2)}/$${monthlyMax.toFixed(2)}`); await config.onBudgetWarning(newMonthlyTotal, monthlyMax); } } } } /** * Update spent amounts in storage */ async function updateSpentAmounts(cost, storage) { await storage.addSpent("monthly", cost); await storage.addSpent("daily", cost); await storage.addSpent("hourly", cost); } /** * Calculate remaining budget */ async function calculateRemainingBudget(config, storage) { const { monthlyBudget, budget } = config; const monthlyMax = budget?.monthly ?? monthlyBudget; if (monthlyMax !== undefined) { const monthlySpent = await storage.getSpent("monthly"); return monthlyMax - monthlySpent; } return undefined; } /** * Execute an operation with cost tracking */ async function costTracking(config) { const { execute, costPerToken, tags, costThresholdWarning, onCostCalculated, onExpensiveOperation, logger = common_1.defaultLogger, storage = getDefaultStorage(), } = config; const timestamp = Date.now(); try { // Execute the operation logger.debug("Executing operation with cost tracking", { tags }); const result = await execute(); // Calculate cost const tokens = result.tokens ?? 0; const cost = tokens * costPerToken; logger.info(`Operation completed: ${tokens} tokens, $${cost.toFixed(4)}`, { tags }); // Check for expensive operation if (costThresholdWarning !== undefined && cost > costThresholdWarning) { logger.warn(`Expensive operation detected: $${cost.toFixed(4)} (threshold: $${costThresholdWarning})`, { tags }); if (onExpensiveOperation) { await onExpensiveOperation(cost, tags); } } // Check budget limits (will throw if exceeded) await checkBudgetLimits(cost, config, storage); // Trigger alerts await triggerAlerts(cost, config, storage); // Update spent amounts await updateSpentAmounts(cost, storage); // Call onCostCalculated callback if (onCostCalculated) { await onCostCalculated(cost, tags); } // Calculate remaining budget const remainingBudget = await calculateRemainingBudget(config, storage); return { value: result.value, cost, tokens, remainingBudget, tags, timestamp, }; } catch (error) { logger.error("Operation failed with cost tracking", { error: error instanceof Error ? error.message : String(error), tags, }); throw error; } } /** * Create a cost tracking wrapper function */ function createCostTracker(baseConfig) { return (execute) => { return costTracking({ ...baseConfig, execute, }); }; } //# sourceMappingURL=cost-tracking.js.map