Module: ActiveHarness::Pricing::PriceResolver

Defined in:
lib/active_harness/pricing/price_resolver.rb

Overview

Queries all pricing sources for a given model and calculates costs.

All cost methods accept either an agent/result object OR explicit keyword args:

PriceResolver.max_cost(result)          # from ActiveHarness agent result
PriceResolver.max_cost(agent)           # from ActiveHarness agent instance
PriceResolver.max_cost(model_id: "gpt-4o", tokens_input: 1000, tokens_output: 500)

Resolved keys are cached in memory (TTL: 24h) — in production only a handful of models are used, so the first lookup pays the search cost and every subsequent call returns instantly from cache.

Constant Summary collapse

DATA_DIR =
File.expand_path("../../../data", __dir__)
CACHE_TTL =

24 hours

86_400
SOURCES =
{
  pricepertoken: Source.new(File.join(DATA_DIR, "pricepertoken.json"), :pricepertoken),
  modelsdev:     Source.new(File.join(DATA_DIR, "modelsdev.json"),     :modelsdev),
  openrouter:    Source.new(File.join(DATA_DIR, "openrouter.json"),    :openrouter)
}.freeze

Class Method Summary collapse

Class Method Details

.clear_cache!Object

Clears the resolve cache. Useful in tests or after manually refreshing data files.



89
90
91
92
# File 'lib/active_harness/pricing/price_resolver.rb', line 89

def clear_cache!
  @resolve_cache  = nil
  @cache_built_at = nil
end

.costs(subject = nil, model_id: nil, tokens_input: 0, tokens_output: 0) ⇒ Object

Returns { source_name => Float (USD) } — calculated cost per source. Accepts a result/agent object or keyword args.



44
45
46
47
48
49
50
51
52
# File 'lib/active_harness/pricing/price_resolver.rb', line 44

def costs(subject = nil, model_id: nil, tokens_input: 0, tokens_output: 0)
  args = extract_args(subject, model_id: model_id, tokens_input: tokens_input, tokens_output: tokens_output)
  tokens_in  = args[:tokens_input].to_i
  tokens_out = args[:tokens_output].to_i
  resolve(args[:model_id]).transform_values do |p|
    (tokens_in  * p.input_per_1m  / 1_000_000.0) +
    (tokens_out * p.output_per_1m / 1_000_000.0)
  end
end

.max_cost(subject = nil, model_id: nil, tokens_input: 0, tokens_output: 0, provider_cost: nil) ⇒ Object

Returns the highest cost estimate across all sources (conservative upper bound). Accepts a result/agent object or keyword args. Returns nil when no pricing data found. Returns { cost: Float, source: Symbol, all: Hash } otherwise.



58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/active_harness/pricing/price_resolver.rb', line 58

def max_cost(subject = nil, model_id: nil, tokens_input: 0, tokens_output: 0, provider_cost: nil)
  args = extract_args(subject, model_id: model_id, tokens_input: tokens_input,
                               tokens_output: tokens_output, provider_cost: provider_cost)

  return provider_result(args[:provider_cost], args[:model_id]) if args[:provider_cost].to_f > 0

  all = costs(model_id: args[:model_id], tokens_input: args[:tokens_input], tokens_output: args[:tokens_output])
  return nil if all.empty?

  src, cost = all.max_by { |_, v| v }
  { cost: cost, source: src, all: all }
end

.min_cost(subject = nil, model_id: nil, tokens_input: 0, tokens_output: 0, provider_cost: nil) ⇒ Object

Returns the lowest cost estimate across all sources (optimistic lower bound). Accepts a result/agent object or keyword args. Returns nil when no pricing data found. Returns { cost: Float, source: Symbol, all: Hash } otherwise.



75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/active_harness/pricing/price_resolver.rb', line 75

def min_cost(subject = nil, model_id: nil, tokens_input: 0, tokens_output: 0, provider_cost: nil)
  args = extract_args(subject, model_id: model_id, tokens_input: tokens_input,
                               tokens_output: tokens_output, provider_cost: provider_cost)

  return provider_result(args[:provider_cost], args[:model_id]) if args[:provider_cost].to_f > 0

  all = costs(model_id: args[:model_id], tokens_input: args[:tokens_input], tokens_output: args[:tokens_output])
  return nil if all.empty?

  src, cost = all.min_by { |_, v| v }
  { cost: cost, source: src, all: all }
end

.resolve(model_id) ⇒ Object

Returns { source_name => PricingData } for every source that has this model. Result is cached by canonical key for CACHE_TTL seconds.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/active_harness/pricing/price_resolver.rb', line 27

def resolve(model_id)
  key = Normalizer.to_key(model_id)

  cached = resolve_cache[key]
  return cached unless cached.nil?

  result = SOURCES.each_with_object({}) do |(name, source), h|
    hit = source.find(key)
    h[name] = hit if hit&.input_per_1m && hit&.output_per_1m
  end

  resolve_cache[key] = result
  result
end