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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/README.md +195 -109
  4. data/app/services/llm_cost_tracker/dashboard/data_quality.rb +46 -55
  5. data/app/services/llm_cost_tracker/dashboard/data_quality_aggregate.rb +81 -0
  6. data/lib/llm_cost_tracker/budget.rb +34 -37
  7. data/lib/llm_cost_tracker/configuration/instrumentation.rb +37 -0
  8. data/lib/llm_cost_tracker/configuration.rb +10 -5
  9. data/lib/llm_cost_tracker/doctor.rb +166 -0
  10. data/lib/llm_cost_tracker/generators/llm_cost_tracker/install_generator.rb +33 -0
  11. data/lib/llm_cost_tracker/generators/llm_cost_tracker/prices_generator.rb +12 -6
  12. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/add_period_totals_to_llm_cost_tracker.rb.erb +38 -8
  13. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/create_llm_api_calls.rb.erb +1 -2
  14. data/lib/llm_cost_tracker/generators/llm_cost_tracker/templates/initializer.rb.erb +53 -21
  15. data/lib/llm_cost_tracker/integrations/anthropic.rb +75 -0
  16. data/lib/llm_cost_tracker/integrations/base.rb +72 -0
  17. data/lib/llm_cost_tracker/integrations/object_reader.rb +56 -0
  18. data/lib/llm_cost_tracker/integrations/openai.rb +95 -0
  19. data/lib/llm_cost_tracker/integrations/registry.rb +41 -0
  20. data/lib/llm_cost_tracker/middleware/faraday.rb +4 -3
  21. data/lib/llm_cost_tracker/parsed_usage.rb +8 -1
  22. data/lib/llm_cost_tracker/parsers/anthropic.rb +17 -49
  23. data/lib/llm_cost_tracker/parsers/base.rb +80 -0
  24. data/lib/llm_cost_tracker/parsers/gemini.rb +12 -35
  25. data/lib/llm_cost_tracker/parsers/openai.rb +1 -6
  26. data/lib/llm_cost_tracker/parsers/openai_compatible.rb +6 -15
  27. data/lib/llm_cost_tracker/parsers/openai_usage.rb +8 -30
  28. data/lib/llm_cost_tracker/parsers/registry.rb +17 -2
  29. data/lib/llm_cost_tracker/price_freshness.rb +38 -0
  30. data/lib/llm_cost_tracker/price_registry.rb +14 -0
  31. data/lib/llm_cost_tracker/price_sync/fetcher.rb +2 -1
  32. data/lib/llm_cost_tracker/price_sync/refresh_plan_builder.rb +4 -2
  33. data/lib/llm_cost_tracker/price_sync.rb +10 -0
  34. data/lib/llm_cost_tracker/prices.json +394 -41
  35. data/lib/llm_cost_tracker/pricing.rb +8 -1
  36. data/lib/llm_cost_tracker/request_url.rb +20 -0
  37. data/lib/llm_cost_tracker/storage/active_record_rollups.rb +47 -27
  38. data/lib/llm_cost_tracker/storage/active_record_store.rb +4 -0
  39. data/lib/llm_cost_tracker/stream_collector.rb +3 -3
  40. data/lib/llm_cost_tracker/tag_context.rb +52 -0
  41. data/lib/llm_cost_tracker/tags_column.rb +62 -24
  42. data/lib/llm_cost_tracker/tracker.rb +5 -2
  43. data/lib/llm_cost_tracker/version.rb +1 -1
  44. data/lib/llm_cost_tracker.rb +14 -4
  45. data/lib/tasks/llm_cost_tracker.rake +21 -3
  46. metadata +13 -3
  47. 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
- tags_jsonb_column? || tags_mysql_json_column?
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
- column = columns_hash["tags"]
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
- column = columns_hash["tags"]
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
- columns_hash.key?("latency_ms")
37
+ lct_schema_capabilities.fetch(:latency)
26
38
  end
27
39
 
28
40
  def stream_column?
29
- columns_hash.key?("stream")
41
+ lct_schema_capabilities.fetch(:stream)
30
42
  end
31
43
 
32
44
  def usage_source_column?
33
- columns_hash.key?("usage_source")
45
+ lct_schema_capabilities.fetch(:usage_source)
34
46
  end
35
47
 
36
48
  def provider_response_id_column?
37
- columns_hash.key?("provider_response_id")
49
+ lct_schema_capabilities.fetch(:provider_response_id)
38
50
  end
39
51
 
40
52
  def pricing_mode_column?
41
- columns_hash.key?("pricing_mode")
53
+ lct_schema_capabilities.fetch(:pricing_mode)
42
54
  end
43
55
 
44
56
  def usage_breakdown_columns?
45
- %w[
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
- %w[
54
- cache_read_input_cost
55
- cache_write_input_cost
56
- ].all? { |column| columns_hash.key?(column) }
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.configuration.default_tags.merge(EventMetadata.tags(metadata)).freeze,
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),
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LlmCostTracker
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -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.normalize_openai_compatible_providers!
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 track(provider:, model:, input_tokens:, output_tokens:, latency_ms: nil, stream: false, usage_source: :manual,
73
- enforce_budget: false, provider_response_id: nil, pricing_mode: nil, **metadata)
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:, latency_ms: nil, enforce_budget: false, provider_response_id: nil,
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 built-in pricing data from LiteLLM/OpenRouter JSON sources. " \
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 = ENV["OUTPUT"] || LlmCostTracker.configuration.prices_file || LlmCostTracker::PriceSync::DEFAULT_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 = ENV["OUTPUT"] || LlmCostTracker.configuration.prices_file || LlmCostTracker::PriceSync::DEFAULT_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.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-24 00:00:00.000000000 Z
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
@@ -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: