lex-llm-vertex 0.2.9 → 0.2.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: acd43f914dff394b9635eb7596dd108016764e573fe35a477f267c3ab8465971
4
- data.tar.gz: 2ee16d4671f38bd7c5ce4de50ea6abd3b1ad4609c06fcf559d89e224c36baa77
3
+ metadata.gz: 31112ce3580ae2a99c27285cbc5385d742faf52f4aaa854212802254c423112a
4
+ data.tar.gz: 64ba36b8b97732e2d2b63605e2f7edb33f82b42065f5f79d8c2328d0dd3e3b35
5
5
  SHA512:
6
- metadata.gz: 47e8e573e518356cc1de63b8f0120bd96024d22266a7a0e37ff9d3e8ed5528028f0a30253d7a48a59d881a5043be6b7100becd6d45ffe13815b5fad715d9647a
7
- data.tar.gz: '09d51a90aa46a6595aa611e998f2de12e67d761563c17bab6919fa77ec25c3e7eabf139ae2644915ce0a435e887b217d9c509dcec6f74faf4b75e484300f76e7'
6
+ metadata.gz: 924fe07b104555a416524b8d7e9994e0a09e6051e307034161d485d285cad2bd44c7cc26653420cfee4d629e757b6cdbd02088d2c375e032c437eb660fd7a28d
7
+ data.tar.gz: a1467f3de89eba4f4cc7e852e49f33153fe8caa79878a2da1b57e147a95a50a8122a1b1c60ab018cbb90e12c6b2a7c0b774dc9962f0288815b7e871fae6fdcc6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.13 - 2026-06-16
4
+
5
+ - Dependency updates and code quality improvements.
6
+
7
+ ## 0.2.12 - 2026-06-15
8
+
9
+ - **CapabilityPolicy integration** — Model-family heuristics tagged as `:provider_catalog`; Vertex features as `:model_metadata`. Settings overrides at provider/instance/model level supported.
10
+
11
+ ## 0.2.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
+ - 29 examples, 0 failures; 13 files, 0 rubocop offenses.
18
+
19
+ ## 0.2.10 - 2026-06-02
20
+
21
+ - Add per-provider scoped discovery refresh actor
22
+
3
23
  ## 0.2.9 - 2026-05-21
4
24
 
5
25
  - Add `default_transport`/`default_tier` class declarations, remove `configured_transport`/`configured_tier`
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 Vertex
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, :vertex, :discovery_interval) || REFRESH_INTERVAL
32
+ end
33
+
34
+ def manual
35
+ log.debug('[vertex][discovery_refresh] refreshing model list')
36
+ return unless defined?(Legion::LLM::Discovery)
37
+
38
+ Legion::LLM::Discovery.refresh_discovered_models!(provider: :vertex)
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: 'vertex.actor.discovery_refresh')
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -327,6 +327,8 @@ module Legion
327
327
 
328
328
  def build_offering(model:, model_family:, usage_type:, publisher:, api:, instance_id: :default,
329
329
  alias_name: nil, metadata: {})
