llm_cost_tracker 0.14.2 → 0.15.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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +116 -0
  3. data/LICENSE.txt +28 -0
  4. data/README.md +33 -41
  5. data/app/controllers/llm_cost_tracker/application_controller.rb +7 -1
  6. data/app/controllers/llm_cost_tracker/calls_controller.rb +12 -94
  7. data/app/controllers/llm_cost_tracker/dashboard_controller.rb +11 -12
  8. data/app/controllers/llm_cost_tracker/pricing_controller.rb +15 -4
  9. data/app/controllers/llm_cost_tracker/tags_controller.rb +21 -14
  10. data/app/helpers/llm_cost_tracker/application_helper.rb +22 -31
  11. data/app/helpers/llm_cost_tracker/chart_helper.rb +57 -108
  12. data/app/helpers/llm_cost_tracker/dashboard_query_helper.rb +30 -17
  13. data/app/helpers/llm_cost_tracker/sortable_table_helper.rb +18 -25
  14. data/app/helpers/llm_cost_tracker/token_usage_helper.rb +30 -0
  15. data/app/models/llm_cost_tracker/call.rb +6 -4
  16. data/app/models/llm_cost_tracker/call_rollup.rb +6 -6
  17. data/app/services/llm_cost_tracker/dashboard/call_order.rb +36 -0
  18. data/app/services/llm_cost_tracker/dashboard/calls_export.rb +68 -0
  19. data/app/services/llm_cost_tracker/dashboard/data_quality/stats_query.rb +77 -0
  20. data/app/services/llm_cost_tracker/dashboard/data_quality/summary.rb +47 -0
  21. data/app/services/llm_cost_tracker/dashboard/data_quality.rb +66 -210
  22. data/app/services/llm_cost_tracker/dashboard/filter.rb +2 -1
  23. data/app/services/llm_cost_tracker/dashboard/monthly_budget.rb +42 -35
  24. data/app/services/llm_cost_tracker/dashboard/percent.rb +12 -0
  25. data/app/services/llm_cost_tracker/dashboard/sort.rb +9 -0
  26. data/app/services/llm_cost_tracker/dashboard/spend_anomaly.rb +19 -17
  27. data/app/services/llm_cost_tracker/dashboard/spend_chart.rb +50 -0
  28. data/app/services/llm_cost_tracker/dashboard/tag_breakdown.rb +7 -8
  29. data/app/services/llm_cost_tracker/dashboard/tag_key_explorer.rb +1 -4
  30. data/app/services/llm_cost_tracker/dashboard/top_models.rb +3 -4
  31. data/app/views/layouts/llm_cost_tracker/application.html.erb +6 -6
  32. data/app/views/llm_cost_tracker/calls/index.html.erb +2 -1
  33. data/app/views/llm_cost_tracker/calls/show.html.erb +3 -14
  34. data/app/views/llm_cost_tracker/errors/query.html.erb +6 -0
  35. data/app/views/llm_cost_tracker/shared/_filter_pill_stream.html.erb +1 -1
  36. data/app/views/llm_cost_tracker/tags/show.html.erb +1 -1
  37. data/lib/llm_cost_tracker/budget/estimate.rb +35 -0
  38. data/lib/llm_cost_tracker/budget/global.rb +69 -0
  39. data/lib/llm_cost_tracker/budget/limit.rb +52 -0
  40. data/lib/llm_cost_tracker/budget/per_tag.rb +59 -27
  41. data/lib/llm_cost_tracker/budget/tagged.rb +74 -0
  42. data/lib/llm_cost_tracker/budget.rb +19 -157
  43. data/lib/llm_cost_tracker/capture/event_window.rb +16 -13
  44. data/lib/llm_cost_tracker/capture/sdk_payload.rb +22 -17
  45. data/lib/llm_cost_tracker/capture/sse.rb +7 -5
  46. data/lib/llm_cost_tracker/capture/stream_collector.rb +89 -96
  47. data/lib/llm_cost_tracker/capture/stream_tap.rb +2 -2
  48. data/lib/llm_cost_tracker/capture/stream_tracker.rb +32 -37
  49. data/lib/llm_cost_tracker/capture_verifier.rb +2 -3
  50. data/lib/llm_cost_tracker/charges/cost_status.rb +23 -19
  51. data/lib/llm_cost_tracker/charges/line_item.rb +31 -33
  52. data/lib/llm_cost_tracker/configuration/budgets.rb +49 -18
  53. data/lib/llm_cost_tracker/configuration/capture.rb +7 -1
  54. data/lib/llm_cost_tracker/configuration/mutability.rb +3 -3
  55. data/lib/llm_cost_tracker/doctor/ingestion_check.rb +20 -2
  56. data/lib/llm_cost_tracker/doctor/price_check.rb +24 -35
  57. data/lib/llm_cost_tracker/doctor/probe.rb +1 -1
  58. data/lib/llm_cost_tracker/doctor.rb +23 -20
  59. data/lib/llm_cost_tracker/engine.rb +8 -0
  60. data/lib/llm_cost_tracker/errors.rb +2 -4
  61. data/lib/llm_cost_tracker/event.rb +22 -29
  62. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_cost_tracker_calls.rb.erb +4 -2
  63. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +6 -4
  64. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_call_rollups_provider.rb.erb +1 -0
  65. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_call_tags_key_value_index.rb.erb +1 -0
  66. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_indexes.rb.erb +2 -1
  67. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_per_tag_budgets.rb.erb +1 -0
  68. data/lib/llm_cost_tracker/ingestion/batch.rb +33 -19
  69. data/lib/llm_cost_tracker/ingestion/inbox.rb +20 -11
  70. data/lib/llm_cost_tracker/ingestion/worker.rb +58 -37
  71. data/lib/llm_cost_tracker/ingestion.rb +20 -35
  72. data/lib/llm_cost_tracker/integrations/anthropic/batch_results_capture.rb +40 -0
  73. data/lib/llm_cost_tracker/integrations/anthropic/patches.rb +56 -0
  74. data/lib/llm_cost_tracker/integrations/anthropic.rb +59 -120
  75. data/lib/llm_cost_tracker/integrations/base/patch_target.rb +35 -0
  76. data/lib/llm_cost_tracker/integrations/base.rb +65 -81
  77. data/lib/llm_cost_tracker/integrations/openai/batch_capture.rb +20 -19
  78. data/lib/llm_cost_tracker/integrations/openai/patches.rb +51 -40
  79. data/lib/llm_cost_tracker/integrations/openai/websocket_capture.rb +89 -0
  80. data/lib/llm_cost_tracker/integrations/openai.rb +114 -83
  81. data/lib/llm_cost_tracker/integrations/ruby_llm/v1/patches.rb +67 -0
  82. data/lib/llm_cost_tracker/integrations/ruby_llm/v1/reply.rb +155 -0
  83. data/lib/llm_cost_tracker/integrations/ruby_llm/v1.rb +154 -0
  84. data/lib/llm_cost_tracker/integrations/ruby_llm/v2/attempt/token_counts.rb +159 -0
  85. data/lib/llm_cost_tracker/integrations/ruby_llm/v2/attempt.rb +175 -0
  86. data/lib/llm_cost_tracker/integrations/ruby_llm/v2/frame.rb +75 -0
  87. data/lib/llm_cost_tracker/integrations/ruby_llm/v2/recorder.rb +70 -0
  88. data/lib/llm_cost_tracker/integrations/ruby_llm/v2/seams.rb +86 -0
  89. data/lib/llm_cost_tracker/integrations/ruby_llm/v2.rb +161 -0
  90. data/lib/llm_cost_tracker/integrations/ruby_llm.rb +7 -408
  91. data/lib/llm_cost_tracker/ledger/isolation.rb +15 -2
  92. data/lib/llm_cost_tracker/ledger/period/totals.rb +1 -1
  93. data/lib/llm_cost_tracker/ledger/rollups.rb +24 -8
  94. data/lib/llm_cost_tracker/ledger/schema/adapter.rb +23 -6
  95. data/lib/llm_cost_tracker/ledger/schema/base.rb +9 -5
  96. data/lib/llm_cost_tracker/ledger/store.rb +17 -35
  97. data/lib/llm_cost_tracker/ledger/tags/breakdown.rb +2 -2
  98. data/lib/llm_cost_tracker/middleware/faraday/body.rb +57 -0
  99. data/lib/llm_cost_tracker/middleware/faraday/exchange.rb +153 -0
  100. data/lib/llm_cost_tracker/middleware/faraday/response_reader.rb +75 -0
  101. data/lib/llm_cost_tracker/middleware/faraday/stream_tee.rb +47 -0
  102. data/lib/llm_cost_tracker/middleware/faraday.rb +2 -301
  103. data/lib/llm_cost_tracker/parsers.rb +12 -0
  104. data/lib/llm_cost_tracker/prices.json +1179 -208
  105. data/lib/llm_cost_tracker/pricing/backfill.rb +59 -29
  106. data/lib/llm_cost_tracker/pricing/calculation/iterations.rb +49 -0
  107. data/lib/llm_cost_tracker/pricing/calculation/quantities.rb +40 -0
  108. data/lib/llm_cost_tracker/pricing/calculation/snapshot.rb +51 -0
  109. data/lib/llm_cost_tracker/pricing/calculation/totals.rb +59 -0
  110. data/lib/llm_cost_tracker/pricing/calculation.rb +82 -173
  111. data/lib/llm_cost_tracker/pricing/matcher.rb +31 -14
  112. data/lib/llm_cost_tracker/pricing/mode.rb +6 -19
  113. data/lib/llm_cost_tracker/pricing/off_peak.rb +50 -0
  114. data/lib/llm_cost_tracker/pricing/rate.rb +1 -0
  115. data/lib/llm_cost_tracker/pricing/registry/normalizer.rb +110 -0
  116. data/lib/llm_cost_tracker/pricing/registry.rb +38 -111
  117. data/lib/llm_cost_tracker/pricing/sync/change_printer.rb +40 -20
  118. data/lib/llm_cost_tracker/pricing/sync/fetcher.rb +35 -34
  119. data/lib/llm_cost_tracker/pricing/sync/registry_writer.rb +29 -18
  120. data/lib/llm_cost_tracker/pricing/sync/remote_snapshot.rb +78 -0
  121. data/lib/llm_cost_tracker/pricing/sync/snapshot_guard.rb +2 -0
  122. data/lib/llm_cost_tracker/pricing/sync.rb +7 -53
  123. data/lib/llm_cost_tracker/pricing/unknown.rb +1 -1
  124. data/lib/llm_cost_tracker/providers/anthropic/parser.rb +30 -25
  125. data/lib/llm_cost_tracker/providers/anthropic/response_parser.rb +5 -3
  126. data/lib/llm_cost_tracker/providers/anthropic/usage_extractor.rb +122 -101
  127. data/lib/llm_cost_tracker/providers/azure/hosts.rb +1 -1
  128. data/lib/llm_cost_tracker/providers/azure/parser.rb +3 -6
  129. data/lib/llm_cost_tracker/providers/gemini/grounding.rb +87 -0
  130. data/lib/llm_cost_tracker/providers/gemini/parser.rb +120 -183
  131. data/lib/llm_cost_tracker/providers/gemini/usage_extractor.rb +66 -47
  132. data/lib/llm_cost_tracker/providers/openai/hosts.rb +11 -0
  133. data/lib/llm_cost_tracker/providers/openai/model_families.rb +1 -1
  134. data/lib/llm_cost_tracker/providers/openai/output_item.rb +84 -0
  135. data/lib/llm_cost_tracker/providers/openai/parser.rb +2 -1
  136. data/lib/llm_cost_tracker/providers/openai/response_parser.rb +81 -149
  137. data/lib/llm_cost_tracker/providers/openai/service_charges.rb +133 -156
  138. data/lib/llm_cost_tracker/providers/openai/stream_parser.rb +127 -0
  139. data/lib/llm_cost_tracker/providers/openai/usage_extractor.rb +72 -62
  140. data/lib/llm_cost_tracker/providers/openai_compatible/parser.rb +15 -4
  141. data/lib/llm_cost_tracker/providers.rb +3 -0
  142. data/lib/llm_cost_tracker/redaction.rb +13 -0
  143. data/lib/llm_cost_tracker/report/data.rb +25 -28
  144. data/lib/llm_cost_tracker/tags/context.rb +22 -7
  145. data/lib/llm_cost_tracker/tracker.rb +32 -26
  146. data/lib/llm_cost_tracker/usage/dimensions.yml +24 -0
  147. data/lib/llm_cost_tracker/usage/token_usage.rb +8 -4
  148. data/lib/llm_cost_tracker/version.rb +1 -1
  149. data/lib/llm_cost_tracker.rb +8 -4
  150. data/lib/tasks/llm_cost_tracker.rake +35 -5
  151. data/llm_cost_tracker.gemspec +6 -5
  152. metadata +58 -5
