1+ """
2+ Models Costs Command
3+
4+ Estimate costs for running benchmarks with a model.
5+ """
6+
7+ import asyncio
8+ import click
9+ from rich .console import Console
10+ from rich .table import Table
11+
12+ from src .utils .logging import get_logger
13+
14+ console = Console ()
15+ logger = get_logger (__name__ )
16+
17+
18+ @click .command ()
19+ @click .option ('--model' , '-m' , required = True , help = 'Model ID to estimate costs for' )
20+ @click .option ('--questions' , '-q' , type = int , default = 100 , help = 'Number of questions' )
21+ @click .option ('--input-tokens' , type = int , help = 'Average input tokens per question' )
22+ @click .option ('--output-tokens' , type = int , help = 'Average output tokens per question' )
23+ @click .pass_context
24+ def models_costs (ctx , model , questions , input_tokens , output_tokens ):
25+ """Estimate costs for running benchmarks with a model."""
26+
27+ async def calculate_costs_async ():
28+ try :
29+ from src .models .model_registry import model_registry
30+ from src .models .cost_calculator import CostCalculator
31+
32+ # Validate model using dynamic system
33+ models = await model_registry .get_available_models ()
34+ model_info = None
35+
36+ for m in models :
37+ if m .get ('id' , '' ).lower () == model .lower ():
38+ model_info = m
39+ break
40+
41+ if not model_info :
42+ console .print (f"[red]Model not found: { model } [/red]" )
43+ console .print ("[dim]Use 'models list' or 'models search' to find available models[/dim]" )
44+ return
45+
46+ # Use defaults if not specified - fix variable scoping
47+ default_input_tokens = 100
48+ default_output_tokens = 50
49+
50+ config = ctx .obj .get ('config' ) if ctx .obj else None
51+ if config and hasattr (config , 'costs' ) and hasattr (config .costs , 'estimation' ):
52+ try :
53+ default_input_tokens = getattr (config .costs .estimation , 'default_input_tokens_per_question' , 100 )
54+ default_output_tokens = getattr (config .costs .estimation , 'default_output_tokens_per_question' , 50 )
55+ except AttributeError :
56+ pass # Use defaults
57+
58+ # Apply the values - use different variable names to avoid shadowing
59+ actual_input_tokens = input_tokens if input_tokens is not None else default_input_tokens
60+ actual_output_tokens = output_tokens if output_tokens is not None else default_output_tokens
61+
62+ # Calculate costs using the proper ModelRegistry method
63+ total_input_tokens = questions * actual_input_tokens
64+ total_output_tokens = questions * actual_output_tokens
65+ total_tokens = total_input_tokens + total_output_tokens
66+
67+ # Use ModelRegistry.estimate_cost for proper cost calculation
68+ from src .models .model_registry import ModelRegistry
69+ total_cost = ModelRegistry .estimate_cost (model , total_input_tokens , total_output_tokens )
70+ input_cost = ModelRegistry .estimate_cost (model , total_input_tokens , 0 )
71+ output_cost = ModelRegistry .estimate_cost (model , 0 , total_output_tokens )
72+ cost_per_question = total_cost / questions if questions > 0 else 0
73+
74+ # Get pricing information for display purposes
75+ pricing = model_info .get ('pricing' , {})
76+ input_cost_per_1m = pricing .get ('input_cost_per_1m_tokens' , 0 )
77+ output_cost_per_1m = pricing .get ('output_cost_per_1m_tokens' , 0 )
78+
79+ # If not found in dynamic model info, try static config
80+ if input_cost_per_1m == 0 and output_cost_per_1m == 0 :
81+ static_config = ModelRegistry .get_model_config (model )
82+ if static_config :
83+ input_cost_per_1m = static_config .input_cost_per_1m_tokens
84+ output_cost_per_1m = static_config .output_cost_per_1m_tokens
85+
86+ # Display estimate
87+ table = Table (title = f"Cost Estimate: { model_info .get ('name' , model )} " )
88+ table .add_column ("Parameter" , style = "cyan" )
89+ table .add_column ("Value" , style = "green" )
90+
91+ table .add_row ("Model ID" , model )
92+ table .add_row ("Model Name" , model_info .get ('name' , 'N/A' ))
93+ table .add_row ("Provider" , (model_info .get ('provider' , 'Unknown' )).title ())
94+ table .add_row ("Questions" , f"{ questions :,} " )
95+ table .add_row ("Input Tokens per Question" , f"{ actual_input_tokens :,} " )
96+ table .add_row ("Output Tokens per Question" , f"{ actual_output_tokens :,} " )
97+ table .add_row ("Total Input Tokens" , f"{ total_input_tokens :,} " )
98+ table .add_row ("Total Output Tokens" , f"{ total_output_tokens :,} " )
99+ table .add_row ("Total Tokens" , f"{ total_tokens :,} " )
100+ table .add_row ("Input Cost" , f"${ input_cost :.6f} " )
101+ table .add_row ("Output Cost" , f"${ output_cost :.6f} " )
102+ table .add_row ("Total Cost" , f"${ total_cost :.4f} " )
103+ table .add_row ("Cost per Question" , f"${ cost_per_question :.6f} " )
104+
105+ console .print (table )
106+
107+ # Add context about pricing
108+ if input_cost_per_1m == 0 and output_cost_per_1m == 0 :
109+ console .print ("\n [yellow]⚠️ No pricing information available for this model[/yellow]" )
110+ else :
111+ console .print (f"\n [dim]Based on: ${ input_cost_per_1m :.2f} /${ output_cost_per_1m :.2f} per 1M input/output tokens[/dim]" )
112+
113+ except Exception as e :
114+ console .print (f"[red]Error calculating costs: { str (e )} [/red]" )
115+ logger .exception ("Cost calculation failed" )
116+
117+ asyncio .run (calculate_costs_async ())
0 commit comments