Module: Legion::LLM::Metering

Extended by:
Legion::Logging::Helper
Defined in:
lib/legion/llm/metering.rb,
lib/legion/llm/metering/tokens.rb,
lib/legion/llm/metering/tracker.rb,
lib/legion/llm/metering/estimator.rb

Defined Under Namespace

Modules: Pricing, Recorder, Tokens

Class Method Summary collapse

Class Method Details

.const_missing(name) ⇒ Object

Backward-compat: resolve old Legion::LLM::Metering::Exchange, ::Event



117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/legion/llm/metering.rb', line 117

def self.const_missing(name)
  case name
  when :Exchange
    require_relative 'transport/exchanges/metering'
    Transport::Exchanges::Metering
  when :Event
    require_relative 'transport/messages/metering_event'
    Transport::Messages::MeteringEvent
  else
    super
  end
end

.emit(event) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/legion/llm/metering.rb', line 23

def emit(event)
  if transport_connected? && defined?(Legion::LLM::Transport::Messages::MeteringEvent)
    Legion::LLM::Transport::Messages::MeteringEvent.new(**event).publish
    log.info("[llm][metering] published provider=#{event[:provider]} model=#{event[:model_id]}")
    :published
  elsif spool_available?
    spool_event(event)
    log.info("[llm][metering] spooled provider=#{event[:provider]} model=#{event[:model_id]}")
    :spooled
  else
    log.warn("[llm][metering] dropped provider=#{event[:provider]} model=#{event[:model_id]}")
    :dropped
  end
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'llm.metering.emit')
  :dropped
end

.extract_model(response) ⇒ Object



110
111
112
113
114
# File 'lib/legion/llm/metering.rb', line 110

def extract_model(response)
  return nil unless response.is_a?(Hash)

  response.dig(:meta, :model) || response[:model]
end

.extract_provider(response) ⇒ Object



104
105
106
107
108
# File 'lib/legion/llm/metering.rb', line 104

def extract_provider(response)
  return nil unless response.is_a?(Hash)

  response.dig(:meta, :provider) || response[:provider]
end

.extract_usage(response) ⇒ Object



94
95
96
97
98
99
100
101
102
# File 'lib/legion/llm/metering.rb', line 94

def extract_usage(response)
  return { input_tokens: 0, output_tokens: 0 } unless response.is_a?(Hash)

  usage = response[:usage] || {}
  {
    input_tokens:  usage[:input_tokens] || usage[:prompt_tokens] || 0,
    output_tokens: usage[:output_tokens] || usage[:completion_tokens] || 0
  }
end

.flush_spoolObject



41
42
43
44
45
46
47
48
49
50
51
# File 'lib/legion/llm/metering.rb', line 41

def flush_spool
  return 0 unless spool_available? && transport_connected?

  spool = Legion::Data::Spool.for(Legion::LLM)
  flushed = spool.flush(:metering) { |event| emit(event) }
  log.info("[llm][metering] spool_flushed count=#{flushed}")
  flushed
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'llm.metering.flush_spool')
  0
end

.install_hookObject



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/legion/llm/metering.rb', line 53

def install_hook
  Legion::LLM::Hooks.after_chat do |response:, model:, **|
    usage = extract_usage(response)
    next if usage[:input_tokens].zero? && usage[:output_tokens].zero?

    resolved_model    = (extract_model(response) || model).to_s
    resolved_provider = extract_provider(response)

    Metering::Recorder.record(
      model:         resolved_model,
      input_tokens:  usage[:input_tokens],
      output_tokens: usage[:output_tokens],
      provider:      resolved_provider
    )

    emit(
      provider:      resolved_provider,
      model_id:      resolved_model,
      input_tokens:  usage[:input_tokens],
      output_tokens: usage[:output_tokens],
      event_type:    'llm_completion',
      status:        response.is_a?(Hash) && response[:error] ? 'failure' : 'success'
    )
    nil
  end
end

.load_transportObject



14
15
16
17
18
19
# File 'lib/legion/llm/metering.rb', line 14

def self.load_transport
  return unless defined?(Legion::Transport::Message)

  require_relative 'transport/exchanges/metering'
  require_relative 'transport/messages/metering_event'
end

.spool_available?Boolean

Returns:

  • (Boolean)


85
86
87
# File 'lib/legion/llm/metering.rb', line 85

def spool_available?
  !!defined?(Legion::Data::Spool)
end

.spool_event(event) ⇒ Object



89
90
91
92
# File 'lib/legion/llm/metering.rb', line 89

def spool_event(event)
  spool = Legion::Data::Spool.for(Legion::LLM)
  spool.write(:metering, event)
end

.transport_connected?Boolean

Returns:

  • (Boolean)


80
81
82
83
# File 'lib/legion/llm/metering.rb', line 80

def transport_connected?
  !!(defined?(Legion::Settings) &&
    Legion::Settings[:transport][:connected] == true)
end