lex-llm-gemini 0.3.13 → 0.4.4
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 +137 -0
- data/Gemfile +2 -0
- data/lex-llm-gemini.gemspec +2 -2
- data/lib/legion/extensions/llm/gemini/actors/discovery_refresh.rb +1048 -18
- data/lib/legion/extensions/llm/gemini/actors/fleet_worker.rb +7 -3
- data/lib/legion/extensions/llm/gemini/provider.rb +149 -149
- data/lib/legion/extensions/llm/gemini/runners/fleet_worker.rb +10 -4
- data/lib/legion/extensions/llm/gemini/version.rb +1 -1
- data/lib/legion/extensions/llm/gemini.rb +21 -8
- metadata +5 -5
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
begin
|
|
4
4
|
require 'legion/extensions/actors/subscription'
|
|
5
|
-
rescue LoadError
|
|
6
|
-
|
|
5
|
+
rescue LoadError
|
|
6
|
+
nil
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
unless defined?(Legion::Extensions::Actors::Subscription)
|
|
@@ -11,6 +11,7 @@ unless defined?(Legion::Extensions::Actors::Subscription)
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
require 'legion/extensions/llm/gemini'
|
|
14
|
+
require 'legion/extensions/llm/gemini/runners/fleet_worker'
|
|
14
15
|
require 'legion/extensions/llm/fleet/provider_responder'
|
|
15
16
|
|
|
16
17
|
module Legion
|
|
@@ -20,8 +21,11 @@ module Legion
|
|
|
20
21
|
module Actor
|
|
21
22
|
# Subscription actor for Gemini fleet request consumption.
|
|
22
23
|
class FleetWorker < Legion::Extensions::Actors::Subscription
|
|
24
|
+
# The Subscription dispatch path (use_runner? == false) calls
|
|
25
|
+
# runner_class.send(fn, **message) — a String cannot be send-ed,
|
|
26
|
+
# so the runner must be the resolved module constant.
|
|
23
27
|
def runner_class
|
|
24
|
-
|
|
28
|
+
Legion::Extensions::Llm::Gemini::Runners::FleetWorker
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
def runner_function
|
|
@@ -6,124 +6,26 @@ module Legion
|
|
|
6
6
|
module Extensions
|
|
7
7
|
module Llm
|
|
8
8
|
module Gemini
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
include Legion::Logging::Helper
|
|
12
|
-
|
|
13
|
-
class << self
|
|
14
|
-
attr_writer :registry_publisher
|
|
15
|
-
|
|
16
|
-
def slug = 'gemini'
|
|
17
|
-
def configuration_options = %i[gemini_api_key gemini_api_base]
|
|
18
|
-
def configuration_requirements = %i[gemini_api_key]
|
|
19
|
-
def capabilities = Capabilities
|
|
20
|
-
|
|
21
|
-
def registry_publisher
|
|
22
|
-
@registry_publisher ||= Legion::Extensions::Llm::RegistryPublisher.new(provider_family: :gemini)
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Capability predicates for Gemini API models.
|
|
27
|
-
module Capabilities
|
|
28
|
-
module_function
|
|
29
|
-
|
|
30
|
-
def chat?(model) = supported?(model, 'generateContent')
|
|
31
|
-
def streaming?(model) = supported?(model, 'streamGenerateContent')
|
|
32
|
-
def embeddings?(model) = supported?(model, 'embedContent')
|
|
33
|
-
def vision?(model) = chat?(model) && model_id(model).match?(/gemini|flash|pro/)
|
|
34
|
-
def functions?(model) = chat?(model)
|
|
35
|
-
|
|
36
|
-
def critical_capabilities_for(model)
|
|
37
|
-
[
|
|
38
|
-
('streaming' if streaming?(model)),
|
|
39
|
-
('embeddings' if embeddings?(model)),
|
|
40
|
-
('function_calling' if functions?(model)),
|
|
41
|
-
('vision' if vision?(model))
|
|
42
|
-
].compact
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def supported?(model, action)
|
|
46
|
-
methods = generation_methods(model)
|
|
47
|
-
return model_id(model).include?('embedding') if action == 'embedContent' && methods.empty?
|
|
48
|
-
return true if methods.empty? && action != 'embedContent'
|
|
49
|
-
|
|
50
|
-
methods.include?(action)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def generation_methods(model)
|
|
54
|
-
metadata = metadata_for(model)
|
|
55
|
-
Array(metadata[:supported_generation_methods] || metadata['supported_generation_methods'] ||
|
|
56
|
-
metadata['supportedGenerationMethods'])
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def model_id(model)
|
|
60
|
-
return model.fetch('name', '').delete_prefix('models/') if model.is_a?(Hash)
|
|
61
|
-
|
|
62
|
-
model.respond_to?(:id) ? model.id.to_s : model.to_s
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def metadata_for(model)
|
|
66
|
-
return model if model.is_a?(Hash)
|
|
67
|
-
return model.metadata if model.respond_to?(:metadata)
|
|
68
|
-
|
|
69
|
-
{}
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def settings
|
|
74
|
-
Gemini.default_settings
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def api_base
|
|
78
|
-
config.gemini_api_base || settings[:endpoint] || 'https://generativelanguage.googleapis.com/v1beta'
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def headers
|
|
82
|
-
identity_headers.merge('x-goog-api-key' => config.gemini_api_key)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def completion_url = generate_content_url(model: @model)
|
|
86
|
-
def stream_url = stream_generate_content_url(model: @model)
|
|
87
|
-
def models_url = 'models'
|
|
88
|
-
def embedding_url(model:) = embed_content_url(model:)
|
|
89
|
-
|
|
90
|
-
def generate_content_url(model:)
|
|
91
|
-
"#{model_path(model)}:generateContent"
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def stream_generate_content_url(model:)
|
|
95
|
-
"#{model_path(model)}:streamGenerateContent?alt=sse"
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def embed_content_url(model:)
|
|
99
|
-
"#{model_path(model)}:embedContent"
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def list_models(**)
|
|
103
|
-
log.info { 'listing available Gemini models' }
|
|
104
|
-
super.tap do |models|
|
|
105
|
-
log.info { "discovered #{models.size} Gemini model(s); publishing to registry" }
|
|
106
|
-
self.class.registry_publisher.publish_models_async(models, readiness: readiness(live: false))
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
9
|
+
# Message formatting helpers mixed into Provider.
|
|
10
|
+
module MessageFormatter
|
|
110
11
|
private
|
|
111
12
|
|
|
112
|
-
def
|
|
113
|
-
|
|
114
|
-
|
|
13
|
+
def render_payload(messages, **opts)
|
|
14
|
+
model = opts.fetch(:model)
|
|
15
|
+
tools = opts.fetch(:tools)
|
|
16
|
+
temperature = opts.fetch(:temperature)
|
|
17
|
+
@model = model.id
|
|
18
|
+
build_request_payload(messages: messages, tools: tools, temperature: temperature,
|
|
19
|
+
schema: opts[:schema], tool_prefs: opts[:tool_prefs])
|
|
115
20
|
end
|
|
116
21
|
|
|
117
|
-
|
|
118
|
-
def render_payload(messages, tools:, temperature:, model:, stream:, schema:, thinking:, tool_prefs:)
|
|
119
|
-
@model = model.id
|
|
22
|
+
def build_request_payload(messages:, tools:, temperature:, schema:, tool_prefs:)
|
|
120
23
|
payload = { contents: format_messages(messages), generationConfig: generation_config(temperature, schema) }
|
|
121
24
|
payload[:systemInstruction] = system_instruction(messages)
|
|
122
25
|
payload[:tools] = format_tools(tools) unless tools.empty?
|
|
123
26
|
payload[:toolConfig] = tool_config(tool_prefs) if tool_prefs
|
|
124
27
|
payload.compact
|
|
125
28
|
end
|
|
126
|
-
# rubocop:enable Metrics/ParameterLists,Lint/UnusedMethodArgument
|
|
127
29
|
|
|
128
30
|
def generation_config(temperature, schema)
|
|
129
31
|
{
|
|
@@ -187,7 +89,6 @@ module Legion
|
|
|
187
89
|
end
|
|
188
90
|
|
|
189
91
|
def tool_call_parts(message)
|
|
190
|
-
# Array is canonical (name-keyed hashes dropped parallel same-name calls)
|
|
191
92
|
calls = message.tool_calls.is_a?(Hash) ? message.tool_calls.values : Array(message.tool_calls)
|
|
192
93
|
calls.map do |tool_call|
|
|
193
94
|
{ functionCall: { name: tool_call.name, args: tool_call.arguments } }
|
|
@@ -223,6 +124,16 @@ module Legion
|
|
|
223
124
|
{ functionCallingConfig: { mode: choice.to_s } }
|
|
224
125
|
end
|
|
225
126
|
|
|
127
|
+
def model_path(model)
|
|
128
|
+
value = model.respond_to?(:id) ? model.id : model.to_s
|
|
129
|
+
value.start_with?('models/') ? value : "models/#{value}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Response parsing helpers mixed into Provider.
|
|
134
|
+
module ResponseParser
|
|
135
|
+
private
|
|
136
|
+
|
|
226
137
|
def parse_completion_response(response)
|
|
227
138
|
body = response.body
|
|
228
139
|
parts = response_parts(body)
|
|
@@ -312,7 +223,27 @@ module Legion
|
|
|
312
223
|
end
|
|
313
224
|
end
|
|
314
225
|
|
|
315
|
-
|
|
226
|
+
def render_embedding_payload(text, model:, dimensions:)
|
|
227
|
+
{
|
|
228
|
+
model: model_path(model),
|
|
229
|
+
content: { parts: [{ text: text.to_s }] },
|
|
230
|
+
outputDimensionality: dimensions
|
|
231
|
+
}.compact
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def parse_embedding_response(response, model:, **)
|
|
235
|
+
Legion::Extensions::Llm::Embedding.new(
|
|
236
|
+
vectors: response.body.dig('embedding', 'values'),
|
|
237
|
+
model: model,
|
|
238
|
+
input_tokens: response.body.dig('usageMetadata', 'promptTokenCount').to_i
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Offering and capability building helpers mixed into Provider.
|
|
244
|
+
module OfferingBuilder
|
|
245
|
+
private
|
|
246
|
+
|
|
316
247
|
def offering_from_model(model, health: {})
|
|
317
248
|
policy = Legion::Extensions::Llm::CapabilityPolicy.resolve(
|
|
318
249
|
real: real_capabilities_from(model),
|
|
@@ -350,54 +281,39 @@ module Legion
|
|
|
350
281
|
methods = Array(meta[:supported_generation_methods] || meta['supported_generation_methods'])
|
|
351
282
|
{
|
|
352
283
|
streaming: methods.include?('streamGenerateContent'),
|
|
353
|
-
|
|
354
|
-
vision: Capabilities.vision?(
|
|
284
|
+
embedding: methods.include?('embedContent'),
|
|
285
|
+
vision: Legion::Extensions::Llm::Gemini::Provider::Capabilities.vision?(
|
|
286
|
+
meta.merge('name' => "models/#{model.id}")
|
|
287
|
+
)
|
|
355
288
|
}.compact
|
|
356
289
|
end
|
|
357
290
|
|
|
358
291
|
def provider_capability_config
|
|
359
|
-
return {} unless defined?(Legion::Extensions::Llm::CredentialSources)
|
|
360
|
-
|
|
361
292
|
conf = Legion::Extensions::Llm::CredentialSources.setting(:extensions, :llm, :gemini)
|
|
362
293
|
conf.is_a?(Hash) ? conf.to_h.except(:instances, 'instances') : {}
|
|
363
294
|
rescue StandardError => e
|
|
364
|
-
handle_exception(e, level: :
|
|
295
|
+
handle_exception(e, level: :warn, handled: true, operation: 'gemini.provider_capability_config')
|
|
365
296
|
{}
|
|
366
297
|
end
|
|
367
298
|
|
|
368
299
|
def instance_capability_config
|
|
369
|
-
|
|
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
|
|
300
|
+
config.to_h.slice(*Legion::Extensions::Llm::Provider::CAPABILITY_CONFIG_KEYS)
|
|
382
301
|
end
|
|
383
302
|
|
|
384
303
|
def model_capability_config(model_id)
|
|
385
|
-
|
|
386
|
-
return {} unless
|
|
304
|
+
hash = resolve_models_config
|
|
305
|
+
return {} unless hash.is_a?(Hash)
|
|
387
306
|
|
|
388
|
-
hash = models_conf.to_h
|
|
389
307
|
hash[model_id.to_s] || hash[model_id.to_sym] || {}
|
|
390
308
|
rescue StandardError => e
|
|
391
|
-
handle_exception(e, level: :
|
|
309
|
+
handle_exception(e, level: :warn, handled: true, operation: 'gemini.model_capability_config')
|
|
392
310
|
{}
|
|
393
311
|
end
|
|
394
312
|
|
|
395
313
|
def resolve_models_config
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
nil
|
|
400
|
-
rescue StandardError
|
|
314
|
+
config.to_h[:models]
|
|
315
|
+
rescue StandardError => e
|
|
316
|
+
handle_exception(e, level: :warn, handled: true, operation: 'gemini.resolve_models_config')
|
|
401
317
|
nil
|
|
402
318
|
end
|
|
403
319
|
|
|
@@ -406,21 +322,105 @@ module Legion
|
|
|
406
322
|
|
|
407
323
|
[%w[text image audio video], %w[text]]
|
|
408
324
|
end
|
|
325
|
+
end
|
|
409
326
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
327
|
+
# Gemini provider implementation for the Legion::Extensions::Llm base provider contract.
|
|
328
|
+
class Provider < Legion::Extensions::Llm::Provider
|
|
329
|
+
include Legion::Logging::Helper
|
|
330
|
+
include MessageFormatter
|
|
331
|
+
include ResponseParser
|
|
332
|
+
include OfferingBuilder
|
|
333
|
+
|
|
334
|
+
class << self
|
|
335
|
+
def slug = 'gemini'
|
|
336
|
+
def configuration_options = %i[gemini_api_key gemini_api_base]
|
|
337
|
+
def configuration_requirements = %i[gemini_api_key]
|
|
338
|
+
def capabilities = Capabilities
|
|
416
339
|
end
|
|
417
340
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
)
|
|
341
|
+
# Capability predicates for Gemini API models.
|
|
342
|
+
module Capabilities
|
|
343
|
+
module_function
|
|
344
|
+
|
|
345
|
+
def chat?(model) = supported?(model, 'generateContent')
|
|
346
|
+
def streaming?(model) = supported?(model, 'streamGenerateContent')
|
|
347
|
+
def embeddings?(model) = supported?(model, 'embedContent')
|
|
348
|
+
def vision?(model) = chat?(model) && model_id(model).match?(/gemini|flash|pro/)
|
|
349
|
+
def functions?(model) = chat?(model)
|
|
350
|
+
|
|
351
|
+
def critical_capabilities_for(model)
|
|
352
|
+
[
|
|
353
|
+
('streaming' if streaming?(model)),
|
|
354
|
+
('embedding' if embeddings?(model)),
|
|
355
|
+
('tools' if functions?(model)),
|
|
356
|
+
('vision' if vision?(model))
|
|
357
|
+
].compact
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# When generation methods are absent, treat all capabilities as unknown
|
|
361
|
+
# (unknown = unsupported for selection). Only fall back to name heuristic
|
|
362
|
+
# for embedContent to preserve legacy embedding detection.
|
|
363
|
+
def supported?(model, action)
|
|
364
|
+
methods = generation_methods(model)
|
|
365
|
+
return model_id(model).include?('embedding') if methods.empty?
|
|
366
|
+
|
|
367
|
+
methods.include?(action)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def generation_methods(model)
|
|
371
|
+
metadata = metadata_for(model)
|
|
372
|
+
Array(metadata[:supported_generation_methods] || metadata['supported_generation_methods'] ||
|
|
373
|
+
metadata['supportedGenerationMethods'])
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def model_id(model)
|
|
377
|
+
return model.fetch('name', '').delete_prefix('models/') if model.is_a?(Hash)
|
|
378
|
+
|
|
379
|
+
model.respond_to?(:id) ? model.id.to_s : model.to_s
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def metadata_for(model)
|
|
383
|
+
return model if model.is_a?(Hash)
|
|
384
|
+
return model.metadata if model.respond_to?(:metadata)
|
|
385
|
+
|
|
386
|
+
{}
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def settings
|
|
391
|
+
Gemini.default_settings
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def api_base
|
|
395
|
+
config.gemini_api_base || settings[:instances][:default][:endpoint]
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def headers
|
|
399
|
+
identity_headers.merge('x-goog-api-key' => config.gemini_api_key)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def completion_url = generate_content_url(model: @model)
|
|
403
|
+
def stream_url = stream_generate_content_url(model: @model)
|
|
404
|
+
def models_url = 'models'
|
|
405
|
+
def embedding_url(model:) = embed_content_url(model:)
|
|
406
|
+
|
|
407
|
+
def generate_content_url(model:)
|
|
408
|
+
"#{model_path(model)}:generateContent"
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def stream_generate_content_url(model:)
|
|
412
|
+
"#{model_path(model)}:streamGenerateContent?alt=sse"
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def embed_content_url(model:)
|
|
416
|
+
"#{model_path(model)}:embedContent"
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def list_models(**)
|
|
420
|
+
log.info { 'listing available Gemini models' }
|
|
421
|
+
super.tap do |models|
|
|
422
|
+
log.info { "discovered #{models.size} Gemini model(s)" }
|
|
423
|
+
end
|
|
424
424
|
end
|
|
425
425
|
end
|
|
426
426
|
end
|
|
@@ -9,17 +9,23 @@ module Legion
|
|
|
9
9
|
module Gemini
|
|
10
10
|
module Runners
|
|
11
11
|
# Runner entrypoint for Gemini fleet request execution.
|
|
12
|
+
#
|
|
13
|
+
# The Subscription dispatch path invokes this as
|
|
14
|
+
# runner_class.send(fn, **message) where message is the fleet
|
|
15
|
+
# request envelope merged with transport metadata (routing_key,
|
|
16
|
+
# message_id, headers, ...). The responder parses the envelope out
|
|
17
|
+
# of that hash; unknown keys are inert.
|
|
12
18
|
module FleetWorker
|
|
13
19
|
module_function
|
|
14
20
|
|
|
15
|
-
def handle_fleet_request(
|
|
21
|
+
def handle_fleet_request(**opts)
|
|
16
22
|
Legion::Extensions::Llm::Fleet::ProviderResponder.call(
|
|
17
|
-
payload:
|
|
23
|
+
payload: opts,
|
|
18
24
|
provider_family: Gemini::PROVIDER_FAMILY,
|
|
19
25
|
provider_class: Gemini::Provider,
|
|
20
26
|
provider_instances: -> { Gemini.discover_instances },
|
|
21
|
-
delivery: delivery,
|
|
22
|
-
properties: properties
|
|
27
|
+
delivery: opts[:delivery],
|
|
28
|
+
properties: opts[:properties]
|
|
23
29
|
)
|
|
24
30
|
end
|
|
25
31
|
end
|
|
@@ -3,7 +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
|
-
|
|
6
|
+
require 'legion/extensions/llm/gemini/actors/discovery_refresh'
|
|
7
7
|
|
|
8
8
|
module Legion
|
|
9
9
|
module Extensions
|
|
@@ -11,7 +11,6 @@ module Legion
|
|
|
11
11
|
module Llm
|
|
12
12
|
# Gemini provider extension namespace.
|
|
13
13
|
module Gemini
|
|
14
|
-
extend ::Legion::Extensions::Core if ::Legion::Extensions.const_defined?(:Core, false)
|
|
15
14
|
extend Legion::Logging::Helper
|
|
16
15
|
extend Legion::Extensions::Llm::AutoRegistration
|
|
17
16
|
|
|
@@ -22,7 +21,7 @@ module Legion
|
|
|
22
21
|
family: PROVIDER_FAMILY,
|
|
23
22
|
instance: {
|
|
24
23
|
endpoint: 'https://generativelanguage.googleapis.com/v1beta',
|
|
25
|
-
|
|
24
|
+
discovery_interval: 3600,
|
|
26
25
|
tier: :frontier,
|
|
27
26
|
transport: :http,
|
|
28
27
|
credentials: { api_key: 'env://GEMINI_API_KEY' },
|
|
@@ -69,7 +68,7 @@ module Legion
|
|
|
69
68
|
|
|
70
69
|
candidates[:settings] = normalize_instance_config(cfg).merge(api_key: api_key,
|
|
71
70
|
gemini_api_key: api_key,
|
|
72
|
-
tier: :cloud)
|
|
71
|
+
tier: cfg[:tier] || cfg['tier'] || :cloud)
|
|
73
72
|
end
|
|
74
73
|
|
|
75
74
|
def self.add_settings_instances(candidates, cfg)
|
|
@@ -83,17 +82,30 @@ module Legion
|
|
|
83
82
|
next unless normalized[:gemini_api_key]
|
|
84
83
|
|
|
85
84
|
normalized[:api_key] = normalized[:gemini_api_key]
|
|
86
|
-
|
|
85
|
+
normalized[:tier] ||= :cloud
|
|
86
|
+
candidates[name.to_sym] = normalized
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
def self.normalize_instance_config(config)
|
|
91
|
-
normalized = config
|
|
90
|
+
def self.normalize_instance_config(config)
|
|
91
|
+
normalized = symbolize_config_keys(config)
|
|
92
|
+
promote_api_key(normalized)
|
|
93
|
+
promote_api_base(normalized)
|
|
94
|
+
normalized.compact.except(:instances)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.symbolize_config_keys(config)
|
|
98
|
+
config.to_h.transform_keys { |key| key.respond_to?(:to_sym) ? key.to_sym : key }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.promote_api_key(normalized)
|
|
92
102
|
normalized[:gemini_api_key] ||= normalized.delete(:api_key)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.promote_api_base(normalized)
|
|
93
106
|
normalized[:gemini_api_base] ||= normalized.delete(:base_url)
|
|
94
107
|
normalized[:gemini_api_base] ||= normalized.delete(:api_base)
|
|
95
108
|
normalized[:gemini_api_base] ||= normalized.delete(:endpoint)
|
|
96
|
-
normalized.compact.except(:instances)
|
|
97
109
|
end
|
|
98
110
|
|
|
99
111
|
def self.sanitize_instance_config(config)
|
|
@@ -102,6 +114,7 @@ module Legion
|
|
|
102
114
|
|
|
103
115
|
private_class_method :discover_from_env, :discover_from_settings,
|
|
104
116
|
:add_settings_api_key, :add_settings_instances, :normalize_instance_config,
|
|
117
|
+
:symbolize_config_keys, :promote_api_key, :promote_api_base,
|
|
105
118
|
:sanitize_instance_config
|
|
106
119
|
|
|
107
120
|
Legion::Extensions::Llm::Configuration.register_provider_options(Provider.configuration_options)
|
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.
|
|
4
|
+
version: 0.4.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LegionIO
|
|
@@ -43,14 +43,14 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 1.
|
|
46
|
+
version: 1.4.2
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: 1.
|
|
53
|
+
version: 1.4.2
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: legion-transport
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -71,14 +71,14 @@ dependencies:
|
|
|
71
71
|
requirements:
|
|
72
72
|
- - ">="
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: 0.
|
|
74
|
+
version: 0.7.1
|
|
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.7.1
|
|
82
82
|
description: Gemini provider integration for the LegionIO LLM routing framework.
|
|
83
83
|
email:
|
|
84
84
|
- matthewdiverson@gmail.com
|