UNPKG

@just-every/ensemble

Version:

LLM provider abstraction layer with unified streaming interface

244 lines 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CostTracker = exports.costTracker = void 0; const model_data_js_1 = require("../data/model_data.cjs"); const event_controller_js_1 = require("./event_controller.cjs"); class CostTracker { entries = []; started = new Date(); onAddUsageCallbacks = []; calculateCost(usage) { if (typeof usage.cost === 'number') { return usage; } if (usage.isFreeTierUsage) { usage.cost = 0; return usage; } const model = (0, model_data_js_1.findModel)(usage.model); if (!model) { console.error(`Model not found when recording usage: ${usage.model}`); throw new Error(`Model not found when recording usage: ${usage.model}`); } if (usage.cost !== undefined && usage.cost !== null && usage.cost > 0) { return usage; } usage.cost = 0; const original_input_tokens = usage.input_tokens || 0; const output_tokens = usage.output_tokens || 0; const cached_tokens = usage.cached_tokens || 0; const image_count = usage.image_count || 0; const calculationTime = usage.timestamp || new Date(); const usesTimeBasedPricing = (typeof model.cost?.input_per_million === 'object' && model.cost.input_per_million !== null && 'peak_price_per_million' in model.cost.input_per_million) || (typeof model.cost?.output_per_million === 'object' && model.cost.output_per_million !== null && 'peak_price_per_million' in model.cost.output_per_million) || (typeof model.cost?.cached_input_per_million === 'object' && model.cost.cached_input_per_million !== null && 'peak_price_per_million' in model.cost.cached_input_per_million); if (!usage.timestamp && usesTimeBasedPricing) { } const getPrice = (tokensForTierCheck, costStructure, modality) => { if (typeof costStructure === 'number') { return costStructure; } if (typeof costStructure === 'object' && costStructure !== null) { if ('text' in costStructure || 'audio' in costStructure || 'video' in costStructure || 'image' in costStructure) { const modalityPrice = costStructure; const selectedModality = modality || 'text'; const modalityCost = modalityPrice[selectedModality]; if (modalityCost !== undefined) { return getPrice(tokensForTierCheck, modalityCost); } return getPrice(tokensForTierCheck, modalityPrice.text || 0); } if ('peak_price_per_million' in costStructure) { const timeBasedCost = costStructure; const utcHour = calculationTime.getUTCHours(); const utcMinute = calculationTime.getUTCMinutes(); const currentTimeInMinutes = utcHour * 60 + utcMinute; const peakStartInMinutes = timeBasedCost.peak_utc_start_hour * 60 + timeBasedCost.peak_utc_start_minute; const peakEndInMinutes = timeBasedCost.peak_utc_end_hour * 60 + timeBasedCost.peak_utc_end_minute; let isPeakTime; if (peakStartInMinutes <= peakEndInMinutes) { isPeakTime = currentTimeInMinutes >= peakStartInMinutes && currentTimeInMinutes < peakEndInMinutes; } else { isPeakTime = currentTimeInMinutes >= peakStartInMinutes || currentTimeInMinutes < peakEndInMinutes; } return isPeakTime ? timeBasedCost.peak_price_per_million : timeBasedCost.off_peak_price_per_million; } else if ('threshold_tokens' in costStructure) { const tieredCost = costStructure; if (tokensForTierCheck <= tieredCost.threshold_tokens) { return tieredCost.price_below_threshold_per_million; } else { return tieredCost.price_above_threshold_per_million; } } } return 0; }; let nonCachedInputTokens = 0; let actualCachedTokens = 0; if (cached_tokens > 0 && model.cost?.cached_input_per_million !== undefined) { actualCachedTokens = cached_tokens; nonCachedInputTokens = Math.max(0, original_input_tokens - cached_tokens); } else { nonCachedInputTokens = original_input_tokens; actualCachedTokens = 0; } if (nonCachedInputTokens > 0 && model.cost?.input_per_million !== undefined) { const inputPricePerMillion = getPrice(original_input_tokens, model.cost.input_per_million, usage.input_modality); usage.cost += (nonCachedInputTokens / 1000000) * inputPricePerMillion; } if (actualCachedTokens > 0 && model.cost?.cached_input_per_million !== undefined) { const cachedPricePerMillion = getPrice(actualCachedTokens, model.cost.cached_input_per_million); usage.cost += (actualCachedTokens / 1000000) * cachedPricePerMillion; } if (output_tokens > 0 && model.cost?.output_per_million !== undefined) { const outputPricePerMillion = getPrice(output_tokens, model.cost.output_per_million, usage.output_modality); usage.cost += (output_tokens / 1000000) * outputPricePerMillion; } if (image_count > 0 && model.cost?.per_image) { usage.cost += image_count * model.cost.per_image; } usage.cost = Math.max(0, usage.cost); return usage; } onAddUsage(callback) { this.onAddUsageCallbacks.push(callback); } addUsage(usage) { try { usage = this.calculateCost({ ...usage }); usage.timestamp = new Date(); this.entries.push(usage); if ((0, event_controller_js_1.hasEventHandler)()) { const costUpdateEvent = { type: 'cost_update', usage: { ...usage, total_tokens: (usage.input_tokens || 0) + (usage.output_tokens || 0), }, timestamp: new Date().toISOString(), }; (0, event_controller_js_1.emitEvent)(costUpdateEvent).catch(error => { console.error('Error emitting cost_update event:', error); }); } for (const callback of this.onAddUsageCallbacks) { try { callback(usage); } catch (error) { console.error('Error in cost tracker callback:', error); } } return usage; } catch (err) { console.error('Error recording usage:', err); return usage; } } getTotalCost() { return this.entries.reduce((sum, entry) => sum + (entry.cost || 0), 0); } getCostsByModel() { const models = {}; for (const entry of this.entries) { if (!models[entry.model]) { models[entry.model] = { cost: 0, calls: 0, }; } models[entry.model].cost += entry.cost || 0; models[entry.model].calls += 1; } return models; } printSummary() { if (!this.entries.length) { return; } const totalCost = this.getTotalCost(); const costsByModel = this.getCostsByModel(); const runtime = Math.round((new Date().getTime() - this.started.getTime()) / 1000); console.log('\n\nCOST SUMMARY'); console.log(`Runtime: ${runtime} seconds`); console.log(`Total API Cost: $${totalCost.toFixed(6)}`); console.log('\nModels:'); for (const [model, modelData] of Object.entries(costsByModel)) { console.log(`\t${model}:\t$${modelData.cost.toFixed(6)} (${modelData.calls} calls)`); } this.reset(); } getCostInTimeWindow(seconds) { const cutoffTime = new Date(Date.now() - seconds * 1000); return this.entries .filter(entry => entry.timestamp && entry.timestamp >= cutoffTime) .reduce((sum, entry) => sum + (entry.cost || 0), 0); } getCostRate(windowSeconds = 60) { const costInWindow = this.getCostInTimeWindow(windowSeconds); return (costInWindow / windowSeconds) * 60; } getUsageInTimeWindow(seconds) { const cutoffTime = new Date(Date.now() - seconds * 1000); return this.entries.filter(entry => entry.timestamp && entry.timestamp >= cutoffTime); } getCostsByModelInTimeWindow(seconds) { const models = {}; const entriesInWindow = this.getUsageInTimeWindow(seconds); for (const entry of entriesInWindow) { if (!models[entry.model]) { models[entry.model] = { cost: 0, calls: 0, }; } models[entry.model].cost += entry.cost || 0; models[entry.model].calls += 1; } return models; } reset() { this.entries = []; this.started = new Date(); } static estimateTokens(text) { if (!text) return 0; return Math.ceil(text.length / 4); } addEstimatedUsage(model, inputText, outputText, metadata) { const usage = { model, input_tokens: CostTracker.estimateTokens(inputText), output_tokens: CostTracker.estimateTokens(outputText), metadata: { ...metadata, estimated: true, }, }; return this.addUsage(usage); } } exports.CostTracker = CostTracker; const globalObj = globalThis; if (!globalObj.__ENSEMBLE_COST_TRACKER__) { globalObj.__ENSEMBLE_COST_TRACKER__ = new CostTracker(); } exports.costTracker = globalObj.__ENSEMBLE_COST_TRACKER__; //# sourceMappingURL=cost_tracker.js.map