llm_cost_tracker 0.3.0 → 0.3.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/README.md +14 -1
  4. data/app/assets/llm_cost_tracker/application.css +1 -4
  5. data/app/controllers/llm_cost_tracker/calls_controller.rb +9 -13
  6. data/app/controllers/llm_cost_tracker/dashboard_controller.rb +8 -19
  7. data/app/controllers/llm_cost_tracker/data_quality_controller.rb +1 -2
  8. data/app/controllers/llm_cost_tracker/models_controller.rb +5 -2
  9. data/app/controllers/llm_cost_tracker/tags_controller.rb +2 -4
  10. data/app/helpers/llm_cost_tracker/dashboard_filter_options_helper.rb +1 -7
  11. data/app/helpers/llm_cost_tracker/dashboard_query_helper.rb +5 -9
  12. data/app/services/llm_cost_tracker/dashboard/data_quality.rb +10 -10
  13. data/app/services/llm_cost_tracker/dashboard/filter.rb +6 -26
  14. data/app/services/llm_cost_tracker/dashboard/provider_breakdown.rb +0 -3
  15. data/app/services/llm_cost_tracker/dashboard/tag_breakdown.rb +0 -2
  16. data/app/services/llm_cost_tracker/pagination.rb +1 -9
  17. data/app/views/layouts/llm_cost_tracker/application.html.erb +1 -16
  18. data/app/views/llm_cost_tracker/calls/index.html.erb +13 -13
  19. data/app/views/llm_cost_tracker/calls/show.html.erb +8 -3
  20. data/app/views/llm_cost_tracker/dashboard/index.html.erb +1 -1
  21. data/app/views/llm_cost_tracker/data_quality/index.html.erb +36 -14
  22. data/app/views/llm_cost_tracker/models/index.html.erb +10 -9
  23. data/app/views/llm_cost_tracker/shared/_spend_chart.html.erb +0 -1
  24. data/app/views/llm_cost_tracker/shared/_tag_chips.html.erb +0 -1
  25. data/app/views/llm_cost_tracker/tags/index.html.erb +1 -1
  26. data/app/views/llm_cost_tracker/tags/show.html.erb +1 -1
  27. data/lib/llm_cost_tracker/configuration.rb +0 -1
  28. data/lib/llm_cost_tracker/event.rb +1 -0
  29. data/lib/llm_cost_tracker/event_metadata.rb +1 -0
  30. data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_provider_response_id_generator.rb +29 -0
  31. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_provider_response_id_to_llm_api_calls.rb.erb +15 -0
  32. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb +2 -0
  33. data/lib/llm_cost_tracker/llm_api_call.rb +6 -2
  34. data/lib/llm_cost_tracker/middleware/faraday.rb +1 -0
  35. data/lib/llm_cost_tracker/parameter_hash.rb +33 -0
  36. data/lib/llm_cost_tracker/parsed_usage.rb +14 -3
  37. data/lib/llm_cost_tracker/parsers/anthropic.rb +47 -28
  38. data/lib/llm_cost_tracker/parsers/gemini.rb +28 -4
  39. data/lib/llm_cost_tracker/parsers/openai_compatible.rb +5 -6
  40. data/lib/llm_cost_tracker/parsers/openai_usage.rb +14 -0
  41. data/lib/llm_cost_tracker/price_registry.rb +22 -7
  42. data/lib/llm_cost_tracker/price_sync/refresh_plan_builder.rb +162 -0
  43. data/lib/llm_cost_tracker/price_sync/registry_loader.rb +55 -0
  44. data/lib/llm_cost_tracker/price_sync/registry_writer.rb +25 -0
  45. data/lib/llm_cost_tracker/price_sync.rb +16 -184
  46. data/lib/llm_cost_tracker/pricing.rb +0 -11
  47. data/lib/llm_cost_tracker/railtie.rb +0 -1
  48. data/lib/llm_cost_tracker/report.rb +0 -5
  49. data/lib/llm_cost_tracker/storage/active_record_store.rb +10 -11
  50. data/lib/llm_cost_tracker/stream_collector.rb +17 -13
  51. data/lib/llm_cost_tracker/tags_column.rb +4 -0
  52. data/lib/llm_cost_tracker/tracker.rb +10 -2
  53. data/lib/llm_cost_tracker/version.rb +1 -1
  54. data/lib/llm_cost_tracker.rb +6 -14
  55. metadata +7 -1
@@ -8,10 +8,11 @@ module LlmCostTracker
8
8
  class StreamCollector
9
9
  attr_reader :provider
10
10
 
11
- def initialize(provider:, model:, latency_ms: nil, metadata: {})
11
+ def initialize(provider:, model:, latency_ms: nil, provider_response_id: nil, metadata: {})
12
12
  @provider = provider.to_s
13
13
  @model = model
14
14
  @latency_ms = latency_ms
15
+ @provider_response_id = provider_response_id
15
16
  @metadata = ValueHelpers.deep_dup(metadata || {})
