llm_cost_tracker 0.4.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +195 -109
- data/app/services/llm_cost_tracker/dashboard/data_quality.rb +46 -55
- data/app/services/llm_cost_tracker/dashboard/data_quality_aggregate.rb +81 -0
- data/lib/llm_cost_tracker/budget.rb +34 -37
- data/lib/llm_cost_tracker/configuration/instrumentation.rb +37 -0
- data/lib/llm_cost_tracker/configuration.rb +10 -5
- data/lib/llm_cost_tracker/doctor.rb +166 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb +33 -0
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb +12 -6
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_period_totals_to_llm_cost_tracker.rb.erb +38 -8
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb +1 -2
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +53 -21
- data/lib/llm_cost_tracker/integrations/anthropic.rb +75 -0
- data/lib/llm_cost_tracker/integrations/base.rb +72 -0
- data/lib/llm_cost_tracker/integrations/object_reader.rb +56 -0
- data/lib/llm_cost_tracker/integrations/openai.rb +95 -0
- data/lib/llm_cost_tracker/integrations/registry.rb +41 -0
- data/lib/llm_cost_tracker/middleware/faraday.rb +4 -3
- data/lib/llm_cost_tracker/parsed_usage.rb +8 -1
- data/lib/llm_cost_tracker/parsers/anthropic.rb +17 -49
- data/lib/llm_cost_tracker/parsers/base.rb +80 -0
- data/lib/llm_cost_tracker/parsers/gemini.rb +12 -35
- data/lib/llm_cost_tracker/parsers/openai.rb +1 -6
- data/lib/llm_cost_tracker/parsers/openai_compatible.rb +6 -15
- data/lib/llm_cost_tracker/parsers/openai_usage.rb +8 -30
- data/lib/llm_cost_tracker/parsers/registry.rb +17 -2
- data/lib/llm_cost_tracker/price_freshness.rb +38 -0
- data/lib/llm_cost_tracker/price_registry.rb +14 -0
- data/lib/llm_cost_tracker/price_sync/fetcher.rb +2 -1
- data/lib/llm_cost_tracker/price_sync/refresh_plan_builder.rb +4 -2
- data/lib/llm_cost_tracker/price_sync.rb +10 -0
- data/lib/llm_cost_tracker/prices.json +394 -41
- data/lib/llm_cost_tracker/pricing.rb +8 -1
- data/lib/llm_cost_tracker/request_url.rb +20 -0
- data/lib/llm_cost_tracker/storage/active_record_rollups.rb +47 -27
- data/lib/llm_cost_tracker/storage/active_record_store.rb +4 -0
- data/lib/llm_cost_tracker/stream_collector.rb +3 -3
- data/lib/llm_cost_tracker/tag_context.rb +52 -0
- data/lib/llm_cost_tracker/tags_column.rb +62 -24
- data/lib/llm_cost_tracker/tracker.rb +5 -2
- data/lib/llm_cost_tracker/version.rb +1 -1
- data/lib/llm_cost_tracker.rb +14 -4
- data/lib/tasks/llm_cost_tracker.rake +21 -3
- metadata +13 -3
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/llm_cost_tracker_prices.yml.erb +0 -51
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/isolated_execution_state"
|
|
4
|
+
|
|
5
|
+
require_relative "value_helpers"
|
|
6
|
+
|
|
7
|
+
module LlmCostTracker
|
|
8
|
+
module TagContext
|
|
9
|
+
KEY = :llm_cost_tracker_tags
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def with(tags)
|
|
13
|
+
stack = current_stack
|
|
14
|
+
ActiveSupport::IsolatedExecutionState[KEY] = stack + [normalize(tags)]
|
|
15
|
+
yield
|
|
16
|
+
ensure
|
|
17
|
+
ActiveSupport::IsolatedExecutionState[KEY] = stack
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def tags
|
|
21
|
+
config_tags.merge(scoped_tags)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clear!
|
|
25
|
+
ActiveSupport::IsolatedExecutionState[KEY] = []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def config_tags
|
|
31
|
+
normalize(resolve_default_tags)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def resolve_default_tags
|
|
35
|
+
tags = LlmCostTracker.configuration.default_tags
|
|
36
|
+
tags.respond_to?(:call) ? tags.call : tags
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def scoped_tags
|
|
40
|
+
current_stack.reduce({}) { |merged, tags| merged.merge(tags) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def current_stack
|
|
44
|
+
ActiveSupport::IsolatedExecutionState[KEY] || []
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def normalize(tags)
|
|
48
|
+
ValueHelpers.deep_dup(tags || {}).to_h
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -2,58 +2,96 @@
|
|
|
2
2
|
|
|
3
3
|
module LlmCostTracker
|
|
4
4
|
module TagsColumn
|
|
5
|
+
USAGE_BREAKDOWN_COLUMNS = %w[
|
|
6
|
+
cache_read_input_tokens
|
|
7
|
+
cache_write_input_tokens
|
|
8
|
+
hidden_output_tokens
|
|
9
|
+
].freeze
|
|
10
|
+
|
|
11
|
+
USAGE_BREAKDOWN_COST_COLUMNS = %w[
|
|
12
|
+
cache_read_input_cost
|
|
13
|
+
cache_write_input_cost
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
def reset_column_information
|
|
17
|
+
remove_instance_variable(:@lct_schema_capabilities) if instance_variable_defined?(:@lct_schema_capabilities)
|
|
18
|
+
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
|
|
5
22
|
def tags_json_column?
|
|
6
|
-
|
|
23
|
+
capabilities = lct_schema_capabilities
|
|
24
|
+
|
|
25
|
+
capabilities.fetch(:tags_jsonb) || capabilities.fetch(:tags_mysql_json)
|
|
7
26
|
end
|
|
8
27
|
|
|
9
28
|
def tags_jsonb_column?
|
|
10
|
-
|
|
11
|
-
return false unless column
|
|
12
|
-
|
|
13
|
-
column.type == :jsonb || column.sql_type.to_s.downcase == "jsonb"
|
|
29
|
+
lct_schema_capabilities.fetch(:tags_jsonb)
|
|
14
30
|
end
|
|
15
31
|
|
|
16
32
|
def tags_mysql_json_column?
|
|
17
|
-
|
|
18
|
-
return false unless column
|
|
19
|
-
return false if tags_jsonb_column?
|
|
20
|
-
|
|
21
|
-
column.type == :json && connection.adapter_name.match?(/mysql/i)
|
|
33
|
+
lct_schema_capabilities.fetch(:tags_mysql_json)
|
|
22
34
|
end
|
|
23
35
|
|
|
24
36
|
def latency_column?
|
|
25
|
-
|
|
37
|
+
lct_schema_capabilities.fetch(:latency)
|
|
26
38
|
end
|
|
27
39
|
|
|
28
40
|
def stream_column?
|
|
29
|
-
|
|
41
|
+
lct_schema_capabilities.fetch(:stream)
|
|
30
42
|
end
|
|
31
43
|
|
|
32
44
|
def usage_source_column?
|
|
33
|
-
|
|
45
|
+
lct_schema_capabilities.fetch(:usage_source)
|
|
34
46
|
end
|
|
35
47
|
|
|
36
48
|
def provider_response_id_column?
|
|
37
|
-
|
|
49
|
+
lct_schema_capabilities.fetch(:provider_response_id)
|
|
38
50
|
end
|
|
39
51
|
|
|
40
52
|
def pricing_mode_column?
|
|
41
|
-
|
|
53
|
+
lct_schema_capabilities.fetch(:pricing_mode)
|
|
42
54
|
end
|
|
43
55
|
|
|
44
56
|
def usage_breakdown_columns?
|
|
45
|
-
|
|
46
|
-
cache_read_input_tokens
|
|
47
|
-
cache_write_input_tokens
|
|
48
|
-
hidden_output_tokens
|
|
49
|
-
].all? { |column| columns_hash.key?(column) }
|
|
57
|
+
lct_schema_capabilities.fetch(:usage_breakdown)
|
|
50
58
|
end
|
|
51
59
|
|
|
52
60
|
def usage_breakdown_cost_columns?
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
lct_schema_capabilities.fetch(:usage_breakdown_cost)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def lct_schema_capabilities
|
|
67
|
+
columns = columns_hash
|
|
68
|
+
adapter_name = connection.adapter_name
|
|
69
|
+
cache = @lct_schema_capabilities
|
|
70
|
+
|
|
71
|
+
return cache.fetch(:values) if cache && cache.fetch(:columns).equal?(columns) &&
|
|
72
|
+
cache.fetch(:adapter_name) == adapter_name
|
|
73
|
+
|
|
74
|
+
values = build_lct_schema_capabilities(columns, adapter_name)
|
|
75
|
+
@lct_schema_capabilities = { columns: columns, adapter_name: adapter_name, values: values }
|
|
76
|
+
values
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def build_lct_schema_capabilities(columns, adapter_name)
|
|
80
|
+
tag_column = columns["tags"]
|
|
81
|
+
tags_jsonb = tag_column && (tag_column.type == :jsonb || tag_column.sql_type.to_s.downcase == "jsonb")
|
|
82
|
+
tags_mysql_json = tag_column && !tags_jsonb && tag_column.type == :json && adapter_name.match?(/mysql/i)
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
tags_jsonb: tags_jsonb ? true : false,
|
|
86
|
+
tags_mysql_json: tags_mysql_json ? true : false,
|
|
87
|
+
latency: columns.key?("latency_ms"),
|
|
88
|
+
stream: columns.key?("stream"),
|
|
89
|
+
usage_source: columns.key?("usage_source"),
|
|
90
|
+
provider_response_id: columns.key?("provider_response_id"),
|
|
91
|
+
pricing_mode: columns.key?("pricing_mode"),
|
|
92
|
+
usage_breakdown: USAGE_BREAKDOWN_COLUMNS.all? { |column| columns.key?(column) },
|
|
93
|
+
usage_breakdown_cost: USAGE_BREAKDOWN_COST_COLUMNS.all? { |column| columns.key?(column) }
|
|
94
|
+
}
|
|
57
95
|
end
|
|
58
96
|
end
|
|
59
97
|
end
|
|
@@ -6,7 +6,7 @@ module LlmCostTracker
|
|
|
6
6
|
class Tracker
|
|
7
7
|
EVENT_NAME = "llm_request.llm_cost_tracker"
|
|
8
8
|
|
|
9
|
-
USAGE_SOURCES = %i[response stream_final manual unknown].freeze
|
|
9
|
+
USAGE_SOURCES = %i[response stream_final sdk_response manual unknown].freeze
|
|
10
10
|
|
|
11
11
|
class << self
|
|
12
12
|
def enforce_budget!
|
|
@@ -19,6 +19,7 @@ module LlmCostTracker
|
|
|
19
19
|
usage_source: nil, provider_response_id: nil, pricing_mode: nil, metadata: {})
|
|
20
20
|
return unless LlmCostTracker.configuration.enabled
|
|
21
21
|
|
|
22
|
+
model = normalize_model(model)
|
|
22
23
|
usage = usage_data(input_tokens, output_tokens, metadata, pricing_mode)
|
|
23
24
|
cost_data = cost_for_usage(provider, model, usage)
|
|
24
25
|
|
|
@@ -68,6 +69,8 @@ module LlmCostTracker
|
|
|
68
69
|
)
|
|
69
70
|
end
|
|
70
71
|
|
|
72
|
+
def normalize_model(value) = value.to_s.strip.then { |model| model.empty? ? ParsedUsage::UNKNOWN_MODEL : model }
|
|
73
|
+
|
|
71
74
|
def build_event(provider:, model:, usage:, cost_data:, metadata:, latency_ms:, stream:, usage_source:,
|
|
72
75
|
provider_response_id:)
|
|
73
76
|
Event.new(
|
|
@@ -81,7 +84,7 @@ module LlmCostTracker
|
|
|
81
84
|
hidden_output_tokens: usage[:hidden_output_tokens],
|
|
82
85
|
pricing_mode: usage[:pricing_mode],
|
|
83
86
|
cost: cost_data,
|
|
84
|
-
tags: LlmCostTracker.
|
|
87
|
+
tags: LlmCostTracker::TagContext.tags.merge(EventMetadata.tags(metadata)).freeze,
|
|
85
88
|
latency_ms: normalized_latency_ms(latency_ms),
|
|
86
89
|
stream: stream ? true : false,
|
|
87
90
|
usage_source: normalized_usage_source(usage_source),
|
data/lib/llm_cost_tracker.rb
CHANGED
|
@@ -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.
|
|
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
|
|
73
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
+
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-
|
|
11
|
+
date: 2026-04-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -240,6 +240,7 @@ files:
|
|
|
240
240
|
- app/helpers/llm_cost_tracker/dashboard_query_helper.rb
|
|
241
241
|
- app/helpers/llm_cost_tracker/pagination_helper.rb
|
|
242
242
|
- app/services/llm_cost_tracker/dashboard/data_quality.rb
|
|
243
|
+
- app/services/llm_cost_tracker/dashboard/data_quality_aggregate.rb
|
|
243
244
|
- app/services/llm_cost_tracker/dashboard/filter.rb
|
|
244
245
|
- app/services/llm_cost_tracker/dashboard/overview_stats.rb
|
|
245
246
|
- app/services/llm_cost_tracker/dashboard/provider_breakdown.rb
|
|
@@ -271,7 +272,9 @@ files:
|
|
|
271
272
|
- lib/llm_cost_tracker/assets.rb
|
|
272
273
|
- lib/llm_cost_tracker/budget.rb
|
|
273
274
|
- lib/llm_cost_tracker/configuration.rb
|
|
275
|
+
- lib/llm_cost_tracker/configuration/instrumentation.rb
|
|
274
276
|
- lib/llm_cost_tracker/cost.rb
|
|
277
|
+
- lib/llm_cost_tracker/doctor.rb
|
|
275
278
|
- lib/llm_cost_tracker/engine.rb
|
|
276
279
|
- lib/llm_cost_tracker/engine_compatibility.rb
|
|
277
280
|
- lib/llm_cost_tracker/errors.rb
|
|
@@ -291,11 +294,15 @@ files:
|
|
|
291
294
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_usage_breakdown_to_llm_api_calls.rb.erb
|
|
292
295
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb
|
|
293
296
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb
|
|
294
|
-
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/llm_cost_tracker_prices.yml.erb
|
|
295
297
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_cost_precision.rb.erb
|
|
296
298
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_tags_to_jsonb.rb.erb
|
|
297
299
|
- lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_cost_precision_generator.rb
|
|
298
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
|
|
299
306
|
- lib/llm_cost_tracker/llm_api_call.rb
|
|
300
307
|
- lib/llm_cost_tracker/logging.rb
|
|
301
308
|
- lib/llm_cost_tracker/middleware/faraday.rb
|
|
@@ -311,6 +318,7 @@ files:
|
|
|
311
318
|
- lib/llm_cost_tracker/parsers/sse.rb
|
|
312
319
|
- lib/llm_cost_tracker/period_grouping.rb
|
|
313
320
|
- lib/llm_cost_tracker/period_total.rb
|
|
321
|
+
- lib/llm_cost_tracker/price_freshness.rb
|
|
314
322
|
- lib/llm_cost_tracker/price_registry.rb
|
|
315
323
|
- lib/llm_cost_tracker/price_sync.rb
|
|
316
324
|
- lib/llm_cost_tracker/price_sync/fetcher.rb
|
|
@@ -331,11 +339,13 @@ files:
|
|
|
331
339
|
- lib/llm_cost_tracker/report.rb
|
|
332
340
|
- lib/llm_cost_tracker/report_data.rb
|
|
333
341
|
- lib/llm_cost_tracker/report_formatter.rb
|
|
342
|
+
- lib/llm_cost_tracker/request_url.rb
|
|
334
343
|
- lib/llm_cost_tracker/retention.rb
|
|
335
344
|
- lib/llm_cost_tracker/storage/active_record_rollups.rb
|
|
336
345
|
- lib/llm_cost_tracker/storage/active_record_store.rb
|
|
337
346
|
- lib/llm_cost_tracker/stream_collector.rb
|
|
338
347
|
- lib/llm_cost_tracker/tag_accessors.rb
|
|
348
|
+
- lib/llm_cost_tracker/tag_context.rb
|
|
339
349
|
- lib/llm_cost_tracker/tag_key.rb
|
|
340
350
|
- lib/llm_cost_tracker/tag_query.rb
|
|
341
351
|
- lib/llm_cost_tracker/tags_column.rb
|
data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/llm_cost_tracker_prices.yml.erb
DELETED
|
@@ -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:
|