Module: LlmCostTracker::Budget

Defined in:
lib/llm_cost_tracker/budget.rb,
lib/llm_cost_tracker/budget/per_tag.rb

Defined Under Namespace

Modules: PerTag

Constant Summary collapse

BUDGET_TYPE_TO_PERIOD =
{ monthly: :month, daily: :day }.freeze

Class Method Summary collapse

Class Method Details

.check!(event, behavior_override: nil) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/llm_cost_tracker/budget.rb', line 36

def check!(event, behavior_override: nil)
  config = LlmCostTracker.configuration
  return unless event.total_cost

  check_per_call_budget(event, config, behavior_override)
  check_windowed({ daily: config.budgets.daily, monthly: config.budgets.monthly }.compact,
                 time: event.tracked_at) do |budget_type, total, budget|
    handle_exceeded(budget_type: budget_type,
                    total: total,
                    budget: budget,
                    previous_total: total - event.total_cost,
                    last_event: event,
                    behavior: behavior_override)
  end
end

.check_persisted!(events, behavior_override: nil) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/llm_cost_tracker/budget.rb', line 52

def check_persisted!(events, behavior_override: nil)
  by_rule = PerTag.rules_for_events(events.select(&:total_cost))
  by_rule = by_rule.reject { |rule, _| rule.on_exceeded.nil? } if behavior_override == :notify
  window_buckets(by_rule).each do |(key, window, bucket), scored|
    totals = PerTag.spend_by_value(key, scored.keys.map(&:value), window, bucket)
    scored.each do |rule, recorded|
      total = totals.fetch(rule.value, 0).to_d
      limit = rule.windows.fetch(window)
      next if total < limit

      handle_exceeded(
        budget_type: window,
        total: total,
        budget: limit,
        previous_total: total - recorded.sum(&:total_cost),
        last_event: recorded.last,
        scope: scope_for(rule),
        behavior: behavior_override || rule.behavior,
        on_exceeded: rule.on_exceeded
      )
    end
  end
end

.enforce!(provider: nil, model: nil, request: nil, estimate: nil, tags: nil, force: false) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/llm_cost_tracker/budget.rb', line 15

def enforce!(provider: nil, model: nil, request: nil, estimate: nil, tags: nil, force: false)
  config = LlmCostTracker.configuration
  return unless config.enabled

  globally = force || config.budgets.exceeded_behavior == :block_requests
  per_tag = force || PerTag.blocking?
  return unless globally || per_tag

  estimate ||= estimate_cost(provider: provider, model: model, request: request)
  now = Time.now.utc
  enforce_globally(config, estimate: estimate, time: now) if globally
  return unless per_tag

  check_per_tag(tags || Tags::Context.tags,
                time: now,
                estimate: estimate,
                blocking_only: !force) do |rule, window, total, limit|
    raise_pre_send(budget_type: window, total: total, budget: limit, scope: scope_for(rule))
  end
end

.notify_persisted_safely!(events) ⇒ Object



76
77
78
79
80
# File 'lib/llm_cost_tracker/budget.rb', line 76

def notify_persisted_safely!(events)
  check_persisted!(events, behavior_override: :notify)
rescue StandardError => e
  Logging.warn("Per-tag budget check failed after ingest: #{e.class}: #{e.message}")
end