llm_cost_tracker 0.6.1 → 0.7.1

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 (180) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +13 -12
  4. data/app/assets/llm_cost_tracker/application.css +3 -0
  5. data/app/controllers/llm_cost_tracker/application_controller.rb +22 -4
  6. data/app/controllers/llm_cost_tracker/calls_controller.rb +6 -11
  7. data/app/controllers/llm_cost_tracker/dashboard_controller.rb +2 -1
  8. data/app/controllers/llm_cost_tracker/data_quality_controller.rb +5 -1
  9. data/app/controllers/llm_cost_tracker/models_controller.rb +0 -1
  10. data/app/controllers/llm_cost_tracker/tags_controller.rb +1 -8
  11. data/app/helpers/llm_cost_tracker/application_helper.rb +2 -1
  12. data/app/helpers/llm_cost_tracker/dashboard_filter_helper.rb +1 -2
  13. data/app/helpers/llm_cost_tracker/dashboard_filter_options_helper.rb +1 -1
  14. data/app/helpers/llm_cost_tracker/dashboard_query_helper.rb +10 -27
  15. data/app/helpers/llm_cost_tracker/token_usage_helper.rb +58 -0
  16. data/app/models/llm_cost_tracker/ingestion/event.rb +13 -0
  17. data/app/models/llm_cost_tracker/ingestion/lease.rb +11 -0
  18. data/app/models/llm_cost_tracker/ledger/call.rb +45 -0
  19. data/app/models/llm_cost_tracker/ledger/call_metrics.rb +66 -0
  20. data/app/models/llm_cost_tracker/ledger/period/grouping.rb +71 -0
  21. data/app/models/llm_cost_tracker/ledger/period/total.rb +13 -0
  22. data/app/models/llm_cost_tracker/ledger/tags/accessors.rb +19 -0
  23. data/app/services/llm_cost_tracker/dashboard/data_quality.rb +111 -94
  24. data/app/services/llm_cost_tracker/dashboard/date_range.rb +2 -2
  25. data/app/services/llm_cost_tracker/dashboard/filter.rb +7 -18
  26. data/app/services/llm_cost_tracker/dashboard/overview_stats.rb +58 -67
  27. data/app/services/llm_cost_tracker/dashboard/pagination.rb +59 -0
  28. data/app/services/llm_cost_tracker/dashboard/params.rb +26 -0
  29. data/app/services/llm_cost_tracker/dashboard/provider_breakdown.rb +18 -20
  30. data/app/services/llm_cost_tracker/dashboard/spend_anomaly.rb +4 -13
  31. data/app/services/llm_cost_tracker/dashboard/tag_breakdown.rb +28 -61
  32. data/app/services/llm_cost_tracker/dashboard/tag_key_explorer.rb +8 -37
  33. data/app/services/llm_cost_tracker/dashboard/time_series.rb +1 -1
  34. data/app/services/llm_cost_tracker/dashboard/top_models.rb +12 -47
  35. data/app/views/llm_cost_tracker/calls/index.html.erb +12 -18
  36. data/app/views/llm_cost_tracker/calls/show.html.erb +30 -32
  37. data/app/views/llm_cost_tracker/dashboard/index.html.erb +17 -19
  38. data/app/views/llm_cost_tracker/data_quality/index.html.erb +108 -135
  39. data/app/views/llm_cost_tracker/models/index.html.erb +8 -9
  40. data/app/views/llm_cost_tracker/shared/setup_required.html.erb +13 -2
  41. data/app/views/llm_cost_tracker/tags/show.html.erb +20 -20
  42. data/config/routes.rb +1 -1
  43. data/lib/llm_cost_tracker/assets.rb +0 -6
  44. data/lib/llm_cost_tracker/budget.rb +10 -24
  45. data/lib/llm_cost_tracker/capture/stream.rb +9 -0
  46. data/lib/llm_cost_tracker/capture/stream_collector.rb +182 -0
  47. data/lib/llm_cost_tracker/{integrations → capture}/stream_tracker.rb +40 -72
  48. data/lib/llm_cost_tracker/configuration/instrumentation.rb +3 -7
  49. data/lib/llm_cost_tracker/configuration.rb +30 -45
  50. data/lib/llm_cost_tracker/doctor/capture_verifier.rb +61 -0
  51. data/lib/llm_cost_tracker/doctor/check.rb +7 -0
  52. data/lib/llm_cost_tracker/doctor/ingestion_check.rb +22 -61
  53. data/lib/llm_cost_tracker/doctor/price_check.rb +60 -0
  54. data/lib/llm_cost_tracker/doctor.rb +66 -79
  55. data/lib/llm_cost_tracker/engine.rb +0 -3
  56. data/lib/llm_cost_tracker/errors.rb +4 -15
  57. data/lib/llm_cost_tracker/event.rb +6 -6
  58. data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_token_usage_generator.rb +42 -0
  59. data/lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb +2 -0
  60. data/lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb +7 -7
  61. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_period_totals_to_llm_cost_tracker.rb.erb +5 -5
  62. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_token_usage_to_llm_api_calls.rb.erb +22 -0
  63. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb +15 -14
  64. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +1 -21
  65. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_cost_precision.rb.erb +12 -1
  66. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/upgrade_llm_api_call_tags_to_jsonb.rb.erb +2 -2
  67. data/lib/llm_cost_tracker/{storage/active_record_inbox_batch.rb → ingestion/batch.rb} +21 -20
  68. data/lib/llm_cost_tracker/ingestion/inbox.rb +105 -0
  69. data/lib/llm_cost_tracker/{storage/active_record_ingestor_lease.rb → ingestion/lease_claim.rb} +5 -7
  70. data/lib/llm_cost_tracker/{storage/active_record_ingestor.rb → ingestion/worker.rb} +38 -48
  71. data/lib/llm_cost_tracker/ingestion.rb +129 -0
  72. data/lib/llm_cost_tracker/integrations/anthropic.rb +52 -34
  73. data/lib/llm_cost_tracker/integrations/base.rb +73 -34
  74. data/lib/llm_cost_tracker/integrations/openai.rb +45 -39
  75. data/lib/llm_cost_tracker/integrations/ruby_llm.rb +40 -30
  76. data/lib/llm_cost_tracker/integrations.rb +43 -0
  77. data/lib/llm_cost_tracker/ledger/period/totals.rb +66 -0
  78. data/lib/llm_cost_tracker/{storage/active_record_periods.rb → ledger/period.rb} +2 -2
  79. data/lib/llm_cost_tracker/ledger/rollups/batch.rb +43 -0
  80. data/lib/llm_cost_tracker/ledger/rollups/upsert_sql.rb +46 -0
  81. data/lib/llm_cost_tracker/ledger/rollups.rb +87 -0
  82. data/lib/llm_cost_tracker/ledger/schema/adapter.rb +51 -0
  83. data/lib/llm_cost_tracker/ledger/schema/calls.rb +101 -0
  84. data/lib/llm_cost_tracker/ledger/schema/period_totals.rb +32 -0
  85. data/lib/llm_cost_tracker/ledger/store.rb +60 -0
  86. data/lib/llm_cost_tracker/ledger/tags/query.rb +29 -0
  87. data/lib/llm_cost_tracker/ledger/tags/sql.rb +33 -0
  88. data/lib/llm_cost_tracker/ledger.rb +13 -0
  89. data/lib/llm_cost_tracker/logging.rb +3 -6
  90. data/lib/llm_cost_tracker/middleware/faraday.rb +35 -36
  91. data/lib/llm_cost_tracker/parsers/anthropic.rb +38 -27
  92. data/lib/llm_cost_tracker/parsers/base.rb +10 -19
  93. data/lib/llm_cost_tracker/parsers/gemini.rb +15 -16
  94. data/lib/llm_cost_tracker/parsers/openai_usage.rb +24 -19
  95. data/lib/llm_cost_tracker/parsers/sse.rb +4 -7
  96. data/lib/llm_cost_tracker/parsers.rb +20 -0
  97. data/lib/llm_cost_tracker/prices.json +52 -11
  98. data/lib/llm_cost_tracker/pricing/components.rb +37 -0
  99. data/lib/llm_cost_tracker/pricing/effective_prices.rb +40 -50
  100. data/lib/llm_cost_tracker/pricing/explainer.rb +12 -23
  101. data/lib/llm_cost_tracker/pricing/lookup.rb +24 -25
  102. data/lib/llm_cost_tracker/pricing/registry.rb +156 -0
  103. data/lib/llm_cost_tracker/pricing/sync/fetcher.rb +107 -0
  104. data/lib/llm_cost_tracker/pricing/sync/registry_diff.rb +53 -0
  105. data/lib/llm_cost_tracker/pricing/sync/registry_loader.rb +63 -0
  106. data/lib/llm_cost_tracker/pricing/sync/registry_writer.rb +31 -0
  107. data/lib/llm_cost_tracker/pricing/sync.rb +143 -0
  108. data/lib/llm_cost_tracker/pricing/unknown.rb +46 -0
  109. data/lib/llm_cost_tracker/pricing.rb +33 -32
  110. data/lib/llm_cost_tracker/railtie.rb +7 -10
  111. data/lib/llm_cost_tracker/report/data.rb +72 -0
  112. data/lib/llm_cost_tracker/report/formatter.rb +69 -0
  113. data/lib/llm_cost_tracker/report.rb +8 -10
  114. data/lib/llm_cost_tracker/retention.rb +27 -10
  115. data/lib/llm_cost_tracker/tags/context.rb +35 -0
  116. data/lib/llm_cost_tracker/tags/key.rb +18 -0
  117. data/lib/llm_cost_tracker/tags/sanitizer.rb +68 -0
  118. data/lib/llm_cost_tracker/token_usage.rb +67 -0
  119. data/lib/llm_cost_tracker/tracker.rb +38 -70
  120. data/lib/llm_cost_tracker/usage_capture.rb +37 -0
  121. data/lib/llm_cost_tracker/version.rb +1 -1
  122. data/lib/llm_cost_tracker.rb +56 -90
  123. data/lib/tasks/llm_cost_tracker.rake +18 -13
  124. metadata +85 -99
  125. data/app/services/llm_cost_tracker/dashboard/data_quality_aggregate.rb +0 -81
  126. data/app/services/llm_cost_tracker/pagination.rb +0 -57
  127. data/lib/llm_cost_tracker/active_record_adapter.rb +0 -49
  128. data/lib/llm_cost_tracker/capture_verifier.rb +0 -71
  129. data/lib/llm_cost_tracker/configuration/storage_backend.rb +0 -26
  130. data/lib/llm_cost_tracker/cost.rb +0 -12
  131. data/lib/llm_cost_tracker/doctor/capture_check.rb +0 -39
  132. data/lib/llm_cost_tracker/engine_compatibility.rb +0 -15
  133. data/lib/llm_cost_tracker/event_metadata.rb +0 -52
  134. data/lib/llm_cost_tracker/generators/llm_cost_tracker/add_usage_breakdown_generator.rb +0 -29
  135. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_usage_breakdown_to_llm_api_calls.rb.erb +0 -29
  136. data/lib/llm_cost_tracker/inbox_event.rb +0 -9
  137. data/lib/llm_cost_tracker/ingestor_lease.rb +0 -9
  138. data/lib/llm_cost_tracker/integrations/object_reader.rb +0 -56
  139. data/lib/llm_cost_tracker/integrations/registry.rb +0 -73
  140. data/lib/llm_cost_tracker/llm_api_call.rb +0 -60
  141. data/lib/llm_cost_tracker/llm_api_call_metrics.rb +0 -63
  142. data/lib/llm_cost_tracker/parameter_hash.rb +0 -33
  143. data/lib/llm_cost_tracker/parsed_usage.rb +0 -72
  144. data/lib/llm_cost_tracker/parsers/registry.rb +0 -58
  145. data/lib/llm_cost_tracker/period_grouping.rb +0 -69
  146. data/lib/llm_cost_tracker/period_total.rb +0 -9
  147. data/lib/llm_cost_tracker/price_freshness.rb +0 -38
  148. data/lib/llm_cost_tracker/price_registry.rb +0 -144
  149. data/lib/llm_cost_tracker/price_sync/fetcher.rb +0 -104
  150. data/lib/llm_cost_tracker/price_sync/registry_diff.rb +0 -51
  151. data/lib/llm_cost_tracker/price_sync/registry_loader.rb +0 -61
  152. data/lib/llm_cost_tracker/price_sync/registry_writer.rb +0 -29
  153. data/lib/llm_cost_tracker/price_sync.rb +0 -144
  154. data/lib/llm_cost_tracker/report_data.rb +0 -94
  155. data/lib/llm_cost_tracker/report_formatter.rb +0 -67
  156. data/lib/llm_cost_tracker/request_url.rb +0 -20
  157. data/lib/llm_cost_tracker/storage/active_record_backend.rb +0 -166
  158. data/lib/llm_cost_tracker/storage/active_record_connection_cleanup.rb +0 -13
  159. data/lib/llm_cost_tracker/storage/active_record_inbox.rb +0 -165
  160. data/lib/llm_cost_tracker/storage/active_record_period_totals.rb +0 -84
  161. data/lib/llm_cost_tracker/storage/active_record_rollup_batch.rb +0 -41
  162. data/lib/llm_cost_tracker/storage/active_record_rollup_upsert_sql.rb +0 -42
  163. data/lib/llm_cost_tracker/storage/active_record_rollups.rb +0 -146
  164. data/lib/llm_cost_tracker/storage/active_record_store.rb +0 -145
  165. data/lib/llm_cost_tracker/storage/custom_backend.rb +0 -32
  166. data/lib/llm_cost_tracker/storage/dispatcher.rb +0 -45
  167. data/lib/llm_cost_tracker/storage/log_backend.rb +0 -38
  168. data/lib/llm_cost_tracker/storage/registry.rb +0 -63
  169. data/lib/llm_cost_tracker/stream_capture.rb +0 -7
  170. data/lib/llm_cost_tracker/stream_collector.rb +0 -199
  171. data/lib/llm_cost_tracker/tag_accessors.rb +0 -15
  172. data/lib/llm_cost_tracker/tag_context.rb +0 -52
  173. data/lib/llm_cost_tracker/tag_key.rb +0 -16
  174. data/lib/llm_cost_tracker/tag_query.rb +0 -43
  175. data/lib/llm_cost_tracker/tag_sanitizer.rb +0 -81
  176. data/lib/llm_cost_tracker/tag_sql.rb +0 -34
  177. data/lib/llm_cost_tracker/tags_column.rb +0 -103
  178. data/lib/llm_cost_tracker/unknown_pricing.rb +0 -54
  179. data/lib/llm_cost_tracker/usage_breakdown.rb +0 -30
  180. data/lib/llm_cost_tracker/value_helpers.rb +0 -40
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
-
5
- module LlmCostTracker
6
- module TagSanitizer
7
- REDACTED_VALUE = "[REDACTED]"
8
-
9
- class << self
10
- def call(tags, config: LlmCostTracker.configuration)
11
- tags = (tags || {}).to_h
12
- tags.first(max_tag_count(config)).each_with_object({}) do |(key, value), sanitized|
13
- sanitized[key] = sanitized_value(key, value, config)
14
- end
15
- end
16
-
17
- private
18
-
19
- def sanitized_value(key, value, config)
20
- return REDACTED_VALUE if redacted_key?(key, config)
21
-
22
- string = value_string(value)
23
- return value if string.bytesize <= max_tag_value_bytesize(config)
24
-
25
- truncate_bytes(string, max_tag_value_bytesize(config))
26
- end
27
-
28
- def redacted_key?(key, config)
29
- normalized = normalized_key(key)
30
- redacted_keys(config).any? do |candidate|
31
- redacted_key_component?(normalized, candidate)
32
- end
33
- end
34
-
35
- def redacted_keys(config)
36
- Array(config.redacted_tag_keys).map { |key| normalized_key(key) }
37
- end
38
-
39
- def normalized_key(key)
40
- key.to_s
41
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
42
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
43
- .downcase
44
- .gsub(/[^a-z0-9]+/, "_")
45
- .gsub(/_+/, "_")
46
- .delete_prefix("_")
47
- .delete_suffix("_")
48
- end
49
-
50
- def redacted_key_component?(key, candidate)
51
- key == candidate ||
52
- key.start_with?("#{candidate}_") ||
53
- key.end_with?("_#{candidate}") ||
54
- key.include?("_#{candidate}_")
55
- end
56
-
57
- def value_string(value)
58
- case value
59
- when Hash, Array
60
- JSON.generate(value)
61
- else
62
- value.to_s
63
- end
64
- rescue JSON::GeneratorError, TypeError
65
- value.to_s
66
- end
67
-
68
- def truncate_bytes(string, limit)
69
- string.byteslice(0, limit).to_s.encode("UTF-8", invalid: :replace, undef: :replace)
70
- end
71
-
72
- def max_tag_count(config)
73
- [config.max_tag_count.to_i, 0].max
74
- end
75
-
76
- def max_tag_value_bytesize(config)
77
- [config.max_tag_value_bytesize.to_i, 0].max
78
- end
79
- end
80
- end
81
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "active_record_adapter"
4
- require_relative "tag_key"
5
-
6
- module LlmCostTracker
7
- module TagSql
8
- class << self
9
- def value_expression(model, key, table_name:)
10
- key = TagKey.validate!(key)
11
- column = "#{table_name}.#{model.connection.quote_column_name('tags')}"
12
-
13
- if ActiveRecordAdapter.postgresql?(model.connection)
14
- json_column = model.tags_jsonb_column? ? column : "(#{column})::jsonb"
15
- "#{json_column}->>#{model.connection.quote(key)}"
16
- elsif ActiveRecordAdapter.mysql?(model.connection)
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
@@ -1,103 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "active_record_adapter"
4
-
5
- module LlmCostTracker
6
- module TagsColumn
7
- USAGE_BREAKDOWN_COLUMNS = %w[
8
- cache_read_input_tokens
9
- cache_write_input_tokens
10
- hidden_output_tokens
11
- ].freeze
12
-
13
- USAGE_BREAKDOWN_COST_COLUMNS = %w[
14
- cache_read_input_cost
15
- cache_write_input_cost
16
- ].freeze
17
-
18
- def reset_column_information
19
- remove_instance_variable(:@lct_schema_capabilities) if instance_variable_defined?(:@lct_schema_capabilities)
20
-
21
- super
22
- end
23
-
24
- def tags_json_column?
25
- capabilities = lct_schema_capabilities
26
-
27
- capabilities.fetch(:tags_jsonb) || capabilities.fetch(:tags_mysql_json)
28
- end
29
-
30
- def tags_jsonb_column?
31
- lct_schema_capabilities.fetch(:tags_jsonb)
32
- end
33
-
34
- def tags_mysql_json_column?
35
- lct_schema_capabilities.fetch(:tags_mysql_json)
36
- end
37
-
38
- def latency_column?
39
- lct_schema_capabilities.fetch(:latency)
40
- end
41
-
42
- def stream_column?
43
- lct_schema_capabilities.fetch(:stream)
44
- end
45
-
46
- def usage_source_column?
47
- lct_schema_capabilities.fetch(:usage_source)
48
- end
49
-
50
- def provider_response_id_column?
51
- lct_schema_capabilities.fetch(:provider_response_id)
52
- end
53
-
54
- def pricing_mode_column?
55
- lct_schema_capabilities.fetch(:pricing_mode)
56
- end
57
-
58
- def usage_breakdown_columns?
59
- lct_schema_capabilities.fetch(:usage_breakdown)
60
- end
61
-
62
- def usage_breakdown_cost_columns?
63
- lct_schema_capabilities.fetch(:usage_breakdown_cost)
64
- end
65
-
66
- private
67
-
68
- def lct_schema_capabilities
69
- columns = columns_hash
70
- adapter_name = connection.adapter_name
71
- cache = @lct_schema_capabilities
72
-
73
- return cache.fetch(:values) if cache && cache.fetch(:columns).equal?(columns) &&
74
- cache.fetch(:adapter_name) == adapter_name
75
-
76
- values = build_lct_schema_capabilities(columns, adapter_name)
77
- @lct_schema_capabilities = { columns: columns, adapter_name: adapter_name, values: values }
78
- values
79
- end
80
-
81
- def build_lct_schema_capabilities(columns, adapter_name)
82
- tag_column = columns["tags"]
83
- tags_jsonb = tag_column && (tag_column.type == :jsonb || tag_column.sql_type.to_s.downcase == "jsonb")
84
- tags_mysql_json =
85
- tag_column &&
86
- !tags_jsonb &&
87
- tag_column.type == :json &&
88
- ActiveRecordAdapter.mysql?(adapter_name)
89
-
90
- {
91
- tags_jsonb: tags_jsonb ? true : false,
92
- tags_mysql_json: tags_mysql_json ? true : false,
93
- latency: columns.key?("latency_ms"),
94
- stream: columns.key?("stream"),
95
- usage_source: columns.key?("usage_source"),
96
- provider_response_id: columns.key?("provider_response_id"),
97
- pricing_mode: columns.key?("pricing_mode"),
98
- usage_breakdown: USAGE_BREAKDOWN_COLUMNS.all? { |column| columns.key?(column) },
99
- usage_breakdown_cost: USAGE_BREAKDOWN_COST_COLUMNS.all? { |column| columns.key?(column) }
100
- }
101
- end
102
- end
103
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "monitor"
4
-
5
- require_relative "logging"
6
-
7
- module LlmCostTracker
8
- class UnknownPricing
9
- MUTEX = Monitor.new
10
-
11
- class << self
12
- def handle!(model)
13
- model = normalized_model_name(model)
14
-
15
- case behavior
16
- when :ignore
17
- nil
18
- when :warn
19
- warn_missing(model)
20
- when :raise
21
- raise UnknownPricingError.new(model: model)
22
- end
23
- end
24
-
25
- def reset!
26
- MUTEX.synchronize { @warned_models = Set.new }
27
- end
28
-
29
- private
30
-
31
- def normalized_model_name(model)
32
- model.to_s.empty? ? "unknown" : model.to_s
33
- end
34
-
35
- def warn_missing(model)
36
- should_warn = MUTEX.synchronize do
37
- @warned_models ||= Set.new
38
- @warned_models.add?(model)
39
- end
40
- return unless should_warn
41
-
42
- Logging.warn(
43
- "No pricing configured for model #{model.inspect}. " \
44
- "Cost and budget guardrails will be skipped for this event. " \
45
- "Add a pricing_overrides entry or set unknown_pricing_behavior."
46
- )
47
- end
48
-
49
- def behavior
50
- LlmCostTracker.configuration.unknown_pricing_behavior
51
- end
52
- end
53
- end
54
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LlmCostTracker
4
- UsageBreakdown = Data.define(
5
- :input_tokens,
6
- :cache_read_input_tokens,
7
- :cache_write_input_tokens,
8
- :output_tokens,
9
- :hidden_output_tokens
10
- ) do
11
- def self.build(input_tokens:, output_tokens:, cache_read_input_tokens: 0,
12
- cache_write_input_tokens: 0, hidden_output_tokens: 0)
13
- new(
14
- input_tokens: input_tokens.to_i,
15
- cache_read_input_tokens: cache_read_input_tokens.to_i,
16
- cache_write_input_tokens: cache_write_input_tokens.to_i,
17
- output_tokens: output_tokens.to_i,
18
- hidden_output_tokens: hidden_output_tokens.to_i
19
- )
20
- end
21
-
22
- def total_tokens
23
- input_tokens + cache_read_input_tokens + cache_write_input_tokens + output_tokens
24
- end
25
-
26
- def to_h
27
- super.merge(total_tokens: total_tokens).compact
28
- end
29
- end
30
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LlmCostTracker
4
- module ValueHelpers
5
- class << self
6
- def deep_dup(value)
7
- case value
8
- when Hash
9
- value.each_with_object({}) do |(key, nested_value), duplicated|
10
- duplicated[deep_dup(key)] = deep_dup(nested_value)
11
- end
12
- when Array
13
- value.map { |nested_value| deep_dup(nested_value) }
14
- when String
15
- value.dup
16
- else
17
- value
18
- end
19
- end
20
-
21
- def deep_freeze(value)
22
- case value
23
- when Hash
24
- value.each do |key, nested_value|
25
- deep_freeze(key)
26
- deep_freeze(nested_value)
27
- end
28
- value.frozen? ? value : value.freeze
29
- when Array
30
- value.each { |nested_value| deep_freeze(nested_value) }
31
- value.frozen? ? value : value.freeze
32
- when String
33
- value.frozen? ? value : value.freeze
34
- else
35
- value
36
- end
37
- end
38
- end
39
- end
40
- end