16
17
  @events = []
17
18
  @explicit_usage = nil
@@ -20,13 +21,11 @@ module LlmCostTracker
20
21
  @monitor = Monitor.new
21
22
  end
22
23
 
23
- def model
24
- @monitor.synchronize { @model }
25
- end
24
+ def model = @monitor.synchronize { @model }
26
25
 
27
- def metadata
28
- @monitor.synchronize { ValueHelpers.deep_dup(@metadata) }
29
- end
26
+ def metadata = @monitor.synchronize { ValueHelpers.deep_dup(@metadata) }
27
+
28
+ def provider_response_id = @monitor.synchronize { @provider_response_id }
30
29
 
31
30
  def model=(value)
32
31
  @monitor.synchronize do
@@ -35,6 +34,13 @@ module LlmCostTracker
35
34
  end
36
35
  end
37
36
 
37
+ def provider_response_id=(value)
38
+ @monitor.synchronize do
39
+ ensure_open!
40
+ @provider_response_id = value
41
+ end
42
+ end
43
+
38
44
  def event(data, type: nil)
39
45
  @monitor.synchronize do
40
46
  ensure_open!
@@ -67,6 +73,7 @@ module LlmCostTracker
67
73
  explicit_usage: ValueHelpers.deep_dup(@explicit_usage),
68
74
  model: @model,
69
75
  latency_ms: @latency_ms,
76
+ provider_response_id: @provider_response_id,
70
77
  metadata: ValueHelpers.deep_dup(@metadata)
71
78
  }
72
79
  end
@@ -80,6 +87,7 @@ module LlmCostTracker
80
87
  latency_ms: snapshot[:latency_ms] || elapsed_ms,
81
88
  stream: true,
82
89
  usage_source: parsed.usage_source,
90
+ provider_response_id: parsed.provider_response_id || snapshot[:provider_response_id],
83
91
  metadata: error_metadata(errored).merge(snapshot[:metadata]).merge(parsed.metadata)
84
92
  )
85
93
  end
@@ -147,12 +155,8 @@ module LlmCostTracker
147
155
  )
148
156
  end
149
157
 
150
- def error_metadata(errored)
151
- errored ? { stream_errored: true } : {}
152
- end
158
+ def error_metadata(errored) = errored ? { stream_errored: true } : {}
153
159
 
154
- def elapsed_ms
155
- ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at) * 1000).round
156
- end
160
+ def elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at) * 1000).round
157
161
  end
158
162
  end
@@ -32,5 +32,9 @@ module LlmCostTracker
32
32
  def usage_source_column?
33
33
  columns_hash.key?("usage_source")
34
34
  end
35
+
36
+ def provider_response_id_column?
37
+ columns_hash.key?("provider_response_id")
38
+ end
35
39
  end
36
40
  end
@@ -13,8 +13,8 @@ module LlmCostTracker
13
13
  Budget.enforce!
14
14
  end
15
15
 
16
- def record(provider:, model:, input_tokens:, output_tokens:,
17
- metadata: {}, latency_ms: nil, stream: false, usage_source: nil)
16
+ def record(provider:, model:, input_tokens:, output_tokens:, latency_ms: nil, stream: false,
17
+ usage_source: nil, provider_response_id: nil, metadata: {})
18
18
  usage = EventMetadata.usage_data(input_tokens, output_tokens, metadata)
19
19
 
20
20
  cost_data = Pricing.cost_for(
@@ -39,6 +39,7 @@ module LlmCostTracker
39
39
  latency_ms: normalized_latency_ms(latency_ms),
40
40
  stream: stream ? true : false,
41
41
  usage_source: normalized_usage_source(usage_source),
42
+ provider_response_id: normalized_provider_response_id(provider_response_id),
42
43
  tracked_at: Time.now.utc
43
44
  )
44
45
 
@@ -122,6 +123,13 @@ module LlmCostTracker
122
123
  symbol = value.to_sym
123
124
  USAGE_SOURCES.include?(symbol) ? symbol.to_s : nil
124
125
  end
126
+
127
+ def normalized_provider_response_id(value)
128
+ return nil if value.nil?
129
+
130
+ string = value.to_s
131
+ string.empty? ? nil : string
132
+ end
125
133
  end
126
134
  end
127
135
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LlmCostTracker
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -8,6 +8,7 @@ require_relative "llm_cost_tracker/version"
8
8
  require_relative "llm_cost_tracker/configuration"
9
9
  require_relative "llm_cost_tracker/errors"
10
10
  require_relative "llm_cost_tracker/logging"
11
+ require_relative "llm_cost_tracker/parameter_hash"
11
12
  require_relative "llm_cost_tracker/cost"
12
13
  require_relative "llm_cost_tracker/event"
13
14
  require_relative "llm_cost_tracker/parsed_usage"
