lex-llm-gemini 0.3.9 → 0.3.13

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: ff2a80df83dbda542e9400aeacc0451a3d1d2b2952e0bd37baab899e81628ed2
4
- data.tar.gz: ed6ebc89bccce9798b540bd977f99dd73cd31b51b104f5047e16ce9d322a41ca
3
+ metadata.gz: d81052fa4455e1c73b7567864422ae494442f0fce0d9206fb0634bc238c939eb
4
+ data.tar.gz: 9f56e59676cc93aa0b55e4d8958975ce3e0a2c02308daaaeecb40c46c8535564
5
5
  SHA512:
6
- metadata.gz: 9aed6d6d7b58f6dcd45d565106589b656b3e04cc99bc9ab74311a53bb3b8495e3bb587bc7e35cf7278e8a06ccd54fdc4b351d681cf08c903687ba6959b1c41ab
7
- data.tar.gz: 50dd084cad1e94c3e5a16083b6b5a5f3b21080addf3f81a25bc5335163694f994baf075619878de9f87c978fde06a0b3a266d4fdcd11d1ce61eafdee936af1d2
6
+ metadata.gz: 0fe88e33de1fa4575867f35ffe717528a3759aa1b0e3c15fe42a9cfe99ff8e00826b0a8069f1d4788091b17448c307cf8e4ad694684c166249b9a57360c8dccc
7
+ data.tar.gz: 999f873f14e211211232b72ba3f5d5dd6dd31560de9e44081a1e2355c6574baa7a4a169701492a00803fcb9fb1bc6b9d73de37f399cb2439505a7c3c970717a7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.13 - 2026-06-16
4
+
5
+ - Dependency updates, code quality improvements.
6
+
7
+ ## 0.3.12 - 2026-06-15
8
+
9
+ - **CapabilityPolicy integration** — `supportedGenerationMethods` mapped to `:model_metadata`. Settings overrides at provider/instance/model level supported.
10
+
11
+ ## 0.3.11 - 2026-06-13
12
+
13
+ - **Gemfile cleanup** — Remove local path overrides; dependencies resolve from gemspec via rubygems.
14
+ - **Dependency bump** — Require `lex-llm >= 0.5.0` for canonical types support.
15
+ - **Canonical tool support** — Use `ToolSchema.extract` and add `:tools` capability.
16
+ - **Bug fix** — Handle Array tool_calls in `tool_call_parts`.
17
+ - 22 examples, 0 failures; 13 files, 0 rubocop offenses.
18
+
19
+ ## 0.3.10 - 2026-06-02
20
+
21
+ - Add per-provider scoped discovery refresh actor
22
+
3
23
  ## 0.3.9 - 2026-05-21
4
24
 
5
25
  - api_base reads from settings[:endpoint] fallback
data/Gemfile CHANGED
@@ -2,13 +2,6 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- group :test do
6
- llm_base_path = ENV.fetch('LEX_LLM_PATH', File.expand_path('../lex-llm', __dir__))
7
- transport_path = ENV.fetch('LEGION_TRANSPORT_PATH', File.expand_path('../../legion-transport', __dir__))
8
- gem 'legion-transport', path: transport_path if File.directory?(transport_path)
9
- gem 'lex-llm', path: llm_base_path if File.directory?(llm_base_path)
10
- end
11
-
12
5
  gemspec
13
6
 
14
7
  group :development do
@@ -27,5 +27,5 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency 'legion-logging', '>= 1.3.2'
28
28
  spec.add_dependency 'legion-settings', '>= 1.3.14'
29
29
  spec.add_dependency 'legion-transport', '>= 1.4.14'
30
- spec.add_dependency 'lex-llm', '>= 0.4.3'
30
+ spec.add_dependency 'lex-llm', '>= 0.5.0'
31
31
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'legion/extensions/actors/every'
5
+ rescue LoadError => e
6
+ warn(e.message) if $VERBOSE
7
+ end
8
+
9
+ return unless defined?(Legion::Extensions::Actors::Every)
10
+
11
+ module Legion
12
+ module Extensions
13
+ module Llm
14
+ module Gemini
15
+ module Actor
16
+ class DiscoveryRefresh < Legion::Extensions::Actors::Every # rubocop:disable Style/Documentation
17
+ include Legion::Logging::Helper
18
+
19
+ REFRESH_INTERVAL = 1800
20
+
21
+ def runner_class = self.class
22
+ def runner_function = 'manual'
23
+ def run_now? = true
24
+ def use_runner? = false
25
+ def check_subtask? = false
26
+ def generate_task? = false
27
+
28
+ def time
29
+ return REFRESH_INTERVAL unless defined?(Legion::Settings)
30
+
31
+ Legion::Settings.dig(:extensions, :llm, :gemini, :discovery_interval) || REFRESH_INTERVAL
32
+ end
33
+
34
+ def manual
35
+ log.debug('[gemini][discovery_refresh] refreshing model list')
36
+ return unless defined?(Legion::LLM::Discovery)
37
+
38
+ Legion::LLM::Discovery.refresh_discovered_models!(provider: :gemini)
39
+
40
+ if defined?(Legion::LLM::Router) && Legion::LLM::Router.respond_to?(:populate_auto_rules)
41
+ Legion::LLM::Router.populate_auto_rules(Legion::LLM::Discovery.discovered_instances)
42
+ end
43
+ if defined?(Legion::LLM::Inventory) && Legion::LLM::Inventory.respond_to?(:invalidate_offerings_cache!)
44
+ Legion::LLM::Inventory.invalidate_offerings_cache!
45
+ end
46
+ rescue StandardError => e
47
+ handle_exception(e, level: :warn, handled: true, operation: 'gemini.actor.discovery_refresh')
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -187,7 +187,9 @@ module Legion
187
187
  end
188
188
 
189
189
  def tool_call_parts(message)
190
- message.tool_calls.values.map do |tool_call|
190
+ # Array is canonical (name-keyed hashes dropped parallel same-name calls)
191
+ calls = message.tool_calls.is_a?(Hash) ? message.tool_calls.values : Array(message.tool_calls)
192
+ calls.map do |tool_call|
191
193
  { functionCall: { name: tool_call.name, args: tool_call.arguments } }
192
194
  end
193
195
  end
@@ -204,9 +206,12 @@ module Legion
204
206
  def format_tools(tools)
205
207
  [{
206
208
  functionDeclarations: tools.values.map do |tool|
207
- declaration = { name: tool.name, description: tool.description }
208
- declaration[:parameters] = tool.params_schema if tool.params_schema
209
- declaration
209
+ schema = Legion::Extensions::Llm::Canonical::ToolSchema.extract(tool)
210
+ {
211
+ name: Legion::Extensions::Llm::Canonical::ToolSchema.tool_name(tool),
212
+ description: Legion::Extensions::Llm::Canonical::ToolSchema.tool_description(tool),
213
+ parameters: schema
214
+ }
210
215
  end
211
216
  }]
212
217
  end
@@ -307,6 +312,95 @@ module Legion
307
312
  end
308
313
  end
309
314
 
315
+ # -- policy resolution requires many sources
316
+ def offering_from_model(model, health: {})
317
+ policy = Legion::Extensions::Llm::CapabilityPolicy.resolve(
318
+ real: real_capabilities_from(model),
319
+ provider_catalog: {},
320
+ probe: {},
321
+ provider_envelope: {},
322
+ provider_config: provider_capability_config,
323
+ instance_config: instance_capability_config,
324
+ model_config: model_capability_config(model.id)
325
+ )
326
+
327
+ build_model_offering(model, policy, health)
328
+ end
329
+
330
+ def build_model_offering(model, policy, health)
331
+ Routing::ModelOffering.new(
332
+ provider_family: slug.to_sym,
333
+ provider_instance: model.instance || provider_instance_id,
334
+ transport: offering_transport,
335
+ tier: offering_tier,
336
+ model: model.id,
337
+ canonical_model_alias: model.name,
338
+ model_family: model.family,
339
+ usage_type: offering_usage_type(model),
340
+ capabilities: policy[:capabilities],
341
+ capability_sources: policy[:sources],
342
+ limits: offering_limits(model),
343
+ health:,
344
+ metadata: offering_metadata(model)
345
+ )
346
+ end
347
+
348
+ def real_capabilities_from(model)
349
+ meta = model.respond_to?(:metadata) ? (model.metadata || {}) : {}
350
+ methods = Array(meta[:supported_generation_methods] || meta['supported_generation_methods'])
351
+ {
352
+ streaming: methods.include?('streamGenerateContent'),
353
+ embeddings: methods.include?('embedContent'),
354
+ vision: Capabilities.vision?(meta.merge('name' => "models/#{model.id}"))
355
+ }.compact
356
+ end
357
+
358
+ def provider_capability_config
359
+ return {} unless defined?(Legion::Extensions::Llm::CredentialSources)
360
+
361
+ conf = Legion::Extensions::Llm::CredentialSources.setting(:extensions, :llm, :gemini)
362
+ conf.is_a?(Hash) ? conf.to_h.except(:instances, 'instances') : {}
363
+ rescue StandardError => e
364
+ handle_exception(e, level: :debug, handled: true, operation: 'gemini.provider_capability_config')
365
+ {}
366
+ end
367
+
368
+ def instance_capability_config
369
+ cfg = config
370
+ result = {}
371
+ %i[capabilities enable_streaming enable_tools enable_thinking enable_vision enable_embeddings
372
+ thinking_flag tools_flag streaming_flag vision_flag embedding_flag embeddings_flag
373
+ tool_flag images_flag image_flag].each do |key|
374
+ next unless cfg.respond_to?(key)
375
+
376
+ val = cfg.send(key)
377
+ result[key] = val unless val.nil?
378
+ rescue StandardError
379
+ next
380
+ end
381
+ result
382
+ end
383
+
384
+ def model_capability_config(model_id)
385
+ models_conf = resolve_models_config
386
+ return {} unless models_conf.respond_to?(:to_h)
387
+
388
+ hash = models_conf.to_h
389
+ hash[model_id.to_s] || hash[model_id.to_sym] || {}
390
+ rescue StandardError => e
391
+ handle_exception(e, level: :debug, handled: true, operation: 'gemini.model_capability_config')
392
+ {}
393
+ end
394
+
395
+ def resolve_models_config
396
+ return config.models if config.respond_to?(:models)
397
+ return config[:models] if config.respond_to?(:[])
398
+
399
+ nil
400
+ rescue StandardError
401
+ nil
402
+ end
403
+
310
404
  def modalities_for(methods)
311
405
  return [%w[text], %w[embeddings]] if methods.include?('embedContent')
312
406
 
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Gemini
7
- VERSION = '0.3.9'
7
+ VERSION = '0.3.13'
8
8
  end
9
9
  end
10
10
  end
@@ -3,6 +3,7 @@
3
3
  require 'legion/extensions/llm'
4
4
  require 'legion/extensions/llm/gemini/provider'
5
5
  require 'legion/extensions/llm/gemini/version'
6
+ require_relative 'gemini/actors/discovery_refresh'
6
7
 
7
8
  module Legion
8
9
  module Extensions
@@ -30,10 +31,7 @@ module Legion
30
31
  fleet: {
31
32
  enabled: false,
32
33
  respond_to_requests: false,
33
- capabilities: %i[chat stream_chat embed],
34
- lanes: [],
35
- concurrency: 4,
36
- queue_suffix: nil
34
+ capabilities: %i[chat stream_chat embed tools]
37
35
  }
38
36
  }
39
37
  )
@@ -106,8 +104,7 @@ module Legion
106
104
  :add_settings_api_key, :add_settings_instances, :normalize_instance_config,
107
105
  :sanitize_instance_config
108
106
 
109
- Legion::Extensions::Llm::Configuration.register_provider_options(Provider.configuration_options) if
110
- Legion::Extensions::Llm::Configuration.respond_to?(:register_provider_options)
107
+ Legion::Extensions::Llm::Configuration.register_provider_options(Provider.configuration_options)
111
108
  end
112
109
  end
113
110
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-gemini
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.9
4
+ version: 0.3.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -71,14 +71,14 @@ dependencies:
71
71
  requirements:
72
72
  - - ">="
73
73
  - !ruby/object:Gem::Version
74
- version: 0.4.3
74
+ version: 0.5.0
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - ">="
80
80
  - !ruby/object:Gem::Version
81
- version: 0.4.3
81
+ version: 0.5.0
82
82
  description: Gemini provider integration for the LegionIO LLM routing framework.
83
83
  email:
84
84
  - matthewdiverson@gmail.com
@@ -97,6 +97,7 @@ files:
97
97
  - README.md
98
98
  - lex-llm-gemini.gemspec
99
99
  - lib/legion/extensions/llm/gemini.rb
100
+ - lib/legion/extensions/llm/gemini/actors/discovery_refresh.rb
100
101
  - lib/legion/extensions/llm/gemini/actors/fleet_worker.rb
101
102
  - lib/legion/extensions/llm/gemini/provider.rb
102
103
  - lib/legion/extensions/llm/gemini/runners/fleet_worker.rb