Module: LlmCostTracker::Integrations::Anthropic

Extended by:
Base
Defined in:
lib/llm_cost_tracker/integrations/anthropic.rb

Defined Under Namespace

Modules: MessagesPatch

Constant Summary

Constants included from Base

Base::Result

Class Method Summary collapse

Methods included from Base

active?, enforce_budget!, install, minimum_version, normalize_sdk_args, object_dig, object_value, patch_target, patch_targets, record_safely, request_params, status, stream_collector, stream_pricing_mode, track_stream, version_constant

Class Method Details

.inference_geo(request:, usage:) ⇒ Object



133
134
135
# File 'lib/llm_cost_tracker/integrations/anthropic.rb', line 133

def inference_geo(request:, usage:)
  object_value(usage, :inference_geo) || request[:inference_geo]
end

.integration_nameObject



13
14
15
# File 'lib/llm_cost_tracker/integrations/anthropic.rb', line 13

def integration_name
  :anthropic
end

.line_item_for_server_tool(server_tool_use, component_key, count_key, provider_field) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/llm_cost_tracker/integrations/anthropic.rb', line 77

def line_item_for_server_tool(server_tool_use, component_key, count_key, provider_field)
  quantity = object_value(server_tool_use, count_key).to_i
  return nil if quantity.zero?

  Billing::LineItem.build(
    component_key: component_key,
    quantity: quantity,
    cost_status: Billing::CostStatus::UNKNOWN,
    pricing_basis: :provider_usage,
    provider_field: provider_field
  )
end

.minimum_versionObject



17
18
19
# File 'lib/llm_cost_tracker/integrations/anthropic.rb', line 17

def minimum_version
  "1.36.0"
end

.patch_targetsObject



25
26
27
28
29
30
31
32
33
34
35
# File 'lib/llm_cost_tracker/integrations/anthropic.rb', line 25

def patch_targets
  [
    patch_target("Anthropic::Resources::Messages", with: MessagesPatch, methods: %i[create stream stream_raw]),
    patch_target(
      "Anthropic::Resources::Beta::Messages",
      with: MessagesPatch,
      methods: %i[create stream stream_raw],
      optional: true
    )
  ]
end

.pricing_mode(request:, usage:) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/llm_cost_tracker/integrations/anthropic.rb', line 114

def pricing_mode(request:, usage:)
  service_tier = object_value(usage, :service_tier) || request[:service_tier]
  tier = Providers::Anthropic::TierClassification
  service_tier = nil if tier.standard_equivalent_tier?(service_tier)

  modes = [
    Pricing.normalize_mode(object_value(usage, :speed) || request[:speed]),
    Pricing.normalize_mode(service_tier)
  ]
  geo = inference_geo(request: request, usage: usage).to_s.downcase
  modes << "data_residency" if tier.data_residency_geo?(geo)
  modes = modes.compact.uniq
  modes.empty? ? nil : modes.join("_")
end

.record_message(message, request:, latency_ms:) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/llm_cost_tracker/integrations/anthropic.rb', line 37

def record_message(message, request:, latency_ms:)
  return unless active?

  record_safely do
    usage = object_value(message, :usage)
    next unless usage

    input_tokens = object_value(usage, :input_tokens)
    output_tokens = object_value(usage, :output_tokens)
    next if input_tokens.nil? && output_tokens.nil?

    LlmCostTracker::Tracker.record(
      event: Event.build(
        provider: "anthropic",
        model: object_value(message, :model) || request[:model],
        pricing_mode: pricing_mode(request: request, usage: usage),
        token_usage: token_usage(usage: usage, input_tokens: input_tokens, output_tokens: output_tokens),
        usage_source: :sdk_response,
        provider_response_id: object_value(message, :id),
        service_line_items: service_line_items_from(usage)
      ),
      latency_ms: latency_ms
    )
  end
end

.service_line_items_from(usage) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/llm_cost_tracker/integrations/anthropic.rb', line 63

def service_line_items_from(usage)
  server_tool_use = object_value(usage, :server_tool_use)
  return [] unless server_tool_use

  [
    line_item_for_server_tool(server_tool_use, :web_search_request, :web_search_requests,
                              "usage.server_tool_use.web_search_requests"),
    line_item_for_server_tool(server_tool_use, :web_fetch_request, :web_fetch_requests,
                              "usage.server_tool_use.web_fetch_requests"),
    line_item_for_server_tool(server_tool_use, :code_execution_request, :code_execution_requests,
                              "usage.server_tool_use.code_execution_requests")
  ].compact
end

.stream_pricing_mode(request) ⇒ Object



129
130
131
# File 'lib/llm_cost_tracker/integrations/anthropic.rb', line 129

def stream_pricing_mode(request)
  pricing_mode(request: request || {}, usage: nil)
end

.token_usage(usage:, input_tokens:, output_tokens:) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/llm_cost_tracker/integrations/anthropic.rb', line 90

def token_usage(usage:, input_tokens:, output_tokens:)
  cache_creation = object_value(usage, :cache_creation)
  if cache_creation
    cache_write_default = object_value(cache_creation, :ephemeral_5m_input_tokens).to_i
    cache_write_extended = object_value(cache_creation, :ephemeral_1h_input_tokens).to_i
  else
    cache_write_default = object_value(usage, :cache_creation_input_tokens).to_i
    cache_write_extended = 0
  end
  hidden_output = (
    object_value(usage, :thinking_tokens, :thinking_output_tokens) ||
    object_dig(usage, :output_tokens_details, :reasoning_tokens)
  ).to_i

  TokenUsage.build(
    input_tokens: input_tokens.to_i,
    output_tokens: output_tokens.to_i,
    cache_read_input_tokens: object_value(usage, :cache_read_input_tokens).to_i,
    cache_write_input_tokens: cache_write_default,
    cache_write_extended_input_tokens: cache_write_extended,
    hidden_output_tokens: hidden_output
  )
end

.version_constantObject



21
22
23
# File 'lib/llm_cost_tracker/integrations/anthropic.rb', line 21

def version_constant
  "Anthropic::VERSION"
end

.wrap_stream_call(args, kwargs) ⇒ Object



137
138
139
140
141
142
143
# File 'lib/llm_cost_tracker/integrations/anthropic.rb', line 137

def wrap_stream_call(args, kwargs)
  request = request_params(args, kwargs)
  enforce_budget!(request: request)
  collector = stream_collector(request)
  stream = yield
  track_stream(stream, collector: collector)
end