llm_cost_tracker 0.4.1 → 0.5.0

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/README.md +182 -100
  4. data/lib/llm_cost_tracker/configuration/instrumentation.rb +37 -0
  5. data/lib/llm_cost_tracker/configuration.rb +10 -5
  6. data/lib/llm_cost_tracker/doctor.rb +166 -0
  7. data/lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb +33 -0
  8. data/lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb +12 -6
  9. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +53 -21
  10. data/lib/llm_cost_tracker/integrations/anthropic.rb +75 -0
  11. data/lib/llm_cost_tracker/integrations/base.rb +72 -0
  12. data/lib/llm_cost_tracker/integrations/object_reader.rb +56 -0
  13. data/lib/llm_cost_tracker/integrations/openai.rb +95 -0
  14. data/lib/llm_cost_tracker/integrations/registry.rb +41 -0
  15. data/lib/llm_cost_tracker/middleware/faraday.rb +4 -3
  16. data/lib/llm_cost_tracker/parsed_usage.rb +8 -1
  17. data/lib/llm_cost_tracker/parsers/base.rb +1 -1
  18. data/lib/llm_cost_tracker/parsers/openai_usage.rb +1 -1
  19. data/lib/llm_cost_tracker/price_freshness.rb +38 -0
  20. data/lib/llm_cost_tracker/price_registry.rb +14 -0
  21. data/lib/llm_cost_tracker/price_sync/fetcher.rb +2 -1
  22. data/lib/llm_cost_tracker/price_sync/refresh_plan_builder.rb +4 -2
  23. data/lib/llm_cost_tracker/price_sync.rb +10 -0
  24. data/lib/llm_cost_tracker/prices.json +394 -41
  25. data/lib/llm_cost_tracker/pricing.rb +8 -1
  26. data/lib/llm_cost_tracker/request_url.rb +20 -0
  27. data/lib/llm_cost_tracker/stream_collector.rb +3 -3
  28. data/lib/llm_cost_tracker/tag_context.rb +52 -0
  29. data/lib/llm_cost_tracker/tracker.rb +5 -2
  30. data/lib/llm_cost_tracker/version.rb +1 -1
  31. data/lib/llm_cost_tracker.rb +14 -4
  32. data/lib/tasks/llm_cost_tracker.rake +21 -3
  33. metadata +12 -3
  34. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/llm_cost_tracker_prices.yml.erb +0 -51
@@ -25,9 +25,11 @@ require_relative "llm_cost_tracker/parsers/gemini"
25
25
  require_relative "llm_cost_tracker/parsers/sse"
26
26
  require_relative "llm_cost_tracker/parsers/registry"
27
27
  require_relative "llm_cost_tracker/middleware/faraday"
28
+ require_relative "llm_cost_tracker/integrations/registry"
28
29
  require_relative "llm_cost_tracker/budget"
29
30
  require_relative "llm_cost_tracker/unknown_pricing"
30
31
  require_relative "llm_cost_tracker/event_metadata"
32
+ require_relative "llm_cost_tracker/tag_context"
31
33
  require_relative "llm_cost_tracker/tags_column"
32
34
  require_relative "llm_cost_tracker/tag_key"
33
35
  require_relative "llm_cost_tracker/tag_query"
@@ -37,6 +39,7 @@ require_relative "llm_cost_tracker/retention"
37
39
  require_relative "llm_cost_tracker/report_data"
38
40
  require_relative "llm_cost_tracker/report_formatter"
39
41
  require_relative "llm_cost_tracker/report"
42
+ require_relative "llm_cost_tracker/doctor"
40
43
 
41
44
  module LlmCostTracker
42
45
  CONFIGURATION_MUTEX = Monitor.new
@@ -52,10 +55,11 @@ module LlmCostTracker
52
55
  current = current.dup_for_configuration if current.finalized?
53
56
  @configuration = current
54
57
  yield(current)
55
- current.normalize_openai_compatible_providers!
58
+ current.openai_compatible_providers = current.openai_compatible_providers.dup
56
59
  current.finalize!
57
60
  current
58
61
  end
62
+ Integrations.install!
59
63
  warn_for_configuration!(config)
60
64
  end
61
65
 
@@ -63,14 +67,20 @@ module LlmCostTracker
63
67
  CONFIGURATION_MUTEX.synchronize { @configuration = Configuration.new }
64
68
  UnknownPricing.reset! if defined?(UnknownPricing)
65
69
  Storage::ActiveRecordStore.reset! if defined?(Storage::ActiveRecordStore)
70
+ TagContext.clear! if defined?(TagContext)
66
71
  end
67
72
 
68
73
  def enforce_budget!
69
74
  Tracker.enforce_budget!
70
75
  end
71
76
 