@@ -128,12 +128,16 @@ module LlmCostTracker
128
128
  pricing_mode: pricing_mode,
129
129
  metadata: tags
130
130
  )
131
+ completed = false
131
132
  yield collector
132
- rescue Exception # rubocop:disable Lint/RescueException -- record the spend and keep the caller's exception
133
- collector&.finish!(errored: true)
134
- raise
135
- else
133
+ completed = true
136
134
  collector.finish!
135
+ rescue Exception # rubocop:disable Lint/RescueException
136
+ collector&.finish!(errored: true) unless completed
137
+ completed = true
138
+ raise
139
+ ensure
140
+ collector&.finish! unless completed
137
141
  end
138
142
  end
139
143
  end
@@ -12,6 +12,34 @@ module LlmCostTrackerTasks
12
12
  FileUtils.mkdir_p(File.dirname(path))
13
13
  path
14
14
  end
15
+
16
+ def self.batch_size(default)
17
+ text = ENV.fetch("BATCH_SIZE", default.to_s)
18
+ size = Integer(text, 10, exception: false)
19
+ return size if size&.positive?
20
+
21
+ abort("llm_cost_tracker: BATCH_SIZE=#{text} is not a positive integer, e.g. BATCH_SIZE=#{default}")
22
+ end
23
+
24
+ def self.backfill_after_refresh(path)
25
+ file = LlmCostTracker.configuration.pricing.file
26
+ return unless file && File.expand_path(file) == File.expand_path(path)
27
+
28
+ if ledger_reachable?
29
+ Rake::Task["llm_cost_tracker:backfill_unknown_pricing"].execute
30
+ else
31
+ puts "llm_cost_tracker: calls ledger not reachable; " \
32
+ "run bin/rails llm_cost_tracker:backfill_unknown_pricing where it is"
33
+ end
34
+ end
35
+
36
+ def self.ledger_reachable?
37
+ return false unless Rake::Task.task_defined?("environment")
38
+
39
+ LlmCostTracker::Call.with_connection { |connection| connection.table_exists?(LlmCostTracker::Call.table_name) }
40
+ rescue ActiveRecord::NoDatabaseError, ActiveRecord::ConnectionNotEstablished
41
+ false
42
+ end
15
43
  end
