Class: LanguageOperator::Agent::Safety::BudgetTracker

Inherits:
Object
  • Object
show all
Includes:
Loggable
Defined in:
lib/language_operator/agent/safety/budget_tracker.rb

Overview

Budget Tracker for enforcing cost and token limits

Tracks cumulative costs and token usage per agent instance and enforces configured budgets.

Examples:

tracker = BudgetTracker.new(daily_budget: 10.0, hourly_budget: 1.0)
tracker.check_budget!(estimated_cost: 0.05, estimated_tokens: 500)

Defined Under Namespace

Classes: BudgetExceededError

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Loggable

#logger

Constructor Details

#initialize(daily_budget: nil, hourly_budget: nil, token_budget: nil) ⇒ BudgetTracker

Returns a new instance of BudgetTracker.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/language_operator/agent/safety/budget_tracker.rb', line 24

def initialize(daily_budget: nil, hourly_budget: nil, token_budget: nil)
  @daily_budget = daily_budget&.to_f
  @hourly_budget = hourly_budget&.to_f
  @token_budget = token_budget&.to_i

  @daily_spending = 0.0
  @hourly_spending = 0.0
  @daily_tokens = 0
  @hourly_tokens = 0

  @day_start = Time.now
  @hour_start = Time.now

  logger.info('Budget tracker initialized',
              daily_budget: @daily_budget&.round(2),
              hourly_budget: @hourly_budget&.round(2),
              token_budget: @token_budget)
end

Instance Attribute Details

#daily_budgetObject (readonly)

Returns the value of attribute daily_budget.



20
21
22
# File 'lib/language_operator/agent/safety/budget_tracker.rb', line 20

def daily_budget
  @daily_budget
end

#hourly_budgetObject (readonly)

Returns the value of attribute hourly_budget.



20
21
22
# File 'lib/language_operator/agent/safety/budget_tracker.rb', line 20

def hourly_budget
  @hourly_budget
end

#token_budgetObject (readonly)

Returns the value of attribute token_budget.



20
21
22
# File 'lib/language_operator/agent/safety/budget_tracker.rb', line 20

def token_budget
  @token_budget
end

Instance Method Details

#check_budget!(estimated_cost: 0.0, estimated_tokens: 0) ⇒ Object

Check if a request would exceed budget limits

Parameters:

  • estimated_cost (Float) (defaults to: 0.0)

    Estimated cost for the request

  • estimated_tokens (Integer) (defaults to: 0)

    Estimated token count

Raises:



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/language_operator/agent/safety/budget_tracker.rb', line 48

def check_budget!(estimated_cost: 0.0, estimated_tokens: 0)
  reset_if_needed

  # Check daily budget
  if @daily_budget && (@daily_spending + estimated_cost) > @daily_budget
    remaining = [@daily_budget - @daily_spending, 0].max
    logger.error('Daily budget exceeded',
                 current: @daily_spending.round(4),
                 limit: @daily_budget,
                 remaining: remaining.round(4),
                 requested: estimated_cost.round(4))
    raise BudgetExceededError,
          "Daily budget exceeded: $#{@daily_spending.round(4)}/$#{@daily_budget} " \
          "(requested: $#{estimated_cost.round(4)})"
  end

  # Check hourly budget
  if @hourly_budget && (@hourly_spending + estimated_cost) > @hourly_budget
    remaining = [@hourly_budget - @hourly_spending, 0].max
    logger.error('Hourly budget exceeded',
                 current: @hourly_spending.round(4),
                 limit: @hourly_budget,
                 remaining: remaining.round(4),
                 requested: estimated_cost.round(4))
    raise BudgetExceededError,
          "Hourly budget exceeded: $#{@hourly_spending.round(4)}/$#{@hourly_budget} " \
          "(requested: $#{estimated_cost.round(4)})"
  end

  # Check token budget
  if @token_budget && (@daily_tokens + estimated_tokens) > @token_budget
    remaining = [@token_budget - @daily_tokens, 0].max
    logger.error('Token budget exceeded',
                 current: @daily_tokens,
                 limit: @token_budget,
                 remaining: remaining,
                 requested: estimated_tokens)
    raise BudgetExceededError,
          "Token budget exceeded: #{@daily_tokens}/#{@token_budget} " \
          "(requested: #{estimated_tokens})"
  end

  logger.debug('Budget check passed',
               estimated_cost: estimated_cost.round(4),
               estimated_tokens: estimated_tokens)
end

#record_spending(cost:, tokens:) ⇒ Object

Record actual spending after a request

Parameters:

  • cost (Float)

    Actual cost of the request

  • tokens (Integer)

    Actual token count



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/language_operator/agent/safety/budget_tracker.rb', line 99

def record_spending(cost:, tokens:)
  reset_if_needed

  @daily_spending += cost
  @hourly_spending += cost
  @daily_tokens += tokens
  @hourly_tokens += tokens

  logger.debug('Spending recorded',
               cost: cost.round(4),
               tokens: tokens,
               daily_total: @daily_spending.round(4),
               hourly_total: @hourly_spending.round(4),
               daily_tokens: @daily_tokens)
end

#statusHash

Get current budget status

Returns:

  • (Hash)

    Budget status information



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/language_operator/agent/safety/budget_tracker.rb', line 118

def status
  reset_if_needed

  {
    daily: {
      spending: @daily_spending.round(4),
      budget: @daily_budget&.round(2),
      remaining: @daily_budget ? (@daily_budget - @daily_spending).round(4) : nil,
      percentage: @daily_budget ? ((@daily_spending / @daily_budget) * 100).round(1) : nil
    },
    hourly: {
      spending: @hourly_spending.round(4),
      budget: @hourly_budget&.round(2),
      remaining: @hourly_budget ? (@hourly_budget - @hourly_spending).round(4) : nil,
      percentage: @hourly_budget ? ((@hourly_spending / @hourly_budget) * 100).round(1) : nil
    },
    tokens: {
      used: @daily_tokens,
      budget: @token_budget,
      remaining: @token_budget ? (@token_budget - @daily_tokens) : nil,
      percentage: @token_budget ? ((@daily_tokens.to_f / @token_budget) * 100).round(1) : nil
    }
  }
end