Class: LlmCostTracker::LlmApiCall

Inherits:
ActiveRecord::Base
  • Object
show all
Extended by:
PeriodGrouping, TagsColumn
Includes:
TagAccessors
Defined in:
lib/llm_cost_tracker/llm_api_call.rb

Constant Summary

Constants included from TagsColumn

TagsColumn::USAGE_BREAKDOWN_COLUMNS, TagsColumn::USAGE_BREAKDOWN_COST_COLUMNS

Class Method Summary collapse

Methods included from PeriodGrouping

daily_costs, group_by_period

Methods included from TagsColumn

latency_column?, pricing_mode_column?, provider_response_id_column?, reset_column_information, stream_column?, tags_json_column?, tags_jsonb_column?, tags_mysql_json_column?, usage_breakdown_columns?, usage_breakdown_cost_columns?, usage_source_column?

Methods included from TagAccessors

#parsed_tags

Class Method Details

.average_latency_msObject



89
90
91
92
93
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 89

def self.average_latency_ms
  return nil unless latency_column?

  average(:latency_ms)&.to_f
end

.by_tag(key, value) ⇒ Object



51
52
53
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 51

def self.by_tag(key, value)
  by_tags(key => value)
end

.by_tags(tags) ⇒ Object



55
56
57
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 55

def self.by_tags(tags)
  TagQuery.apply(self, tags)
end

.cost_by_modelObject



67
68
69
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 67

def self.cost_by_model
  group(:model).sum(:total_cost)
end

.cost_by_providerObject



71
72
73
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 71

def self.cost_by_provider
  group(:provider).sum(:total_cost)
end

.cost_by_tag(key, limit: nil) ⇒ Object



79
80
81
82
83
84
85
86
87
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 79

def self.cost_by_tag(key, limit: nil)
  relation = group_by_tag(key).order(Arel.sql("COALESCE(SUM(total_cost), 0) DESC"))
  relation = relation.limit(limit) if limit

  costs = relation.sum(:total_cost).each_with_object(Hash.new(0.0)) do |(tag_value, cost), grouped|
    grouped[tag_value_label(tag_value)] += cost.to_f
  end
  costs.sort_by { |_label, cost| -cost }.to_h
end

.group_by_tag(key) ⇒ Object



75
76
77
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 75

def self.group_by_tag(key)
  group(Arel.sql(tag_value_expression(key)))
end

.latency_by_modelObject



95
96
97
98
99
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 95

def self.latency_by_model
  return {} unless latency_column?

  group(:model).average(:latency_ms).transform_values(&:to_f)
end

.latency_by_providerObject



101
102
103
104
105
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 101

def self.latency_by_provider
  return {} unless latency_column?

  group(:provider).average(:latency_ms).transform_values(&:to_f)
end

.tag_value_expression(key, table_name: quoted_table_name) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 111

def self.tag_value_expression(key, table_name: quoted_table_name)
  key = validated_tag_key(key)
  column = "#{table_name}.#{connection.quote_column_name('tags')}"

  case connection.adapter_name
  when /postgres/i
    json_column = tags_jsonb_column? ? column : "(#{column})::jsonb"
    "#{json_column}->>#{connection.quote(key)}"
  when /mysql/i
    "JSON_UNQUOTE(JSON_EXTRACT(#{column}, #{connection.quote(json_path(key))}))"
  else
    "json_extract(#{column}, #{connection.quote(json_path(key))})"
  end
end

.tag_value_label(value) ⇒ Object



107
108
109
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 107

def self.tag_value_label(value)
  value.nil? || value == "" ? "(untagged)" : value.to_s
end

.total_costObject



59
60
61
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 59

def self.total_cost
  sum(:total_cost).to_f
end

.total_tokensObject



63
64
65
# File 'lib/llm_cost_tracker/llm_api_call.rb', line 63

def self.total_tokens
  sum(:total_tokens).to_i
end