16
44
 
17
45
  # rubocop:disable-next Metrics/BlockLength
@@ -58,7 +86,7 @@ namespace :llm_cost_tracker do
58
86
  "Use BATCH_SIZE=N to tune."
59
87
  task backfill_unknown_pricing: :environment do
60
88
  require_relative "../llm_cost_tracker/pricing/backfill"
61
- batch_size = (ENV["BATCH_SIZE"] || LlmCostTracker::Pricing::Backfill::DEFAULT_BATCH_SIZE).to_i
89
+ batch_size = LlmCostTrackerTasks.batch_size(LlmCostTracker::Pricing::Backfill::DEFAULT_BATCH_SIZE)
62
90
  result = LlmCostTracker::Pricing::Backfill.call(batch_size: batch_size)
63
91
  puts "llm_cost_tracker: examined #{result.examined} calls, recomputed #{result.recomputed}, " \
64
92
  "still unknown #{result.still_unknown}"
@@ -73,7 +101,7 @@ namespace :llm_cost_tracker do
73
101
  to_text = ENV["TO"].presence
74
102
  to = to_text && Time.zone.parse(to_text)
75
103
  abort("llm_cost_tracker: TO=#{to_text} is not a date, e.g. TO=2026-10-01") if to_text && !to
76
- batch_size = (ENV["BATCH_SIZE"] || LlmCostTracker::Pricing::Backfill::DEFAULT_BATCH_SIZE).to_i
104
+ batch_size = LlmCostTrackerTasks.batch_size(LlmCostTracker::Pricing::Backfill::DEFAULT_BATCH_SIZE)
77
105
  scope = LlmCostTracker::Pricing::Backfill.reprice_scope(from...to)
