lex-llm-gemini 0.1.6 → 0.2.0

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: dfe3fa2fee00d45f7908b00a559606ad3d64b7b7de557da8dec385e7e0e07ada
4
- data.tar.gz: d03f2f8fde3f41aa6273743b738122ee1ea22c03e4a165308526ddd00919ce3d
3
+ metadata.gz: '0788daea481b47eead29eb391fa566a2f02540f1f0f5b4c8aca382b457de8c33'
4
+ data.tar.gz: 356fd313c188e9d585ca9d7a3f9f7fd8b1ab9d687edba232266ea168c306135f
5
5
  SHA512:
6
- metadata.gz: 9d25e055451f57d145b12389d89bc92d4010236151aae33450396d738d959dfcb6950e5f80f39388a02870a71fb16e881164654b8c204be7165da8b22bb58816
7
- data.tar.gz: 35144cf3d1e3ccab244650ce8e7b0d4f929eb94c663db4be9fe5930520f31e789084f8962331653c70c0c2fff6f159bdc1293ebf13519cb8a3c7cbed73325444
6
+ metadata.gz: '09edc833e89707be34a90292622615b2fcd391d19cee2baa73d29ed7a6f12b1d520cee837684f9db28349488e7d54f7ff312414b6e3ede280627da4d821d456e'
7
+ data.tar.gz: 634a5703a52c3d0554a47141265430ea1dbe10b43dfef892923ea4b0b4e7a278a914a76a9c1c109196ea27f09d9c5fb2dabdade8b302aff888185f9731a236b4
data/.rubocop.yml CHANGED
@@ -14,5 +14,7 @@ Metrics/BlockLength:
14
14
  - spec/**/*
15
15
  Metrics/MethodLength:
16
16
  Enabled: false
17
+ RSpec/ExampleLength:
18
+ Enabled: false
17
19
  RSpec/MultipleExpectations:
18
20
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 - 2026-04-30
4
+
5
+ - **Breaking**: Adopt the base contract from lex-llm 0.1.9.
6
+ - Replace `default_settings` with a flat settings hash (enabled, default_model, api_key, whitelist/blacklist, tls, instances).
7
+ - Remove local `RegistryPublisher` and `RegistryEventBuilder`; use the parameterized base classes from lex-llm.
8
+ - Remove local `transport/` directory (exchanges and messages); the shared `llm.registry` transport in lex-llm is used instead.
9
+ - Remove the deprecated `Provider.register` call; configuration options are registered directly.
10
+ - Update `parse_list_models_response` to use the new `Model::Info` constructor (context_length, modalities_input/output, metadata for max_output_tokens).
11
+ - Require `lex-llm >= 0.1.9`.
12
+
13
+ ## 0.1.7 - 2026-04-30
14
+
15
+ - Audit logging, rescue blocks, and README for full observability.
16
+ - Add `Legion::Logging::Helper` to every module and class in lib/.
17
+ - Replace all bare rescue blocks and custom `log_publish_failure` with `handle_exception(e, level:, handled:, operation:)`.
18
+ - Add info-level action logging for model listing and registry publishing.
19
+ - Update README to reflect registry event publishing and observability patterns.
20
+
3
21
  ## 0.1.6 - 2026-04-28
4
22
 
5
23
  - Publish best-effort `llm.registry` discovered-model availability events when transport is already loaded.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # lex-llm-gemini
2
2
 
3
- LegionIO LLM provider extension for Gemini.
3
+ LegionIO LLM provider extension for Google Gemini.
4
4
 
5
5
  This gem lives under `Legion::Extensions::Llm::Gemini` and depends on `lex-llm` for shared provider-neutral routing, fleet, and schema primitives.
6
6
 
@@ -10,11 +10,24 @@ Load it with `require 'legion/extensions/llm/gemini'`.
10
10
 
11
11
  - `Legion::Extensions::Llm::Provider` registration as `:gemini`
12
12
  - Gemini-native chat requests through `POST /v1beta/{model=models/*}:generateContent`
13
- - streaming chat support through `POST /v1beta/{model=models/*}:streamGenerateContent?alt=sse`
14
- - model discovery through `GET /v1beta/models`
15
- - embeddings through `POST /v1beta/{model=models/*}:embedContent`
16
- - normalized chat, streaming, vision, function calling, and embedding capability mapping from `supportedGenerationMethods`
17
- - shared fleet/default settings via `Legion::Extensions::Llm.provider_settings`
13
+ - Streaming chat support through `POST /v1beta/{model=models/*}:streamGenerateContent?alt=sse`
14
+ - Model discovery through `GET /v1beta/models`
15
+ - Embeddings through `POST /v1beta/{model=models/*}:embedContent`
16
+ - Normalized chat, streaming, vision, function calling, and embedding capability mapping from `supportedGenerationMethods`
17
+ - Best-effort `llm.registry` availability events published to AMQP when transport is loaded
18
+ - Shared fleet/default settings via `Legion::Extensions::Llm.provider_settings`
19
+
20
+ ## File Map
21
+
22
+ | Path | Purpose |
23
+ |------|---------|
24
+ | `lib/legion/extensions/llm/gemini.rb` | Entry point; provider registration |
25
+ | `lib/legion/extensions/llm/gemini/provider.rb` | Gemini provider (chat, streaming, models, embeddings) |
26
+ | `lib/legion/extensions/llm/gemini/registry_event_builder.rb` | Builds sanitized lex-llm registry event envelopes |
27
+ | `lib/legion/extensions/llm/gemini/registry_publisher.rb` | Best-effort async publisher for model availability events |
28
+ | `lib/legion/extensions/llm/gemini/transport/exchanges/llm_registry.rb` | `llm.registry` AMQP topic exchange |
29
+ | `lib/legion/extensions/llm/gemini/transport/messages/registry_event.rb` | AMQP message wrapper for registry events |
30
+ | `lib/legion/extensions/llm/gemini/version.rb` | `VERSION` constant |
18
31
 
19
32
  ## Defaults
20
33
 
@@ -45,3 +58,24 @@ Legion::Extensions::Llm.configure do |config|
45
58
  config.default_embedding_model = "gemini-embedding-001"
46
59
  end
