#!/usr/bin/env python3 """ Pricing Data Update Script Updates config/pricing.json with latest LLM provider pricing. Run periodically to keep cost estimates accurate. Usage: python utils/update_pricing.py [--dry-run] [--output PATH] Examples: # Update pricing.json python utils/update_pricing.py # Preview changes without writing python utils/update_pricing.py --dry-run # Write to custom location python utils/update_pricing.py --output /path/to/pricing.json """ import argparse import json import logging from datetime import datetime from pathlib import Path from typing import Dict # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) # Pricing data (as of 2026-01-19) # Update these values periodically from provider documentation PROVIDER_PRICING = { "last_updated": datetime.now().isoformat(), "version": "2.0", "providers": { "openai": { "models": { "gpt-4-turbo": { "input_cost_per_1m_tokens": 10.00, "output_cost_per_1m_tokens": 30.00, "context_window": 128000, "notes": "GPT-4 Turbo - Latest model", }, "gpt-4": { "input_cost_per_1m_tokens": 30.00, "output_cost_per_1m_tokens": 60.00, "context_window": 8192, "notes": "GPT-4 Original", }, "gpt-3.5-turbo": { "input_cost_per_1m_tokens": 0.50, "output_cost_per_1m_tokens": 1.50, "context_window": 16385, "notes": "GPT-3.5 Turbo", }, }, "default_model": "gpt-3.5-turbo", }, "anthropic": { "models": { "claude-3-5-sonnet-20241022": { "input_cost_per_1m_tokens": 3.00, "output_cost_per_1m_tokens": 15.00, "context_window": 200000, "notes": "Claude 3.5 Sonnet - Best overall", }, "claude-3-opus-20240229": { "input_cost_per_1m_tokens": 15.00, "output_cost_per_1m_tokens": 75.00, "context_window": 200000, "notes": "Claude 3 Opus - Highest intelligence", }, "claude-3-haiku-20240307": { "input_cost_per_1m_tokens": 0.25, "output_cost_per_1m_tokens": 1.25, "context_window": 200000, "notes": "Claude 3 Haiku - Fastest, most affordable", }, }, "default_model": "claude-3-5-sonnet-20241022", }, "huggingface": { "routing_policies": { ":cheapest": { "estimated_cost_range": { "min_per_1m_tokens": 0.00, "max_per_1m_tokens": 0.20, }, "free_tier_available": True, "notes": "Automatically selects lowest-cost provider, often free tier", }, ":fastest": { "estimated_cost_range": { "min_per_1m_tokens": 0.50, "max_per_1m_tokens": 3.00, }, "free_tier_available": False, "notes": "Selects highest-quality models, typically paid tier", }, "auto": { "estimated_cost_range": { "min_per_1m_tokens": 0.05, "max_per_1m_tokens": 0.20, }, "free_tier_available": True, "notes": "Default routing, usually Llama 3.3 70B", }, }, "providers": { "groq": { "estimated_cost_per_1m_tokens": 0.00, "free_tier": True, "notes": "Ultra-fast inference, free tier available", }, "together": { "estimated_cost_per_1m_tokens": 0.10, "free_tier": True, "notes": "Good balance of cost and quality, some free models", }, "replicate": { "estimated_cost_per_1m_tokens": 0.15, "free_tier": False, "notes": "Wide model selection", }, "cerebras": { "estimated_cost_per_1m_tokens": 0.20, "free_tier": False, "notes": "High-performance inference", }, "fireworks": { "estimated_cost_per_1m_tokens": 0.10, "free_tier": False, "notes": "Fast and cost-effective", }, "deepinfra": { "estimated_cost_per_1m_tokens": 0.08, "free_tier": False, "notes": "Budget-friendly option", }, }, "models": { "meta-llama/Llama-3.3-70B-Instruct": { "estimated_cost_per_1m_tokens": 0.10, "free_tier_available": True, "context_window": 128000, "notes": "Default model for auto routing", } }, "default_routing": "auto", }, "qwen": { "models": { "qwen-turbo": { "input_cost_per_1m_tokens": 0.20, "output_cost_per_1m_tokens": 0.60, "context_window": 8192, "notes": "Qwen Turbo via DashScope", } }, "default_model": "qwen-turbo", }, }, "free_tier_detection": { "enabled": True, "providers": ["huggingface"], "keywords": ["free", "groq", "together"], "notes": "Automatically detect and display $0.00 for free tier usage", }, "notes": [ "Prices are estimates and may vary based on provider discounts, credits, and billing variations", "Always verify final costs with provider billing", "Update this file periodically using: python utils/update_pricing.py", "Free tier availability may change - check provider documentation", ], } def load_current_pricing(path: Path) -> Dict: """Load current pricing from file.""" try: with open(path, "r") as f: return json.load(f) except FileNotFoundError: logger.warning(f"Pricing file not found: {path}") return {} except json.JSONDecodeError as e: logger.error(f"Invalid JSON in pricing file: {e}") return {} def compare_pricing(old: Dict, new: Dict) -> Dict: """Compare old and new pricing, return changes.""" changes = { "added": [], "removed": [], "modified": [], } # Check for version change old_version = old.get("version", "unknown") new_version = new.get("version", "unknown") if old_version != new_version: changes["modified"].append(f"Version: {old_version} → {new_version}") # Check for provider changes old_providers = set(old.get("providers", {}).keys()) new_providers = set(new.get("providers", {}).keys()) changes["added"].extend([f"Provider: {p}" for p in new_providers - old_providers]) changes["removed"].extend([f"Provider: {p}" for p in old_providers - new_providers]) return changes def format_changes(changes: Dict) -> str: """Format changes as readable string.""" lines = [] if changes["added"]: lines.append("Added:") for item in changes["added"]: lines.append(f" + {item}") if changes["removed"]: lines.append("Removed:") for item in changes["removed"]: lines.append(f" - {item}") if changes["modified"]: lines.append("Modified:") for item in changes["modified"]: lines.append(f" ✎ {item}") if not any(changes.values()): lines.append("No changes detected") return "\n".join(lines) def write_pricing(pricing: Dict, path: Path, dry_run: bool = False): """Write pricing to file.""" if dry_run: logger.info("DRY RUN - Would write to: %s", path) logger.info("Preview:") logger.info(json.dumps(pricing, indent=2)) return try: # Create directory if needed path.parent.mkdir(parents=True, exist_ok=True) # Write with pretty formatting with open(path, "w") as f: json.dump(pricing, f, indent=2) logger.info(f"✅ Pricing updated: {path}") except Exception as e: logger.error(f"❌ Failed to write pricing: {e}") raise def main(): """Main entry point.""" parser = argparse.ArgumentParser( description="Update LLM provider pricing data", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Update pricing.json python utils/update_pricing.py # Preview changes without writing python utils/update_pricing.py --dry-run # Write to custom location python utils/update_pricing.py --output /path/to/pricing.json """, ) parser.add_argument( "--dry-run", action="store_true", help="Preview changes without writing to file" ) parser.add_argument( "--output", type=Path, help="Output path for pricing.json (default: config/pricing.json)", ) args = parser.parse_args() # Determine output path if args.output: output_path = args.output else: # Default to config/pricing.json relative to script location script_dir = Path(__file__).parent output_path = script_dir.parent / "config" / "pricing.json" logger.info("=" * 60) logger.info("LLM Provider Pricing Update") logger.info("=" * 60) logger.info(f"Output path: {output_path}") logger.info(f"Dry run: {args.dry_run}") logger.info("") # Load current pricing current_pricing = load_current_pricing(output_path) # Compare with new pricing changes = compare_pricing(current_pricing, PROVIDER_PRICING) logger.info("Changes:") logger.info(format_changes(changes)) logger.info("") # Write new pricing write_pricing(PROVIDER_PRICING, output_path, dry_run=args.dry_run) if not args.dry_run: logger.info("") logger.info("✅ Pricing update complete!") logger.info(f"Updated: {output_path}") logger.info(f"Last updated: {PROVIDER_PRICING['last_updated']}") if __name__ == "__main__": main()