78
106
  result = LlmCostTracker::Pricing::Backfill.call(scope: scope, batch_size: batch_size, reprice: true)
79
107
  puts "llm_cost_tracker: examined #{result.examined} calls, repriced #{result.recomputed}"
@@ -82,7 +110,7 @@ namespace :llm_cost_tracker do
82
110
  desc "Delete llm_cost_tracker_calls and ingestion inbox rows older than DAYS (default: 90). Use BATCH_SIZE=N to tune."
83
111
  task prune: :environment do
84
112
  days = (ENV["DAYS"] || 90).to_i
85
- batch_size = (ENV["BATCH_SIZE"] || LlmCostTracker::Retention::DEFAULT_BATCH_SIZE).to_i
113
+ batch_size = LlmCostTrackerTasks.batch_size(LlmCostTracker::Retention::DEFAULT_BATCH_SIZE)
86
114
  deleted = LlmCostTracker::Retention.prune(older_than: days, batch_size: batch_size)
87
115
  puts "llm_cost_tracker: pruned #{deleted} calls older than #{days} days"
88
116
  inbox_pruned = LlmCostTracker::Retention.prune_inbox(older_than: days)
@@ -105,14 +133,15 @@ namespace :llm_cost_tracker do
105
133
  abort("llm_cost_tracker: llm_cost_tracker_call_tags is missing the cost columns; " \
106
134
  "run the upgrade_per_tag_budgets generator and migrate first")
