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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -0
  3. data/README.md +333 -30
  4. data/lib/llm_cost_tracker/budget.rb +85 -0
  5. data/lib/llm_cost_tracker/configuration.rb +82 -3
  6. data/lib/llm_cost_tracker/cost.rb +15 -0
  7. data/lib/llm_cost_tracker/errors.rb +37 -0
  8. data/lib/llm_cost_tracker/event.rb +24 -0
  9. data/lib/llm_cost_tracker/event_metadata.rb +54 -0
  10. data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_latency_ms_generator.rb +29 -0
  11. data/lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb +20 -0
  12. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_latency_ms_to_llm_api_calls.rb.erb +9 -0
  13. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb +16 -4
  14. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +14 -1
  15. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/llm_cost_tracker_prices.yml.erb +36 -0
  16. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_cost_precision.rb.erb +15 -0
  17. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_tags_to_jsonb.rb.erb +41 -0
  18. data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_cost_precision_generator.rb +29 -0
  19. data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_tags_to_jsonb_generator.rb +29 -0
  20. data/lib/llm_cost_tracker/llm_api_call.rb +45 -14
  21. data/lib/llm_cost_tracker/logging.rb +44 -0
  22. data/lib/llm_cost_tracker/middleware/faraday.rb +54 -13
  23. data/lib/llm_cost_tracker/parsed_usage.rb +45 -0
  24. data/lib/llm_cost_tracker/parsers/anthropic.rb +6 -4
  25. data/lib/llm_cost_tracker/parsers/base.rb +2 -0
  26. data/lib/llm_cost_tracker/parsers/gemini.rb +12 -5
  27. data/lib/llm_cost_tracker/parsers/openai.rb +11 -22
  28. data/lib/llm_cost_tracker/parsers/openai_compatible.rb +48 -0
  29. data/lib/llm_cost_tracker/parsers/openai_usage.rb +33 -0
  30. data/lib/llm_cost_tracker/parsers/registry.rb +16 -7
  31. data/lib/llm_cost_tracker/price_registry.rb +99 -0
  32. data/lib/llm_cost_tracker/prices.json +51 -0
  33. data/lib/llm_cost_tracker/pricing.rb +103 -77
  34. data/lib/llm_cost_tracker/railtie.rb +8 -0
  35. data/lib/llm_cost_tracker/report.rb +29 -0
  36. data/lib/llm_cost_tracker/report_data.rb +84 -0
  37. data/lib/llm_cost_tracker/report_formatter.rb +59 -0
  38. data/lib/llm_cost_tracker/storage/active_record_backend.rb +19 -0
  39. data/lib/llm_cost_tracker/storage/active_record_store.rb +21 -12
  40. data/lib/llm_cost_tracker/storage/backends.rb +26 -0
  41. data/lib/llm_cost_tracker/storage/custom_backend.rb +16 -0
  42. data/lib/llm_cost_tracker/storage/log_backend.rb +28 -0
  43. data/lib/llm_cost_tracker/tag_accessors.rb +23 -0
  44. data/lib/llm_cost_tracker/tag_query.rb +38 -0
  45. data/lib/llm_cost_tracker/tags_column.rb +16 -0
  46. data/lib/llm_cost_tracker/tracker.rb +43 -97
  47. data/lib/llm_cost_tracker/unknown_pricing.rb +40 -0
  48. data/lib/llm_cost_tracker/value_object.rb +45 -0
  49. data/lib/llm_cost_tracker/version.rb +1 -1
  50. data/lib/llm_cost_tracker.rb +49 -6
  51. data/lib/tasks/llm_cost_tracker.rake +9 -0
  52. data/llm_cost_tracker.gemspec +4 -3
  53. 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 record(provider:, model:, input_tokens:, output_tokens:, metadata: {})
9
- usage = usage_data(input_tokens, output_tokens, metadata)
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
- event = {
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
- case config.storage_backend
49
- when :log
50
- log_event(event)
51
- when :active_record
52
- store_active_record(event)
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 log_event(event)
59
- cost_str = event[:cost] ? "$#{format('%.6f', event[:cost][:total_cost])}" : "unknown"
60
-
61
- message = "[LlmCostTracker] #{event[:provider]}/#{event[:model]} " \
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
- Rails.logger.warn(message) if defined?(Rails)
71
- else
72
- Rails.logger.info(message) if defined?(Rails)
73
- end
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 usage_data(input_tokens, output_tokens, metadata)
115
- cache_read_input_tokens = integer_metadata(metadata, :cache_read_input_tokens, :cache_read_tokens)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LlmCostTracker
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
@@ -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
- # Manual tracking for non-Faraday clients
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
- def track(provider:, model:, input_tokens:, output_tokens:, **metadata)
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
@@ -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 tracking for Ruby and Rails"
12
- spec.description = "Tracks token usage and estimated costs for OpenAI, Anthropic, and Google Gemini calls. " \
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 alerts."
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.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-17 00:00:00.000000000 Z
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, and Google
144
- Gemini calls. Works as Faraday middleware for Ruby clients, with ActiveRecord storage,
145
- per-user/per-feature attribution, and budget alerts.
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 tracking for Ruby and Rails
234
+ summary: Self-hosted LLM API cost guardrails for Ruby and Rails
202
235
  test_files: []