UNPKG

devibe

Version:

Intelligent repository cleanup with auto mode, AI learning, markdown consolidation, auto-consolidate workflow, context-aware classification, and cost optimization

312 lines (306 loc) 12.2 kB
/** * AI Batch Optimizer * * Intelligent batching strategy to maximize files per API call * while staying within context window limits. */ // Default configuration (can be overridden by model) const DEFAULT_BATCH_CONFIG = { // Token estimation (conservative) CHARS_PER_TOKEN: 3.3, // ~0.3 tokens per char BASE_PROMPT_TOKENS: 200, PER_FILE_OVERHEAD_TOKENS: 50, PER_FILE_RESPONSE_TOKENS: 100, SAFETY_MARGIN_PERCENT: 0.15, // Reserve 15% for safety // Sampling strategies by file size TIERS: { micro: { maxSize: 200, contentChars: 200, name: 'micro' }, small: { maxSize: 2000, contentChars: 1000, name: 'small' }, medium: { maxSize: 10000, contentChars: 1500, name: 'medium' }, large: { maxSize: Infinity, contentChars: 1200, name: 'large' }, }, }; /** * Create model-specific batch configuration */ export function createBatchConfig(model) { const maxContextTokens = model.contextWindow; const safetyMargin = Math.floor(maxContextTokens * DEFAULT_BATCH_CONFIG.SAFETY_MARGIN_PERCENT); const targetInputTokens = maxContextTokens - safetyMargin; return { maxContextTokens, targetInputTokens, safetyMargin, charsPerToken: DEFAULT_BATCH_CONFIG.CHARS_PER_TOKEN, basePromptTokens: DEFAULT_BATCH_CONFIG.BASE_PROMPT_TOKENS, perFileOverheadTokens: DEFAULT_BATCH_CONFIG.PER_FILE_OVERHEAD_TOKENS, perFileResponseTokens: DEFAULT_BATCH_CONFIG.PER_FILE_RESPONSE_TOKENS, tiers: DEFAULT_BATCH_CONFIG.TIERS, }; } // For backwards compatibility export const BATCH_CONFIG = DEFAULT_BATCH_CONFIG; export class AIBatchOptimizer { model; config; constructor(model) { this.model = model; // Use model-specific config or default if (model) { this.config = createBatchConfig(model); } else { this.config = { maxContextTokens: 200000, targetInputTokens: 170000, safetyMargin: 30000, charsPerToken: DEFAULT_BATCH_CONFIG.CHARS_PER_TOKEN, basePromptTokens: DEFAULT_BATCH_CONFIG.BASE_PROMPT_TOKENS, perFileOverheadTokens: DEFAULT_BATCH_CONFIG.PER_FILE_OVERHEAD_TOKENS, perFileResponseTokens: DEFAULT_BATCH_CONFIG.PER_FILE_RESPONSE_TOKENS, tiers: DEFAULT_BATCH_CONFIG.TIERS, }; } } /** * Estimate tokens for a string (conservative estimate) */ estimateTokens(text) { return Math.ceil(text.length / this.config.charsPerToken); } /** * Determine optimal content sampling for a file */ sampleFileContent(file) { const { content, size } = file; // Tier 1: Micro files - include everything if (size <= this.config.tiers.micro.maxSize) { return { sample: content, strategy: 'full', }; } // Tier 2: Small files - first N chars if (size <= this.config.tiers.small.maxSize) { return { sample: content.slice(0, this.config.tiers.small.contentChars), strategy: 'head', }; } // Tier 3: Medium files - strategic sampling (head + middle + tail) if (size <= this.config.tiers.medium.maxSize) { const third = Math.floor(this.config.tiers.medium.contentChars / 3); const head = content.slice(0, third); const middle = content.slice(size / 2 - third / 2, size / 2 + third / 2); const tail = content.slice(-third); return { sample: `${head}\n[...middle...]\n${middle}\n[...end...]\n${tail}`, strategy: 'strategic', }; } // Tier 4: Large files - header + sparse sampling + footer const charsPerSection = Math.floor(this.config.tiers.large.contentChars / 3); const head = content.slice(0, charsPerSection); // Sample every ~10% of the file const samples = []; const step = Math.floor(size / 10); for (let i = step; i < size - step; i += step) { samples.push(content.slice(i, i + 50)); // 50 chars per sample } const middle = samples.join('\n[...]\n'); const tail = content.slice(-charsPerSection); return { sample: `${head}\n[...sampled...]\n${middle}\n[...end...]\n${tail}`, strategy: 'sparse', }; } /** * Calculate optimal batches for maximum throughput * Now uses model-specific context window limits for intelligent packing */ createOptimalBatches(files) { const batches = []; // Sort by size (small first) for better packing const sorted = [...files].sort((a, b) => a.size - b.size); let currentBatch = []; let currentInputTokens = this.config.basePromptTokens; let currentOutputTokens = 0; let currentTier = 'micro'; for (const file of sorted) { // Sample the file content const { sample, strategy } = this.sampleFileContent(file); // Estimate tokens for this file const contentTokens = this.estimateTokens(sample); const overheadTokens = this.config.perFileOverheadTokens; const fileInputTokens = contentTokens + overheadTokens; const fileOutputTokens = this.config.perFileResponseTokens; // Determine tier let tier = 'large'; if (file.size <= this.config.tiers.micro.maxSize) tier = 'micro'; else if (file.size <= this.config.tiers.small.maxSize) tier = 'small'; else if (file.size <= this.config.tiers.medium.maxSize) tier = 'medium'; // Check if adding this file exceeds limits const projectedInputTokens = currentInputTokens + fileInputTokens; const projectedOutputTokens = currentOutputTokens + fileOutputTokens; const totalProjected = projectedInputTokens + projectedOutputTokens; const limit = this.config.targetInputTokens + this.config.perFileResponseTokens * currentBatch.length; if (totalProjected > limit && currentBatch.length > 0) { // Batch is full, save it and start new batch batches.push({ files: currentBatch, estimatedInputTokens: currentInputTokens, estimatedOutputTokens: currentOutputTokens, tier: currentTier, }); // Reset for new batch currentBatch = []; currentInputTokens = this.config.basePromptTokens; currentOutputTokens = 0; currentTier = tier; } // Add file to current batch currentBatch.push({ path: file.path, name: file.name, size: file.size, contentSample: sample, samplingStrategy: strategy, }); currentInputTokens += fileInputTokens; currentOutputTokens += fileOutputTokens; // Update tier to the largest in batch if (tier === 'large' || (tier === 'medium' && currentTier !== 'large') || (tier === 'small' && currentTier === 'micro')) { currentTier = tier; } } // Add final batch if it has files if (currentBatch.length > 0) { batches.push({ files: currentBatch, estimatedInputTokens: currentInputTokens, estimatedOutputTokens: currentOutputTokens, tier: currentTier, }); } return batches; } /** * Build a compact, token-efficient prompt */ buildCompactPrompt(batch, repositories) { // Ultra-compact format to save tokens const repoList = repositories.join('|'); const filesList = batch.files.map((f, idx) => { return `${idx}|${f.name}|${f.path}\n${f.contentSample}\n--`; }).join('\n'); // Minimal prompt - every token counts! return `Classify ${batch.files.length} files. Categories: doc|script|test|src|config|asset. Repos: ${repoList} FILES: ${filesList} JSON: [{i,cat,conf,repo,why}]`; } /** * Get statistics about batching efficiency */ getBatchStats(batches) { const totalFiles = batches.reduce((sum, b) => sum + b.files.length, 0); const totalInputTokens = batches.reduce((sum, b) => sum + b.estimatedInputTokens, 0); const totalOutputTokens = batches.reduce((sum, b) => sum + b.estimatedOutputTokens, 0); // Pricing: $3/M input tokens, $15/M output tokens (Claude 3.5 Sonnet) const inputCost = (totalInputTokens / 1_000_000) * 3; const outputCost = (totalOutputTokens / 1_000_000) * 15; const estimatedCost = inputCost + outputCost; const byTier = { micro: 0, small: 0, medium: 0, large: 0, }; batches.forEach(b => { byTier[b.tier] += b.files.length; }); return { totalFiles, totalBatches: batches.length, avgFilesPerBatch: totalFiles / batches.length, avgInputTokens: totalInputTokens / batches.length, avgOutputTokens: totalOutputTokens / batches.length, estimatedCost, byTier, }; } /** * Validate that batches fit within limits */ validateBatches(batches) { const issues = []; for (let i = 0; i < batches.length; i++) { const batch = batches[i]; const total = batch.estimatedInputTokens + batch.estimatedOutputTokens; if (total > this.config.maxContextTokens) { issues.push(`Batch ${i} exceeds context limit: ${total} > ${this.config.maxContextTokens}`); } if (batch.files.length === 0) { issues.push(`Batch ${i} is empty`); } } return { valid: issues.length === 0, issues, }; } /** * Get model info for reporting */ getModelInfo() { if (!this.model) return null; return { name: this.model.name, contextWindow: this.model.contextWindow, }; } /** * Calculate efficiency metrics */ calculateEfficiency(batches) { const totalFiles = batches.reduce((sum, b) => sum + b.files.length, 0); const totalTokensUsed = batches.reduce((sum, b) => sum + b.estimatedInputTokens + b.estimatedOutputTokens, 0); const maxPossibleTokens = batches.length * this.config.maxContextTokens; return { contextUtilization: maxPossibleTokens > 0 ? totalTokensUsed / maxPossibleTokens : 0, avgBatchSize: totalFiles / batches.length, totalApiCalls: batches.length, estimatedTimeSeconds: batches.length * 2.5, // ~2.5s per API call }; } } // Example usage: /* const optimizer = new AIBatchOptimizer(); // Create optimal batches const files = await loadFilesToClassify(); const batches = optimizer.createOptimalBatches(files); // Get statistics const stats = optimizer.getBatchStats(batches); console.log(`Batching ${stats.totalFiles} files into ${stats.totalBatches} batches`); console.log(`Average: ${stats.avgFilesPerBatch.toFixed(1)} files/batch`); console.log(`Estimated cost: $${stats.estimatedCost.toFixed(4)}`); console.log(`By tier:`, stats.byTier); // Validate const validation = optimizer.validateBatches(batches); if (!validation.valid) { console.error('Batch validation failed:', validation.issues); } // Process each batch for (const batch of batches) { const prompt = optimizer.buildCompactPrompt(batch, ['root', 'app1', 'app2']); const results = await callAnthropicAPI(prompt); // Process results... } */ //# sourceMappingURL=ai-batch-optimizer.js.map