107
135
  end
108
- batch_size = (ENV["BATCH_SIZE"] || LlmCostTracker::Budget::PerTag::DEFAULT_BACKFILL_BATCH).to_i
136
+ batch_size = LlmCostTrackerTasks.batch_size(LlmCostTracker::Budget::PerTag::DEFAULT_BACKFILL_BATCH)
109
137
  filled = LlmCostTracker::Budget::PerTag.backfill(batch_size: batch_size)
110
138
  puts "llm_cost_tracker: filled cost and time on #{filled} tag rows"
111
139
  end
112
140
 
113
141
  namespace :prices do
114
142
  desc(
115
- "Refresh the configured pricing file from the maintained LLM Cost Tracker price snapshot. " \
143
+ "Refresh the configured pricing file from the maintained LLM Cost Tracker price snapshot; after writing a new " \
144
+ "config.pricing.file, run llm_cost_tracker:backfill_unknown_pricing if the calls ledger is reachable. " \
116
145
  "Review changes first with llm_cost_tracker:prices:check. Use FORCE=1 to accept suspicious price changes, " \
117
146
  "URL=... to override the source, or OUTPUT=path/to/file.json."
118
147
  )
@@ -130,6 +159,7 @@ namespace :llm_cost_tracker do
130
159
  puts " source: #{result.source_url}"
131
160
  puts " version: #{result.source_version.inspect}" if result.source_version
132
161
  LlmCostTracker::Pricing::Sync::ChangePrinter.call(result.changes, suspicious: result.suspicious)
162
+ LlmCostTrackerTasks.backfill_after_refresh(result.path) if result.written
133
163
  end
134
164
 
135
165
  desc "Compare the current pricing file with the maintained LLM Cost Tracker price snapshot."
@@ -8,11 +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 = "LLM API cost tracking for Rails applications"
12
- spec.description = "Logs every call your Rails app makes to OpenAI, Anthropic, Gemini, RubyLLM, " \
13
- "or an OpenAI-compatible API: tokens, cost, latency, tags. Calls go straight " \
14
- "to the provider — no proxy. Includes price sync, budget guardrails, and a " \
15
- "mountable dashboard."
11
+ spec.summary = "LLM spend tracking and budgets for Rails, in your own database"
12
+ spec.description = "Records every LLM call your Rails app makes through RubyLLM, the official OpenAI " \
13
+ "and Anthropic SDKs, Faraday, or any OpenAI-compatible API: tokens and cost, attributed " \
14
+ "by user, feature, or any tag. Calls go straight to the provider — no proxy. " \
15
+ "Includes budgets that can block the next call, bundled prices, and a dashboard."
16
16
  spec.homepage = "https://github.com/sergey-homenko/llm_cost_tracker"
