llm_cost_tracker 0.3.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +46 -25
- data/app/services/llm_cost_tracker/dashboard/data_quality.rb +96 -23
- data/app/services/llm_cost_tracker/dashboard/data_quality_aggregate.rb +81 -0
- data/app/views/llm_cost_tracker/data_quality/index.html.erb +65 -0
- data/lib/llm_cost_tracker/budget.rb +73 -22
- data/lib/llm_cost_tracker/configuration.rb +4 -0
- data/lib/llm_cost_tracker/cost.rb +1 -2
- data/lib/llm_cost_tracker/errors.rb +22 -3
- data/lib/llm_cost_tracker/event.rb +4 -0
- data/lib/llm_cost_tracker/event_metadata.rb +21 -15
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/{add_monthly_totals_generator.rb → add_period_totals_generator.rb} +4 -4
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_usage_breakdown_generator.rb +29 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_period_totals_to_llm_cost_tracker.rb.erb +96 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_usage_breakdown_to_llm_api_calls.rb.erb +29 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb +11 -5
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +3 -1
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/llm_cost_tracker_prices.yml.erb +11 -3
- data/lib/llm_cost_tracker/parsed_usage.rb +16 -7
- data/lib/llm_cost_tracker/parsers/anthropic.rb +24 -55
- data/lib/llm_cost_tracker/parsers/base.rb +80 -0
- data/lib/llm_cost_tracker/parsers/gemini.rb +17 -37
- data/lib/llm_cost_tracker/parsers/openai.rb +1 -6
- data/lib/llm_cost_tracker/parsers/openai_compatible.rb +6 -15
- data/lib/llm_cost_tracker/parsers/openai_usage.rb +25 -34
- data/lib/llm_cost_tracker/parsers/registry.rb +17 -2
- data/lib/llm_cost_tracker/period_total.rb +9 -0
- data/lib/llm_cost_tracker/price_registry.rb +14 -4
- data/lib/llm_cost_tracker/price_sync/merger.rb +1 -1
- data/lib/llm_cost_tracker/price_sync/raw_price.rb +3 -5
- data/lib/llm_cost_tracker/price_sync/sources/litellm.rb +2 -3
- data/lib/llm_cost_tracker/price_sync/sources/open_router.rb +2 -3
- data/lib/llm_cost_tracker/prices.json +30 -30
- data/lib/llm_cost_tracker/pricing.rb +44 -32
- data/lib/llm_cost_tracker/railtie.rb +2 -1
- data/lib/llm_cost_tracker/storage/active_record_rollups.rb +142 -0
- data/lib/llm_cost_tracker/storage/active_record_store.rb +35 -78
- data/lib/llm_cost_tracker/stream_collector.rb +4 -2
- data/lib/llm_cost_tracker/tags_column.rb +71 -14
- data/lib/llm_cost_tracker/tracker.rb +54 -32
- data/lib/llm_cost_tracker/usage_breakdown.rb +30 -0
- data/lib/llm_cost_tracker/version.rb +1 -1
- data/lib/llm_cost_tracker.rb +10 -3
- metadata +9 -4
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_monthly_totals_to_llm_cost_tracker.rb.erb +0 -48
- data/lib/llm_cost_tracker/monthly_total.rb +0 -9
|
@@ -2,39 +2,96 @@
|
|
|
2
2
|
|
|
3
3
|
module LlmCostTracker
|
|
4
4
|
module TagsColumn
|
|
5
|
+
USAGE_BREAKDOWN_COLUMNS = %w[
|
|
6
|
+
cache_read_input_tokens
|
|
7
|
+
cache_write_input_tokens
|
|
8
|
+
hidden_output_tokens
|
|
9
|
+
].freeze
|
|
10
|
+
|
|
11
|
+
USAGE_BREAKDOWN_COST_COLUMNS = %w[
|
|
12
|
+
cache_read_input_cost
|
|
13
|
+
cache_write_input_cost
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
def reset_column_information
|
|
17
|
+
remove_instance_variable(:@lct_schema_capabilities) if instance_variable_defined?(:@lct_schema_capabilities)
|
|
18
|
+
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
|
|
5
22
|
def tags_json_column?
|
|
6
|
-
|
|
23
|
+
capabilities = lct_schema_capabilities
|
|
24
|
+
|
|
25
|
+
capabilities.fetch(:tags_jsonb) || capabilities.fetch(:tags_mysql_json)
|
|
7
26
|
end
|
|
8
27
|
|
|
9
28
|
def tags_jsonb_column?
|
|
10
|
-
|
|
11
|
-
return false unless column
|
|
12
|
-
|
|
13
|
-
column.type == :jsonb || column.sql_type.to_s.downcase == "jsonb"
|
|
29
|
+
lct_schema_capabilities.fetch(:tags_jsonb)
|
|
14
30
|
end
|
|
15
31
|
|
|
16
32
|
def tags_mysql_json_column?
|
|
17
|
-
|
|
18
|
-
return false unless column
|
|
19
|
-
return false if tags_jsonb_column?
|
|
20
|
-
|
|
21
|
-
column.type == :json && connection.adapter_name.match?(/mysql/i)
|
|
33
|
+
lct_schema_capabilities.fetch(:tags_mysql_json)
|
|
22
34
|
end
|
|
23
35
|
|
|
24
36
|
def latency_column?
|
|
25
|
-
|
|
37
|
+
lct_schema_capabilities.fetch(:latency)
|
|
26
38
|
end
|
|
27
39
|
|
|
28
40
|
def stream_column?
|
|
29
|
-
|
|
41
|
+
lct_schema_capabilities.fetch(:stream)
|
|
30
42
|
end
|
|
31
43
|
|
|
32
44
|
def usage_source_column?
|
|
33
|
-
|
|
45
|
+
lct_schema_capabilities.fetch(:usage_source)
|
|
34
46
|
end
|
|
35
47
|
|
|
36
48
|
def provider_response_id_column?
|
|
37
|
-
|
|
49
|
+
lct_schema_capabilities.fetch(:provider_response_id)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def pricing_mode_column?
|
|
53
|
+
lct_schema_capabilities.fetch(:pricing_mode)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def usage_breakdown_columns?
|
|
57
|
+
lct_schema_capabilities.fetch(:usage_breakdown)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def usage_breakdown_cost_columns?
|
|
61
|
+
lct_schema_capabilities.fetch(:usage_breakdown_cost)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def lct_schema_capabilities
|
|
67
|
+
columns = columns_hash
|
|
68
|
+
adapter_name = connection.adapter_name
|
|
69
|
+
cache = @lct_schema_capabilities
|
|
70
|
+
|
|
71
|
+
return cache.fetch(:values) if cache && cache.fetch(:columns).equal?(columns) &&
|
|
72
|
+
cache.fetch(:adapter_name) == adapter_name
|
|
73
|
+
|
|
74
|
+
values = build_lct_schema_capabilities(columns, adapter_name)
|
|
75
|
+
@lct_schema_capabilities = { columns: columns, adapter_name: adapter_name, values: values }
|
|
76
|
+
values
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def build_lct_schema_capabilities(columns, adapter_name)
|
|
80
|
+
tag_column = columns["tags"]
|
|
81
|
+
tags_jsonb = tag_column && (tag_column.type == :jsonb || tag_column.sql_type.to_s.downcase == "jsonb")
|
|
82
|
+
tags_mysql_json = tag_column && !tags_jsonb && tag_column.type == :json && adapter_name.match?(/mysql/i)
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
tags_jsonb: tags_jsonb ? true : false,
|
|
86
|
+
tags_mysql_json: tags_mysql_json ? true : false,
|
|
87
|
+
latency: columns.key?("latency_ms"),
|
|
88
|
+
stream: columns.key?("stream"),
|
|
89
|
+
usage_source: columns.key?("usage_source"),
|
|
90
|
+
provider_response_id: columns.key?("provider_response_id"),
|
|
91
|
+
pricing_mode: columns.key?("pricing_mode"),
|
|
92
|
+
usage_breakdown: USAGE_BREAKDOWN_COLUMNS.all? { |column| columns.key?(column) },
|
|
93
|
+
usage_breakdown_cost: USAGE_BREAKDOWN_COST_COLUMNS.all? { |column| columns.key?(column) }
|
|
94
|
+
}
|
|
38
95
|
end
|
|
39
96
|
end
|
|
40
97
|
end
|
|
@@ -16,28 +16,70 @@ module LlmCostTracker
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def record(provider:, model:, input_tokens:, output_tokens:, latency_ms: nil, stream: false,
|
|
19
|
-
usage_source: nil, provider_response_id: nil, metadata: {})
|
|
19
|
+
usage_source: nil, provider_response_id: nil, pricing_mode: nil, metadata: {})
|
|
20
20
|
return unless LlmCostTracker.configuration.enabled
|
|
21
21
|
|
|
22
|
-
usage =
|
|
22
|
+
usage = usage_data(input_tokens, output_tokens, metadata, pricing_mode)
|
|
23
|
+
cost_data = cost_for_usage(provider, model, usage)
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
UnknownPricing.handle!(model) unless cost_data
|
|
26
|
+
|
|
27
|
+
event = build_event(
|
|
28
|
+
provider: provider,
|
|
29
|
+
model: model,
|
|
30
|
+
usage: usage,
|
|
31
|
+
cost_data: cost_data,
|
|
32
|
+
metadata: metadata,
|
|
33
|
+
latency_ms: latency_ms,
|
|
34
|
+
stream: stream,
|
|
35
|
+
usage_source: usage_source,
|
|
36
|
+
provider_response_id: provider_response_id
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
ActiveSupport::Notifications.instrument(EVENT_NAME, event.to_h)
|
|
40
|
+
|
|
41
|
+
stored = store(event)
|
|
42
|
+
Budget.check!(event) unless stored == false
|
|
43
|
+
|
|
44
|
+
event
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def usage_data(input_tokens, output_tokens, metadata, pricing_mode)
|
|
50
|
+
metadata = metadata.merge(pricing_mode: pricing_mode) unless pricing_mode.nil?
|
|
51
|
+
|
|
52
|
+
EventMetadata.usage_data(
|
|
53
|
+
input_tokens,
|
|
54
|
+
output_tokens,
|
|
55
|
+
metadata
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def cost_for_usage(provider, model, usage)
|
|
60
|
+
Pricing.cost_for(
|
|
61
|
+
provider: provider,
|
|
25
62
|
model: model,
|
|
26
63
|
input_tokens: usage[:input_tokens],
|
|
27
64
|
output_tokens: usage[:output_tokens],
|
|
28
|
-
cached_input_tokens: usage[:cached_input_tokens],
|
|
29
65
|
cache_read_input_tokens: usage[:cache_read_input_tokens],
|
|
30
|
-
|
|
66
|
+
cache_write_input_tokens: usage[:cache_write_input_tokens],
|
|
67
|
+
pricing_mode: usage[:pricing_mode]
|
|
31
68
|
)
|
|
69
|
+
end
|
|
32
70
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
71
|
+
def build_event(provider:, model:, usage:, cost_data:, metadata:, latency_ms:, stream:, usage_source:,
|
|
72
|
+
provider_response_id:)
|
|
73
|
+
Event.new(
|
|
36
74
|
provider: provider,
|
|
37
75
|
model: model,
|
|
38
76
|
input_tokens: usage[:input_tokens],
|
|
39
77
|
output_tokens: usage[:output_tokens],
|
|
40
78
|
total_tokens: usage[:total_tokens],
|
|
79
|
+
cache_read_input_tokens: usage[:cache_read_input_tokens],
|
|
80
|
+
cache_write_input_tokens: usage[:cache_write_input_tokens],
|
|
81
|
+
hidden_output_tokens: usage[:hidden_output_tokens],
|
|
82
|
+
pricing_mode: usage[:pricing_mode],
|
|
41
83
|
cost: cost_data,
|
|
42
84
|
tags: LlmCostTracker.configuration.default_tags.merge(EventMetadata.tags(metadata)).freeze,
|
|
43
85
|
latency_ms: normalized_latency_ms(latency_ms),
|
|
@@ -46,17 +88,8 @@ module LlmCostTracker
|
|
|
46
88
|
provider_response_id: normalized_provider_response_id(provider_response_id),
|
|
47
89
|
tracked_at: Time.now.utc
|
|
48
90
|
)
|
|
49
|
-
|
|
50
|
-
ActiveSupport::Notifications.instrument(EVENT_NAME, event.to_h)
|
|
51
|
-
|
|
52
|
-
stored = store(event)
|
|
53
|
-
Budget.check!(event) unless stored == false
|
|
54
|
-
|
|
55
|
-
event
|
|
56
91
|
end
|
|
57
92
|
|
|
58
|
-
private
|
|
59
|
-
|
|
60
93
|
def store(event)
|
|
61
94
|
config = LlmCostTracker.configuration
|
|
62
95
|
case config.storage_backend
|
|
@@ -73,7 +106,7 @@ module LlmCostTracker
|
|
|
73
106
|
|
|
74
107
|
def log_event(event, config)
|
|
75
108
|
message = "#{event.provider}/#{event.model} " \
|
|
76
|
-
"tokens=#{event.
|
|
109
|
+
"tokens=#{event.total_tokens} " \
|
|
77
110
|
"cost=#{log_cost_label(event)}"
|
|
78
111
|
message += " latency=#{event.latency_ms}ms" if event.latency_ms
|
|
79
112
|
message += " stream=#{event.stream}" if event.stream
|
|
@@ -84,9 +117,7 @@ module LlmCostTracker
|
|
|
84
117
|
event
|
|
85
118
|
end
|
|
86
119
|
|
|
87
|
-
def log_cost_label(event)
|
|
88
|
-
event.cost ? "$#{format('%.6f', event.cost.total_cost)}" : "unknown"
|
|
89
|
-
end
|
|
120
|
+
def log_cost_label(event) = event.cost ? "$#{format('%.6f', event.cost.total_cost)}" : "unknown"
|
|
90
121
|
|
|
91
122
|
def active_record_save(event)
|
|
92
123
|
require_relative "llm_api_call" unless defined?(LlmCostTracker::LlmApiCall)
|
|
@@ -115,11 +146,7 @@ module LlmCostTracker
|
|
|
115
146
|
end
|
|
116
147
|
end
|
|
117
148
|
|
|
118
|
-
def normalized_latency_ms(latency_ms)
|
|
119
|
-
return nil if latency_ms.nil?
|
|
120
|
-
|
|
121
|
-
[latency_ms.to_i, 0].max
|
|
122
|
-
end
|
|
149
|
+
def normalized_latency_ms(latency_ms) = latency_ms.nil? ? nil : [latency_ms.to_i, 0].max
|
|
123
150
|
|
|
124
151
|
def normalized_usage_source(value)
|
|
125
152
|
return nil if value.nil?
|
|
@@ -128,12 +155,7 @@ module LlmCostTracker
|
|
|
128
155
|
USAGE_SOURCES.include?(symbol) ? symbol.to_s : nil
|
|
129
156
|
end
|
|
130
157
|
|
|
131
|
-
def normalized_provider_response_id(value)
|
|
132
|
-
return nil if value.nil?
|
|
133
|
-
|
|
134
|
-
string = value.to_s
|
|
135
|
-
string.empty? ? nil : string
|
|
136
|
-
end
|
|
158
|
+
def normalized_provider_response_id(value) = value.nil? || value.to_s.empty? ? nil : value.to_s
|
|
137
159
|
end
|
|
138
160
|
end
|
|
139
161
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LlmCostTracker
|
|
4
|
+
UsageBreakdown = Data.define(
|
|
5
|
+
:input_tokens,
|
|
6
|
+
:cache_read_input_tokens,
|
|
7
|
+
:cache_write_input_tokens,
|
|
8
|
+
:output_tokens,
|
|
9
|
+
:hidden_output_tokens
|
|
10
|
+
) do
|
|
11
|
+
def self.build(input_tokens:, output_tokens:, cache_read_input_tokens: 0,
|
|
12
|
+
cache_write_input_tokens: 0, hidden_output_tokens: 0)
|
|
13
|
+
new(
|
|
14
|
+
input_tokens: input_tokens.to_i,
|
|
15
|
+
cache_read_input_tokens: cache_read_input_tokens.to_i,
|
|
16
|
+
cache_write_input_tokens: cache_write_input_tokens.to_i,
|
|
17
|
+
output_tokens: output_tokens.to_i,
|
|
18
|
+
hidden_output_tokens: hidden_output_tokens.to_i
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def total_tokens
|
|
23
|
+
input_tokens + cache_read_input_tokens + cache_write_input_tokens + output_tokens
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_h
|
|
27
|
+
super.merge(total_tokens: total_tokens).compact
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/llm_cost_tracker.rb
CHANGED
|
@@ -10,6 +10,7 @@ require_relative "llm_cost_tracker/errors"
|
|
|
10
10
|
require_relative "llm_cost_tracker/logging"
|
|
11
11
|
require_relative "llm_cost_tracker/parameter_hash"
|
|
12
12
|
require_relative "llm_cost_tracker/cost"
|
|
13
|
+
require_relative "llm_cost_tracker/usage_breakdown"
|
|
13
14
|
require_relative "llm_cost_tracker/event"
|
|
14
15
|
require_relative "llm_cost_tracker/parsed_usage"
|
|
15
16
|
require_relative "llm_cost_tracker/price_registry"
|
|
@@ -69,7 +70,7 @@ module LlmCostTracker
|
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def track(provider:, model:, input_tokens:, output_tokens:, latency_ms: nil, stream: false, usage_source: :manual,
|
|
72
|
-
enforce_budget: false, provider_response_id: nil, **metadata)
|
|
73
|
+
enforce_budget: false, provider_response_id: nil, pricing_mode: nil, **metadata)
|
|
73
74
|
enforce_budget! if enforce_budget
|
|
74
75
|
Tracker.record(
|
|
75
76
|
provider: provider.to_s,
|
|
@@ -80,11 +81,13 @@ module LlmCostTracker
|
|
|
80
81
|
stream: stream,
|
|
81
82
|
usage_source: usage_source,
|
|
82
83
|
provider_response_id: provider_response_id,
|
|
84
|
+
pricing_mode: pricing_mode,
|
|
83
85
|
metadata: metadata
|
|
84
86
|
)
|
|
85
87
|
end
|
|
86
88
|
|
|
87
|
-
def track_stream(provider:, model:, latency_ms: nil, enforce_budget: false, provider_response_id: nil,
|
|
89
|
+
def track_stream(provider:, model:, latency_ms: nil, enforce_budget: false, provider_response_id: nil,
|
|
90
|
+
pricing_mode: nil, **metadata)
|
|
88
91
|
require_relative "llm_cost_tracker/stream_collector"
|
|
89
92
|
enforce_budget! if enforce_budget
|
|
90
93
|
collector = StreamCollector.new(
|
|
@@ -92,6 +95,7 @@ module LlmCostTracker
|
|
|
92
95
|
model: model,
|
|
93
96
|
latency_ms: latency_ms,
|
|
94
97
|
provider_response_id: provider_response_id,
|
|
98
|
+
pricing_mode: pricing_mode,
|
|
95
99
|
metadata: metadata
|
|
96
100
|
)
|
|
97
101
|
yield collector
|
|
@@ -107,7 +111,10 @@ module LlmCostTracker
|
|
|
107
111
|
return unless config.budget_exceeded_behavior == :block_requests
|
|
108
112
|
return if config.active_record?
|
|
109
113
|
|
|
110
|
-
Logging.warn(
|
|
114
|
+
Logging.warn(
|
|
115
|
+
":block_requests requires storage_backend = :active_record for monthly and daily preflight; " \
|
|
116
|
+
"preflight blocking will be skipped."
|
|
117
|
+
)
|
|
111
118
|
end
|
|
112
119
|
end
|
|
113
120
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm_cost_tracker
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sergii Khomenko
|
|
@@ -240,6 +240,7 @@ files:
|
|
|
240
240
|
- app/helpers/llm_cost_tracker/dashboard_query_helper.rb
|
|
241
241
|
- app/helpers/llm_cost_tracker/pagination_helper.rb
|
|
242
242
|
- app/services/llm_cost_tracker/dashboard/data_quality.rb
|
|
243
|
+
- app/services/llm_cost_tracker/dashboard/data_quality_aggregate.rb
|
|
243
244
|
- app/services/llm_cost_tracker/dashboard/filter.rb
|
|
244
245
|
- app/services/llm_cost_tracker/dashboard/overview_stats.rb
|
|
245
246
|
- app/services/llm_cost_tracker/dashboard/provider_breakdown.rb
|
|
@@ -278,15 +279,17 @@ files:
|
|
|
278
279
|
- lib/llm_cost_tracker/event.rb
|
|
279
280
|
- lib/llm_cost_tracker/event_metadata.rb
|
|
280
281
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/add_latency_ms_generator.rb
|
|
281
|
-
- lib/llm_cost_tracker/generators/llm_cost_tracker/
|
|
282
|
+
- lib/llm_cost_tracker/generators/llm_cost_tracker/add_period_totals_generator.rb
|
|
282
283
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/add_provider_response_id_generator.rb
|
|
283
284
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/add_streaming_generator.rb
|
|
285
|
+
- lib/llm_cost_tracker/generators/llm_cost_tracker/add_usage_breakdown_generator.rb
|
|
284
286
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb
|
|
285
287
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb
|
|
286
288
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_latency_ms_to_llm_api_calls.rb.erb
|
|
287
|
-
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/
|
|
289
|
+
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_period_totals_to_llm_cost_tracker.rb.erb
|
|
288
290
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_provider_response_id_to_llm_api_calls.rb.erb
|
|
289
291
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_streaming_to_llm_api_calls.rb.erb
|
|
292
|
+
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_usage_breakdown_to_llm_api_calls.rb.erb
|
|
290
293
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb
|
|
291
294
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb
|
|
292
295
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/llm_cost_tracker_prices.yml.erb
|
|
@@ -297,7 +300,6 @@ files:
|
|
|
297
300
|
- lib/llm_cost_tracker/llm_api_call.rb
|
|
298
301
|
- lib/llm_cost_tracker/logging.rb
|
|
299
302
|
- lib/llm_cost_tracker/middleware/faraday.rb
|
|
300
|
-
- lib/llm_cost_tracker/monthly_total.rb
|
|
301
303
|
- lib/llm_cost_tracker/parameter_hash.rb
|
|
302
304
|
- lib/llm_cost_tracker/parsed_usage.rb
|
|
303
305
|
- lib/llm_cost_tracker/parsers/anthropic.rb
|
|
@@ -309,6 +311,7 @@ files:
|
|
|
309
311
|
- lib/llm_cost_tracker/parsers/registry.rb
|
|
310
312
|
- lib/llm_cost_tracker/parsers/sse.rb
|
|
311
313
|
- lib/llm_cost_tracker/period_grouping.rb
|
|
314
|
+
- lib/llm_cost_tracker/period_total.rb
|
|
312
315
|
- lib/llm_cost_tracker/price_registry.rb
|
|
313
316
|
- lib/llm_cost_tracker/price_sync.rb
|
|
314
317
|
- lib/llm_cost_tracker/price_sync/fetcher.rb
|
|
@@ -330,6 +333,7 @@ files:
|
|
|
330
333
|
- lib/llm_cost_tracker/report_data.rb
|
|
331
334
|
- lib/llm_cost_tracker/report_formatter.rb
|
|
332
335
|
- lib/llm_cost_tracker/retention.rb
|
|
336
|
+
- lib/llm_cost_tracker/storage/active_record_rollups.rb
|
|
333
337
|
- lib/llm_cost_tracker/storage/active_record_store.rb
|
|
334
338
|
- lib/llm_cost_tracker/stream_collector.rb
|
|
335
339
|
- lib/llm_cost_tracker/tag_accessors.rb
|
|
@@ -338,6 +342,7 @@ files:
|
|
|
338
342
|
- lib/llm_cost_tracker/tags_column.rb
|
|
339
343
|
- lib/llm_cost_tracker/tracker.rb
|
|
340
344
|
- lib/llm_cost_tracker/unknown_pricing.rb
|
|
345
|
+
- lib/llm_cost_tracker/usage_breakdown.rb
|
|
341
346
|
- lib/llm_cost_tracker/value_helpers.rb
|
|
342
347
|
- lib/llm_cost_tracker/version.rb
|
|
343
348
|
- lib/tasks/llm_cost_tracker.rake
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
class AddMonthlyTotalsToLlmCostTracker < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
-
def up
|
|
3
|
-
create_table :llm_cost_tracker_monthly_totals do |t|
|
|
4
|
-
t.date :month_start, null: false
|
|
5
|
-
t.decimal :total_cost, precision: 20, scale: 8, null: false, default: 0
|
|
6
|
-
|
|
7
|
-
t.timestamps
|
|
8
|
-
end unless table_exists?(:llm_cost_tracker_monthly_totals)
|
|
9
|
-
|
|
10
|
-
add_index :llm_cost_tracker_monthly_totals, :month_start,
|
|
11
|
-
unique: true unless index_exists?(:llm_cost_tracker_monthly_totals, :month_start)
|
|
12
|
-
|
|
13
|
-
backfill_monthly_totals
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def down
|
|
17
|
-
remove_index :llm_cost_tracker_monthly_totals, :month_start if index_exists?(:llm_cost_tracker_monthly_totals, :month_start)
|
|
18
|
-
drop_table :llm_cost_tracker_monthly_totals if table_exists?(:llm_cost_tracker_monthly_totals)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
def backfill_monthly_totals
|
|
24
|
-
return unless table_exists?(:llm_api_calls)
|
|
25
|
-
|
|
26
|
-
execute <<~SQL
|
|
27
|
-
INSERT INTO llm_cost_tracker_monthly_totals (month_start, total_cost, created_at, updated_at)
|
|
28
|
-
SELECT #{month_bucket_sql} AS month_start,
|
|
29
|
-
SUM(total_cost) AS total_cost,
|
|
30
|
-
CURRENT_TIMESTAMP,
|
|
31
|
-
CURRENT_TIMESTAMP
|
|
32
|
-
FROM llm_api_calls
|
|
33
|
-
WHERE total_cost IS NOT NULL
|
|
34
|
-
GROUP BY #{month_bucket_sql}
|
|
35
|
-
SQL
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def month_bucket_sql
|
|
39
|
-
case connection.adapter_name
|
|
40
|
-
when /postgres/i
|
|
41
|
-
"DATE_TRUNC('month', tracked_at)::date"
|
|
42
|
-
when /mysql/i
|
|
43
|
-
"DATE_FORMAT(tracked_at, '%Y-%m-01')"
|
|
44
|
-
else
|
|
45
|
-
"strftime('%Y-%m-01', tracked_at)"
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|