330
+ policy = resolve_capability_policy(model, api:, metadata:, instance_id:)
331
+
330
332
  Legion::Extensions::Llm::Routing::ModelOffering.new(
331
333
  provider_family: :vertex,
332
334
  instance_id: instance_id,
@@ -334,7 +336,8 @@ module Legion
334
336
  tier: offering_tier,
335
337
  model: model,
336
338
  usage_type: usage_type,
337
- capabilities: default_capabilities(model, api:),
339
+ capabilities: base_capabilities(model, api:) + policy[:capabilities],
340
+ capability_sources: policy[:sources],
338
341
  limits: metadata.delete(:limits) || {},
339
342
  metadata: metadata.merge(
340
343
  model_family: model_family,
@@ -478,7 +481,9 @@ module Legion
478
481
  end
479
482
 
480
483
  def tool_call_parts(message)
481
- message.tool_calls.values.map do |tool_call|
484
+ # Array is canonical (name-keyed hashes dropped parallel same-name calls)
485
+ calls = message.tool_calls.is_a?(Hash) ? message.tool_calls.values : Array(message.tool_calls)
486
+ calls.map do |tool_call|
482
487
  { functionCall: { name: tool_call.name, args: tool_call.arguments } }
483
488
  end
484
489
  end
@@ -497,9 +502,11 @@ module Legion
497
502
 
498
503
  [{
499
504
  functionDeclarations: tools.values.map do |tool|
500
- declaration = { name: tool.name, description: tool.description }
501
- declaration[:parameters] = tool.params_schema if tool.respond_to?(:params_schema) && tool.params_schema
502
- declaration
505
+ {
506
+ name: Legion::Extensions::Llm::Canonical::ToolSchema.tool_name(tool),
507
+ description: Legion::Extensions::Llm::Canonical::ToolSchema.tool_description(tool),
508
+ parameters: Legion::Extensions::Llm::Canonical::ToolSchema.extract(tool)
509
+ }
503
510
  end
504
511
  }]
505
512
  end
@@ -623,15 +630,109 @@ module Legion
623
630
  end
624
631
 
625
632
  def default_capabilities(model, api:)
633
+ base_capabilities(model, api:) + policy_optional_capabilities(model, api:)
634
+ end
635
+
636
+ def base_capabilities(model, api:)
626
637
  return %i[embedding] if Capabilities.embeddings?(model)
627
638
 
628
639
  capabilities = %i[chat]
629
640
  capabilities << :streaming if %i[generate_content raw_predict].include?(api)
630
- capabilities << :vision if Capabilities.vision?(model)
631
- capabilities << :functions if generate_content_model?(model)
632
641
  capabilities
633
642
  end
634
643
 
644
+ def policy_optional_capabilities(model, api:)
645
+ return [] if Capabilities.embeddings?(model)
646
+
647
+ caps = []
648
+ caps << :vision if Capabilities.vision?(model)
649
+ caps << :tools if generate_content_model?(model) && api == :generate_content
650
+ caps
651
+ end
652
+
653
+ def resolve_capability_policy(model, api:, metadata:, instance_id:)
654
+ provider_catalog = capability_catalog_for(model, api:)
655
+ real_caps = capability_real_for(metadata)
656
+ provider_cfg = vertex_provider_config
657
+ instance_cfg = vertex_instance_config(instance_id)
658
+ model_cfg = vertex_model_config(model)
659
+
660
+ Legion::Extensions::Llm::CapabilityPolicy.resolve(
661
+ real: real_caps,
662
+ provider_catalog: provider_catalog,
663
+ probe: {},
664
+ provider_envelope: {},
665
+ provider_config: provider_cfg,
666
+ instance_config: instance_cfg,
667
+ model_config: model_cfg
668
+ )
669
+ end
670
+
671
+ def capability_catalog_for(model, api:)
672
+ return {} if Capabilities.embeddings?(model)
673
+
674
+ catalog = {}
675
+ catalog[:vision] = Capabilities.vision?(model)
676
+ catalog[:tools] = api == :generate_content
677
+ catalog[:streaming] = %i[generate_content raw_predict].include?(api)
678
+ catalog
679
+ end
680
+
681
+ def capability_real_for(metadata)
682
+ return {} unless metadata.is_a?(Hash)
683
+
684
+ features = metadata[:supportedFeatures] || metadata['supportedFeatures']
685
+ return {} unless features.is_a?(Hash)
686
+
687
+ real = {}
688
+ real[:tools] = features['functionCalling'] if features.key?('functionCalling')
689
+ real[:vision] = features['multimodalInput'] if features.key?('multimodalInput')
690
+ real[:thinking] = features['thinking'] if features.key?('thinking')
691
+ real
692
+ end
693
+
694
+ def vertex_provider_config
695
+ cfg = CredentialSources.setting(:extensions, :llm, :vertex)
696
+ return {} unless cfg.is_a?(Hash)
697
+
698
+ cfg.except(:instances, 'instances')
699
+ rescue StandardError => e
700
+ handle_exception(e, level: :debug, handled: true, operation: 'vertex.provider.capability_policy_config')
701
+ {}
702
+ end
703
+
704
+ def vertex_instance_config(instance_id)
705
+ cfg = CredentialSources.setting(:extensions, :llm, :vertex)
706
+ return {} unless cfg.is_a?(Hash)
707
+
708
+ instances = cfg[:instances] || cfg['instances']
709
+ return {} unless instances.is_a?(Hash)
710
+
711
+ (instances[instance_id] || instances[instance_id.to_s] || {}).to_h
712
+ rescue StandardError => e
713
+ handle_exception(e, level: :debug, handled: true, operation: 'vertex.provider.instance_config')
714
+ {}
715
+ end
716
+
717
+ def vertex_model_config(model)
718
+ cfg = CredentialSources.setting(:extensions, :llm, :vertex)
719
+ return {} unless cfg.is_a?(Hash)
720
+
721
+ models = cfg[:models] || cfg['models']
722
+ return {} unless models.is_a?(Hash)
723
+
724
+ id = short_model_id(model)
725
+ (models[id.to_sym] || models[id.to_s] || {}).to_h
726
+ rescue StandardError => e
727
+ handle_exception(e, level: :debug, handled: true, operation: 'vertex.provider.model_config')
728
+ {}
729
+ end
730
+
731
+ def short_model_id(model)
732
+ id = model_id(model)
733
+ id.include?('/') ? id.split('/').last : id
734
+ end
735
+
635
736
  def bearer_token
636
737
  token = config.vertex_access_token
637
738
  token ? "Bearer #{token}" : nil
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Vertex
7
- VERSION = '0.2.9'
7
+ VERSION = '0.2.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/vertex/provider'
5
5
  require 'legion/extensions/llm/vertex/version'
6
+ require_relative 'vertex/actors/discovery_refresh'
6
7
 
7
8
  module Legion
8
9
  module Extensions
@@ -39,10 +40,7 @@ module Legion
39
40
  fleet: {
40
41
  enabled: false,
41
42
  respond_to_requests: false,
42
- capabilities: %i[chat stream_chat embed],
43
- lanes: [],
44
- concurrency: 4,
45
- queue_suffix: nil
43
+ capabilities: %i[chat stream_chat embed tools]
46
44
  }
47
45
  }
48
46
  )
@@ -103,12 +101,7 @@ module Legion
103
101
  end
104
102
 
105
103
  def self.register_provider_options
106
- configuration = Legion::Extensions::Llm::Configuration
107
- if configuration.respond_to?(:register_provider_options)
108
- configuration.register_provider_options(Provider.configuration_options)
109
- elsif configuration.respond_to?(:option, true)
110
- Provider.configuration_options.each { |key| configuration.send(:option, key) }
111
- end
104
+ Legion::Extensions::Llm::Configuration.register_provider_options(Provider.configuration_options)
112
105
  end
113
106
 
114
107
  private_class_method :discover_default_instance, :discover_named_instances, :vertex_credentials_present?,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-vertex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.2.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: Google Cloud Vertex AI provider integration for the LegionIO LLM routing
83
83
  framework.
84
84
  email:
@@ -98,6 +98,7 @@ files:
98
98
  - README.md
99
99
  - lex-llm-vertex.gemspec
100
100
  - lib/legion/extensions/llm/vertex.rb
101
+ - lib/legion/extensions/llm/vertex/actors/discovery_refresh.rb
101
102
  - lib/legion/extensions/llm/vertex/actors/fleet_worker.rb
102
103
  - lib/legion/extensions/llm/vertex/provider.rb
103
104
  - lib/legion/extensions/llm/vertex/runners/fleet_worker.rb