llm_cost_tracker 0.7.3 → 0.8.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/.ruby-version +1 -0
- data/CHANGELOG.md +66 -1
- data/README.md +58 -225
- data/app/assets/llm_cost_tracker/application.css +218 -41
- data/app/controllers/llm_cost_tracker/application_controller.rb +30 -17
- data/app/controllers/llm_cost_tracker/assets_controller.rb +11 -1
- data/app/controllers/llm_cost_tracker/calls_controller.rb +19 -14
- data/app/controllers/llm_cost_tracker/data_quality_controller.rb +10 -2
- data/app/helpers/llm_cost_tracker/application_helper.rb +11 -24
- data/app/helpers/llm_cost_tracker/dashboard_filter_helper.rb +3 -21
- data/app/helpers/llm_cost_tracker/dashboard_filter_options_helper.rb +4 -4
- data/app/helpers/llm_cost_tracker/dashboard_query_helper.rb +1 -1
- data/app/helpers/llm_cost_tracker/token_usage_helper.rb +20 -7
- data/app/models/llm_cost_tracker/call.rb +169 -0
- data/app/models/llm_cost_tracker/call_line_item.rb +22 -0
- data/app/models/llm_cost_tracker/call_rollup.rb +9 -0
- data/app/models/llm_cost_tracker/call_tag.rb +16 -0
- data/app/models/llm_cost_tracker/ingestion/inbox_entry.rb +13 -0
- data/app/models/llm_cost_tracker/ingestion/lease.rb +1 -1
- data/app/models/llm_cost_tracker/provider_invoice.rb +9 -0
- data/app/services/llm_cost_tracker/dashboard/data_quality.rb +121 -30
- data/app/services/llm_cost_tracker/dashboard/date_range.rb +1 -1
- data/app/services/llm_cost_tracker/dashboard/filter.rb +2 -2
- data/app/services/llm_cost_tracker/dashboard/overview_stats.rb +74 -21
- data/app/services/llm_cost_tracker/dashboard/pagination.rb +6 -4
- data/app/services/llm_cost_tracker/dashboard/params.rb +8 -2
- data/app/services/llm_cost_tracker/dashboard/provider_breakdown.rb +1 -1
- data/app/services/llm_cost_tracker/dashboard/spend_anomaly.rb +4 -3
- data/app/services/llm_cost_tracker/dashboard/tag_breakdown.rb +42 -9
- data/app/services/llm_cost_tracker/dashboard/tag_key_explorer.rb +14 -37
- data/app/services/llm_cost_tracker/dashboard/time_series.rb +1 -1
- data/app/services/llm_cost_tracker/dashboard/top_models.rb +1 -1
- data/app/views/llm_cost_tracker/calls/index.html.erb +33 -75
- data/app/views/llm_cost_tracker/calls/show.html.erb +62 -7
- data/app/views/llm_cost_tracker/dashboard/index.html.erb +9 -50
- data/app/views/llm_cost_tracker/data_quality/index.html.erb +103 -126
- data/app/views/llm_cost_tracker/errors/database.html.erb +1 -1
- data/app/views/llm_cost_tracker/models/index.html.erb +18 -50
- data/app/views/llm_cost_tracker/shared/_filters.html.erb +63 -0
- data/app/views/llm_cost_tracker/shared/_sort.html.erb +13 -0
- data/app/views/llm_cost_tracker/shared/setup_required.html.erb +1 -1
- data/app/views/llm_cost_tracker/tags/index.html.erb +3 -34
- data/app/views/llm_cost_tracker/tags/show.html.erb +5 -37
- data/lib/llm_cost_tracker/billing/components.rb +53 -0
- data/lib/llm_cost_tracker/billing/components.yml +117 -0
- data/lib/llm_cost_tracker/billing/cost_status.rb +45 -0
- data/lib/llm_cost_tracker/billing/line_item.rb +189 -0
- data/lib/llm_cost_tracker/budget.rb +23 -35
- data/lib/llm_cost_tracker/capture/stream_collector.rb +47 -33
- data/lib/llm_cost_tracker/configuration.rb +36 -19
- data/lib/llm_cost_tracker/doctor/cost_drift_check.rb +54 -0
- data/lib/llm_cost_tracker/doctor/ingestion_check.rb +24 -32
- data/lib/llm_cost_tracker/doctor/legacy_audit_check.rb +36 -0
- data/lib/llm_cost_tracker/doctor/legacy_billing_status_check.rb +22 -0
- data/lib/llm_cost_tracker/doctor/price_check.rb +2 -2
- data/lib/llm_cost_tracker/doctor/pricing_snapshot_drift_check.rb +85 -0
- data/lib/llm_cost_tracker/doctor/probe.rb +17 -0
- data/lib/llm_cost_tracker/doctor/schema_check.rb +31 -0
- data/lib/llm_cost_tracker/doctor.rb +43 -45
- data/lib/llm_cost_tracker/errors.rb +5 -19
- data/lib/llm_cost_tracker/event.rb +10 -2
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb +4 -2
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb +2 -6
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_cost_tracker_calls.rb.erb +157 -0
- data/lib/llm_cost_tracker/ingestion/batch.rb +11 -12
- data/lib/llm_cost_tracker/ingestion/inbox.rb +39 -23
- data/lib/llm_cost_tracker/ingestion/worker.rb +14 -5
- data/lib/llm_cost_tracker/ingestion.rb +28 -22
- data/lib/llm_cost_tracker/integrations/anthropic.rb +45 -38
- data/lib/llm_cost_tracker/integrations/base.rb +36 -29
- data/lib/llm_cost_tracker/integrations/openai.rb +85 -40
- data/lib/llm_cost_tracker/integrations/ruby_llm.rb +5 -5
- data/lib/llm_cost_tracker/integrations.rb +2 -2
- data/lib/llm_cost_tracker/ledger/period/totals.rb +12 -9
- data/lib/llm_cost_tracker/ledger/period.rb +5 -5
- data/lib/llm_cost_tracker/ledger/rollups/upsert_sql.rb +2 -2
- data/lib/llm_cost_tracker/ledger/rollups.rb +76 -25
- data/lib/llm_cost_tracker/ledger/schema/adapter.rb +18 -0
- data/lib/llm_cost_tracker/ledger/schema/call_line_items.rb +50 -0
- data/lib/llm_cost_tracker/ledger/schema/call_rollups.rb +37 -0
- data/lib/llm_cost_tracker/ledger/schema/call_tags.rb +26 -0
- data/lib/llm_cost_tracker/ledger/schema/calls.rb +34 -23
- data/lib/llm_cost_tracker/ledger/schema/provider_invoices.rb +57 -0
- data/lib/llm_cost_tracker/ledger/store.rb +96 -13
- data/lib/llm_cost_tracker/ledger/tags/query.rb +4 -10
- data/lib/llm_cost_tracker/ledger/tags/sql.rb +27 -15
- data/lib/llm_cost_tracker/ledger.rb +4 -2
- data/lib/llm_cost_tracker/logging.rb +2 -5
- data/lib/llm_cost_tracker/middleware/faraday.rb +7 -6
- data/lib/llm_cost_tracker/parsers/anthropic.rb +52 -7
- data/lib/llm_cost_tracker/parsers/base.rb +8 -3
- data/lib/llm_cost_tracker/parsers/gemini.rb +101 -15
- data/lib/llm_cost_tracker/parsers/openai_compatible.rb +10 -2
- data/lib/llm_cost_tracker/parsers/openai_service_charges.rb +87 -0
- data/lib/llm_cost_tracker/parsers/openai_usage.rb +48 -21
- data/lib/llm_cost_tracker/parsers/sse.rb +1 -1
- data/lib/llm_cost_tracker/parsers.rb +1 -1
- data/lib/llm_cost_tracker/prices.json +105 -20
- data/lib/llm_cost_tracker/pricing/effective_prices.rb +57 -19
- data/lib/llm_cost_tracker/pricing/explainer.rb +4 -5
- data/lib/llm_cost_tracker/pricing/lookup.rb +38 -34
- data/lib/llm_cost_tracker/pricing/registry.rb +65 -45
- data/lib/llm_cost_tracker/pricing/service_charges.rb +204 -0
- data/lib/llm_cost_tracker/pricing/sync/fetcher.rb +26 -17
- data/lib/llm_cost_tracker/pricing/sync/registry_diff.rb +6 -15
- data/lib/llm_cost_tracker/pricing/sync.rb +57 -10
- data/lib/llm_cost_tracker/pricing/sync_change_printer.rb +32 -0
- data/lib/llm_cost_tracker/pricing.rb +190 -26
- data/lib/llm_cost_tracker/railtie.rb +0 -8
- data/lib/llm_cost_tracker/report/data.rb +16 -8
- data/lib/llm_cost_tracker/report.rb +0 -4
- data/lib/llm_cost_tracker/retention.rb +8 -8
- data/lib/llm_cost_tracker/tags/context.rb +2 -4
- data/lib/llm_cost_tracker/tags/key.rb +4 -0
- data/lib/llm_cost_tracker/tags/sanitizer.rb +12 -17
- data/lib/llm_cost_tracker/timing.rb +15 -0
- data/lib/llm_cost_tracker/token_usage.rb +56 -42
- data/lib/llm_cost_tracker/tracker.rb +67 -24
- data/lib/llm_cost_tracker/usage_capture.rb +29 -8
- data/lib/llm_cost_tracker/version.rb +1 -1
- data/lib/llm_cost_tracker.rb +36 -35
- data/lib/tasks/llm_cost_tracker.rake +22 -17
- metadata +36 -41
- data/app/models/llm_cost_tracker/ingestion/event.rb +0 -13
- data/app/models/llm_cost_tracker/ledger/call.rb +0 -45
- data/app/models/llm_cost_tracker/ledger/call_metrics.rb +0 -66
- data/app/models/llm_cost_tracker/ledger/period/grouping.rb +0 -71
- data/app/models/llm_cost_tracker/ledger/period/total.rb +0 -13
- data/app/models/llm_cost_tracker/ledger/tags/accessors.rb +0 -19
- data/lib/llm_cost_tracker/configuration/instrumentation.rb +0 -33
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_ingestion_generator.rb +0 -29
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_latency_ms_generator.rb +0 -29
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_period_totals_generator.rb +0 -29
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_provider_response_id_generator.rb +0 -29
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_streaming_generator.rb +0 -29
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_token_usage_generator.rb +0 -42
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_ingestion_to_llm_cost_tracker.rb.erb +0 -33
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_latency_ms_to_llm_api_calls.rb.erb +0 -9
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_period_totals_to_llm_cost_tracker.rb.erb +0 -104
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_provider_response_id_to_llm_api_calls.rb.erb +0 -15
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_streaming_to_llm_api_calls.rb.erb +0 -21
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_token_usage_to_llm_api_calls.rb.erb +0 -22
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb +0 -83
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_cost_precision.rb.erb +0 -26
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_tags_to_jsonb.rb.erb +0 -44
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_cost_precision_generator.rb +0 -29
- data/lib/llm_cost_tracker/generators/llm_cost_tracker/upgrade_tags_to_jsonb_generator.rb +0 -29
- data/lib/llm_cost_tracker/ledger/rollups/batch.rb +0 -43
- data/lib/llm_cost_tracker/ledger/schema/period_totals.rb +0 -32
- data/lib/llm_cost_tracker/pricing/components.rb +0 -37
- data/lib/llm_cost_tracker/pricing/sync/registry_loader.rb +0 -63
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "llm_cost_tracker/ledger/tags/sql"
|
|
4
|
-
|
|
5
|
-
module LlmCostTracker
|
|
6
|
-
module Ledger
|
|
7
|
-
module CallMetrics
|
|
8
|
-
def total_cost
|
|
9
|
-
sum(:total_cost).to_f
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def total_tokens
|
|
13
|
-
sum(:total_tokens).to_i
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def cost_by_model(limit: nil)
|
|
17
|
-
cost_by_column(:model, limit: limit)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def cost_by_provider(limit: nil)
|
|
21
|
-
cost_by_column(:provider, limit: limit)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def group_by_tag(key)
|
|
25
|
-
group(Arel.sql(tag_value_expression(key)))
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def cost_by_tag(key, limit: nil)
|
|
29
|
-
expression = tag_value_expression(key)
|
|
30
|
-
label_expression = "COALESCE(NULLIF(#{expression}, ''), #{connection.quote('(untagged)')})"
|
|
31
|
-
relation = select("#{label_expression} AS name, COALESCE(SUM(total_cost), 0) AS total_cost")
|
|
32
|
-
.group(Arel.sql(label_expression))
|
|
33
|
-
.order(Arel.sql("COALESCE(SUM(total_cost), 0) DESC"))
|
|
34
|
-
relation = relation.limit(limit) if limit
|
|
35
|
-
relation
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def average_latency_ms
|
|
39
|
-
average(:latency_ms)&.to_f
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def latency_by_model
|
|
43
|
-
group(:model).average(:latency_ms).transform_values(&:to_f)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def latency_by_provider
|
|
47
|
-
group(:provider).average(:latency_ms).transform_values(&:to_f)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def tag_value_expression(key, table_name: quoted_table_name)
|
|
51
|
-
Ledger::Tags::Sql.value_expression(key, table_name: table_name)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
private
|
|
55
|
-
|
|
56
|
-
def cost_by_column(column, limit:)
|
|
57
|
-
quoted_column = "#{quoted_table_name}.#{connection.quote_column_name(column)}"
|
|
58
|
-
relation = select("#{quoted_column} AS name, COALESCE(SUM(total_cost), 0) AS total_cost")
|
|
59
|
-
.group(column)
|
|
60
|
-
.order(Arel.sql("COALESCE(SUM(total_cost), 0) DESC"))
|
|
61
|
-
relation = relation.limit(limit) if limit
|
|
62
|
-
relation
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "llm_cost_tracker/ledger/schema/adapter"
|
|
4
|
-
|
|
5
|
-
module LlmCostTracker
|
|
6
|
-
module Ledger
|
|
7
|
-
module Period
|
|
8
|
-
module Grouping
|
|
9
|
-
PERIOD_FORMATS = {
|
|
10
|
-
day: {
|
|
11
|
-
postgres: "YYYY-MM-DD",
|
|
12
|
-
mysql: "%Y-%m-%d"
|
|
13
|
-
},
|
|
14
|
-
month: {
|
|
15
|
-
postgres: "YYYY-MM",
|
|
16
|
-
mysql: "%Y-%m"
|
|
17
|
-
}
|
|
18
|
-
}.freeze
|
|
19
|
-
|
|
20
|
-
private_constant :PERIOD_FORMATS
|
|
21
|
-
|
|
22
|
-
def group_by_period(period, column: :tracked_at)
|
|
23
|
-
group(Arel.sql(period_group_expression(period, column: column)))
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def daily_costs(days: 30)
|
|
27
|
-
where(tracked_at: days.days.ago..)
|
|
28
|
-
.group_by_period(:day)
|
|
29
|
-
.sum(:total_cost)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
def period_group_expression(period, column:)
|
|
35
|
-
period = validated_period(period)
|
|
36
|
-
column = period_column_expression(column)
|
|
37
|
-
formats = PERIOD_FORMATS.fetch(period)
|
|
38
|
-
|
|
39
|
-
if Ledger::Schema::Adapter.postgresql?(connection)
|
|
40
|
-
postgres_period_expression(period, column, formats)
|
|
41
|
-
elsif Ledger::Schema::Adapter.mysql?(connection)
|
|
42
|
-
"DATE_FORMAT(#{column}, #{connection.quote(formats.fetch(:mysql))})"
|
|
43
|
-
else
|
|
44
|
-
Ledger::Schema::Adapter.ensure_supported!(connection)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def postgres_period_expression(period, column, formats)
|
|
49
|
-
"TO_CHAR(" \
|
|
50
|
-
"DATE_TRUNC(#{connection.quote(period.to_s)}, #{column}), " \
|
|
51
|
-
"#{connection.quote(formats.fetch(:postgres))}" \
|
|
52
|
-
")"
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def validated_period(period)
|
|
56
|
-
normalized_period = period.try(:to_sym)
|
|
57
|
-
return normalized_period if PERIOD_FORMATS.key?(normalized_period)
|
|
58
|
-
|
|
59
|
-
raise ArgumentError, "invalid period: #{period.inspect}"
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def period_column_expression(column)
|
|
63
|
-
column = column.to_s
|
|
64
|
-
return "#{quoted_table_name}.#{connection.quote_column_name(column)}" if column_names.include?(column)
|
|
65
|
-
|
|
66
|
-
raise ArgumentError, "invalid period column: #{column.inspect}"
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
|
-
module LlmCostTracker
|
|
6
|
-
module Ledger
|
|
7
|
-
module Tags
|
|
8
|
-
module Accessors
|
|
9
|
-
def parsed_tags
|
|
10
|
-
return tags.transform_keys(&:to_s) if tags.is_a?(Hash)
|
|
11
|
-
|
|
12
|
-
JSON.parse(tags || "{}")
|
|
13
|
-
rescue JSON::ParserError
|
|
14
|
-
{}
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module LlmCostTracker
|
|
4
|
-
module ConfigurationInstrumentation
|
|
5
|
-
def instrument(*names)
|
|
6
|
-
ensure_shared_configuration_mutable!
|
|
7
|
-
@instrumented_integrations = (@instrumented_integrations + normalize_instrumentation_names(names)).uniq
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def instrumented?(name)
|
|
11
|
-
@instrumented_integrations.include?(name.to_sym)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
private
|
|
15
|
-
|
|
16
|
-
def normalize_instrumentation_names(names)
|
|
17
|
-
names.flatten.flat_map do |name|
|
|
18
|
-
key = name.to_sym
|
|
19
|
-
next Integrations.names if key == :all
|
|
20
|
-
|
|
21
|
-
validate_instrumentation_name!(key)
|
|
22
|
-
key
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def validate_instrumentation_name!(name)
|
|
27
|
-
return if Integrations.names.include?(name)
|
|
28
|
-
|
|
29
|
-
raise Error, "Unknown integration: #{name.inspect}. " \
|
|
30
|
-
"Use one of: #{Integrations.names.join(', ')}"
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rails/generators"
|
|
4
|
-
require "rails/generators/active_record"
|
|
5
|
-
|
|
6
|
-
module LlmCostTracker
|
|
7
|
-
module Generators
|
|
8
|
-
class AddIngestionGenerator < Rails::Generators::Base
|
|
9
|
-
include ActiveRecord::Generators::Migration
|
|
10
|
-
|
|
11
|
-
source_root File.expand_path("templates", __dir__)
|
|
12
|
-
|
|
13
|
-
desc "Creates a migration to add durable ActiveRecord ingestion"
|
|
14
|
-
|
|
15
|
-
def create_migration_file
|
|
16
|
-
migration_template(
|
|
17
|
-
"add_ingestion_to_llm_cost_tracker.rb.erb",
|
|
18
|
-
"db/migrate/add_ingestion_to_llm_cost_tracker.rb"
|
|
19
|
-
)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def migration_version
|
|
25
|
-
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rails/generators"
|
|
4
|
-
require "rails/generators/active_record"
|
|
5
|
-
|
|
6
|
-
module LlmCostTracker
|
|
7
|
-
module Generators
|
|
8
|
-
class AddLatencyMsGenerator < Rails::Generators::Base
|
|
9
|
-
include ActiveRecord::Generators::Migration
|
|
10
|
-
|
|
11
|
-
source_root File.expand_path("templates", __dir__)
|
|
12
|
-
|
|
13
|
-
desc "Creates a migration to add llm_api_calls.latency_ms"
|
|
14
|
-
|
|
15
|
-
def create_migration_file
|
|
16
|
-
migration_template(
|
|
17
|
-
"add_latency_ms_to_llm_api_calls.rb.erb",
|
|
18
|
-
"db/migrate/add_latency_ms_to_llm_api_calls.rb"
|
|
19
|
-
)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def migration_version
|
|
25
|
-
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rails/generators"
|
|
4
|
-
require "rails/generators/active_record"
|
|
5
|
-
|
|
6
|
-
module LlmCostTracker
|
|
7
|
-
module Generators
|
|
8
|
-
class AddPeriodTotalsGenerator < Rails::Generators::Base
|
|
9
|
-
include ActiveRecord::Generators::Migration
|
|
10
|
-
|
|
11
|
-
source_root File.expand_path("templates", __dir__)
|
|
12
|
-
|
|
13
|
-
desc "Creates a migration to add llm_cost_tracker_period_totals"
|
|
14
|
-
|
|
15
|
-
def create_migration_file
|
|
16
|
-
migration_template(
|
|
17
|
-
"add_period_totals_to_llm_cost_tracker.rb.erb",
|
|
18
|
-
"db/migrate/add_period_totals_to_llm_cost_tracker.rb"
|
|
19
|
-
)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def migration_version
|
|
25
|
-
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rails/generators"
|
|
4
|
-
require "rails/generators/active_record"
|
|
5
|
-
|
|
6
|
-
module LlmCostTracker
|
|
7
|
-
module Generators
|
|
8
|
-
class AddProviderResponseIdGenerator < Rails::Generators::Base
|
|
9
|
-
include ActiveRecord::Generators::Migration
|
|
10
|
-
|
|
11
|
-
source_root File.expand_path("templates", __dir__)
|
|
12
|
-
|
|
13
|
-
desc "Creates a migration to add llm_api_calls.provider_response_id"
|
|
14
|
-
|
|
15
|
-
def create_migration_file
|
|
16
|
-
migration_template(
|
|
17
|
-
"add_provider_response_id_to_llm_api_calls.rb.erb",
|
|
18
|
-
"db/migrate/add_provider_response_id_to_llm_api_calls.rb"
|
|
19
|
-
)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def migration_version
|
|
25
|
-
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rails/generators"
|
|
4
|
-
require "rails/generators/active_record"
|
|
5
|
-
|
|
6
|
-
module LlmCostTracker
|
|
7
|
-
module Generators
|
|
8
|
-
class AddStreamingGenerator < Rails::Generators::Base
|
|
9
|
-
include ActiveRecord::Generators::Migration
|
|
10
|
-
|
|
11
|
-
source_root File.expand_path("templates", __dir__)
|
|
12
|
-
|
|
13
|
-
desc "Creates a migration to add llm_api_calls.stream and llm_api_calls.usage_source"
|
|
14
|
-
|
|
15
|
-
def create_migration_file
|
|
16
|
-
migration_template(
|
|
17
|
-
"add_streaming_to_llm_api_calls.rb.erb",
|
|
18
|
-
"db/migrate/add_streaming_to_llm_api_calls.rb"
|
|
19
|
-
)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def migration_version
|
|
25
|
-
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rails/generators"
|
|
4
|
-
require "rails/generators/active_record"
|
|
5
|
-
|
|
6
|
-
module LlmCostTracker
|
|
7
|
-
module Generators
|
|
8
|
-
class AddTokenUsageGenerator < Rails::Generators::Base
|
|
9
|
-
include ActiveRecord::Generators::Migration
|
|
10
|
-
|
|
11
|
-
TOKEN_COLUMNS = %w[
|
|
12
|
-
cache_read_input_tokens
|
|
13
|
-
cache_write_input_tokens
|
|
14
|
-
cache_write_1h_input_tokens
|
|
15
|
-
hidden_output_tokens
|
|
16
|
-
].freeze
|
|
17
|
-
COST_COLUMNS = %w[
|
|
18
|
-
cache_read_input_cost
|
|
19
|
-
cache_write_input_cost
|
|
20
|
-
cache_write_1h_input_cost
|
|
21
|
-
].freeze
|
|
22
|
-
COLUMN_NAMES = (TOKEN_COLUMNS + COST_COLUMNS + %w[pricing_mode]).freeze
|
|
23
|
-
|
|
24
|
-
source_root File.expand_path("templates", __dir__)
|
|
25
|
-
|
|
26
|
-
desc "Creates a migration to add token usage and token cost columns to llm_api_calls"
|
|
27
|
-
|
|
28
|
-
def create_migration_file
|
|
29
|
-
migration_template(
|
|
30
|
-
"add_token_usage_to_llm_api_calls.rb.erb",
|
|
31
|
-
"db/migrate/add_token_usage_to_llm_api_calls.rb"
|
|
32
|
-
)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def migration_version
|
|
38
|
-
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
class AddIngestionToLlmCostTracker < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
-
def change
|
|
3
|
-
add_column :llm_api_calls, :event_id, :string unless column_exists?(:llm_api_calls, :event_id)
|
|
4
|
-
add_index :llm_api_calls, :event_id, unique: true if column_exists?(:llm_api_calls, :event_id) &&
|
|
5
|
-
!index_exists?(:llm_api_calls, :event_id)
|
|
6
|
-
|
|
7
|
-
create_table :llm_cost_tracker_inbox_events do |t|
|
|
8
|
-
t.string :event_id, null: false
|
|
9
|
-
t.decimal :total_cost, precision: 20, scale: 8
|
|
10
|
-
t.datetime :tracked_at, null: false
|
|
11
|
-
t.text :payload, null: false
|
|
12
|
-
t.datetime :locked_at
|
|
13
|
-
t.string :locked_by
|
|
14
|
-
t.integer :attempts, null: false, default: 0
|
|
15
|
-
t.text :last_error
|
|
16
|
-
|
|
17
|
-
t.timestamps
|
|
18
|
-
end unless table_exists?(:llm_cost_tracker_inbox_events)
|
|
19
|
-
|
|
20
|
-
create_table :llm_cost_tracker_ingestor_leases do |t|
|
|
21
|
-
t.string :name, null: false
|
|
22
|
-
t.string :locked_by
|
|
23
|
-
t.datetime :locked_until
|
|
24
|
-
|
|
25
|
-
t.timestamps
|
|
26
|
-
end unless table_exists?(:llm_cost_tracker_ingestor_leases)
|
|
27
|
-
|
|
28
|
-
add_index :llm_cost_tracker_inbox_events, :event_id, unique: true unless index_exists?(:llm_cost_tracker_inbox_events, :event_id)
|
|
29
|
-
add_index :llm_cost_tracker_inbox_events, :tracked_at unless index_exists?(:llm_cost_tracker_inbox_events, :tracked_at)
|
|
30
|
-
add_index :llm_cost_tracker_inbox_events, [:locked_at, :id] unless index_exists?(:llm_cost_tracker_inbox_events, [:locked_at, :id])
|
|
31
|
-
add_index :llm_cost_tracker_ingestor_leases, :name, unique: true unless index_exists?(:llm_cost_tracker_ingestor_leases, :name)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
class AddLatencyMsToLlmApiCalls < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
-
def up
|
|
3
|
-
add_column :llm_api_calls, :latency_ms, :integer unless column_exists?(:llm_api_calls, :latency_ms)
|
|
4
|
-
end
|
|
5
|
-
|
|
6
|
-
def down
|
|
7
|
-
remove_column :llm_api_calls, :latency_ms if column_exists?(:llm_api_calls, :latency_ms)
|
|
8
|
-
end
|
|
9
|
-
end
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
require "llm_cost_tracker/ledger/schema/adapter"
|
|
2
|
-
|
|
3
|
-
class AddPeriodTotalsToLlmCostTracker < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
-
def up
|
|
5
|
-
create_table :llm_cost_tracker_period_totals do |t|
|
|
6
|
-
t.string :period, null: false
|
|
7
|
-
t.date :period_start, null: false
|
|
8
|
-
t.decimal :total_cost, precision: 20, scale: 8, null: false, default: 0
|
|
9
|
-
|
|
10
|
-
t.timestamps
|
|
11
|
-
end unless table_exists?(:llm_cost_tracker_period_totals)
|
|
12
|
-
|
|
13
|
-
backfill_period_totals
|
|
14
|
-
|
|
15
|
-
add_index :llm_cost_tracker_period_totals, [:period, :period_start],
|
|
16
|
-
unique: true unless index_exists?(:llm_cost_tracker_period_totals, [:period, :period_start])
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def down
|
|
20
|
-
remove_index :llm_cost_tracker_period_totals, [:period, :period_start] if index_exists?(:llm_cost_tracker_period_totals, [:period, :period_start])
|
|
21
|
-
drop_table :llm_cost_tracker_period_totals if table_exists?(:llm_cost_tracker_period_totals)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
private
|
|
25
|
-
|
|
26
|
-
def backfill_period_totals
|
|
27
|
-
backfill_legacy_monthly_totals if table_exists?(:llm_cost_tracker_monthly_totals)
|
|
28
|
-
return unless table_exists?(:llm_api_calls)
|
|
29
|
-
|
|
30
|
-
backfill_period_total("day", day_bucket_sql)
|
|
31
|
-
backfill_period_total("month", month_bucket_sql)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def backfill_legacy_monthly_totals
|
|
35
|
-
execute <<~SQL
|
|
36
|
-
INSERT INTO llm_cost_tracker_period_totals (period, period_start, total_cost, created_at, updated_at)
|
|
37
|
-
SELECT #{connection.quote("month")} AS period,
|
|
38
|
-
month AS period_start,
|
|
39
|
-
total_cost,
|
|
40
|
-
CURRENT_TIMESTAMP,
|
|
41
|
-
CURRENT_TIMESTAMP
|
|
42
|
-
FROM llm_cost_tracker_monthly_totals legacy
|
|
43
|
-
WHERE NOT EXISTS (
|
|
44
|
-
SELECT 1
|
|
45
|
-
FROM llm_cost_tracker_period_totals existing
|
|
46
|
-
WHERE existing.period = #{connection.quote("month")}
|
|
47
|
-
AND existing.period_start = legacy.month
|
|
48
|
-
)
|
|
49
|
-
SQL
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def backfill_period_total(period, bucket_sql)
|
|
53
|
-
execute <<~SQL
|
|
54
|
-
INSERT INTO llm_cost_tracker_period_totals (period, period_start, total_cost, created_at, updated_at)
|
|
55
|
-
SELECT aggregated.period,
|
|
56
|
-
aggregated.period_start,
|
|
57
|
-
aggregated.total_cost,
|
|
58
|
-
CURRENT_TIMESTAMP,
|
|
59
|
-
CURRENT_TIMESTAMP
|
|
60
|
-
FROM (
|
|
61
|
-
SELECT #{connection.quote(period)} AS period,
|
|
62
|
-
#{bucket_sql} AS period_start,
|
|
63
|
-
SUM(total_cost) AS total_cost
|
|
64
|
-
FROM llm_api_calls
|
|
65
|
-
WHERE total_cost IS NOT NULL
|
|
66
|
-
GROUP BY #{bucket_sql}
|
|
67
|
-
) aggregated
|
|
68
|
-
WHERE NOT EXISTS (
|
|
69
|
-
SELECT 1
|
|
70
|
-
FROM llm_cost_tracker_period_totals existing
|
|
71
|
-
WHERE existing.period = aggregated.period
|
|
72
|
-
AND existing.period_start = aggregated.period_start
|
|
73
|
-
)
|
|
74
|
-
SQL
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def day_bucket_sql
|
|
78
|
-
if postgresql?
|
|
79
|
-
"DATE_TRUNC('day', tracked_at)::date"
|
|
80
|
-
elsif mysql?
|
|
81
|
-
"DATE(tracked_at)"
|
|
82
|
-
else
|
|
83
|
-
raise "LLM Cost Tracker supports PostgreSQL and MySQL only"
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def month_bucket_sql
|
|
88
|
-
if postgresql?
|
|
89
|
-
"DATE_TRUNC('month', tracked_at)::date"
|
|
90
|
-
elsif mysql?
|
|
91
|
-
"DATE_FORMAT(tracked_at, '%Y-%m-01')"
|
|
92
|
-
else
|
|
93
|
-
raise "LLM Cost Tracker supports PostgreSQL and MySQL only"
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def postgresql?
|
|
98
|
-
LlmCostTracker::Ledger::Schema::Adapter.postgresql?(connection)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def mysql?
|
|
102
|
-
LlmCostTracker::Ledger::Schema::Adapter.mysql?(connection)
|
|
103
|
-
end
|
|
104
|
-
end
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
class AddProviderResponseIdToLlmApiCalls < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
-
def up
|
|
3
|
-
return if column_exists?(:llm_api_calls, :provider_response_id)
|
|
4
|
-
|
|
5
|
-
add_column :llm_api_calls, :provider_response_id, :string
|
|
6
|
-
add_index :llm_api_calls, :provider_response_id
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def down
|
|
10
|
-
return unless column_exists?(:llm_api_calls, :provider_response_id)
|
|
11
|
-
|
|
12
|
-
remove_index :llm_api_calls, :provider_response_id if index_exists?(:llm_api_calls, :provider_response_id)
|
|
13
|
-
remove_column :llm_api_calls, :provider_response_id
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
class AddStreamingToLlmApiCalls < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
-
def up
|
|
3
|
-
unless column_exists?(:llm_api_calls, :stream)
|
|
4
|
-
add_column :llm_api_calls, :stream, :boolean, null: false, default: false
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
unless column_exists?(:llm_api_calls, :usage_source)
|
|
8
|
-
add_column :llm_api_calls, :usage_source, :string
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def down
|
|
13
|
-
if column_exists?(:llm_api_calls, :usage_source)
|
|
14
|
-
remove_column :llm_api_calls, :usage_source
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
if column_exists?(:llm_api_calls, :stream)
|
|
18
|
-
remove_column :llm_api_calls, :stream
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
class AddTokenUsageToLlmApiCalls < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
-
def up
|
|
3
|
-
<% LlmCostTracker::Generators::AddTokenUsageGenerator::TOKEN_COLUMNS.each do |column| -%>
|
|
4
|
-
unless column_exists?(:llm_api_calls, :<%= column %>)
|
|
5
|
-
add_column :llm_api_calls, :<%= column %>, :integer, null: false, default: 0
|
|
6
|
-
end
|
|
7
|
-
<% end -%>
|
|
8
|
-
<% LlmCostTracker::Generators::AddTokenUsageGenerator::COST_COLUMNS.each do |column| -%>
|
|
9
|
-
unless column_exists?(:llm_api_calls, :<%= column %>)
|
|
10
|
-
add_column :llm_api_calls, :<%= column %>, :decimal, precision: 20, scale: 8
|
|
11
|
-
end
|
|
12
|
-
<% end -%>
|
|
13
|
-
add_column :llm_api_calls, :pricing_mode, :string unless column_exists?(:llm_api_calls, :pricing_mode)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def down
|
|
17
|
-
remove_column :llm_api_calls, :pricing_mode if column_exists?(:llm_api_calls, :pricing_mode)
|
|
18
|
-
<% (LlmCostTracker::Generators::AddTokenUsageGenerator::COST_COLUMNS + LlmCostTracker::Generators::AddTokenUsageGenerator::TOKEN_COLUMNS).reverse.each do |column| -%>
|
|
19
|
-
remove_column :llm_api_calls, :<%= column %> if column_exists?(:llm_api_calls, :<%= column %>)
|
|
20
|
-
<% end -%>
|
|
21
|
-
end
|
|
22
|
-
end
|