llm_cost_tracker 0.15.0 → 0.15.2

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.
@@ -8,7 +8,7 @@ module LlmCostTracker
8
8
  include StreamParser
9
9
 
10
10
  TRACKED_ENDPOINTS = %w[
11
- chat/completions completions embeddings moderations responses responses/compact
11
+ chat/completions completions decisions embeddings moderations responses responses/compact
12
12
  audio/transcriptions audio/translations audio/speech
13
13
  images/generations images/edits images/variations
14
14
  ].freeze
@@ -14,6 +14,7 @@ module LlmCostTracker
14
14
  PENDING_STATUSES = %w[queued in_progress].freeze
15
15
  RETRIEVE_PATH_PATTERN = %r{/(?:responses|agent)/resp_}
16
16
  TRANSCRIPTION_PATH_PATTERN = %r{/audio/(?:transcriptions|translations)\z}
17
+ UNBILLED_DECISION_USAGE = %i[output_tokens output_tokens_details total_tokens].freeze
17
18
 
18
19
  class << self
19
20
  def included(base) = base.include(StreamParser)
@@ -29,7 +30,7 @@ module LlmCostTracker
29
30
  end
30
31
 
31
32
  def event_from_response(response:, request:, provider:, host:, usage_source:, pricing_mode: nil)
32
- usage = response["usage"]&.deep_symbolize_keys
33
+ usage = billed_usage(response, provider)
33
34
  return nil if usage.nil? || pending?(response)
34
35
 
35
36
  model = response["model"] || request["model"]
@@ -73,6 +74,15 @@ module LlmCostTracker
73
74
  def pending?(response)
74
75
  PENDING_STATUSES.include?(response["status"].to_s)
75
76
  end
77
+
78
+ def billed_usage(response, provider)
79
+ usage = response["usage"]&.deep_symbolize_keys
80
+ return usage unless usage && decision?(response, provider)
81
+
82
+ usage.except(*UNBILLED_DECISION_USAGE)
83
+ end
84
+
85
+ def decision?(response, provider) = provider.to_s == "openai" && response.key?("answers")
76
86
  end
77
87
 
78
88
  def parse(request_url:, request_body:, response_status:, response_body:, **)
@@ -16,12 +16,14 @@ module LlmCostTracker
16
16
  def token_usage(usage, model: nil, default_to_image: ModelFamilies.image_output?(model))
17
17
  cache_read = cache_read_input_tokens(usage)
18
18
  cache_write = cache_write_input_tokens(usage)
19
+ extended = cache_write_extended_input_tokens(usage).clamp(0, [cache_write, 0].max)
19
20
  Usage::TokenUsage.build(
20
21
  **input_counts(usage, cached: cache_read + cache_write),
21
22
  **output_counts(usage, default_to_image: default_to_image),
22
23
  total_tokens: usage[:total_tokens],
23
24
  cache_read_input_tokens: cache_read,
24
- cache_write_input_tokens: cache_write,
25
+ cache_write_input_tokens: cache_write - extended,
26
+ cache_write_extended_input_tokens: extended,
25
27
  hidden_output_tokens: hidden_output_tokens(usage)
26
28
  )
27
29
  end
@@ -48,6 +50,12 @@ module LlmCostTracker
48
50
 
49
51
  def cache_read_input_tokens(usage) = detail(usage, INPUT_DETAIL_KEYS, :cached_tokens)
50
52
  def cache_write_input_tokens(usage) = detail(usage, INPUT_DETAIL_KEYS, :cache_write_tokens)
53
+
54
+ def cache_write_extended_input_tokens(usage)
55
+ details = INPUT_DETAIL_KEYS.filter_map { |key| usage.dig(key, :cache_write_tokens_details) }.first
56
+ details.is_a?(Hash) ? details[:ttl_12h_tokens].to_i : 0
57
+ end
58
+
51
59
  def hidden_output_tokens(usage) = detail(usage, OUTPUT_DETAIL_KEYS, :reasoning_tokens)
52
60
  def audio_input_tokens(usage) = detail(usage, INPUT_DETAIL_KEYS, :audio_tokens)
53
61
  def audio_output_tokens(usage) = detail(usage, OUTPUT_DETAIL_KEYS, :audio_tokens)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LlmCostTracker
4
- VERSION = "0.15.0"
4
+ VERSION = "0.15.2"
5
5
  end
@@ -49,7 +49,7 @@ Gem::Specification.new do |spec|
49
49
  spec.add_dependency "faraday", ">= 2.0", "< 3.0"
50
50
  spec.add_dependency "railties", ">= 8.0", "< 9.0"
51
51
 
52
- spec.add_development_dependency "anthropic", "~> 1.61"
52
+ spec.add_development_dependency "anthropic", ">= 1.61", "< 3"
53
53
  spec.add_development_dependency "aws-eventstream", "~> 1.4"
54
54
  spec.add_development_dependency "nokogiri", "~> 1.19"
55
55
  spec.add_development_dependency "openai", "~> 0.80"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm_cost_tracker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.15.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergii Khomenko
@@ -107,16 +107,22 @@ dependencies:
107
107
  name: anthropic
108
108
  requirement: !ruby/object:Gem::Requirement
109
109
  requirements:
110
- - - "~>"
110
+ - - ">="
111
111
  - !ruby/object:Gem::Version
112
112
  version: '1.61'
113
+ - - "<"
114
+ - !ruby/object:Gem::Version
115
+ version: '3'
113
116
  type: :development
114
117
  prerelease: false
115
118
  version_requirements: !ruby/object:Gem::Requirement
116
119
  requirements:
117
- - - "~>"
120
+ - - ">="
118
121
  - !ruby/object:Gem::Version
119
122
  version: '1.61'
123
+ - - "<"
124
+ - !ruby/object:Gem::Version
125
+ version: '3'
120
126
  - !ruby/object:Gem::Dependency
121
127
  name: aws-eventstream
122
128
  requirement: !ruby/object:Gem::Requirement