legion-llm 0.3.22 → 0.3.23

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db13bc01a538ce15c213a0ee49dae011b79c0bdf0148ebb940dad6b54cc769c4
4
- data.tar.gz: a68d77b17f0eeff3e841620cc43bae9601e9a9069b555ca583b507ab258677db
3
+ metadata.gz: 45a5b0befbd5b6ea879f539a30ecb6675f7481e349c115f52ffc2e167c9e7c8d
4
+ data.tar.gz: 66323d03c6aac956cb0ad78b9bb708cbe7e3c834ede3ab4b34c820e1289f2320
5
5
  SHA512:
6
- metadata.gz: ab9351b4781dcf146d552f555d0da7eaa444a94d15125af387af33b5ae3741863fccccaafd1fd981faca6e3781589fd6f5a4a273b9a02bf940d389763c55150c
7
- data.tar.gz: 4e17454656a9baf87b78a75e99bfe5cc6215a48d1135cc053abab4e85b8b300e3ece10ab5e6628dabbd7a22cbc167d1d3e1d5c63d87da226a8092db6f1ed3b64
6
+ metadata.gz: 37ace42f654c110b9e633c53f4999fadd52d447f76145f95bd8ea18a3bcd816ad7ff4b6d3c7270eceaa12ab3b2f4a00f0192066df2a0adc53643d4614f534bb8
7
+ data.tar.gz: 7c6d639f5f45dbc4c3fccb94e8fece4e933da6037758d8eec3590c35fa15c24f55c19cdc9cc534b848a879d5de585d1c2cef070faa0b3bf354417cef25f86ab7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Legion LLM Changelog
2
2
 
3
+ ## [0.3.23] - 2026-03-23
4
+
5
+ ### Added
6
+ - Auto-metering hook: records token usage after every LLM call via gateway MeteringWriter or AMQP transport
7
+ - `Hooks::Metering.install` registers an `after_chat` hook during `LLM.start`
8
+ - Extracts input/output tokens, provider, model, status from response
9
+ - Opt-out via `llm.metering.auto: false` in settings
10
+ - 11 specs covering hook installation, data extraction, availability checks, and edge cases
11
+
3
12
  ## [0.3.22] - 2026-03-23
4
13
 
5
14
  ### Changed
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module LLM
5
+ module Hooks
6
+ module Metering
7
+ module_function
8
+
9
+ def install
10
+ Legion::LLM::Hooks.after_chat do |response:, model:, **|
11
+ record(response, model)
12
+ nil
13
+ end
14
+ end
15
+
16
+ def record(response, model)
17
+ return unless metering_available?
18
+
19
+ payload = extract_metering_data(response, model)
20
+ return if payload[:input_tokens].zero? && payload[:output_tokens].zero?
21
+
22
+ publish_metering(payload)
23
+ rescue StandardError => e
24
+ Legion::Logging.debug("[LLM::Metering] record failed: #{e.message}") if defined?(Legion::Logging)
25
+ end
26
+
27
+ def extract_metering_data(response, model)
28
+ usage = extract_usage(response)
29
+ {
30
+ provider: extract_provider(response),
31
+ model_id: (extract_model(response) || model).to_s,
32
+ input_tokens: usage[:input_tokens],
33
+ output_tokens: usage[:output_tokens],
34
+ event_type: 'llm_completion',
35
+ status: response.is_a?(Hash) && response[:error] ? 'failure' : 'success'
36
+ }
37
+ end
38
+
39
+ def extract_usage(response)
40
+ return { input_tokens: 0, output_tokens: 0 } unless response.is_a?(Hash)
41
+
42
+ usage = response[:usage] || {}
43
+ {
44
+ input_tokens: usage[:input_tokens] || usage[:prompt_tokens] || 0,
45
+ output_tokens: usage[:output_tokens] || usage[:completion_tokens] || 0
46
+ }
47
+ end
48
+
49
+ def extract_provider(response)
50
+ return nil unless response.is_a?(Hash)
51
+
52
+ response.dig(:meta, :provider) || response[:provider]
53
+ end
54
+
55
+ def extract_model(response)
56
+ return nil unless response.is_a?(Hash)
57
+
58
+ response.dig(:meta, :model) || response[:model]
59
+ end
60
+
61
+ def publish_metering(payload)
62
+ if gateway_metering?
63
+ Legion::Extensions::LLM::Gateway::Runners::MeteringWriter.write_metering_record(payload)
64
+ elsif transport_metering?
65
+ Legion::Transport.publish(
66
+ 'lex.metering.record',
67
+ Legion::JSON.dump(payload)
68
+ )
69
+ end
70
+ end
71
+
72
+ def gateway_metering?
73
+ defined?(Legion::Extensions::LLM::Gateway::Runners::MeteringWriter)
74
+ end
75
+
76
+ def transport_metering?
77
+ defined?(Legion::Transport) &&
78
+ Legion::Transport.respond_to?(:connected?) &&
79
+ Legion::Transport.connected?
80
+ rescue StandardError
81
+ false
82
+ end
83
+
84
+ def metering_available?
85
+ gateway_metering? || transport_metering?
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'legion/llm/hooks/rag_guard'
4
4
  require 'legion/llm/hooks/response_guard'
5
+ require 'legion/llm/hooks/metering'
5
6
 
6
7
  module Legion
7
8
  module LLM
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module LLM
5
- VERSION = '0.3.22'
5
+ VERSION = '0.3.23'
6
6
  end
7
7
  end
data/lib/legion/llm.rb CHANGED
@@ -46,6 +46,8 @@ module Legion
46
46
  run_discovery
47
47
  set_defaults
48
48
 
49
+ install_hooks
50
+
49
51
  @started = true
50
52
  Legion::Settings[:llm][:connected] = true
51
53
  Legion::Logging.info 'Legion::LLM started'
@@ -494,6 +496,13 @@ module Legion
494
496
  cloud_providers.include?(resolved&.to_sym)
495
497
  end
496
498
 
499
+ def install_hooks
500
+ metering_enabled = settings.dig(:metering, :auto) != false
501
+ Hooks::Metering.install if metering_enabled
502
+ rescue StandardError => e
503
+ Legion::Logging.debug("LLM hook installation failed: #{e.message}") if defined?(Legion::Logging)
504
+ end
505
+
497
506
  def set_defaults
498
507
  default_model = settings[:default_model]
499
508
  default_provider = settings[:default_provider]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.22
4
+ version: 0.3.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -145,6 +145,7 @@ files:
145
145
  - lib/legion/llm/escalation_history.rb
146
146
  - lib/legion/llm/helpers/llm.rb
147
147
  - lib/legion/llm/hooks.rb
148
+ - lib/legion/llm/hooks/metering.rb
148
149
  - lib/legion/llm/hooks/rag_guard.rb
149
150
  - lib/legion/llm/hooks/response_guard.rb
150
151
  - lib/legion/llm/off_peak.rb