72
- def track(provider:, model:, input_tokens:, output_tokens:, latency_ms: nil, stream: false, usage_source: :manual,
73
- enforce_budget: false, provider_response_id: nil, pricing_mode: nil, **metadata)
77
+ def with_tags(tags = nil, **kwargs, &)
78
+ merged = (tags || {}).to_h.merge(kwargs)
79
+ TagContext.with(merged, &)
80
+ end
81
+
82
+ def track(provider:, input_tokens:, output_tokens:, model: nil, latency_ms: nil, stream: false,
83
+ usage_source: :manual, enforce_budget: false, provider_response_id: nil, pricing_mode: nil, **metadata)
74
84
  enforce_budget! if enforce_budget
75
85
  Tracker.record(
76
86
  provider: provider.to_s,
@@ -86,7 +96,7 @@ module LlmCostTracker
86
96
  )
87
97
  end
88
98
 
89
- def track_stream(provider:, model:, latency_ms: nil, enforce_budget: false, provider_response_id: nil,
99
+ def track_stream(provider:, model: nil, latency_ms: nil, enforce_budget: false, provider_response_id: nil,
90
100
  pricing_mode: nil, **metadata)
91
101
  require_relative "llm_cost_tracker/stream_collector"
92
102
  enforce_budget! if enforce_budget
@@ -2,6 +2,14 @@
2
2
 
3
3
  # rubocop:disable Metrics/BlockLength
4
4
  namespace :llm_cost_tracker do
5
+ desc "Check LLM Cost Tracker setup"
6
+ task :doctor do
7
+ Rake::Task["environment"].invoke if Rake::Task.task_defined?("environment")
8
+ checks = LlmCostTracker::Doctor.call
9
+ puts LlmCostTracker::Doctor.report(checks)
10
+ abort("llm_cost_tracker: doctor found setup errors") unless LlmCostTracker::Doctor.healthy?(checks)
11
+ end
12
+
5
13
  desc "Print an LLM cost report from ActiveRecord storage"
6
14
  task report: :environment do
7
15
  days = (ENV["DAYS"] || LlmCostTracker::Report::DEFAULT_DAYS).to_i
@@ -18,7 +26,7 @@ namespace :llm_cost_tracker do
18
26
 
19
27
  namespace :prices do
20
28
  desc(
21
- "Sync built-in pricing data from LiteLLM/OpenRouter JSON sources. " \
29
+ "Sync the configured pricing file from LiteLLM/OpenRouter JSON sources. " \
22
30
  "Use PREVIEW=1 to preview, STRICT=1 to fail on provider errors, " \
23
31
  "or OUTPUT=path/to/file.json."
24
32
  )
@@ -26,7 +34,7 @@ namespace :llm_cost_tracker do
26
34
  Rake::Task["environment"].invoke if Rake::Task.task_defined?("environment")
27
35
  require_relative "../llm_cost_tracker"
28
36
 
29
- output_path = ENV["OUTPUT"] || LlmCostTracker.configuration.prices_file || LlmCostTracker::PriceSync::DEFAULT_OUTPUT_PATH
37
+ output_path = price_sync_output_path
30
38
  strict = ENV["STRICT"] == "1" || ARGV.include?("--strict")
31
39
  result = LlmCostTracker::PriceSync.sync(
32
40
  path: output_path,
@@ -57,7 +65,7 @@ namespace :llm_cost_tracker do
57
65
  Rake::Task["environment"].invoke if Rake::Task.task_defined?("environment")
58
66
  require_relative "../llm_cost_tracker"
59
67
 
60
- output_path = ENV["OUTPUT"] || LlmCostTracker.configuration.prices_file || LlmCostTracker::PriceSync::DEFAULT_OUTPUT_PATH
68
+ output_path = price_sync_output_path
61
69
  result = LlmCostTracker::PriceSync.check(path: output_path)
62
70
 
63
71
  puts "llm_cost_tracker: checked pricing file #{result.path}"
@@ -131,3 +139,13 @@ def print_failures(failed_sources, heading:)
131
139
  puts " - #{source}: #{message}"
132
140
  end
133
141
  end
142
+
143
+ def price_sync_output_path
144
+ path = LlmCostTracker::PriceSync.configured_output_path
145
+ return path if path
146
+
147
+ abort(
148
+ "llm_cost_tracker: configure prices_file, run bin/rails generate llm_cost_tracker:prices, " \
149
+ "or set OUTPUT=config/llm_cost_tracker_prices.yml"
150
+ )
151
+ end
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.4.1
4
+ version: 0.5.0
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-24 00:00:00.000000000 Z
11
+ date: 2026-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -272,7 +272,9 @@ files:
272
272
  - lib/llm_cost_tracker/assets.rb
273
273
  - lib/llm_cost_tracker/budget.rb
274
274
  - lib/llm_cost_tracker/configuration.rb
275
+ - lib/llm_cost_tracker/configuration/instrumentation.rb
275
276
  - lib/llm_cost_tracker/cost.rb
277
+ - lib/llm_cost_tracker/doctor.rb
276
278
  - lib/llm_cost_tracker/engine.rb
277
279
  - lib/llm_cost_tracker/engine_compatibility.rb
278
280
  - lib/llm_cost_tracker/errors.rb
@@ -292,11 +294,15 @@ files:
292
294
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_usage_breakdown_to_llm_api_calls.rb.erb
293
295
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb
294
296
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb
295
- - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/llm_cost_tracker_prices.yml.erb
296
297
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_cost_precision.rb.erb
297
298
  - lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_tags_to_jsonb.rb.erb
298
299
  - lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_cost_precision_generator.rb
299
300
  - lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_tags_to_jsonb_generator.rb
301
+ - lib/llm_cost_tracker/integrations/anthropic.rb
302
+ - lib/llm_cost_tracker/integrations/base.rb
303
+ - lib/llm_cost_tracker/integrations/object_reader.rb
304
+ - lib/llm_cost_tracker/integrations/openai.rb
305
+ - lib/llm_cost_tracker/integrations/registry.rb
300
306
  - lib/llm_cost_tracker/llm_api_call.rb
301
307
  - lib/llm_cost_tracker/logging.rb
302
308
  - lib/llm_cost_tracker/middleware/faraday.rb
@@ -312,6 +318,7 @@ files:
312
318
  - lib/llm_cost_tracker/parsers/sse.rb
313
319
  - lib/llm_cost_tracker/period_grouping.rb
314
320
  - lib/llm_cost_tracker/period_total.rb
321
+ - lib/llm_cost_tracker/price_freshness.rb
315
322
  - lib/llm_cost_tracker/price_registry.rb
316
323
  - lib/llm_cost_tracker/price_sync.rb
317
324
  - lib/llm_cost_tracker/price_sync/fetcher.rb
@@ -332,11 +339,13 @@ files:
332
339
  - lib/llm_cost_tracker/report.rb
333
340
  - lib/llm_cost_tracker/report_data.rb
334
341
  - lib/llm_cost_tracker/report_formatter.rb
342
+ - lib/llm_cost_tracker/request_url.rb
335
343
  - lib/llm_cost_tracker/retention.rb
336
344
  - lib/llm_cost_tracker/storage/active_record_rollups.rb
337
345
  - lib/llm_cost_tracker/storage/active_record_store.rb
338
346
  - lib/llm_cost_tracker/stream_collector.rb
339
347
  - lib/llm_cost_tracker/tag_accessors.rb
348
+ - lib/llm_cost_tracker/tag_context.rb
340
349
  - lib/llm_cost_tracker/tag_key.rb
341
350
  - lib/llm_cost_tracker/tag_query.rb
342
351
  - lib/llm_cost_tracker/tags_column.rb
@@ -1,51 +0,0 @@
1
- # Local LlmCostTracker price overrides.
2
- #
3
- # Add only the models you want to override or add. Built-in prices still come
4
- # from the gem's prices.json, and Ruby pricing_overrides still take precedence.
5
- #
6
- # Units: USD per 1M tokens.
7
- #
8
- # Supported price keys:
9
- # - input
10
- # - output
11
- # - cache_read_input
12
- # - cache_write_input
13
- # - mode_input / mode_output / mode_cache_read_input / mode_cache_write_input
14
- #
15
- # Optional metadata keys, ignored by cost calculation:
16
- # - _source
17
- # - _source_version
18
- # - _fetched_at
19
- # - _updated
20
- # - _notes
21
- # - _validator_override
22
- #
23
- # Example: custom fine-tune
24
- # models:
25
- # "ft:gpt-4o-mini:my-org":
26
- # input: 0.30
27
- # cache_read_input: 0.15
28
- # output: 1.20
29
- # _notes: "Internal fine-tune rate"
30
- #
31
- # Example: alternate pricing mode
32
- # models:
33
- # "batchable-model":
34
- # input: 1.00
35
- # output: 2.00
36
- # batch_input: 0.50
37
- # batch_output: 1.00
38
- #
39
- # Example: negotiated provider discount
40
- # models:
41
- # "gpt-4o":
42
- # input: 2.00
43
- # output: 8.00
44
- # _source: "manual"
45
- # _updated: "2026-04-18"
46
- #
47
- # Use _source: "manual" for custom or orphaned entries you never want sync to touch.
48
- # Use _validator_override: ["skip_relative_change"] if a negotiated price would
49
- # otherwise trip the >3x sync warning.
50
-
51
- models: