legion-llm 0.3.20 → 0.3.21

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: 19f442b46ed976f59b4f44878bff395fc497dde75bc9b3df8a4471442e0cb3eb
4
- data.tar.gz: 590d08bf5499d7a95989ea2a8e098c386a6a4b9eae1e5bcbda89b390f50c20a9
3
+ metadata.gz: 1b13e641291a41a378dba7a7eca32aaf67c46bc45cb5d459ded5bc3bdab8e0b0
4
+ data.tar.gz: cc8c372089f5de6518222d608255d65f58968bb97d6eb4f77aac61bb475149d1
5
5
  SHA512:
6
- metadata.gz: 490852eca96d4356949c3dc2a3bfa811c35a539ea47d89f8a12cc5a5098b3709ec03cf83677927378f8f96357af2de88dfbb24ffed68f2268535d3b692c0d1ce
7
- data.tar.gz: ae3efe882cde912a9cae6bca01605200027e13f6ff72232f04140bd67f030224600a6a6f4692d221c986e4d4d2228e3c64d1c7bbb50112521f78612f5aad3f46
6
+ metadata.gz: b8f38cc2091b3f09f55325192cc6d92bda63aeddbdbe1548ebb04e6c68037219d7c1a892e86dd5663ef0334289c7107c2f8326cc30faf218e53dee0c4b19e1b9
7
+ data.tar.gz: 50c0618742b5006211029aef080fd04c044b20f6b5e33a6bd6d5c070c8971defb3b21bf0f70341ac23900b20f3e80df2071505edf66879519e913a7e95ea1d33
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Legion LLM Changelog
2
2
 
3
+ ## [0.3.21] - 2026-03-23
4
+
5
+ ### Added
6
+ - `Legion::LLM::ToolRegistry` thread-safe tool class registry for auto-attaching tools to chat sessions
7
+ - Wire ToolRegistry into `chat_single` so globally registered tools are available in every session
8
+
9
+ ### Fixed
10
+ - Fix `CostTracker.settings_pricing` reading from wrong settings key (`:'legion-llm'` instead of `:llm`)
11
+ - Fix `ShadowEval.evaluate` not passing `messages:` to shadow model (shadow got no context to respond to)
12
+
3
13
  ## [0.3.20] - 2026-03-22
4
14
 
5
15
  ### Changed
@@ -84,7 +84,7 @@ module Legion
84
84
  def settings_pricing
85
85
  return {} unless defined?(Legion::Settings)
86
86
 
87
- pricing = Legion::Settings.dig(:'legion-llm', :pricing)
87
+ pricing = Legion::Settings.dig(:llm, :pricing)
88
88
  pricing.is_a?(Hash) ? pricing : {}
89
89
  rescue StandardError => e
90
90
  Legion::Logging.warn("CostTracker settings unavailable: #{e.message}") if defined?(Legion::Logging)
@@ -15,14 +15,14 @@ module Legion
15
15
  rand < rate
16
16
  end
17
17
 
18
- def evaluate(primary_response:, messages: nil, shadow_model: nil) # rubocop:disable Lint/UnusedMethodArgument
18
+ def evaluate(primary_response:, messages: nil, shadow_model: nil)
19
19
  shadow_model ||= Legion::Settings.dig(:llm, :shadow, :model) || 'gpt-4o-mini'
20
20
  Legion::Logging.debug("ShadowEval triggered primary_model=#{primary_response[:model]} shadow_model=#{shadow_model}") if defined?(Legion::Logging)
21
21
 
22
22
  shadow_response = Legion::LLM.send(:chat_single,
23
23
  model: shadow_model, provider: nil,
24
- intent: nil, tier: nil,
25
- skip_shadow: true)
24
+ messages: messages, intent: nil,
25
+ tier: nil)
26
26
 
27
27
  comparison = compare(primary_response, shadow_response, shadow_model)