17
17
  spec.license = "MIT"
18
18
 
@@ -50,6 +50,7 @@ Gem::Specification.new do |spec|
50
50
  spec.add_dependency "railties", ">= 8.0", "< 9.0"
51
51
 
52
52
  spec.add_development_dependency "anthropic", "~> 1.61"
53
+ spec.add_development_dependency "aws-eventstream", "~> 1.4"
53
54
  spec.add_development_dependency "nokogiri", "~> 1.19"
54
55
  spec.add_development_dependency "openai", "~> 0.80"
55
56
  spec.add_development_dependency "pg", "~> 1.6"
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.14.2
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergii Khomenko
@@ -117,6 +117,20 @@ dependencies:
117
117
  - - "~>"
118
118
  - !ruby/object:Gem::Version
119
119
  version: '1.61'
120
+ - !ruby/object:Gem::Dependency
121
+ name: aws-eventstream
122
+ requirement: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - "~>"
125
+ - !ruby/object:Gem::Version
126
+ version: '1.4'
127
+ type: :development
128
+ prerelease: false
129
+ version_requirements: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - "~>"
132
+ - !ruby/object:Gem::Version
133
+ version: '1.4'
120
134
  - !ruby/object:Gem::Dependency
121
135
  name: nokogiri
122
136
  requirement: !ruby/object:Gem::Requirement
@@ -263,9 +277,10 @@ dependencies:
263
277
  - - "~>"
264
278
  - !ruby/object:Gem::Version
265
279
  version: '3.0'
266
- description: 'Logs every call your Rails app makes to OpenAI, Anthropic, Gemini, RubyLLM,
267
- or an OpenAI-compatible API: tokens, cost, latency, tags. Calls go straight to the
268
- provider — no proxy. Includes price sync, budget guardrails, and a mountable dashboard.'
280
+ description: 'Records every LLM call your Rails app makes through RubyLLM, the official
281
+ OpenAI and Anthropic SDKs, Faraday, or any OpenAI-compatible API: tokens and cost,
282
+ attributed by user, feature, or any tag. Calls go straight to the provider — no
283
+ proxy. Includes budgets that can block the next call, bundled prices, and a dashboard.'
269
284
  email:
270
285
  - sergey@mm.st
271
286
  executables: []
@@ -303,7 +318,11 @@ files:
303
318
  - app/models/llm_cost_tracker/call_tag.rb
304
319
  - app/models/llm_cost_tracker/ingestion/inbox_entry.rb
305
320
  - app/models/llm_cost_tracker/ingestion/lease.rb
321
+ - app/services/llm_cost_tracker/dashboard/call_order.rb
322
+ - app/services/llm_cost_tracker/dashboard/calls_export.rb
306
323
  - app/services/llm_cost_tracker/dashboard/data_quality.rb
324
+ - app/services/llm_cost_tracker/dashboard/data_quality/stats_query.rb
325
+ - app/services/llm_cost_tracker/dashboard/data_quality/summary.rb
307
326
  - app/services/llm_cost_tracker/dashboard/date_range.rb
308
327
  - app/services/llm_cost_tracker/dashboard/filter.rb
309
328
  - app/services/llm_cost_tracker/dashboard/masking.rb
@@ -311,11 +330,13 @@ files:
311
330
  - app/services/llm_cost_tracker/dashboard/overview_stats.rb
312
331
  - app/services/llm_cost_tracker/dashboard/pagination.rb
313
332
  - app/services/llm_cost_tracker/dashboard/params.rb
333
+ - app/services/llm_cost_tracker/dashboard/percent.rb
314
334
  - app/services/llm_cost_tracker/dashboard/pricing_overview.rb
315
335
  - app/services/llm_cost_tracker/dashboard/provider_breakdown.rb
316
336
  - app/services/llm_cost_tracker/dashboard/setup_state.rb
317
337
  - app/services/llm_cost_tracker/dashboard/sort.rb
318
338
  - app/services/llm_cost_tracker/dashboard/spend_anomaly.rb
