lex-llm-gemini 0.3.13 → 0.3.18
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 +4 -4
- data/CHANGELOG.md +30 -0
- data/lex-llm-gemini.gemspec +1 -1
- data/lib/legion/extensions/llm/gemini/actors/discovery_refresh.rb +136 -13
- data/lib/legion/extensions/llm/gemini/provider.rb +11 -18
- data/lib/legion/extensions/llm/gemini/version.rb +1 -1
- data/lib/legion/extensions/llm/gemini.rb +3 -2
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 564af323ba9c521e6404595eb47f3c1470da8459aec2790c42f51d004e3c2394
|
|
4
|
+
data.tar.gz: 4a7bce02b3c4399425ed0749ca5fb580e67d445af04c85480c7b1c0d33a01782
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 62157a365a6a265791ae7ec076332976241b3137076e5b8fa5f389625b035679ac38afa9ff16324b578f1ab2d9cd35e33b30564eb37a7870eda05dee66ae40cf
|
|
7
|
+
data.tar.gz: 98c631924f43613cf28f034a972ef8b2326d88286364768094640080a68e5daf73bdd945ce60867cce6f58f75d6640fc65c0235738c3e7a3d7dd374602f0069f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.18] - 2026-08-04
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Prepare the Gemini provider standardization baseline for a patch release.
|
|
7
|
+
|
|
8
|
+
## [0.3.17] - 2026-07-02
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Emit canonical capability vocabulary (`:embedding`, `:tools`) from `Capabilities.critical_capabilities_for`. `lex-llm` 0.6.1+ retains both the raw and canonical forms on `Model::Info`, so the pre-canonical aliases (`embeddings`, `function_calling`) leaked duplicate tokens (e.g. `[:embeddings, :embedding]`) into discovered model capabilities.
|
|
12
|
+
|
|
13
|
+
## [0.3.16] - 2026-06-20
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Stub shared registry publishing through `RegistryPublisher#schedule` in specs so async availability-event coverage stays stable after the shared publisher moved off raw `Thread.new`.
|
|
17
|
+
|
|
18
|
+
## [0.3.15] - 2026-06-20
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- Align Gemini instance discovery with the shared `lex-llm` contract by preserving explicit tier overrides while defaulting unconfigured instances to `:cloud`.
|
|
22
|
+
- Expand instance-level capability override passthrough to the shared `enable_*` vocabulary, including `structured_output`.
|
|
23
|
+
- Normalize Gemini contract coverage around canonical `:tools` and `:embedding` capabilities.
|
|
24
|
+
|
|
25
|
+
## [0.3.14] - 2026-06-19
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- Adopt `Legion::Extensions::Llm::Inventory::ScopedRefresher` mixin (lex-llm 0.6.0). Discovery
|
|
29
|
+
refresh actors now write directly to the live `Inventory` catalog via `Inventory.write_lane`.
|
|
30
|
+
- Pin `lex-llm >= 0.6.0` and `legion-llm >= 0.14.0` in gemspec.
|
|
31
|
+
- Standard `weight: 100` default added to provider instance settings schema.
|
|
32
|
+
|
|
3
33
|
## 0.3.13 - 2026-06-16
|
|
4
34
|
|
|
5
35
|
- Dependency updates, code quality improvements.
|
data/lex-llm-gemini.gemspec
CHANGED
|
@@ -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.
|
|
30
|
+
spec.add_dependency 'lex-llm', '>= 0.6.0'
|
|
31
31
|
end
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'digest'
|
|
4
|
+
|
|
3
5
|
begin
|
|
4
6
|
require 'legion/extensions/actors/every'
|
|
5
7
|
rescue LoadError => e
|
|
6
8
|
warn(e.message) if $VERBOSE
|
|
7
9
|
end
|
|
8
10
|
|
|
11
|
+
begin
|
|
12
|
+
require 'legion/extensions/llm/inventory/scoped_refresher'
|
|
13
|
+
rescue LoadError => e
|
|
14
|
+
warn(e.message) if $VERBOSE
|
|
15
|
+
end
|
|
16
|
+
|
|
9
17
|
return unless defined?(Legion::Extensions::Actors::Every)
|
|
10
18
|
|
|
11
19
|
module Legion
|
|
@@ -13,10 +21,14 @@ module Legion
|
|
|
13
21
|
module Llm
|
|
14
22
|
module Gemini
|
|
15
23
|
module Actor
|
|
16
|
-
class DiscoveryRefresh < Legion::Extensions::Actors::Every # rubocop:disable Style/Documentation
|
|
24
|
+
class DiscoveryRefresh < Legion::Extensions::Actors::Every # rubocop:disable Style/Documentation,Metrics/ClassLength
|
|
17
25
|
include Legion::Logging::Helper
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
if defined?(Legion::Extensions::Llm::Inventory::ScopedRefresher)
|
|
28
|
+
include Legion::Extensions::Llm::Inventory::ScopedRefresher
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.every_seconds = 3600
|
|
20
32
|
|
|
21
33
|
def runner_class = self.class
|
|
22
34
|
def runner_function = 'manual'
|
|
@@ -26,26 +38,137 @@ module Legion
|
|
|
26
38
|
def generate_task? = false
|
|
27
39
|
|
|
28
40
|
def time
|
|
29
|
-
return
|
|
41
|
+
return self.class.every_seconds unless defined?(Legion::Settings)
|
|
30
42
|
|
|
31
|
-
Legion::Settings.dig(:extensions, :llm, :gemini, :discovery_interval) ||
|
|
43
|
+
Legion::Settings.dig(:extensions, :llm, :gemini, :discovery_interval) || self.class.every_seconds
|
|
32
44
|
end
|
|
33
45
|
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
def scope_key(**)
|
|
47
|
+
{ provider: :gemini }
|
|
48
|
+
end
|
|
37
49
|
|
|
38
|
-
|
|
50
|
+
def compute_lanes_for_scope(**)
|
|
51
|
+
return [] unless defined?(Legion::LLM::Call::Registry)
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
lanes = []
|
|
54
|
+
gemini_instances.each do |instance|
|
|
55
|
+
collect_lanes_for_instance(instance, lanes)
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
handle_exception(e, level: :warn, handled: true,
|
|
58
|
+
operation: 'gemini.discovery_refresh.compute_lanes',
|
|
59
|
+
instance: instance[:id])
|
|
45
60
|
end
|
|
61
|
+
lanes
|
|
62
|
+
rescue StandardError => e
|
|
63
|
+
handle_exception(e, level: :warn, handled: true,
|
|
64
|
+
operation: 'gemini.discovery_refresh.compute_lanes_for_scope')
|
|
65
|
+
[]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def credential_hash(**)
|
|
69
|
+
settings = gemini_settings
|
|
70
|
+
Digest::SHA256.hexdigest(settings[:api_key].to_s + settings[:instances].to_s)[0, 16]
|
|
71
|
+
rescue StandardError => e
|
|
72
|
+
handle_exception(e, level: :warn, handled: true, operation: 'gemini.discovery_refresh.credential_hash')
|
|
73
|
+
'unknown'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def manual(**)
|
|
77
|
+
tick if defined?(Legion::Extensions::Llm::Inventory::ScopedRefresher) &&
|
|
78
|
+
respond_to?(:tick, true)
|
|
46
79
|
rescue StandardError => e
|
|
47
80
|
handle_exception(e, level: :warn, handled: true, operation: 'gemini.actor.discovery_refresh')
|
|
48
81
|
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def gemini_instances
|
|
86
|
+
Legion::LLM::Call::Registry.all_instances.select do |e|
|
|
87
|
+
(e[:provider] || '').to_sym == :gemini
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def collect_lanes_for_instance(instance, lanes)
|
|
92
|
+
adapter = instance[:adapter]
|
|
93
|
+
return unless adapter.respond_to?(:discover_offerings)
|
|
94
|
+
|
|
95
|
+
Array(adapter.discover_offerings(live: false)).each do |raw_offering|
|
|
96
|
+
offering = offering_to_hash(raw_offering)
|
|
97
|
+
next unless offering
|
|
98
|
+
|
|
99
|
+
model = offering[:model] || offering['model']
|
|
100
|
+
next unless model
|
|
101
|
+
|
|
102
|
+
lane = build_lane(offering, instance)
|
|
103
|
+
lanes << lane
|
|
104
|
+
lanes << fleet_lane(lane, instance) if emit_fleet_lane?(lane)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def offering_to_hash(offering)
|
|
109
|
+
return nil if offering.nil?
|
|
110
|
+
return offering if offering.is_a?(Hash)
|
|
111
|
+
|
|
112
|
+
hash = offering.to_h
|
|
113
|
+
hash[:type] ||= hash[:usage_type]
|
|
114
|
+
hash[:enabled] = offering.respond_to?(:enabled?) ? offering.enabled? : true
|
|
115
|
+
hash
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def build_lane(offering, instance) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
119
|
+
instance_id = instance[:instance] || instance[:instance_id] || instance[:id]
|
|
120
|
+
raw_tier = offering[:tier] || :frontier
|
|
121
|
+
offer_type = offering[:type]
|
|
122
|
+
type = %i[embed embedding].include?(offer_type) ? :embedding : :inference
|
|
123
|
+
capabilities = normalize_capabilities(offering[:capabilities] || [])
|
|
124
|
+
model = offering[:model] || offering['model']
|
|
125
|
+
|
|
126
|
+
lane_id = Legion::Extensions::Llm::Inventory::ScopedRefresher.compose_id(
|
|
127
|
+
tier: raw_tier, provider_family: :gemini,
|
|
128
|
+
instance_id: instance_id, type: type, model: model
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
{
|
|
132
|
+
id: lane_id,
|
|
133
|
+
tier: raw_tier,
|
|
134
|
+
provider_family: :gemini,
|
|
135
|
+
instance_id: instance_id,
|
|
136
|
+
model: model,
|
|
137
|
+
canonical_model_alias: offering[:canonical_model_alias] || offering[:name],
|
|
138
|
+
type: type,
|
|
139
|
+
capabilities: capabilities,
|
|
140
|
+
limits: offering[:limits] || {},
|
|
141
|
+
enabled: offering.fetch(:enabled, true),
|
|
142
|
+
cost: offering[:cost] || {}
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def emit_fleet_lane?(lane)
|
|
147
|
+
return false unless lane[:type] == :inference
|
|
148
|
+
|
|
149
|
+
gemini_settings&.dig(:fleet, :dispatch, :enabled)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def fleet_lane(lane, instance)
|
|
153
|
+
fleet_id = Legion::Extensions::Llm::Inventory::ScopedRefresher.compose_id(
|
|
154
|
+
tier: :fleet, provider_family: :gemini,
|
|
155
|
+
instance_id: instance[:instance] || instance[:instance_id] || instance[:id],
|
|
156
|
+
type: lane[:type], model: lane[:model]
|
|
157
|
+
)
|
|
158
|
+
lane.merge(id: fleet_id, tier: :fleet)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def normalize_capabilities(caps)
|
|
162
|
+
return [] unless defined?(Legion::Extensions::Llm::Inventory::Capabilities)
|
|
163
|
+
|
|
164
|
+
Legion::Extensions::Llm::Inventory::Capabilities.normalize(caps)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def gemini_settings
|
|
168
|
+
Legion::Settings.dig(:extensions, :llm, :gemini)
|
|
169
|
+
rescue StandardError
|
|
170
|
+
{}
|
|
171
|
+
end
|
|
49
172
|
end
|
|
50
173
|
end
|
|
51
174
|
end
|
|
@@ -33,11 +33,14 @@ module Legion
|
|
|
33
33
|
def vision?(model) = chat?(model) && model_id(model).match?(/gemini|flash|pro/)
|
|
34
34
|
def functions?(model) = chat?(model)
|
|
35
35
|
|
|
36
|
+
# Emit canonical capability vocabulary (see Legion::Extensions::Llm::Capabilities::CANONICAL).
|
|
37
|
+
# Model::Info retains both raw and canonical forms, so aliases like :embeddings/:function_calling
|
|
38
|
+
# would otherwise leak duplicate tokens into the capability list.
|
|
36
39
|
def critical_capabilities_for(model)
|
|
37
40
|
[
|
|
38
41
|
('streaming' if streaming?(model)),
|
|
39
|
-
('
|
|
40
|
-
('
|
|
42
|
+
('embedding' if embeddings?(model)),
|
|
43
|
+
('tools' if functions?(model)),
|
|
41
44
|
('vision' if vision?(model))
|
|
42
45
|
].compact
|
|
43
46
|
end
|
|
@@ -350,7 +353,7 @@ module Legion
|
|
|
350
353
|
methods = Array(meta[:supported_generation_methods] || meta['supported_generation_methods'])
|
|
351
354
|
{
|
|
352
355
|
streaming: methods.include?('streamGenerateContent'),
|
|
353
|
-
|
|
356
|
+
embedding: methods.include?('embedContent'),
|
|
354
357
|
vision: Capabilities.vision?(meta.merge('name' => "models/#{model.id}"))
|
|
355
358
|
}.compact
|
|
356
359
|
end
|
|
@@ -361,24 +364,14 @@ module Legion
|
|
|
361
364
|
conf = Legion::Extensions::Llm::CredentialSources.setting(:extensions, :llm, :gemini)
|
|
362
365
|
conf.is_a?(Hash) ? conf.to_h.except(:instances, 'instances') : {}
|
|
363
366
|
rescue StandardError => e
|
|
364
|
-
handle_exception(e, level: :
|
|
367
|
+
handle_exception(e, level: :warn, handled: true, operation: 'gemini.provider_capability_config')
|
|
365
368
|
{}
|
|
366
369
|
end
|
|
367
370
|
|
|
368
371
|
def instance_capability_config
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
|
372
|
+
return {} unless config.respond_to?(:to_h)
|
|
373
|
+
|
|
374
|
+
config.to_h.slice(*Legion::Extensions::Llm::Provider::CAPABILITY_CONFIG_KEYS)
|
|
382
375
|
end
|
|
383
376
|
|
|
384
377
|
def model_capability_config(model_id)
|
|
@@ -388,7 +381,7 @@ module Legion
|
|
|
388
381
|
hash = models_conf.to_h
|
|
389
382
|
hash[model_id.to_s] || hash[model_id.to_sym] || {}
|
|
390
383
|
rescue StandardError => e
|
|
391
|
-
handle_exception(e, level: :
|
|
384
|
+
handle_exception(e, level: :warn, handled: true, operation: 'gemini.model_capability_config')
|
|
392
385
|
{}
|
|
393
386
|
end
|
|
394
387
|
|
|
@@ -69,7 +69,7 @@ module Legion
|
|
|
69
69
|
|
|
70
70
|
candidates[:settings] = normalize_instance_config(cfg).merge(api_key: api_key,
|
|
71
71
|
gemini_api_key: api_key,
|
|
72
|
-
tier: :cloud)
|
|
72
|
+
tier: cfg[:tier] || cfg['tier'] || :cloud)
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def self.add_settings_instances(candidates, cfg)
|
|
@@ -83,7 +83,8 @@ module Legion
|
|
|
83
83
|
next unless normalized[:gemini_api_key]
|
|
84
84
|
|
|
85
85
|
normalized[:api_key] = normalized[:gemini_api_key]
|
|
86
|
-
|
|
86
|
+
normalized[:tier] ||= :cloud
|
|
87
|
+
candidates[name.to_sym] = normalized
|
|
87
88
|
end
|
|
88
89
|
end
|
|
89
90
|
|
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.
|
|
4
|
+
version: 0.3.18
|
|
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.
|
|
74
|
+
version: 0.6.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.
|
|
81
|
+
version: 0.6.0
|
|
82
82
|
description: Gemini provider integration for the LegionIO LLM routing framework.
|
|
83
83
|
email:
|
|
84
84
|
- matthewdiverson@gmail.com
|