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
- .attributed_event(event) ⇒ Object
-
.const_missing(name) ⇒ Object
Backward-compat: resolve old Legion::LLM::Metering::Exchange, ::Event.
- .emit(event) ⇒ Object
- .extract_hash_value(hash, key) ⇒ Object
- .extract_model(response) ⇒ Object
- .extract_provider(response) ⇒ Object
- .extract_usage(response) ⇒ Object
- .flush_spool ⇒ Object
- .install_hook ⇒ Object
- .load_transport ⇒ Object
- .metering_event_class ⇒ Object
- .transport_connected? ⇒ Boolean
Class Method Details
.attributed_event(event) ⇒ Object
41 42 43 44 45 46 |
# File 'lib/legion/llm/metering.rb', line 41 def attributed_event(event) source = event.is_a?(Hash) ? event.dup : {} source[:identity] = Legion::LLM::PublisherIdentity.current source[:caller] ||= Legion::LLM::PublisherIdentity.caller_hash source end |
.const_missing(name) ⇒ Object
Backward-compat: resolve old Legion::LLM::Metering::Exchange, ::Event
132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/legion/llm/metering.rb', line 132 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
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/legion/llm/metering.rb', line 24 def emit(event) event = attributed_event(event) event_class = metering_event_class if transport_connected? if event_class event_class.new(**event).publish log.info("[llm][metering] published provider=#{event[:provider]} model=#{event[:model_id]}") :published else log.warn("[llm][metering] dropped provider=#{event[:provider]} model=#{event[:model_id]} reason=transport_unavailable") :dropped end rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.metering.emit') :dropped end |
.extract_hash_value(hash, key) ⇒ Object
122 123 124 125 126 127 128 129 |
# File 'lib/legion/llm/metering.rb', line 122 def extract_hash_value(hash, key) return nil unless hash.respond_to?(:key?) string_key = key.to_s return hash[string_key] if hash.key?(string_key) hash[key] if hash.key?(key) end |
.extract_model(response) ⇒ Object
116 117 118 119 120 |
# File 'lib/legion/llm/metering.rb', line 116 def extract_model(response) return nil unless response.is_a?(Hash) extract_hash_value(extract_hash_value(response, :meta), :model) || extract_hash_value(response, :model) end |
.extract_provider(response) ⇒ Object
110 111 112 113 114 |
# File 'lib/legion/llm/metering.rb', line 110 def extract_provider(response) return nil unless response.is_a?(Hash) extract_hash_value(extract_hash_value(response, :meta), :provider) || extract_hash_value(response, :provider) end |
.extract_usage(response) ⇒ Object
100 101 102 103 104 105 106 107 108 |
# File 'lib/legion/llm/metering.rb', line 100 def extract_usage(response) return { input_tokens: 0, output_tokens: 0 } unless response.is_a?(Hash) usage = extract_hash_value(response, :usage) || {} { input_tokens: extract_hash_value(usage, :input_tokens) || extract_hash_value(usage, :prompt_tokens) || 0, output_tokens: extract_hash_value(usage, :output_tokens) || extract_hash_value(usage, :completion_tokens) || 0 } end |
.flush_spool ⇒ Object
48 49 50 51 52 53 54 |
# File 'lib/legion/llm/metering.rb', line 48 def flush_spool log.debug('[llm][metering] spool disabled; metering events are transport-only') 0 rescue StandardError => e handle_exception(e, level: :warn, operation: 'llm.metering.flush_spool') 0 end |
.install_hook ⇒ Object
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/legion/llm/metering.rb', line 56 def install_hook Legion::LLM::Hooks.after_chat do |response:, model:, caller: nil, **| 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], caller: caller, event_type: 'llm_completion', status: response.is_a?(Hash) && response[:error] ? 'failure' : 'success' ) nil end end |
.load_transport ⇒ Object
15 16 17 18 19 20 |
# File 'lib/legion/llm/metering.rb', line 15 def self.load_transport return unless defined?(Legion::Transport::Message) require_relative 'transport/exchanges/metering' require_relative 'transport/messages/metering_event' end |
.metering_event_class ⇒ Object
88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/legion/llm/metering.rb', line 88 def metering_event_class return Legion::LLM::Transport::Messages::MeteringEvent if defined?(Legion::LLM::Transport::Messages::MeteringEvent) load_transport return Legion::LLM::Transport::Messages::MeteringEvent if defined?(Legion::LLM::Transport::Messages::MeteringEvent) Legion::LLM::Metering::Event rescue NameError, LoadError => e handle_exception(e, level: :warn, handled: true, operation: 'llm.metering.event_class') nil end |
.transport_connected? ⇒ Boolean
84 85 86 |
# File 'lib/legion/llm/metering.rb', line 84 def transport_connected? Legion::LLM::Settings.transport_connected? end |