Class: SolidAgent::ModelPricing

Inherits:
Object
  • Object
show all
Defined in:
lib/solid_agent/model_pricing.rb

Overview

Estimates LLM spend from token counts (USD). Generation records store tokens only; pricing is layered on top for cost reporting, so figures are always estimates.

Rates come from RubyLLM's model registry (USD per million tokens, maintained upstream per model) when that gem is available and knows the model; the static pattern table below is the fallback for aliases and self-hosted models, and a conservative blended rate covers everything else so totals stay meaningful.

Examples:

Estimate a generation's cost

SolidAgent::ModelPricing.estimate(
  model: "claude-sonnet-5", input_tokens: 12_000, output_tokens: 800
) # => 0.048

Look up a model's rates ($/1M input, $/1M output)

SolidAgent::ModelPricing.rate_for("gpt-4o-mini") # => [0.15, 0.60]

Constant Summary collapse

PRICES =
[
  # [pattern, input $/1M, output $/1M] — first match wins, so mock
  # models ("mock-gpt-4o-mini") price free before real-model patterns.
  [ /mock/i, 0.0, 0.0 ],
  [ /gpt-4o-mini/i, 0.15, 0.60 ],
  [ /gpt-4o/i, 2.50, 10.00 ],
  [ /gpt-4\.1-nano/i, 0.10, 0.40 ],
  [ /gpt-4\.1-mini/i, 0.40, 1.60 ],
  [ /gpt-4\.1/i, 2.00, 8.00 ],
  [ /o3-mini|o4-mini/i, 1.10, 4.40 ],
  [ /claude.*(fable|mythos)/i, 10.00, 50.00 ],
  [ /claude.*haiku-?4/i, 1.00, 5.00 ],
  [ /claude.*(haiku)/i, 0.80, 4.00 ],
  [ /claude.*(sonnet)/i, 3.00, 15.00 ],
  [ /claude.*opus-(5|4-[5-9])/i, 5.00, 25.00 ],
  [ /claude.*(opus)/i, 15.00, 75.00 ],
  [ /gemini.*flash/i, 0.10, 0.40 ],
  [ /gemini.*pro/i, 1.25, 10.00 ],
  [ /llama|mistral|mixtral|qwen|deepseek/i, 0.20, 0.60 ]
].freeze
DEFAULT_RATE =

Fallback blended rate for unknown models ($/1M input, $/1M output)

[ 1.00, 4.00 ].freeze

Class Method Summary collapse

Class Method Details

.estimate(model:, input_tokens:, output_tokens:) ⇒ Float?

Returns estimated USD cost, nil when there is nothing to price.

Returns:

  • (Float, nil)

    estimated USD cost, nil when there is nothing to price



49
50
51
52
53
54
55
56
# File 'lib/solid_agent/model_pricing.rb', line 49

def estimate(model:, input_tokens:, output_tokens:)
  input = input_tokens.to_i
  output = output_tokens.to_i
  return nil if input.zero? && output.zero?

  input_rate, output_rate = rate_for(model)
  ((input * input_rate) + (output * output_rate)) / 1_000_000.0
end

.rate_for(model) ⇒ Array(Float, Float)

Returns $/1M input and output token rates.

Returns:

  • (Array(Float, Float))

    $/1M input and output token rates



59
60
61
62
63
# File 'lib/solid_agent/model_pricing.rb', line 59

def rate_for(model)
  return DEFAULT_RATE if model.blank?

  registry_rate(model) || static_rate(model)
end

.registry_rate(model) ⇒ Object

Exact per-model rates from RubyLLM's registry when that gem is loaded. Lookups are memoized — the registry scan is not free and cost reporting calls this per row.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/solid_agent/model_pricing.rb', line 68

def registry_rate(model)
  return nil unless defined?(::RubyLLM)

  @registry_rates ||= {}
  return @registry_rates[model] if @registry_rates.key?(model)

  @registry_rates[model] = begin
    info = ::RubyLLM.models.find(model.to_s)
    tokens = info&.pricing&.text_tokens
    if tokens&.input && tokens&.output
      [ tokens.input, tokens.output ]
    end
  rescue ::StandardError
    nil
  end
end

.static_rate(model) ⇒ Object



85
86
87
88
89
90
# File 'lib/solid_agent/model_pricing.rb', line 85

def static_rate(model)
  PRICES.each do |pattern, input_rate, output_rate|
    return [ input_rate, output_rate ] if model.to_s.match?(pattern)
  end
  DEFAULT_RATE
end