339
+ - app/services/llm_cost_tracker/dashboard/spend_chart.rb
319
340
  - app/services/llm_cost_tracker/dashboard/tag_breakdown.rb
320
341
  - app/services/llm_cost_tracker/dashboard/tag_key_explorer.rb
321
342
  - app/services/llm_cost_tracker/dashboard/time_series.rb
@@ -328,6 +349,7 @@ files:
328
349
  - app/views/llm_cost_tracker/errors/database.html.erb
329
350
  - app/views/llm_cost_tracker/errors/invalid_filter.html.erb
330
351
  - app/views/llm_cost_tracker/errors/not_found.html.erb
352
+ - app/views/llm_cost_tracker/errors/query.html.erb
331
353
  - app/views/llm_cost_tracker/models/index.html.erb
332
354
  - app/views/llm_cost_tracker/pricing/index.html.erb
333
355
  - app/views/llm_cost_tracker/shared/_bar.html.erb
@@ -345,7 +367,11 @@ files:
345
367
  - lib/llm_cost_tracker.rb
346
368
  - lib/llm_cost_tracker/assets.rb
347
369
  - lib/llm_cost_tracker/budget.rb
370
+ - lib/llm_cost_tracker/budget/estimate.rb
371
+ - lib/llm_cost_tracker/budget/global.rb
372
+ - lib/llm_cost_tracker/budget/limit.rb
348
373
  - lib/llm_cost_tracker/budget/per_tag.rb
374
+ - lib/llm_cost_tracker/budget/tagged.rb
349
375
  - lib/llm_cost_tracker/capture/event_window.rb
350
376
  - lib/llm_cost_tracker/capture/sdk_payload.rb
351
377
  - lib/llm_cost_tracker/capture/sse.rb
@@ -401,11 +427,24 @@ files:
401
427
  - lib/llm_cost_tracker/ingestion/worker.rb
402
428
  - lib/llm_cost_tracker/integrations.rb
403
429
  - lib/llm_cost_tracker/integrations/anthropic.rb
430
+ - lib/llm_cost_tracker/integrations/anthropic/batch_results_capture.rb
431
+ - lib/llm_cost_tracker/integrations/anthropic/patches.rb
404
432
  - lib/llm_cost_tracker/integrations/base.rb
433
+ - lib/llm_cost_tracker/integrations/base/patch_target.rb
405
434
  - lib/llm_cost_tracker/integrations/openai.rb
406
435
  - lib/llm_cost_tracker/integrations/openai/batch_capture.rb
407
436
  - lib/llm_cost_tracker/integrations/openai/patches.rb
437
+ - lib/llm_cost_tracker/integrations/openai/websocket_capture.rb
408
438
  - lib/llm_cost_tracker/integrations/ruby_llm.rb
439
+ - lib/llm_cost_tracker/integrations/ruby_llm/v1.rb
440
+ - lib/llm_cost_tracker/integrations/ruby_llm/v1/patches.rb
441
+ - lib/llm_cost_tracker/integrations/ruby_llm/v1/reply.rb
442
+ - lib/llm_cost_tracker/integrations/ruby_llm/v2.rb
443
+ - lib/llm_cost_tracker/integrations/ruby_llm/v2/attempt.rb
444
+ - lib/llm_cost_tracker/integrations/ruby_llm/v2/attempt/token_counts.rb
445
+ - lib/llm_cost_tracker/integrations/ruby_llm/v2/frame.rb
446
+ - lib/llm_cost_tracker/integrations/ruby_llm/v2/recorder.rb
447
+ - lib/llm_cost_tracker/integrations/ruby_llm/v2/seams.rb
409
448
  - lib/llm_cost_tracker/ledger.rb
410
449
  - lib/llm_cost_tracker/ledger/isolation.rb
411
450
  - lib/llm_cost_tracker/ledger/period.rb
@@ -427,18 +466,28 @@ files:
427
466
  - lib/llm_cost_tracker/ledger/tags/query.rb
428
467
  - lib/llm_cost_tracker/logging.rb
429
468
  - lib/llm_cost_tracker/middleware/faraday.rb
