llm_cost_tracker 0.1.1 → 0.1.3
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 +69 -0
- data/README.md +333 -30
- data/lib/llm_cost_tracker/budget.rb +85 -0
- data/lib/llm_cost_tracker/configuration.rb +82 -3
- data/lib/llm_cost_tracker/cost.rb +15 -0
- data/lib/llm_cost_tracker/errors.rb +37 -0
- data/lib/llm_cost_tracker/event.rb +24 -0
- data/lib/llm_cost_tracker/event_metadata.rb +54 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_latency_ms_generator.rb +29 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb +20 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_latency_ms_to_llm_api_calls.rb.erb +9 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb +16 -4
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +14 -1
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/llm_cost_tracker_prices.yml.erb +36 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_cost_precision.rb.erb +15 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_tags_to_jsonb.rb.erb +41 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_cost_precision_generator.rb +29 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_tags_to_jsonb_generator.rb +29 -0
- data/lib/llm_cost_tracker/llm_api_call.rb +45 -14
- data/lib/llm_cost_tracker/logging.rb +44 -0
- data/lib/llm_cost_tracker/middleware/faraday.rb +54 -13
- data/lib/llm_cost_tracker/parsed_usage.rb +45 -0
- data/lib/llm_cost_tracker/parsers/anthropic.rb +6 -4
- data/lib/llm_cost_tracker/parsers/base.rb +2 -0
- data/lib/llm_cost_tracker/parsers/gemini.rb +12 -5
- data/lib/llm_cost_tracker/parsers/openai.rb +11 -22
- data/lib/llm_cost_tracker/parsers/openai_compatible.rb +48 -0
- data/lib/llm_cost_tracker/parsers/openai_usage.rb +33 -0
- data/lib/llm_cost_tracker/parsers/registry.rb +16 -7
- data/lib/llm_cost_tracker/price_registry.rb +99 -0
- data/lib/llm_cost_tracker/prices.json +51 -0
- data/lib/llm_cost_tracker/pricing.rb +103 -77
- data/lib/llm_cost_tracker/railtie.rb +8 -0
- data/lib/llm_cost_tracker/report.rb +29 -0
- data/lib/llm_cost_tracker/report_data.rb +84 -0
- data/lib/llm_cost_tracker/report_formatter.rb +59 -0
- data/lib/llm_cost_tracker/storage/active_record_backend.rb +19 -0
- data/lib/llm_cost_tracker/storage/active_record_store.rb +21 -12
- data/lib/llm_cost_tracker/storage/backends.rb +26 -0
- data/lib/llm_cost_tracker/storage/custom_backend.rb +16 -0
- data/lib/llm_cost_tracker/storage/log_backend.rb +28 -0
- data/lib/llm_cost_tracker/tag_accessors.rb +23 -0
- data/lib/llm_cost_tracker/tag_query.rb +38 -0
- data/lib/llm_cost_tracker/tags_column.rb +16 -0
- data/lib/llm_cost_tracker/tracker.rb +43 -97
- data/lib/llm_cost_tracker/unknown_pricing.rb +40 -0
- data/lib/llm_cost_tracker/value_object.rb +45 -0
- data/lib/llm_cost_tracker/version.rb +1 -1
- data/lib/llm_cost_tracker.rb +49 -6
- data/lib/tasks/llm_cost_tracker.rake +9 -0
- data/llm_cost_tracker.gemspec +4 -3
- metadata +39 -6
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "logging"
|
|
4
|
+
|
|
3
5
|
module LlmCostTracker
|
|
4
6
|
class Tracker
|
|
5
7
|
EVENT_NAME = "llm_request.llm_cost_tracker"
|
|
6
8
|
|
|
7
9
|
class << self
|
|
8
|
-
def
|
|
9
|
-
|
|
10
|
+
def enforce_budget!
|
|
11
|
+
Budget.enforce!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Build, notify, persist, and budget-check a single LLM usage event.
|
|
15
|
+
#
|
|
16
|
+
# @param provider [String] Provider name.
|
|
17
|
+
# @param model [String] Model identifier.
|
|
18
|
+
# @param input_tokens [Integer] Input token count.
|
|
19
|
+
# @param output_tokens [Integer] Output token count.
|
|
20
|
+
# @param metadata [Hash] Attribution tags plus provider-specific usage metadata.
|
|
21
|
+
# @param latency_ms [Integer, nil] Optional latency in milliseconds.
|
|
22
|
+
# @return [LlmCostTracker::Event]
|
|
23
|
+
def record(provider:, model:, input_tokens:, output_tokens:, metadata: {}, latency_ms: nil)
|
|
24
|
+
usage = EventMetadata.usage_data(input_tokens, output_tokens, metadata)
|
|
10
25
|
|
|
11
26
|
cost_data = Pricing.cost_for(
|
|
12
27
|
model: model,
|
|
@@ -17,25 +32,26 @@ module LlmCostTracker
|
|
|
17
32
|
cache_creation_input_tokens: usage[:cache_creation_input_tokens]
|
|
18
33
|
)
|
|
19
34
|
|
|
20
|
-
|
|
35
|
+
UnknownPricing.handle!(model) unless cost_data
|
|
36
|
+
|
|
37
|
+
event = Event.new(
|
|
21
38
|
provider: provider,
|
|
22
39
|
model: model,
|
|
23
40
|
input_tokens: usage[:input_tokens],
|
|
24
41
|
output_tokens: usage[:output_tokens],
|
|
25
42
|
total_tokens: usage[:total_tokens],
|
|
26
43
|
cost: cost_data,
|
|
27
|
-
tags: LlmCostTracker.configuration.default_tags.merge(metadata),
|
|
44
|
+
tags: LlmCostTracker.configuration.default_tags.merge(EventMetadata.tags(metadata)).freeze,
|
|
45
|
+
latency_ms: normalized_latency_ms(latency_ms),
|
|
28
46
|
tracked_at: Time.now.utc
|
|
29
|
-
|
|
47
|
+
)
|
|
30
48
|
|
|
31
49
|
# Emit ActiveSupport::Notifications event
|
|
32
|
-
ActiveSupport::Notifications.instrument(EVENT_NAME, event)
|
|
50
|
+
ActiveSupport::Notifications.instrument(EVENT_NAME, event.to_h)
|
|
33
51
|
|
|
34
52
|
# Store based on backend
|
|
35
|
-
store(event)
|
|
36
|
-
|
|
37
|
-
# Budget check
|
|
38
|
-
check_budget(event)
|
|
53
|
+
stored = store(event)
|
|
54
|
+
Budget.check!(event) unless stored == false
|
|
39
55
|
|
|
40
56
|
event
|
|
41
57
|
end
|
|
@@ -44,100 +60,30 @@ module LlmCostTracker
|
|
|
44
60
|
|
|
45
61
|
def store(event)
|
|
46
62
|
config = LlmCostTracker.configuration
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
when :custom
|
|
54
|
-
config.custom_storage&.call(event)
|
|
55
|
-
end
|
|
63
|
+
Storage::Backends.fetch(config.storage_backend).save(event, config: config)
|
|
64
|
+
rescue BudgetExceededError, UnknownPricingError
|
|
65
|
+
raise
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
handle_storage_error(e)
|
|
68
|
+
false
|
|
56
69
|
end
|
|
57
70
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"tokens=#{event[:input_tokens]}+#{event[:output_tokens]} " \
|
|
63
|
-
"cost=#{cost_str}"
|
|
64
|
-
message += " tags=#{event[:tags]}" unless event[:tags].empty?
|
|
65
|
-
|
|
66
|
-
case LlmCostTracker.configuration.log_level
|
|
67
|
-
when :debug
|
|
68
|
-
Rails.logger.debug(message) if defined?(Rails)
|
|
71
|
+
def handle_storage_error(error)
|
|
72
|
+
case LlmCostTracker.configuration.storage_error_behavior
|
|
73
|
+
when :ignore
|
|
74
|
+
nil
|
|
69
75
|
when :warn
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
# Fallback if Rails is not available
|
|
76
|
-
warn(message) unless defined?(Rails)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def store_active_record(event)
|
|
80
|
-
require_relative "llm_api_call" unless defined?(LlmCostTracker::LlmApiCall)
|
|
81
|
-
require_relative "storage/active_record_store" unless defined?(LlmCostTracker::Storage::ActiveRecordStore)
|
|
82
|
-
|
|
83
|
-
LlmCostTracker::Storage::ActiveRecordStore.save(event)
|
|
84
|
-
rescue LoadError => e
|
|
85
|
-
raise Error, "ActiveRecord storage requires the active_record gem: #{e.message}"
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def check_budget(event)
|
|
89
|
-
config = LlmCostTracker.configuration
|
|
90
|
-
return unless config.monthly_budget && config.on_budget_exceeded
|
|
91
|
-
return unless event[:cost]
|
|
92
|
-
|
|
93
|
-
monthly_total = calculate_monthly_total(event[:cost][:total_cost])
|
|
94
|
-
return unless monthly_total > config.monthly_budget
|
|
95
|
-
|
|
96
|
-
config.on_budget_exceeded.call(
|
|
97
|
-
monthly_total: monthly_total,
|
|
98
|
-
budget: config.monthly_budget,
|
|
99
|
-
last_event: event
|
|
100
|
-
)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def calculate_monthly_total(latest_cost)
|
|
104
|
-
# For :active_record backend, query the DB
|
|
105
|
-
if LlmCostTracker.configuration.active_record? &&
|
|
106
|
-
defined?(LlmCostTracker::Storage::ActiveRecordStore)
|
|
107
|
-
LlmCostTracker::Storage::ActiveRecordStore.monthly_total
|
|
108
|
-
else
|
|
109
|
-
# For other backends, we can only report the latest cost
|
|
110
|
-
latest_cost
|
|
76
|
+
Logging.warn("Storage failed; tracking event was not persisted: #{error.class}: #{error.message}")
|
|
77
|
+
when :raise
|
|
78
|
+
storage_error = StorageError.new(error)
|
|
79
|
+
raise storage_error
|
|
111
80
|
end
|
|
112
81
|
end
|
|
113
82
|
|
|
114
|
-
def
|
|
115
|
-
|
|
116
|
-
cache_creation_input_tokens = integer_metadata(
|
|
117
|
-
metadata,
|
|
118
|
-
:cache_creation_input_tokens,
|
|
119
|
-
:cache_creation_tokens
|
|
120
|
-
)
|
|
121
|
-
cached_input_tokens = integer_metadata(metadata, :cached_input_tokens)
|
|
122
|
-
|
|
123
|
-
{
|
|
124
|
-
input_tokens: input_tokens.to_i,
|
|
125
|
-
output_tokens: output_tokens.to_i,
|
|
126
|
-
cached_input_tokens: cached_input_tokens,
|
|
127
|
-
cache_read_input_tokens: cache_read_input_tokens,
|
|
128
|
-
cache_creation_input_tokens: cache_creation_input_tokens,
|
|
129
|
-
total_tokens: input_tokens.to_i + output_tokens.to_i +
|
|
130
|
-
cache_read_input_tokens + cache_creation_input_tokens
|
|
131
|
-
}
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def integer_metadata(metadata, *keys)
|
|
135
|
-
keys.each do |key|
|
|
136
|
-
value = metadata[key] || metadata[key.to_s]
|
|
137
|
-
return value.to_i unless value.nil?
|
|
138
|
-
end
|
|
83
|
+
def normalized_latency_ms(latency_ms)
|
|
84
|
+
return nil if latency_ms.nil?
|
|
139
85
|
|
|
140
|
-
0
|
|
86
|
+
[latency_ms.to_i, 0].max
|
|
141
87
|
end
|
|
142
88
|
end
|
|
143
89
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "logging"
|
|
4
|
+
|
|
5
|
+
module LlmCostTracker
|
|
6
|
+
class UnknownPricing
|
|
7
|
+
class << self
|
|
8
|
+
def handle!(model)
|
|
9
|
+
model = normalized_model_name(model)
|
|
10
|
+
|
|
11
|
+
case behavior
|
|
12
|
+
when :ignore
|
|
13
|
+
nil
|
|
14
|
+
when :warn
|
|
15
|
+
warn_missing(model)
|
|
16
|
+
when :raise
|
|
17
|
+
raise UnknownPricingError.new(model: model)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def normalized_model_name(model)
|
|
24
|
+
model.to_s.empty? ? "unknown" : model.to_s
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def warn_missing(model)
|
|
28
|
+
Logging.warn(
|
|
29
|
+
"No pricing configured for model #{model.inspect}. " \
|
|
30
|
+
"Cost and budget enforcement will be skipped for this event. " \
|
|
31
|
+
"Add a pricing_overrides entry or set unknown_pricing_behavior."
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def behavior
|
|
36
|
+
LlmCostTracker.configuration.unknown_pricing_behavior
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LlmCostTracker
|
|
4
|
+
module ValueObject
|
|
5
|
+
class << self
|
|
6
|
+
def define(*members, &block)
|
|
7
|
+
klass = data_class(*members)
|
|
8
|
+
add_hash_like_readers(klass)
|
|
9
|
+
klass.class_eval(&block) if block
|
|
10
|
+
klass
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def data_class(*members)
|
|
16
|
+
return Data.define(*members) if defined?(Data)
|
|
17
|
+
|
|
18
|
+
Struct.new(*members, keyword_init: true) do
|
|
19
|
+
def initialize(**kwargs)
|
|
20
|
+
super
|
|
21
|
+
freeze
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_hash_like_readers(klass)
|
|
27
|
+
klass.class_eval do
|
|
28
|
+
def [](key)
|
|
29
|
+
public_send(key.to_sym)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def dig(key, *rest)
|
|
33
|
+
value = self[key]
|
|
34
|
+
rest.empty? ? value : value&.dig(*rest)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def except(*keys)
|
|
38
|
+
excluded = keys.map(&:to_sym)
|
|
39
|
+
to_h.reject { |key, _value| excluded.include?(key.to_sym) }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/llm_cost_tracker.rb
CHANGED
|
@@ -5,34 +5,59 @@ require "active_support/notifications"
|
|
|
5
5
|
|
|
6
6
|
require_relative "llm_cost_tracker/version"
|
|
7
7
|
require_relative "llm_cost_tracker/configuration"
|
|
8
|
+
require_relative "llm_cost_tracker/errors"
|
|
9
|
+
require_relative "llm_cost_tracker/logging"
|
|
10
|
+
require_relative "llm_cost_tracker/value_object"
|
|
11
|
+
require_relative "llm_cost_tracker/cost"
|
|
12
|
+
require_relative "llm_cost_tracker/event"
|
|
13
|
+
require_relative "llm_cost_tracker/parsed_usage"
|
|
14
|
+
require_relative "llm_cost_tracker/price_registry"
|
|
8
15
|
require_relative "llm_cost_tracker/pricing"
|
|
9
16
|
require_relative "llm_cost_tracker/parsers/base"
|
|
17
|
+
require_relative "llm_cost_tracker/parsers/openai_usage"
|
|
10
18
|
require_relative "llm_cost_tracker/parsers/openai"
|
|
19
|
+
require_relative "llm_cost_tracker/parsers/openai_compatible"
|
|
11
20
|
require_relative "llm_cost_tracker/parsers/anthropic"
|
|
12
21
|
require_relative "llm_cost_tracker/parsers/gemini"
|
|
13
22
|
require_relative "llm_cost_tracker/parsers/registry"
|
|
14
23
|
require_relative "llm_cost_tracker/middleware/faraday"
|
|
24
|
+
require_relative "llm_cost_tracker/budget"
|
|
25
|
+
require_relative "llm_cost_tracker/unknown_pricing"
|
|
26
|
+
require_relative "llm_cost_tracker/event_metadata"
|
|
27
|
+
require_relative "llm_cost_tracker/tags_column"
|
|
28
|
+
require_relative "llm_cost_tracker/tag_query"
|
|
29
|
+
require_relative "llm_cost_tracker/tag_accessors"
|
|
30
|
+
require_relative "llm_cost_tracker/storage/backends"
|
|
15
31
|
require_relative "llm_cost_tracker/tracker"
|
|
32
|
+
require_relative "llm_cost_tracker/report_data"
|
|
33
|
+
require_relative "llm_cost_tracker/report_formatter"
|
|
34
|
+
require_relative "llm_cost_tracker/report"
|
|
16
35
|
|
|
17
36
|
module LlmCostTracker
|
|
18
|
-
class Error < StandardError; end
|
|
19
|
-
|
|
20
37
|
class << self
|
|
38
|
+
CONFIGURATION_MUTEX = Mutex.new
|
|
39
|
+
|
|
21
40
|
attr_writer :configuration
|
|
22
41
|
|
|
23
42
|
def configuration
|
|
24
|
-
@configuration ||= Configuration.new
|
|
43
|
+
@configuration || CONFIGURATION_MUTEX.synchronize { @configuration ||= Configuration.new }
|
|
25
44
|
end
|
|
26
45
|
|
|
46
|
+
# Configure the gem once during application boot.
|
|
47
|
+
#
|
|
48
|
+
# @yieldparam configuration [LlmCostTracker::Configuration]
|
|
49
|
+
# @return [void]
|
|
27
50
|
def configure
|
|
28
51
|
yield(configuration)
|
|
52
|
+
configuration.normalize_openai_compatible_providers!
|
|
53
|
+
warn_for_configuration!
|
|
29
54
|
end
|
|
30
55
|
|
|
31
56
|
def reset_configuration!
|
|
32
|
-
@configuration = Configuration.new
|
|
57
|
+
CONFIGURATION_MUTEX.synchronize { @configuration = Configuration.new }
|
|
33
58
|
end
|
|
34
59
|
|
|
35
|
-
#
|
|
60
|
+
# Track an LLM request manually for non-Faraday clients.
|
|
36
61
|
#
|
|
37
62
|
# LlmCostTracker.track(
|
|
38
63
|
# provider: :openai,
|
|
@@ -42,15 +67,33 @@ module LlmCostTracker
|
|
|
42
67
|
# feature: "chat",
|
|
43
68
|
# user_id: current_user.id
|
|
44
69
|
# )
|
|
45
|
-
|
|
70
|
+
#
|
|
71
|
+
# @param provider [String, Symbol] Provider name, such as :openai or :anthropic.
|
|
72
|
+
# @param model [String] Provider model identifier.
|
|
73
|
+
# @param input_tokens [Integer] Billed input token count.
|
|
74
|
+
# @param output_tokens [Integer] Billed output token count.
|
|
75
|
+
# @param latency_ms [Integer, nil] Optional request latency in milliseconds.
|
|
76
|
+
# @param metadata [Hash] Attribution tags and provider-specific usage metadata.
|
|
77
|
+
# @return [LlmCostTracker::Event] The tracked event.
|
|
78
|
+
def track(provider:, model:, input_tokens:, output_tokens:, latency_ms: nil, **metadata)
|
|
46
79
|
Tracker.record(
|
|
47
80
|
provider: provider.to_s,
|
|
48
81
|
model: model,
|
|
49
82
|
input_tokens: input_tokens,
|
|
50
83
|
output_tokens: output_tokens,
|
|
84
|
+
latency_ms: latency_ms,
|
|
51
85
|
metadata: metadata
|
|
52
86
|
)
|
|
53
87
|
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def warn_for_configuration!
|
|
92
|
+
return unless configuration.budget_exceeded_behavior == :block_requests
|
|
93
|
+
return if configuration.active_record?
|
|
94
|
+
|
|
95
|
+
Logging.warn(":block_requests requires storage_backend = :active_record; preflight blocking will be skipped.")
|
|
96
|
+
end
|
|
54
97
|
end
|
|
55
98
|
end
|
|
56
99
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :llm_cost_tracker do
|
|
4
|
+
desc "Print an LLM cost report from ActiveRecord storage"
|
|
5
|
+
task report: :environment do
|
|
6
|
+
days = (ENV["DAYS"] || LlmCostTracker::Report::DEFAULT_DAYS).to_i
|
|
7
|
+
puts LlmCostTracker::Report.generate(days: days)
|
|
8
|
+
end
|
|
9
|
+
end
|
data/llm_cost_tracker.gemspec
CHANGED
|
@@ -8,10 +8,11 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ["Sergii Khomenko"]
|
|
9
9
|
spec.email = ["sergey@mm.st"]
|
|
10
10
|
|
|
11
|
-
spec.summary = "Self-hosted LLM API cost
|
|
12
|
-
spec.description = "Tracks token usage and estimated costs for OpenAI, Anthropic,
|
|
11
|
+
spec.summary = "Self-hosted LLM API cost guardrails for Ruby and Rails"
|
|
12
|
+
spec.description = "Tracks token usage and estimated costs for OpenAI, Anthropic, Google Gemini, " \
|
|
13
|
+
"OpenRouter, DeepSeek, and OpenAI-compatible calls. " \
|
|
13
14
|
"Works as Faraday middleware for Ruby clients, with ActiveRecord storage, " \
|
|
14
|
-
"per-user/per-feature attribution, and budget
|
|
15
|
+
"per-user/per-feature attribution, budget alerts, and budget enforcement."
|
|
15
16
|
spec.homepage = "https://github.com/sergey-homenko/llm_cost_tracker"
|
|
16
17
|
spec.license = "MIT"
|
|
17
18
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm_cost_tracker
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sergii Khomenko
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -140,9 +140,10 @@ dependencies:
|
|
|
140
140
|
- - "~>"
|
|
141
141
|
- !ruby/object:Gem::Version
|
|
142
142
|
version: '3.0'
|
|
143
|
-
description: Tracks token usage and estimated costs for OpenAI, Anthropic,
|
|
144
|
-
Gemini calls. Works as Faraday middleware
|
|
145
|
-
per-user/per-feature attribution,
|
|
143
|
+
description: Tracks token usage and estimated costs for OpenAI, Anthropic, Google
|
|
144
|
+
Gemini, OpenRouter, DeepSeek, and OpenAI-compatible calls. Works as Faraday middleware
|
|
145
|
+
for Ruby clients, with ActiveRecord storage, per-user/per-feature attribution, budget
|
|
146
|
+
alerts, and budget enforcement.
|
|
146
147
|
email:
|
|
147
148
|
- sergey@mm.st
|
|
148
149
|
executables: []
|
|
@@ -156,22 +157,54 @@ files:
|
|
|
156
157
|
- README.md
|
|
157
158
|
- Rakefile
|
|
158
159
|
- lib/llm_cost_tracker.rb
|
|
160
|
+
- lib/llm_cost_tracker/budget.rb
|
|
159
161
|
- lib/llm_cost_tracker/configuration.rb
|
|
162
|
+
- lib/llm_cost_tracker/cost.rb
|
|
163
|
+
- lib/llm_cost_tracker/errors.rb
|
|
164
|
+
- lib/llm_cost_tracker/event.rb
|
|
165
|
+
- lib/llm_cost_tracker/event_metadata.rb
|
|
166
|
+
- lib/llm_cost_tracker/generators/llm_cost_tracker/add_latency_ms_generator.rb
|
|
160
167
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb
|
|
168
|
+
- lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb
|
|
169
|
+
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_latency_ms_to_llm_api_calls.rb.erb
|
|
161
170
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb
|
|
162
171
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb
|
|
172
|
+
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/llm_cost_tracker_prices.yml.erb
|
|
173
|
+
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_cost_precision.rb.erb
|
|
174
|
+
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_tags_to_jsonb.rb.erb
|
|
175
|
+
- lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_cost_precision_generator.rb
|
|
176
|
+
- lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_tags_to_jsonb_generator.rb
|
|
163
177
|
- lib/llm_cost_tracker/llm_api_call.rb
|
|
178
|
+
- lib/llm_cost_tracker/logging.rb
|
|
164
179
|
- lib/llm_cost_tracker/middleware/faraday.rb
|
|
180
|
+
- lib/llm_cost_tracker/parsed_usage.rb
|
|
165
181
|
- lib/llm_cost_tracker/parsers/anthropic.rb
|
|
166
182
|
- lib/llm_cost_tracker/parsers/base.rb
|
|
167
183
|
- lib/llm_cost_tracker/parsers/gemini.rb
|
|
168
184
|
- lib/llm_cost_tracker/parsers/openai.rb
|
|
185
|
+
- lib/llm_cost_tracker/parsers/openai_compatible.rb
|
|
186
|
+
- lib/llm_cost_tracker/parsers/openai_usage.rb
|
|
169
187
|
- lib/llm_cost_tracker/parsers/registry.rb
|
|
188
|
+
- lib/llm_cost_tracker/price_registry.rb
|
|
189
|
+
- lib/llm_cost_tracker/prices.json
|
|
170
190
|
- lib/llm_cost_tracker/pricing.rb
|
|
171
191
|
- lib/llm_cost_tracker/railtie.rb
|
|
192
|
+
- lib/llm_cost_tracker/report.rb
|
|
193
|
+
- lib/llm_cost_tracker/report_data.rb
|
|
194
|
+
- lib/llm_cost_tracker/report_formatter.rb
|
|
195
|
+
- lib/llm_cost_tracker/storage/active_record_backend.rb
|
|
172
196
|
- lib/llm_cost_tracker/storage/active_record_store.rb
|
|
197
|
+
- lib/llm_cost_tracker/storage/backends.rb
|
|
198
|
+
- lib/llm_cost_tracker/storage/custom_backend.rb
|
|
199
|
+
- lib/llm_cost_tracker/storage/log_backend.rb
|
|
200
|
+
- lib/llm_cost_tracker/tag_accessors.rb
|
|
201
|
+
- lib/llm_cost_tracker/tag_query.rb
|
|
202
|
+
- lib/llm_cost_tracker/tags_column.rb
|
|
173
203
|
- lib/llm_cost_tracker/tracker.rb
|
|
204
|
+
- lib/llm_cost_tracker/unknown_pricing.rb
|
|
205
|
+
- lib/llm_cost_tracker/value_object.rb
|
|
174
206
|
- lib/llm_cost_tracker/version.rb
|
|
207
|
+
- lib/tasks/llm_cost_tracker.rake
|
|
175
208
|
- llm_cost_tracker.gemspec
|
|
176
209
|
homepage: https://github.com/sergey-homenko/llm_cost_tracker
|
|
177
210
|
licenses:
|
|
@@ -198,5 +231,5 @@ requirements: []
|
|
|
198
231
|
rubygems_version: 3.5.9
|
|
199
232
|
signing_key:
|
|
200
233
|
specification_version: 4
|
|
201
|
-
summary: Self-hosted LLM API cost
|
|
234
|
+
summary: Self-hosted LLM API cost guardrails for Ruby and Rails
|
|
202
235
|
test_files: []
|