47
60
  ```
61
+
62
+ ## Observability
63
+
64
+ Every module and class includes or extends `Legion::Logging::Helper`:
65
+
66
+ - **Info-level logging** on `list_models` and registry event publishing.
67
+ - **All rescue blocks** call `handle_exception(e, level:, handled:, operation:)` for structured exception telemetry.
68
+ - Registry publishing is best-effort; failures are handled at `:debug` or `:warn` level and never block the caller.
69
+
70
+ ## Development
71
+
72
+ ```bash
73
+ bundle install
74
+ bundle exec rspec # 0 failures
75
+ bundle exec rubocop -A # auto-fix
76
+ bundle exec rubocop # lint check
77
+ ```
78
+
79
+ ## License
80
+
81
+ Apache-2.0
@@ -26,5 +26,5 @@ Gem::Specification.new do |spec|
26
26
  spec.add_dependency 'legion-json', '>= 1.2.1'
27
27
  spec.add_dependency 'legion-logging', '>= 1.3.2'
28
28
  spec.add_dependency 'legion-settings', '>= 1.3.14'
29
- spec.add_dependency 'lex-llm', '>= 0.1.5'
29
+ spec.add_dependency 'lex-llm', '>= 0.2.0'
30
30
  end
@@ -8,6 +8,8 @@ module Legion
8
8
  module Gemini
9
9
  # Gemini provider implementation for the Legion::Extensions::Llm base provider contract.
10
10
  class Provider < Legion::Extensions::Llm::Provider # rubocop:disable Metrics/ClassLength
11
+ include Legion::Logging::Helper
12
+
11
13
  class << self
12
14
  attr_writer :registry_publisher
13
15
 
@@ -17,7 +19,7 @@ module Legion
17
19
  def capabilities = Capabilities
18
20
 
19
21
  def registry_publisher
20
- @registry_publisher ||= RegistryPublisher.new
22
+ @registry_publisher ||= Legion::Extensions::Llm::RegistryPublisher.new(provider_family: :gemini)
21
23
  end
22
24
  end
23
25
 
@@ -94,7 +96,9 @@ module Legion
94
96
  end
95
97
 
96
98
  def list_models
99
+ log.info { 'listing available Gemini models' }
97
100
  super.tap do |models|
101
+ log.info { "discovered #{models.size} Gemini model(s); publishing to registry" }
98
102
  self.class.registry_publisher.publish_models_async(models, readiness: readiness(live: false))
99
103
  end
100
104
  end
@@ -279,16 +283,18 @@ module Legion
279
283
  Array(response.body['models']).map do |model_data|
280
284
  model_id = model_data.fetch('name').delete_prefix('models/')
281
285
  methods = Array(model_data['supportedGenerationMethods'])
286
+ input_mods, output_mods = modalities_for(methods)
282
287
 
283
288
  Legion::Extensions::Llm::Model::Info.new(
284
289
  id: model_id,
285
290
  name: model_data['displayName'] || model_id,
286
291
  provider: provider,
287
- context_window: model_data['inputTokenLimit'],
288
- max_output_tokens: model_data['outputTokenLimit'],
292
+ context_length: model_data['inputTokenLimit'],
289
293
  capabilities: capabilities.critical_capabilities_for(model_data),
290
- modalities: modalities_for(methods),
294
+ modalities_input: input_mods,
295
+ modalities_output: output_mods,
291
296
  metadata: {
297
+ max_output_tokens: model_data['outputTokenLimit'],
292
298
  version: model_data['version'],
293
299
  description: model_data['description'],
294
300
  supported_generation_methods: methods
@@ -298,9 +304,9 @@ module Legion
298
304
  end
299
305
 
300
306
  def modalities_for(methods)
301
- return { input: %w[text], output: %w[embeddings] } if methods.include?('embedContent')
307
+ return [%w[text], %w[embeddings]] if methods.include?('embedContent')
302
308
 
303
- { input: %w[text image audio video], output: %w[text] }
309
+ [%w[text image audio video], %w[text]]
304
310
  end
305
311
 
306
312
  def render_embedding_payload(text, model:, dimensions:)
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Gemini
7
- VERSION = '0.1.6'
7
+ VERSION = '0.2.0'
8
8
  end
9
9
  end
10
10
  end
@@ -1,41 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/extensions/llm'
4
- require 'legion/extensions/llm/gemini/registry_event_builder'
5
- require 'legion/extensions/llm/gemini/registry_publisher'
6
4
  require 'legion/extensions/llm/gemini/provider'
7
5
  require 'legion/extensions/llm/gemini/version'
8
6
 
9
7
  module Legion
10
8
  module Extensions
9
+ # LLM provider framework namespace (reopened by provider extensions).
11
10
  module Llm
12
11
  # Gemini provider extension namespace.
13
12
  module Gemini
14
13
  extend ::Legion::Extensions::Core if ::Legion::Extensions.const_defined?(:Core, false)
14
+ extend Legion::Logging::Helper
15
15
 
16
16
  PROVIDER_FAMILY = :gemini
17
17
 
18
18
  def self.default_settings
19
- ::Legion::Extensions::Llm.provider_settings(
20
- family: PROVIDER_FAMILY,
21
- instance: {
22
- endpoint: 'https://generativelanguage.googleapis.com/v1beta',
23
- tier: :frontier,
24
- transport: :http,
25
- credentials: { api_key: 'env://GEMINI_API_KEY' },
26
- usage: { inference: true, embedding: true },
27
- limits: { concurrency: 4 }
28
- }
29
- )
19
+ {
20
+ enabled: false,
21
+ default_model: 'gemini-2.0-flash',
22
+ api_key: nil,
23
+ model_whitelist: [],
24
+ model_blacklist: [],
25
+ model_cache_ttl: 3600,
26
+ tls: { enabled: false, verify: :peer },
27
+ instances: {}
28
+ }
30
29
  end
31
30
 
32
31
  def self.provider_class
33
32
  Provider
34
33
  end
35
34
  end
35
+
36
+ Configuration.register_provider_options(Gemini::Provider.configuration_options)
36
37
  end
37
38
  end
38
39
  end
39
-
40
- Legion::Extensions::Llm::Provider.register(Legion::Extensions::Llm::Gemini::PROVIDER_FAMILY,
41
- Legion::Extensions::Llm::Gemini::Provider)
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.1.6
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -57,14 +57,14 @@ dependencies:
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 0.1.5
60
+ version: 0.2.0
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - ">="
66
66
  - !ruby/object:Gem::Version
67
- version: 0.1.5
67
+ version: 0.2.0
68
68
  description: Gemini provider integration for the LegionIO LLM routing framework.
69
69
  email:
70
70
  - matthewdiverson@gmail.com
@@ -84,10 +84,6 @@ files:
84
84
  - lex-llm-gemini.gemspec
85
85
  - lib/legion/extensions/llm/gemini.rb
86
86
  - lib/legion/extensions/llm/gemini/provider.rb
87
- - lib/legion/extensions/llm/gemini/registry_event_builder.rb
88
- - lib/legion/extensions/llm/gemini/registry_publisher.rb
89
- - lib/legion/extensions/llm/gemini/transport/exchanges/llm_registry.rb
90
- - lib/legion/extensions/llm/gemini/transport/messages/registry_event.rb
91
87
  - lib/legion/extensions/llm/gemini/version.rb
92
88
  homepage: https://github.com/LegionIO/lex-llm-gemini
93
89
  licenses:
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- module Gemini
7
- # Builds sanitized lex-llm registry envelopes for Gemini provider state.
8
- class RegistryEventBuilder
9
- def model_available(model, readiness:)
10
- registry_event_class.available(
11
- model_offering(model),
12
- runtime: runtime_metadata,
13
- health: model_health(readiness),
14
- metadata: model_metadata(model)
15
- )
16
- end
17
-
18
- private
19
-
20
- def model_offering(model)
21
- {
22
- provider_family: :gemini,
23
- provider_instance: provider_instance,
24
- transport: :http,
25
- model: model.id,
26
- usage_type: usage_type_for(model),
27
- capabilities: Array(model.capabilities).map(&:to_sym),
28
- limits: model_limits(model),
29
- metadata: { lex: :llm_gemini, model_name: model.name }.compact
30
- }
31
- end
32
-
33
- def model_health(readiness)
34
- ready = readiness.fetch(:ready, true) == true
35
- { ready:, status: ready ? :available : :degraded }
36
- end
37
-
38
- def model_metadata(model)
39
- { extension: :lex_llm_gemini, provider: :gemini, model_type: model.type }
40
- end
41
-
42
- def runtime_metadata
43
- { node: provider_instance }
44
- end
45
-
46
- def model_limits(model)
47
- {
48
- context_window: model.context_window,
49
- max_output_tokens: model.max_output_tokens
50
- }.compact
51
- end
52
-
53
- def usage_type_for(model)
54
- model.type == 'embedding' ? :embedding : :inference
55
- end
56
-
57
- def provider_instance
58
- configured_node = (::Legion::Settings.dig(:node, :canonical_name) if defined?(::Legion::Settings))
59
- value = configured_node.to_s.strip
60
- value.empty? ? :gemini : value.to_sym
61
- rescue StandardError
62
- :gemini
63
- end
64
-
65
- def registry_event_class
66
- ::Legion::Extensions::Llm::Routing::RegistryEvent
67
- end
68
- end
69
- end
70
- end
71
- end
72
- end
@@ -1,96 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- module Gemini
7
- # Best-effort publisher for Gemini provider availability events.
8
- class RegistryPublisher
9
- APP_ID = 'lex-llm-gemini'
10
-
11
- def initialize(builder: RegistryEventBuilder.new)
12
- @builder = builder
13
- end
14
-
15
- def publish_models_async(models, readiness:)
16
- schedule do
17
- Array(models).each do |model|
18
- publish_event(@builder.model_available(model, readiness:))
19
- end
20
- end
21
- end
22
-
23
- private
24
-
25
- def schedule(&)
26
- return false unless publishing_available?
27
-
28
- Thread.new do
29
- Thread.current.abort_on_exception = false
30
- yield
31
- rescue StandardError => e
32
- log_publish_failure(e, level: :debug)
33
- end
34
- rescue StandardError => e
35
- log_publish_failure(e, level: :debug)
36
- false
37
- end
38
-
39
- def publish_event(event)
40
- return false unless publishing_available?
41
-
42
- message_class.new(event:, app_id: APP_ID).publish(spool: false)
43
- rescue StandardError => e
44
- log_publish_failure(e)
45
- false
46
- end
47
-
48
- def publishing_available?
49
- return false unless registry_event_available?
50
- return false unless transport_message_available?
51
- return true unless defined?(::Legion::Transport::Connection)
52
- return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
53
-
54
- ::Legion::Transport::Connection.session_open?
55
- rescue StandardError
56
- false
57
- end
58
-
59
- def registry_event_available?
60
- defined?(::Legion::Extensions::Llm::Routing::RegistryEvent)
61
- end
62
-
63
- def transport_message_available?
64
- return true if message_class_defined?
65
- return false unless defined?(::Legion::Transport::Message) && defined?(::Legion::Transport::Exchange)
66
-
67
- require 'legion/extensions/llm/gemini/transport/messages/registry_event'
68
- message_class_defined?
69
- rescue LoadError
70
- false
71
- end
72
-
73
- def message_class_defined?
74
- defined?(::Legion::Extensions::Llm::Gemini::Transport::Messages::RegistryEvent)
75
- end
76
-
77
- def message_class
78
- ::Legion::Extensions::Llm::Gemini::Transport::Messages::RegistryEvent
79
- end
80
-
81
- def log_publish_failure(error, level: :warn)
82
- message = "[lex-llm-gemini] llm.registry publish failed: #{error.class}: #{error.message}"
83
- logger = ::Legion::Extensions::Llm.logger if defined?(::Legion::Extensions::Llm)
84
- if logger.respond_to?(level)
85
- logger.public_send(level, message)
86
- elsif logger.respond_to?(:debug)
87
- logger.debug(message)
88
- end
89
- rescue StandardError
90
- nil
91
- end
92
- end
93
- end
94
- end
95
- end
96
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- module Gemini
7
- module Transport
8
- module Exchanges
9
- # Topic exchange for Gemini provider availability events.
10
- class LlmRegistry < ::Legion::Transport::Exchange
11
- def exchange_name
12
- 'llm.registry'
13
- end
14
-
15
- def default_type
16
- 'topic'
17
- end
18
- end
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'legion/extensions/llm/gemini/transport/exchanges/llm_registry'
4
-
5
- module Legion
6
- module Extensions
7
- module Llm
8
- module Gemini
9
- module Transport
10
- module Messages
11
- # Publishes lex-llm RegistryEvent envelopes to the llm.registry exchange.
12
- class RegistryEvent < ::Legion::Transport::Message
13
- def initialize(event:, **options)
14
- super(**event.to_h.merge(options))
15
- end
16
-
17
- def exchange
18
- Transport::Exchanges::LlmRegistry
19
- end
20
-
21
- def routing_key
22
- @options[:routing_key] || "llm.registry.#{@options.fetch(:event_type)}"
23
- end
24
-
25
- def type
26
- 'llm.registry.event'
27
- end
28
-
29
- def app_id
30
- @options[:app_id] || RegistryPublisher::APP_ID
31
- end
32
-
33
- def persistent # rubocop:disable Naming/PredicateMethod
34
- false
35
- end
36
- end
37
- end
38
- end
39
- end
40
- end
41
- end
42
- end