llm_cost_tracker 0.5.2 → 0.5.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 +16 -0
- data/README.md +8 -3
- data/docs/architecture.md +28 -0
- data/docs/budgets.md +45 -0
- data/docs/configuration.md +65 -0
- data/docs/cookbook.md +185 -0
- data/docs/dashboard-overview.png +0 -0
- data/docs/dashboard.md +38 -0
- data/docs/extending.md +32 -0
- data/docs/operations.md +44 -0
- data/docs/pricing.md +94 -0
- data/docs/querying.md +36 -0
- data/docs/streaming.md +70 -0
- data/docs/technical/README.md +10 -0
- data/docs/technical/data-flow.md +67 -0
- data/docs/technical/extension-points.md +111 -0
- data/docs/technical/module-map.md +197 -0
- data/docs/technical/operational-notes.md +77 -0
- data/docs/upgrading.md +46 -0
- data/lib/llm_cost_tracker/capture_verifier.rb +71 -0
- data/lib/llm_cost_tracker/configuration/instrumentation.rb +1 -1
- data/lib/llm_cost_tracker/configuration/storage_backend.rb +26 -0
- data/lib/llm_cost_tracker/configuration.rb +2 -1
- data/lib/llm_cost_tracker/doctor/capture_check.rb +39 -0
- data/lib/llm_cost_tracker/doctor.rb +6 -1
- data/lib/llm_cost_tracker/integrations/anthropic.rb +41 -2
- data/lib/llm_cost_tracker/integrations/openai.rb +66 -2
- data/lib/llm_cost_tracker/integrations/registry.rb +33 -3
- data/lib/llm_cost_tracker/integrations/stream_tracker.rb +166 -0
- data/lib/llm_cost_tracker/llm_api_call.rb +2 -78
- data/lib/llm_cost_tracker/llm_api_call_metrics.rb +63 -0
- data/lib/llm_cost_tracker/parsers/openai_usage.rb +1 -1
- data/lib/llm_cost_tracker/pricing/effective_prices.rb +75 -0
- data/lib/llm_cost_tracker/pricing/explainer.rb +77 -0
- data/lib/llm_cost_tracker/pricing/lookup.rb +110 -0
- data/lib/llm_cost_tracker/pricing.rb +25 -108
- data/lib/llm_cost_tracker/retention.rb +3 -9
- data/lib/llm_cost_tracker/storage/active_record_backend.rb +115 -0
- data/lib/llm_cost_tracker/storage/active_record_rollups.rb +42 -0
- data/lib/llm_cost_tracker/storage/active_record_store.rb +26 -0
- data/lib/llm_cost_tracker/storage/custom_backend.rb +32 -0
- data/lib/llm_cost_tracker/storage/dispatcher.rb +11 -34
- data/lib/llm_cost_tracker/storage/log_backend.rb +38 -0
- data/lib/llm_cost_tracker/storage/registry.rb +63 -0
- data/lib/llm_cost_tracker/tag_sql.rb +34 -0
- data/lib/llm_cost_tracker/version.rb +1 -1
- data/lib/llm_cost_tracker.rb +3 -0
- data/lib/tasks/llm_cost_tracker.rake +49 -0
- metadata +32 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "registry"
|
|
4
|
+
|
|
5
|
+
module LlmCostTracker
|
|
6
|
+
module Storage
|
|
7
|
+
class CustomBackend
|
|
8
|
+
class << self
|
|
9
|
+
def save(event)
|
|
10
|
+
result = LlmCostTracker.configuration.custom_storage&.call(event)
|
|
11
|
+
result == false ? false : event
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def verify
|
|
15
|
+
if LlmCostTracker.configuration.custom_storage.respond_to?(:call)
|
|
16
|
+
return [
|
|
17
|
+
VerificationResult.new(
|
|
18
|
+
:ok,
|
|
19
|
+
"storage",
|
|
20
|
+
"custom storage callable configured; external sink was not invoked"
|
|
21
|
+
)
|
|
22
|
+
]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
[
|
|
26
|
+
VerificationResult.new(:error, "storage", "custom storage backend requires config.custom_storage")
|
|
27
|
+
]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../logging"
|
|
4
|
+
require_relative "registry"
|
|
5
|
+
require_relative "active_record_backend"
|
|
6
|
+
require_relative "custom_backend"
|
|
7
|
+
require_relative "log_backend"
|
|
4
8
|
|
|
5
9
|
module LlmCostTracker
|
|
6
10
|
module Storage
|
|
7
11
|
class Dispatcher
|
|
8
12
|
class << self
|
|
9
13
|
def save(event)
|
|
10
|
-
|
|
11
|
-
case config.storage_backend
|
|
12
|
-
when :log then log_event(event, config)
|
|
13
|
-
when :active_record then active_record_save(event)
|
|
14
|
-
when :custom then custom_save(event, config)
|
|
15
|
-
end
|
|
14
|
+
backend.save(event)
|
|
16
15
|
rescue LlmCostTracker::BudgetExceededError, LlmCostTracker::UnknownPricingError
|
|
17
16
|
raise
|
|
18
17
|
rescue StandardError => e
|
|
@@ -22,34 +21,8 @@ module LlmCostTracker
|
|
|
22
21
|
|
|
23
22
|
private
|
|
24
23
|
|
|
25
|
-
def
|
|
26
|
-
|
|
27
|
-
"tokens=#{event.total_tokens} " \
|
|
28
|
-
"cost=#{log_cost_label(event)}"
|
|
29
|
-
message += " latency=#{event.latency_ms}ms" if event.latency_ms
|
|
30
|
-
message += " stream=#{event.stream}" if event.stream
|
|
31
|
-
message += " source=#{event.usage_source}" if event.usage_source
|
|
32
|
-
message += " tags=#{event.tags}" unless event.tags.empty?
|
|
33
|
-
|
|
34
|
-
Logging.log(config.log_level, message)
|
|
35
|
-
event
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def log_cost_label(event) = event.cost ? "$#{format('%.6f', event.cost.total_cost)}" : "unknown"
|
|
39
|
-
|
|
40
|
-
def active_record_save(event)
|
|
41
|
-
require_relative "../llm_api_call" unless defined?(LlmCostTracker::LlmApiCall)
|
|
42
|
-
require_relative "active_record_store" unless defined?(LlmCostTracker::Storage::ActiveRecordStore)
|
|
43
|
-
|
|
44
|
-
ActiveRecordStore.save(event)
|
|
45
|
-
event
|
|
46
|
-
rescue LoadError => e
|
|
47
|
-
raise Error, "ActiveRecord storage requires the active_record gem: #{e.message}"
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def custom_save(event, config)
|
|
51
|
-
result = config.custom_storage&.call(event)
|
|
52
|
-
result == false ? false : event
|
|
24
|
+
def backend
|
|
25
|
+
Registry.fetch(LlmCostTracker.configuration.storage_backend)
|
|
53
26
|
end
|
|
54
27
|
|
|
55
28
|
def handle_error(error)
|
|
@@ -64,5 +37,9 @@ module LlmCostTracker
|
|
|
64
37
|
end
|
|
65
38
|
end
|
|
66
39
|
end
|
|
40
|
+
|
|
41
|
+
Registry.register(:log, LogBackend)
|
|
42
|
+
Registry.register(:active_record, ActiveRecordBackend)
|
|
43
|
+
Registry.register(:custom, CustomBackend)
|
|
67
44
|
end
|
|
68
45
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../logging"
|
|
4
|
+
require_relative "registry"
|
|
5
|
+
|
|
6
|
+
module LlmCostTracker
|
|
7
|
+
module Storage
|
|
8
|
+
class LogBackend
|
|
9
|
+
class << self
|
|
10
|
+
def save(event)
|
|
11
|
+
config = LlmCostTracker.configuration
|
|
12
|
+
message = "#{event.provider}/#{event.model} " \
|
|
13
|
+
"tokens=#{event.total_tokens} " \
|
|
14
|
+
"cost=#{cost_label(event)}"
|
|
15
|
+
message += " latency=#{event.latency_ms}ms" if event.latency_ms
|
|
16
|
+
message += " stream=#{event.stream}" if event.stream
|
|
17
|
+
message += " source=#{event.usage_source}" if event.usage_source
|
|
18
|
+
message += " tags=#{event.tags}" unless event.tags.empty?
|
|
19
|
+
|
|
20
|
+
Logging.log(config.log_level, message)
|
|
21
|
+
event
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def verify
|
|
25
|
+
[
|
|
26
|
+
VerificationResult.new(:ok, "storage", "log backend configured; capture writes to logs only")
|
|
27
|
+
]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def cost_label(event)
|
|
33
|
+
event.cost ? "$#{format('%.6f', event.cost.total_cost)}" : "unknown"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "monitor"
|
|
4
|
+
|
|
5
|
+
require_relative "../errors"
|
|
6
|
+
|
|
7
|
+
module LlmCostTracker
|
|
8
|
+
module Storage
|
|
9
|
+
VerificationResult = Data.define(:status, :name, :message)
|
|
10
|
+
|
|
11
|
+
module Registry
|
|
12
|
+
MUTEX = Monitor.new
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def register(name, backend)
|
|
16
|
+
name = normalize_name(name)
|
|
17
|
+
validate_backend!(backend)
|
|
18
|
+
MUTEX.synchronize { @backends = backends.merge(name => backend).freeze }
|
|
19
|
+
backend
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def fetch(name)
|
|
23
|
+
key = normalize_name(name)
|
|
24
|
+
backends.fetch(key) do
|
|
25
|
+
raise Error, "Unknown storage_backend: #{key.inspect}. Use one of: #{names.join(', ')}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def registered?(name)
|
|
30
|
+
backends.key?(normalize_name(name))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def names
|
|
34
|
+
backends.keys
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def backends
|
|
40
|
+
@backends || MUTEX.synchronize { @backends ||= {}.freeze }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def normalize_name(name)
|
|
44
|
+
name.to_sym
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def validate_backend!(backend)
|
|
48
|
+
return if backend.respond_to?(:save)
|
|
49
|
+
|
|
50
|
+
raise ArgumentError, "storage backend must respond to save"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.register(name, backend)
|
|
56
|
+
Registry.register(name, backend)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.backends
|
|
60
|
+
Registry.names
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tag_key"
|
|
4
|
+
|
|
5
|
+
module LlmCostTracker
|
|
6
|
+
module TagSql
|
|
7
|
+
class << self
|
|
8
|
+
def value_expression(model, key, table_name:)
|
|
9
|
+
key = TagKey.validate!(key)
|
|
10
|
+
column = "#{table_name}.#{model.connection.quote_column_name('tags')}"
|
|
11
|
+
|
|
12
|
+
case model.connection.adapter_name
|
|
13
|
+
when /postgres/i
|
|
14
|
+
json_column = model.tags_jsonb_column? ? column : "(#{column})::jsonb"
|
|
15
|
+
"#{json_column}->>#{model.connection.quote(key)}"
|
|
16
|
+
when /mysql/i
|
|
17
|
+
"JSON_UNQUOTE(JSON_EXTRACT(#{column}, #{model.connection.quote(json_path(key))}))"
|
|
18
|
+
else
|
|
19
|
+
"json_extract(#{column}, #{model.connection.quote(json_path(key))})"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def value_label(value)
|
|
24
|
+
value.nil? || value == "" ? "(untagged)" : value.to_s
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def json_path(key)
|
|
30
|
+
"$.\"#{key}\""
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/llm_cost_tracker.rb
CHANGED
|
@@ -33,14 +33,17 @@ require_relative "llm_cost_tracker/tag_context"
|
|
|
33
33
|
require_relative "llm_cost_tracker/tag_sanitizer"
|
|
34
34
|
require_relative "llm_cost_tracker/tags_column"
|
|
35
35
|
require_relative "llm_cost_tracker/tag_key"
|
|
36
|
+
require_relative "llm_cost_tracker/tag_sql"
|
|
36
37
|
require_relative "llm_cost_tracker/tag_query"
|
|
37
38
|
require_relative "llm_cost_tracker/tag_accessors"
|
|
39
|
+
require_relative "llm_cost_tracker/llm_api_call_metrics"
|
|
38
40
|
require_relative "llm_cost_tracker/tracker"
|
|
39
41
|
require_relative "llm_cost_tracker/retention"
|
|
40
42
|
require_relative "llm_cost_tracker/report_data"
|
|
41
43
|
require_relative "llm_cost_tracker/report_formatter"
|
|
42
44
|
require_relative "llm_cost_tracker/report"
|
|
43
45
|
require_relative "llm_cost_tracker/doctor"
|
|
46
|
+
require_relative "llm_cost_tracker/capture_verifier"
|
|
44
47
|
|
|
45
48
|
module LlmCostTracker
|
|
46
49
|
CONFIGURATION_MUTEX = Monitor.new
|
|
@@ -7,11 +7,21 @@ namespace :llm_cost_tracker do
|
|
|
7
7
|
desc "Check LLM Cost Tracker setup"
|
|
8
8
|
task :doctor do
|
|
9
9
|
Rake::Task["environment"].invoke if Rake::Task.task_defined?("environment")
|
|
10
|
+
require_relative "../llm_cost_tracker"
|
|
10
11
|
checks = LlmCostTracker::Doctor.call
|
|
11
12
|
puts LlmCostTracker::Doctor.report(checks)
|
|
12
13
|
abort("llm_cost_tracker: doctor found setup errors") unless LlmCostTracker::Doctor.healthy?(checks)
|
|
13
14
|
end
|
|
14
15
|
|
|
16
|
+
desc "Verify that LLM Cost Tracker can capture and persist a synthetic event"
|
|
17
|
+
task :verify_capture do
|
|
18
|
+
Rake::Task["environment"].invoke if Rake::Task.task_defined?("environment")
|
|
19
|
+
require_relative "../llm_cost_tracker"
|
|
20
|
+
checks = LlmCostTracker::CaptureVerifier.call
|
|
21
|
+
puts LlmCostTracker::CaptureVerifier.report(checks)
|
|
22
|
+
abort("llm_cost_tracker: capture verification failed") unless LlmCostTracker::CaptureVerifier.healthy?(checks)
|
|
23
|
+
end
|
|
24
|
+
|
|
15
25
|
desc "Print an LLM cost report from ActiveRecord storage"
|
|
16
26
|
task report: :environment do
|
|
17
27
|
days = (ENV["DAYS"] || LlmCostTracker::Report::DEFAULT_DAYS).to_i
|
|
@@ -74,6 +84,17 @@ namespace :llm_cost_tracker do
|
|
|
74
84
|
puts " pricing is up to date" if result.up_to_date
|
|
75
85
|
abort("llm_cost_tracker: pricing check failed") unless result.up_to_date
|
|
76
86
|
end
|
|
87
|
+
|
|
88
|
+
desc "Explain how a provider/model price is matched. Use PROVIDER=... MODEL=..."
|
|
89
|
+
task :explain do
|
|
90
|
+
Rake::Task["environment"].invoke if Rake::Task.task_defined?("environment")
|
|
91
|
+
require_relative "../llm_cost_tracker"
|
|
92
|
+
|
|
93
|
+
explanation = price_explanation_from_env
|
|
94
|
+
puts "llm_cost_tracker: #{explanation.message}"
|
|
95
|
+
print_price_explanation(explanation)
|
|
96
|
+
abort("llm_cost_tracker: price is incomplete or unknown") unless explanation.complete?
|
|
97
|
+
end
|
|
77
98
|
end
|
|
78
99
|
end
|
|
79
100
|
# rubocop:enable Metrics/BlockLength
|
|
@@ -95,3 +116,31 @@ def price_refresh_output_path
|
|
|
95
116
|
FileUtils.mkdir_p(File.dirname(path))
|
|
96
117
|
path
|
|
97
118
|
end
|
|
119
|
+
|
|
120
|
+
def price_explanation_from_env
|
|
121
|
+
provider = ENV["PROVIDER"].to_s.strip
|
|
122
|
+
model = ENV["MODEL"].to_s.strip
|
|
123
|
+
abort("llm_cost_tracker: use PROVIDER=... MODEL=...") if provider.empty? || model.empty?
|
|
124
|
+
|
|
125
|
+
LlmCostTracker::Pricing.explain(
|
|
126
|
+
provider: provider,
|
|
127
|
+
model: model,
|
|
128
|
+
pricing_mode: ENV.fetch("PRICING_MODE", nil),
|
|
129
|
+
input_tokens: ENV.fetch("INPUT_TOKENS", 1).to_i,
|
|
130
|
+
output_tokens: ENV.fetch("OUTPUT_TOKENS", 1).to_i,
|
|
131
|
+
cache_read_input_tokens: ENV.fetch("CACHE_READ_INPUT_TOKENS", 0).to_i,
|
|
132
|
+
cache_write_input_tokens: ENV.fetch("CACHE_WRITE_INPUT_TOKENS", 0).to_i
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def print_price_explanation(explanation)
|
|
137
|
+
return unless explanation.matched?
|
|
138
|
+
|
|
139
|
+
puts " source: #{explanation.source}"
|
|
140
|
+
puts " matched_key: #{explanation.matched_key}"
|
|
141
|
+
puts " matched_by: #{explanation.matched_by}"
|
|
142
|
+
puts " pricing_mode: #{explanation.pricing_mode || 'standard'}"
|
|
143
|
+
explanation.effective_prices.each do |key, value|
|
|
144
|
+
puts " #{key}: #{value.nil? ? 'missing' : value}"
|
|
145
|
+
end
|
|
146
|
+
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.5.
|
|
4
|
+
version: 0.5.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-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -283,13 +283,33 @@ files:
|
|
|
283
283
|
- app/views/llm_cost_tracker/tags/index.html.erb
|
|
284
284
|
- app/views/llm_cost_tracker/tags/show.html.erb
|
|
285
285
|
- config/routes.rb
|
|
286
|
+
- docs/architecture.md
|
|
287
|
+
- docs/budgets.md
|
|
288
|
+
- docs/configuration.md
|
|
289
|
+
- docs/cookbook.md
|
|
290
|
+
- docs/dashboard-overview.png
|
|
291
|
+
- docs/dashboard.md
|
|
292
|
+
- docs/extending.md
|
|
293
|
+
- docs/operations.md
|
|
294
|
+
- docs/pricing.md
|
|
295
|
+
- docs/querying.md
|
|
296
|
+
- docs/streaming.md
|
|
297
|
+
- docs/technical/README.md
|
|
298
|
+
- docs/technical/data-flow.md
|
|
299
|
+
- docs/technical/extension-points.md
|
|
300
|
+
- docs/technical/module-map.md
|
|
301
|
+
- docs/technical/operational-notes.md
|
|
302
|
+
- docs/upgrading.md
|
|
286
303
|
- lib/llm_cost_tracker.rb
|
|
287
304
|
- lib/llm_cost_tracker/assets.rb
|
|
288
305
|
- lib/llm_cost_tracker/budget.rb
|
|
306
|
+
- lib/llm_cost_tracker/capture_verifier.rb
|
|
289
307
|
- lib/llm_cost_tracker/configuration.rb
|
|
290
308
|
- lib/llm_cost_tracker/configuration/instrumentation.rb
|
|
309
|
+
- lib/llm_cost_tracker/configuration/storage_backend.rb
|
|
291
310
|
- lib/llm_cost_tracker/cost.rb
|
|
292
311
|
- lib/llm_cost_tracker/doctor.rb
|
|
312
|
+
- lib/llm_cost_tracker/doctor/capture_check.rb
|
|
293
313
|
- lib/llm_cost_tracker/engine.rb
|
|
294
314
|
- lib/llm_cost_tracker/engine_compatibility.rb
|
|
295
315
|
- lib/llm_cost_tracker/errors.rb
|
|
@@ -319,7 +339,9 @@ files:
|
|
|
319
339
|
- lib/llm_cost_tracker/integrations/openai.rb
|
|
320
340
|
- lib/llm_cost_tracker/integrations/registry.rb
|
|
321
341
|
- lib/llm_cost_tracker/integrations/ruby_llm.rb
|
|
342
|
+
- lib/llm_cost_tracker/integrations/stream_tracker.rb
|
|
322
343
|
- lib/llm_cost_tracker/llm_api_call.rb
|
|
344
|
+
- lib/llm_cost_tracker/llm_api_call_metrics.rb
|
|
323
345
|
- lib/llm_cost_tracker/logging.rb
|
|
324
346
|
- lib/llm_cost_tracker/middleware/faraday.rb
|
|
325
347
|
- lib/llm_cost_tracker/parameter_hash.rb
|
|
@@ -343,15 +365,22 @@ files:
|
|
|
343
365
|
- lib/llm_cost_tracker/price_sync/registry_writer.rb
|
|
344
366
|
- lib/llm_cost_tracker/prices.json
|
|
345
367
|
- lib/llm_cost_tracker/pricing.rb
|
|
368
|
+
- lib/llm_cost_tracker/pricing/effective_prices.rb
|
|
369
|
+
- lib/llm_cost_tracker/pricing/explainer.rb
|
|
370
|
+
- lib/llm_cost_tracker/pricing/lookup.rb
|
|
346
371
|
- lib/llm_cost_tracker/railtie.rb
|
|
347
372
|
- lib/llm_cost_tracker/report.rb
|
|
348
373
|
- lib/llm_cost_tracker/report_data.rb
|
|
349
374
|
- lib/llm_cost_tracker/report_formatter.rb
|
|
350
375
|
- lib/llm_cost_tracker/request_url.rb
|
|
351
376
|
- lib/llm_cost_tracker/retention.rb
|
|
377
|
+
- lib/llm_cost_tracker/storage/active_record_backend.rb
|
|
352
378
|
- lib/llm_cost_tracker/storage/active_record_rollups.rb
|
|
353
379
|
- lib/llm_cost_tracker/storage/active_record_store.rb
|
|
380
|
+
- lib/llm_cost_tracker/storage/custom_backend.rb
|
|
354
381
|
- lib/llm_cost_tracker/storage/dispatcher.rb
|
|
382
|
+
- lib/llm_cost_tracker/storage/log_backend.rb
|
|
383
|
+
- lib/llm_cost_tracker/storage/registry.rb
|
|
355
384
|
- lib/llm_cost_tracker/stream_capture.rb
|
|
356
385
|
- lib/llm_cost_tracker/stream_collector.rb
|
|
357
386
|
- lib/llm_cost_tracker/tag_accessors.rb
|
|
@@ -359,6 +388,7 @@ files:
|
|
|
359
388
|
- lib/llm_cost_tracker/tag_key.rb
|
|
360
389
|
- lib/llm_cost_tracker/tag_query.rb
|
|
361
390
|
- lib/llm_cost_tracker/tag_sanitizer.rb
|
|
391
|
+
- lib/llm_cost_tracker/tag_sql.rb
|
|
362
392
|
- lib/llm_cost_tracker/tags_column.rb
|
|
363
393
|
- lib/llm_cost_tracker/tracker.rb
|
|
364
394
|
- lib/llm_cost_tracker/unknown_pricing.rb
|