28
28
  Legion::Events.emit('llm.shadow_eval', comparison) if defined?(Legion::Events)
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module LLM
5
+ module ToolRegistry
6
+ @tools = []
7
+ @mutex = Mutex.new
8
+
9
+ class << self
10
+ def register(tool_class)
11
+ @mutex.synchronize do
12
+ @tools << tool_class unless @tools.include?(tool_class)
13
+ end
14
+ end
15
+
16
+ def tools
17
+ @mutex.synchronize { @tools.dup }
18
+ end
19
+
20
+ def clear
21
+ @mutex.synchronize { @tools.clear }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module LLM
5
- VERSION = '0.3.20'
5
+ VERSION = '0.3.21'
6
6
  end
7
7
  end
data/lib/legion/llm.rb CHANGED
@@ -17,6 +17,7 @@ require_relative 'llm/batch'
17
17
  require_relative 'llm/scheduling'
18
18
  require_relative 'llm/off_peak'
19
19
  require_relative 'llm/cost_tracker'
20
+ require_relative 'llm/tool_registry'
20
21
 
21
22
  begin
22
23
  require 'legion/extensions/llm/gateway'
@@ -119,6 +120,9 @@ module Legion
119
120
  end
120
121
  end
121
122
 
123
+ if defined?(Legion::Logging)
124
+ Legion::Logging.debug "[LLM] chat_direct escalate=#{escalate} message_present=#{!message.nil?} model=#{model} provider=#{provider}"
125
+ end
122
126
  result = if escalate && message
123
127
  chat_with_escalation(
124
128
  model: model, provider: provider, intent: intent, tier: tier,
@@ -129,6 +133,7 @@ module Legion
129
133
  chat_single(model: model, provider: provider, intent: intent, tier: tier,
130
134
  temperature: temperature, message: message, **kwargs)
131
135
  end
136
+ Legion::Logging.debug "[LLM] chat_direct result_class=#{result.class} result_nil=#{result.nil?}" if defined?(Legion::Logging)
132
137
 
133
138
  if cache_key && result.is_a?(Hash)
134
139
  ttl = settings.dig(:prompt_caching, :response_cache, :ttl_seconds) || Cache::DEFAULT_TTL
@@ -283,7 +288,11 @@ module Legion
283
288
  Legion::Extensions::LLM::Gateway::Runners::Inference.chat(**)
284
289
  end
285
290
 
286
- def chat_single(model:, provider:, intent:, tier:, message: nil, **kwargs)
291
+ def chat_single(model:, provider:, intent:, tier:, message: nil, **kwargs) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
292
+ explicit_tools = kwargs.delete(:tools)
293
+ tools = explicit_tools || ToolRegistry.tools
294
+ tools = nil if tools.empty?
295
+
287
296
  if (intent || tier) && Router.routing_enabled?
288
297
  resolution = Router.resolve(intent: intent, tier: tier, model: model, provider: provider)
289
298
  if resolution
@@ -307,10 +316,17 @@ module Legion
307
316
 
308
317
  inject_anthropic_cache_control!(opts, provider)
309
318
 
319
+ if defined?(Legion::Logging)
320
+ Legion::Logging.debug "[LLM] chat_single model=#{opts[:model]} provider=#{opts[:provider]} message_present=#{!message.nil?} tools=#{tools&.size || 0}"
321
+ end
310
322
  session = RubyLLM.chat(**opts)
323
+ tools&.each { |tool| session.with_tool(tool) }
311
324
  return session unless message
312
325
 
313
- session.ask(message)
326
+ Legion::Logging.debug '[LLM] chat_single calling session.ask' if defined?(Legion::Logging)
327
+ response = session.ask(message)
328
+ Legion::Logging.debug "[LLM] chat_single response_class=#{response.class} response_nil=#{response.nil?}" if defined?(Legion::Logging)
329
+ response
314
330
  end
315
331
 
316
332
  def chat_with_escalation(model:, provider:, intent:, tier:, max_escalations:, quality_check:, message:, **kwargs)
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.20
4
+ version: 0.3.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -161,6 +161,7 @@ files:
161
161
  - lib/legion/llm/settings.rb
162
162
  - lib/legion/llm/shadow_eval.rb
163
163
  - lib/legion/llm/structured_output.rb
164
+ - lib/legion/llm/tool_registry.rb
164
165
  - lib/legion/llm/transport/exchanges/escalation.rb
165
166
  - lib/legion/llm/transport/messages/escalation_event.rb
166
167
  - lib/legion/llm/version.rb