Module: LlmCostTracker::Pricing::Registry

Defined in:
lib/llm_cost_tracker/pricing/registry.rb

Constant Summary collapse

DEFAULT_PRICES_PATH =
File.expand_path("../prices.json", __dir__)
CONTEXT_THRESHOLD_KEY =
"_context_price_threshold_tokens"
PRICE_KEYS =
Usage::Catalog.token_priced.map(&:key).freeze
METADATA_KEYS =
["_source", CONTEXT_THRESHOLD_KEY].freeze

Class Method Summary collapse

Class Method Details

.builtin_pricesObject



36
37
38
39
40
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 36

def builtin_prices
  @builtin_prices ||= normalize_price_entries(
    raw_registry.fetch("models", {}), context: "bundled prices"
  ).freeze
end

.builtin_ratesObject



81
82
83
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 81

def builtin_rates
  @builtin_rates ||= rates_from_registry(raw_registry, context: DEFAULT_PRICES_PATH).freeze
end

.file_metadata(path) ⇒ Object

Raises:



46
47
48
49
50
51
52
53
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 46

def (path)
  return {} unless path

  meta = raw_file_registry(path).fetch("metadata", {})
  return meta if meta.is_a?(Hash)

  raise Error, "Unable to load prices_file #{path.inspect}: prices_file metadata must be a hash"
end

.file_prices(path) ⇒ Object



55
56
57
58
59
60
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 55

def file_prices(path)
  return {} unless path

  prices, @file_prices = memoize_in(@file_prices, path) { load_file_prices(path) }
  prices
end

.file_rates(path) ⇒ Object



85
86
87
88
89
90
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 85

def file_rates(path)
  return {} unless path

  rates, @file_rates = memoize_in(@file_rates, path) { load_file_rates(path) }
  rates
end

.metadataObject



42
43
44
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 42

def 
  @metadata ||= raw_registry.fetch("metadata", {}).freeze
end

.normalize_price_entries(table, context:) ⇒ Object

Raises:

  • (ArgumentError)


62
63
64
65
66
67
68
69
70
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 62

def normalize_price_entries(table, context:)
  table = {} if table.nil?
  raise ArgumentError, "#{context} must be a hash of models" unless table.is_a?(Hash)

  table.each_with_object({}) do |(model, price), normalized|
    price = validate_price_entry(price, model: model, context: context)
    normalized[model.to_s] = normalize_price_entry(model, price, context)
  end
end

.prices_file_mtime_isoObject



103
104
105
106
107
108
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 103

def prices_file_mtime_iso
  path = LlmCostTracker.configuration.prices_file
  return nil unless path && File.exist?(path)

  @prices_file_mtime_iso ||= File.mtime(path).utc.iso8601
end

.rates_from_registry(registry, context:) ⇒ Object

Raises:

  • (ArgumentError)


92
93
94
95
96
97
98
99
100
101
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 92

def rates_from_registry(registry, context:)
  data = registry.fetch("service_charges", {})
  raise ArgumentError, "#{context} service_charges must be a hash" unless data.is_a?(Hash)

  currency = upcased_currency(registry.dig("metadata", "currency"))
  data.each_with_object({}) do |(provider, entries), rates|
    section_context = "#{context} service_charges.#{provider}"
    rates[provider] = rates_from_section(entries, currency: currency, context: section_context)
  end
end

.raw_file_registry(path) ⇒ Object



76
77
78
79
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 76

def raw_file_registry(path)
  registry, @raw_file_registries = memoize_in(@raw_file_registries, path) { load_raw_file_registry(path) }
  registry
end

.raw_registryObject



72
73
74
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 72

def raw_registry
  @raw_registry ||= YAML.safe_load_file(DEFAULT_PRICES_PATH, aliases: false).freeze
end

.reset!Object



23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 23

def reset!
  @builtin_prices = nil
  @metadata = nil
  @raw_registry = nil
  @raw_file_registries = nil
  @file_prices = nil
  @builtin_rates = nil
  @file_rates = nil
  @sources = nil
  @sorted_price_keys_cache = nil
  @prices_file_mtime_iso = nil
end

.sorted_price_keys(table) ⇒ Object



139
140
141
142
143
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 139

def sorted_price_keys(table)
  keys, @sorted_price_keys_cache =
    memoize_in(@sorted_price_keys_cache, table, identity: true) { table.keys.sort_by { |key| -key.length } }
  keys
end

.sourcesObject



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/llm_cost_tracker/pricing/registry.rb', line 110

def sources
  @sources ||= begin
    config = LlmCostTracker.configuration
    [
      Source.new(
        name: "pricing_overrides",
        prices: config.pricing_overrides,
        rates: {},
        currency: upcased_currency(nil),
        version: "configuration"
      ),
      Source.new(
        name: "prices_file",
        prices: file_prices(config.prices_file),
        rates: file_rates(config.prices_file),
        currency: upcased_currency((config.prices_file)["currency"]),
        version: prices_file_mtime_iso
      ),
      Source.new(
        name: "bundled",
        prices: builtin_prices,
        rates: builtin_rates,
        currency: upcased_currency(["currency"]),
        version: LlmCostTracker::VERSION
      )
    ].freeze
  end
end