469
+ - lib/llm_cost_tracker/middleware/faraday/body.rb
470
+ - lib/llm_cost_tracker/middleware/faraday/exchange.rb
471
+ - lib/llm_cost_tracker/middleware/faraday/response_reader.rb
472
+ - lib/llm_cost_tracker/middleware/faraday/stream_tee.rb
430
473
  - lib/llm_cost_tracker/parsers.rb
431
474
  - lib/llm_cost_tracker/prices.json
432
475
  - lib/llm_cost_tracker/pricing.rb
433
476
  - lib/llm_cost_tracker/pricing/backfill.rb
434
477
  - lib/llm_cost_tracker/pricing/calculation.rb
478
+ - lib/llm_cost_tracker/pricing/calculation/iterations.rb
479
+ - lib/llm_cost_tracker/pricing/calculation/quantities.rb
480
+ - lib/llm_cost_tracker/pricing/calculation/snapshot.rb
481
+ - lib/llm_cost_tracker/pricing/calculation/totals.rb
435
482
  - lib/llm_cost_tracker/pricing/effective_prices.rb
436
483
  - lib/llm_cost_tracker/pricing/estimator.rb
437
484
  - lib/llm_cost_tracker/pricing/matcher.rb
438
485
  - lib/llm_cost_tracker/pricing/mode.rb
486
+ - lib/llm_cost_tracker/pricing/off_peak.rb
439
487
  - lib/llm_cost_tracker/pricing/price_key.rb
440
488
  - lib/llm_cost_tracker/pricing/rate.rb
441
489
  - lib/llm_cost_tracker/pricing/registry.rb
490
+ - lib/llm_cost_tracker/pricing/registry/normalizer.rb
442
491
  - lib/llm_cost_tracker/pricing/service_rates.rb
443
492
  - lib/llm_cost_tracker/pricing/source.rb
444
493
  - lib/llm_cost_tracker/pricing/sync.rb
@@ -446,6 +495,7 @@ files:
446
495
  - lib/llm_cost_tracker/pricing/sync/fetcher.rb
447
496
  - lib/llm_cost_tracker/pricing/sync/registry_diff.rb
448
497
  - lib/llm_cost_tracker/pricing/sync/registry_writer.rb
498
+ - lib/llm_cost_tracker/pricing/sync/remote_snapshot.rb
449
499
  - lib/llm_cost_tracker/pricing/sync/snapshot_guard.rb
450
500
  - lib/llm_cost_tracker/pricing/unknown.rb
451
501
  - lib/llm_cost_tracker/providers.rb
@@ -454,14 +504,17 @@ files:
454
504
  - lib/llm_cost_tracker/providers/anthropic/usage_extractor.rb
455
505
  - lib/llm_cost_tracker/providers/azure/hosts.rb
456
506
  - lib/llm_cost_tracker/providers/azure/parser.rb
507
+ - lib/llm_cost_tracker/providers/gemini/grounding.rb
457
508
  - lib/llm_cost_tracker/providers/gemini/model_families.rb
458
509
  - lib/llm_cost_tracker/providers/gemini/parser.rb
459
510
  - lib/llm_cost_tracker/providers/gemini/usage_extractor.rb
460
511
  - lib/llm_cost_tracker/providers/openai/hosts.rb
461
512
  - lib/llm_cost_tracker/providers/openai/model_families.rb
513
+ - lib/llm_cost_tracker/providers/openai/output_item.rb
462
514
  - lib/llm_cost_tracker/providers/openai/parser.rb
463
515
  - lib/llm_cost_tracker/providers/openai/response_parser.rb
464
516
  - lib/llm_cost_tracker/providers/openai/service_charges.rb
517
+ - lib/llm_cost_tracker/providers/openai/stream_parser.rb
465
518
  - lib/llm_cost_tracker/providers/openai/usage_extractor.rb
466
519
  - lib/llm_cost_tracker/providers/openai_compatible/parser.rb
467
520
  - lib/llm_cost_tracker/railtie.rb
@@ -508,5 +561,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
508
561
  requirements: []
509
562
  rubygems_version: 3.6.9
510
563
  specification_version: 4
511
- summary: LLM API cost tracking for Rails applications
564
+ summary: LLM spend tracking and budgets for Rails, in your own database
512
565
  test_files: []