@@ -44,10 +45,6 @@ module LlmCostTracker
44
45
  CONFIGURATION_MUTEX.synchronize { @configuration ||= Configuration.new }
45
46
  end
46
47
 
47
- # Configure the gem once during application boot.
48
- #
49
- # @yieldparam configuration [LlmCostTracker::Configuration]
50
- # @return [void]
51
48
  def configure
52
49
  config = CONFIGURATION_MUTEX.synchronize do
53
50
  current = @configuration || Configuration.new
@@ -69,13 +66,8 @@ module LlmCostTracker
69
66
  Tracker.enforce_budget!
70
67
  end
71
68
 
72
- def track(provider:, model:, input_tokens:, output_tokens:, **options)
73
- latency_ms = options.delete(:latency_ms)
74
- stream = options.key?(:stream) ? options.delete(:stream) : false
75
- usage_source = options.key?(:usage_source) ? options.delete(:usage_source) : :manual
76
- enforce_budget = options.key?(:enforce_budget) ? options.delete(:enforce_budget) : false
77
- metadata = options
78
-
69
+ def track(provider:, model:, input_tokens:, output_tokens:, latency_ms: nil, stream: false, usage_source: :manual,
70
+ enforce_budget: false, provider_response_id: nil, **metadata)
79
71
  enforce_budget! if enforce_budget
80
72
  Tracker.record(
81
73
  provider: provider.to_s,
@@ -85,17 +77,19 @@ module LlmCostTracker
85
77
  latency_ms: latency_ms,
86
78
  stream: stream,
87
79
  usage_source: usage_source,
80
+ provider_response_id: provider_response_id,
88
81
  metadata: metadata
89
82
  )
90
83
  end
91
84
 
92
- def track_stream(provider:, model:, latency_ms: nil, enforce_budget: false, **metadata)
85
+ def track_stream(provider:, model:, latency_ms: nil, enforce_budget: false, provider_response_id: nil, **metadata)
93
86
  require_relative "llm_cost_tracker/stream_collector"
94
87
  enforce_budget! if enforce_budget
95
88
  collector = StreamCollector.new(
96
89
  provider: provider.to_s,
97
90
  model: model,
98
91
  latency_ms: latency_ms,
92
+ provider_response_id: provider_response_id,
99
93
  metadata: metadata
100
94
  )
101
95
  yield collector
@@ -116,10 +110,8 @@ module LlmCostTracker
116
110
  end
117
111
  end
118
112
 
119
- # Load Railtie if Rails is present
120
113
  require_relative "llm_cost_tracker/railtie" if defined?(Rails::Railtie)
121
114
 
122
- # Auto-register Faraday middleware
123
115
  if defined?(Faraday)
124
116
  Faraday::Middleware.register_middleware(
125
117
  llm_cost_tracker: LlmCostTracker::Middleware::Faraday
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.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergii Khomenko
@@ -248,10 +248,12 @@ files:
248
248
  - lib/llm_cost_tracker/event.rb
249
249
  - lib/llm_cost_tracker/event_metadata.rb
250
250
  - lib/llm_cost_tracker/generators/llm_cost_tracker/add_latency_ms_generator.rb
251
+ - lib/llm_cost_tracker/generators/llm_cost_tracker/add_provider_response_id_generator.rb
251
252
  - lib/llm_cost_tracker/generators/llm_cost_tracker/add_streaming_generator.rb
252
253
  - lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb
253
254
  - lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb
254
255
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_latency_ms_to_llm_api_calls.rb.erb
256
+ - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_provider_response_id_to_llm_api_calls.rb.erb
255
257
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_streaming_to_llm_api_calls.rb.erb
256
258
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb
257
259
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb
@@ -263,6 +265,7 @@ files:
263
265
  - lib/llm_cost_tracker/llm_api_call.rb
264
266
  - lib/llm_cost_tracker/logging.rb
265
267
  - lib/llm_cost_tracker/middleware/faraday.rb
268
+ - lib/llm_cost_tracker/parameter_hash.rb
266
269
  - lib/llm_cost_tracker/parsed_usage.rb
267
270
  - lib/llm_cost_tracker/parsers/anthropic.rb
268
271
  - lib/llm_cost_tracker/parsers/base.rb
@@ -279,6 +282,9 @@ files:
279
282
  - lib/llm_cost_tracker/price_sync/merger.rb
280
283
  - lib/llm_cost_tracker/price_sync/model_catalog.rb
281
284
  - lib/llm_cost_tracker/price_sync/raw_price.rb
285
+ - lib/llm_cost_tracker/price_sync/refresh_plan_builder.rb
286
+ - lib/llm_cost_tracker/price_sync/registry_loader.rb
287
+ - lib/llm_cost_tracker/price_sync/registry_writer.rb
282
288
  - lib/llm_cost_tracker/price_sync/source.rb
283
289
  - lib/llm_cost_tracker/price_sync/source_result.rb
284
290
  - lib/llm_cost_tracker/price_sync/sources/litellm.rb