llm_cost_tracker 0.6.0 → 0.7.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +2 -3
  4. data/app/services/llm_cost_tracker/dashboard/tag_key_explorer.rb +1 -17
  5. data/config/routes.rb +1 -1
  6. data/lib/llm_cost_tracker/active_record_adapter.rb +9 -5
  7. data/lib/llm_cost_tracker/assets.rb +0 -6
  8. data/lib/llm_cost_tracker/budget.rb +3 -5
  9. data/lib/llm_cost_tracker/capture_verifier.rb +3 -10
  10. data/lib/llm_cost_tracker/configuration.rb +2 -10
  11. data/lib/llm_cost_tracker/doctor/ingestion_check.rb +1 -3
  12. data/lib/llm_cost_tracker/doctor.rb +8 -13
  13. data/lib/llm_cost_tracker/engine.rb +0 -3
  14. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_period_totals_to_llm_cost_tracker.rb.erb +2 -2
  15. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb +7 -1
  16. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +1 -17
  17. data/lib/llm_cost_tracker/integrations/registry.rb +0 -2
  18. data/lib/llm_cost_tracker/period_grouping.rb +3 -5
  19. data/lib/llm_cost_tracker/railtie.rb +2 -4
  20. data/lib/llm_cost_tracker/report.rb +2 -4
  21. data/lib/llm_cost_tracker/storage/active_record_backend.rb +2 -1
  22. data/lib/llm_cost_tracker/storage/active_record_inbox.rb +1 -6
  23. data/lib/llm_cost_tracker/storage/active_record_rollup_upsert_sql.rb +1 -1
  24. data/lib/llm_cost_tracker/storage/{dispatcher.rb → writer.rb} +4 -14
  25. data/lib/llm_cost_tracker/tag_sql.rb +1 -1
  26. data/lib/llm_cost_tracker/tags_column.rb +2 -0
  27. data/lib/llm_cost_tracker/tracker.rb +2 -2
  28. data/lib/llm_cost_tracker/version.rb +1 -1
  29. data/lib/llm_cost_tracker.rb +2 -14
  30. data/lib/tasks/llm_cost_tracker.rake +1 -1
  31. metadata +33 -60
  32. data/docs/architecture.md +0 -28
  33. data/docs/budgets.md +0 -45
  34. data/docs/configuration.md +0 -65
  35. data/docs/cookbook.md +0 -185
  36. data/docs/dashboard-overview.png +0 -0
  37. data/docs/dashboard.md +0 -38
  38. data/docs/extending.md +0 -32
  39. data/docs/operations.md +0 -44
  40. data/docs/pricing.md +0 -94
  41. data/docs/querying.md +0 -36
  42. data/docs/streaming.md +0 -70
  43. data/docs/technical/README.md +0 -10
  44. data/docs/technical/data-flow.md +0 -70
  45. data/docs/technical/extension-points.md +0 -111
  46. data/docs/technical/module-map.md +0 -197
  47. data/docs/technical/operational-notes.md +0 -97
  48. data/docs/upgrading.md +0 -47
  49. data/lib/llm_cost_tracker/configuration/storage_backend.rb +0 -26
  50. data/lib/llm_cost_tracker/engine_compatibility.rb +0 -15
  51. data/lib/llm_cost_tracker/storage/custom_backend.rb +0 -32
  52. data/lib/llm_cost_tracker/storage/log_backend.rb +0 -38
  53. data/lib/llm_cost_tracker/storage/registry.rb +0 -63
data/docs/upgrading.md DELETED
@@ -1,47 +0,0 @@
1
- # Upgrading
2
-
3
- LLM Cost Tracker is still moving quickly, so upgrades should be explicit:
4
- inspect the changelog, run doctor, and apply only the generators your schema is
5
- missing.
6
-
7
- The version-by-version upgrade guide is moving here from the README.
8
-
9
- ## Canonical Sources
10
-
11
- Until this page is expanded, use:
12
-
13
- - [Changelog](../CHANGELOG.md)
14
- - [Quickstart](../README.md#quickstart)
15
- - [Operations](operations.md)
16
-
17
- ## Schema Generators
18
-
19
- Existing installs can add newer optional columns through focused generators:
20
-
21
- ```bash
22
- bin/rails generate llm_cost_tracker:add_period_totals
23
- bin/rails generate llm_cost_tracker:add_ingestion
24
- bin/rails generate llm_cost_tracker:add_streaming
25
- bin/rails generate llm_cost_tracker:add_provider_response_id
26
- bin/rails generate llm_cost_tracker:add_usage_breakdown
27
- bin/rails generate llm_cost_tracker:upgrade_tags_to_jsonb
28
- bin/rails generate llm_cost_tracker:upgrade_cost_precision
29
- bin/rails generate llm_cost_tracker:add_latency_ms
30
- bin/rails db:migrate
31
- bin/rails llm_cost_tracker:doctor
32
- ```
33
-
34
- On PostgreSQL, `upgrade_tags_to_jsonb` rewrites `llm_api_calls`. For large
35
- tables, run it during a maintenance window or replace it with a two-phase
36
- backfill.
37
-
38
- ## Upgrade Habit
39
-
40
- Run:
41
-
42
- ```bash
43
- bin/rails llm_cost_tracker:doctor
44
- ```
45
-
46
- Doctor tells you which optional columns and production-hardening pieces are still
47
- missing.
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LlmCostTracker
4
- module ConfigurationStorageBackend
5
- def storage_backend=(value)
6
- ensure_shared_configuration_mutable!
7
- @storage_backend = normalize_storage_backend(value)
8
- end
9
-
10
- private
11
-
12
- def normalize_storage_backend(value)
13
- value = :log if value.nil?
14
- value = value.to_sym
15
- return value if self.class::STORAGE_BACKENDS.include?(value)
16
- return value if defined?(Storage::Registry) && Storage::Registry.registered?(value)
17
-
18
- names = if defined?(Storage::Registry)
19
- Storage::Registry.names
20
- else
21
- self.class::STORAGE_BACKENDS
22
- end
23
- raise Error, "Unknown storage_backend: #{value.inspect}. Use one of: #{names.join(', ')}"
24
- end
25
- end
26
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LlmCostTracker
4
- module EngineCompatibility
5
- REQUIRED_RAILS_VERSION = Gem::Version.new("7.1.0")
6
-
7
- class << self
8
- def check_rails_version!(version)
9
- return if Gem::Version.new(version) >= REQUIRED_RAILS_VERSION
10
-
11
- raise LlmCostTracker::Error, "LlmCostTracker::Engine requires Rails 7.1+"
12
- end
13
- end
14
- end
15
- end
@@ -1,32 +0,0 @@
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,38 +0,0 @@
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
@@ -1,63 +0,0 @@
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