lex-llm-azure-foundry 0.1.0 → 0.1.2

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: 33f564df8dc0e10567cb8780f15976c66d399ca545a6303674bd67e11896e404
4
- data.tar.gz: bd35a164c1adb9c8c1ed3eaa590dc97908b2c2bfca74d09e2784ae41349d0c24
3
+ metadata.gz: a4c8f175645147e84ce67e151aa2ef886ca23a96f0d00f37f27e90549ea7d9c4
4
+ data.tar.gz: d969e077a6ac3e06ed51dd51b10a83ea7574dfe5495438c3735413def6709dd6
5
5
  SHA512:
6
- metadata.gz: 861eb2e467c3ef97123dd06705cd5eccbfb708573231e85cc171a10a8b7749bca5001cbe92e34eb5a8fb5a0a275277350f245a45daeba38b3efd843d8ae5c65b
7
- data.tar.gz: a78aaace8611a9f72beb67f524d7e6c949f7c737698d77770280ab9d8bbee4305720ebed779d65e3df26971eb2ad2c8a457bd3b9640e5dc905e83b8a8136b593
6
+ metadata.gz: 20122b9eafaa1c5f1240c041ed6f657e95f6909d80e67ee06b133f1adaa0fd1d0816ce000a155590052134fba4139056ab905024e844d8aa6ad7cdcbcd09fdd6
7
+ data.tar.gz: b9e84ee384e6ad871eee3baf65d29ebdeea9c18a874c07f9776874b97b76138c55fccaab82d76787cd123a84c2840aa80f95c1b81861534190ea7d4e2cf5411f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2 - 2026-04-28
4
+
5
+ - Publish best-effort `llm.registry` live readiness and configured deployment model availability events using `lex-llm` registry envelopes when transport is already available.
6
+
7
+ ## 0.1.1 - 2026-04-28
8
+
9
+ - Require `lex-llm >= 0.1.5` for the shared model offering, canonical alias, readiness, and fleet lane contract used by Azure deployment routing metadata.
10
+
3
11
  ## 0.1.0 - 2026-04-28
4
12
 
5
13
  - Initial Legion LLM Azure AI Foundry provider extension scaffold.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  LegionIO LLM provider extension for Azure AI Foundry Models and Azure OpenAI hosted deployments.
4
4
 
5
- This gem lives under `Legion::Extensions::Llm::AzureFoundry` and depends on `lex-llm` for shared provider-neutral routing, fleet, model-offering, and schema primitives.
5
+ This gem lives under `Legion::Extensions::Llm::AzureFoundry` and depends on `lex-llm >= 0.1.5` for shared provider-neutral routing, fleet, model-offering, readiness, canonical-alias, and schema primitives.
6
6
 
7
7
  Load it with `require 'legion/extensions/llm/azure_foundry'`.
8
8
 
@@ -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.4'
29
+ spec.add_dependency 'lex-llm', '>= 0.1.5'
30
30
  end
@@ -18,6 +18,8 @@ module Legion
18
18
  OPENAI_V1_SURFACE = :openai_v1
19
19
 
20
20
  class << self
21
+ attr_writer :registry_publisher
22
+
21
23
  def slug = 'azure_foundry'
22
24
  def configuration_requirements = %i[azure_foundry_endpoint]
23
25
 
@@ -34,6 +36,10 @@ module Legion
34
36
 
35
37
  def capabilities = Capabilities
36
38
 
39
+ def registry_publisher
40
+ @registry_publisher ||= RegistryPublisher.new
41
+ end
42
+
37
43
  def resolve_model_id(model_id, config: nil)
38
44
  deployment = deployment_config(model_id, config:)
39
45
  value_for(deployment, :deployment) || value_for(deployment, :model) || model_id.to_s
@@ -182,21 +188,15 @@ module Legion
182
188
  end
183
189
 
184
190
  def readiness(live: false)
185
- health(live: live).merge(local: false, remote: true, endpoints: endpoint_manifest)
191
+ health(live: live).merge(local: false, remote: true, endpoints: endpoint_manifest).tap do |metadata|
192
+ self.class.registry_publisher.publish_readiness_async(metadata) if live
193
+ end
186
194
  end
187
195
 
188
196
  def list_models
189
- discover_offerings(live: false).map do |offering|
190
- Legion::Extensions::Llm::Model::Info.new(
191
- id: offering.model,
192
- name: offering.metadata[:canonical_model_alias] || offering.model,
193
- provider: :azure_foundry,
194
- family: offering.metadata[:model_family],
195
- capabilities: offering.capabilities.map(&:to_s),
196
- modalities: modalities_for_capabilities(offering.capabilities.map(&:to_s)),
197
- metadata: offering.to_h
198
- )
199
- end
197
+ models = discover_offerings(live: false).map { |offering| model_info_from_offering(offering) }
198
+ self.class.registry_publisher.publish_models_async(models, readiness: readiness(live: false))
199
+ models
200
200
  end
201
201
 
202
202
  def chat(messages, model:, temperature: nil, max_tokens: nil, tools: {}, tool_prefs: nil, params: {}) # rubocop:disable Metrics/ParameterLists
@@ -230,6 +230,19 @@ module Legion
230
230
  (config.azure_foundry_surface || MODEL_INFERENCE_SURFACE).to_sym
231
231
  end
232
232
 
233
+ def model_info_from_offering(offering)
234
+ capabilities = offering.capabilities.map(&:to_s)
235
+ Legion::Extensions::Llm::Model::Info.new(
236
+ id: offering.model,
237
+ name: offering.metadata[:canonical_model_alias] || offering.model,
238
+ provider: :azure_foundry,
239
+ family: offering.metadata[:model_family],
240
+ capabilities: capabilities,
241
+ modalities: modalities_for_capabilities(capabilities),
242
+ metadata: offering.to_h
243
+ )
244
+ end
245
+
233
246
  def api_version
234
247
  config.azure_foundry_api_version || DEFAULT_API_VERSION
235
248
  end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module AzureFoundry
7
+ # Builds sanitized lex-llm registry envelopes for Azure Foundry provider state.
8
+ class RegistryEventBuilder
9
+ def readiness(readiness)
10
+ registry_event_class.public_send(
11
+ readiness[:ready] ? :available : :unavailable,
12
+ provider_offering(readiness),
13
+ runtime: runtime_metadata,
14
+ health: readiness_health(readiness),
15
+ metadata: readiness_metadata(readiness)
16
+ )
17
+ end
18
+
19
+ def model_available(model, readiness:)
20
+ registry_event_class.available(
21
+ model_offering(model),
22
+ runtime: runtime_metadata,
23
+ health: model_health(readiness),
24
+ metadata: model_metadata(model)
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ def provider_offering(readiness)
31
+ {
32
+ provider_family: :azure_foundry,
33
+ provider_instance: provider_instance,
34
+ transport: :http,
35
+ model: 'provider-readiness',
36
+ usage_type: :inference,
37
+ capabilities: [],
38
+ health: readiness_health(readiness),
39
+ metadata: { lex: :llm_azure_foundry, provider_readiness: true }
40
+ }
41
+ end
42
+
43
+ def model_offering(model)
44
+ metadata = model.metadata if model.respond_to?(:metadata)
45
+ return metadata if metadata.is_a?(Hash) && metadata[:provider_family]
46
+
47
+ {
48
+ provider_family: :azure_foundry,
49
+ provider_instance: provider_instance,
50
+ transport: :http,
51
+ model: model.id,
52
+ usage_type: usage_type_for(model),
53
+ capabilities: Array(model.capabilities).map(&:to_sym),
54
+ limits: model_limits(model),
55
+ metadata: { lex: :llm_azure_foundry, model_name: model.name }.compact
56
+ }
57
+ end
58
+
59
+ def readiness_health(readiness)
60
+ health = {
61
+ ready: readiness[:ready] == true,
62
+ status: readiness[:ready] ? :available : :unavailable,
63
+ checked: readiness.dig(:health, :checked) != false
64
+ }
65
+ add_readiness_error(health, readiness[:health])
66
+ end
67
+
68
+ def add_readiness_error(health, source)
69
+ error = source.is_a?(Hash) ? source : {}
70
+ error_class = error[:error] || error['error']
71
+ error_message = error[:message] || error['message']
72
+ health[:error_class] = error_class if error_class
73
+ health[:error] = error_message if error_message
74
+ health
75
+ end
76
+
77
+ def model_health(readiness)
78
+ ready = readiness.fetch(:ready, true) == true
79
+ { ready:, status: ready ? :available : :degraded }
80
+ end
81
+
82
+ def readiness_metadata(readiness)
83
+ {
84
+ extension: :lex_llm_azure_foundry,
85
+ provider: :azure_foundry,
86
+ configured: readiness[:configured] == true,
87
+ live: readiness[:live] == true
88
+ }
89
+ end
90
+
91
+ def model_metadata(model)
92
+ { extension: :lex_llm_azure_foundry, provider: :azure_foundry, model_type: model.type }
93
+ end
94
+
95
+ def runtime_metadata
96
+ { node: provider_instance }
97
+ end
98
+
99
+ def model_limits(model)
100
+ {
101
+ context_window: model.context_window,
102
+ max_output_tokens: model.max_output_tokens
103
+ }.compact
104
+ end
105
+
106
+ def usage_type_for(model)
107
+ model.type == 'embedding' ? :embedding : :inference
108
+ end
109
+
110
+ def provider_instance
111
+ :azure_foundry
112
+ end
113
+
114
+ def registry_event_class
115
+ ::Legion::Extensions::Llm::Routing::RegistryEvent
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module AzureFoundry
7
+ # Best-effort publisher for Azure Foundry provider availability events.
8
+ class RegistryPublisher
9
+ APP_ID = 'lex-llm-azure-foundry'
10
+
11
+ def initialize(builder: RegistryEventBuilder.new)
12
+ @builder = builder
13
+ end
14
+
15
+ def publish_readiness_async(readiness)
16
+ schedule { publish_event(@builder.readiness(readiness)) }
17
+ end
18
+
19
+ def publish_models_async(models, readiness:)
20
+ schedule do
21
+ Array(models).each do |model|
22
+ publish_event(@builder.model_available(model, readiness:))
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def schedule(&)
30
+ return false unless publishing_available?
31
+
32
+ Thread.new do
33
+ Thread.current.abort_on_exception = false
34
+ yield
35
+ rescue StandardError => e
36
+ log_publish_failure(e, level: :debug)
37
+ end
38
+ rescue StandardError => e
39
+ log_publish_failure(e, level: :debug)
40
+ false
41
+ end
42
+
43
+ def publish_event(event)
44
+ return false unless publishing_available?
45
+
46
+ message_class.new(event:, app_id: APP_ID).publish(spool: false)
47
+ rescue StandardError => e
48
+ log_publish_failure(e)
49
+ false
50
+ end
51
+
52
+ def publishing_available?
53
+ return false unless registry_event_available?
54
+ return false unless transport_message_available?
55
+ return true unless defined?(::Legion::Transport::Connection)
56
+ return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
57
+
58
+ ::Legion::Transport::Connection.session_open?
59
+ rescue StandardError
60
+ false
61
+ end
62
+
63
+ def registry_event_available?
64
+ defined?(::Legion::Extensions::Llm::Routing::RegistryEvent)
65
+ end
66
+
67
+ def transport_message_available?
68
+ return true if message_class_defined?
69
+ return false unless defined?(::Legion::Transport::Message) && defined?(::Legion::Transport::Exchange)
70
+
71
+ require 'legion/extensions/llm/azure_foundry/transport/messages/registry_event'
72
+ message_class_defined?
73
+ rescue LoadError
74
+ false
75
+ end
76
+
77
+ def message_class_defined?
78
+ defined?(::Legion::Extensions::Llm::AzureFoundry::Transport::Messages::RegistryEvent)
79
+ end
80
+
81
+ def message_class
82
+ ::Legion::Extensions::Llm::AzureFoundry::Transport::Messages::RegistryEvent
83
+ end
84
+
85
+ def log_publish_failure(error, level: :warn)
86
+ message = "[lex-llm-azure-foundry] llm.registry publish failed: #{error.class}: #{error.message}"
87
+ logger = ::Legion::Extensions::Llm.logger if defined?(::Legion::Extensions::Llm)
88
+ if logger.respond_to?(level)
89
+ logger.public_send(level, message)
90
+ elsif logger.respond_to?(:debug)
91
+ logger.debug(message)
92
+ end
93
+ rescue StandardError
94
+ nil
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module AzureFoundry
7
+ module Transport
8
+ module Exchanges
9
+ # Topic exchange for Azure Foundry 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
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/llm/azure_foundry/transport/exchanges/llm_registry'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Llm
8
+ module AzureFoundry
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
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module AzureFoundry
7
- VERSION = '0.1.0'
7
+ VERSION = '0.1.2'
8
8
  end
9
9
  end
10
10
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'legion/extensions/llm'
4
4
  require 'legion/extensions/llm/azure_foundry/provider'
5
+ require 'legion/extensions/llm/azure_foundry/registry_event_builder'
6
+ require 'legion/extensions/llm/azure_foundry/registry_publisher'
5
7
  require 'legion/extensions/llm/azure_foundry/version'
6
8
 
7
9
  module Legion
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-azure-foundry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
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.4
60
+ version: 0.1.5
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.4
67
+ version: 0.1.5
68
68
  description: Azure AI Foundry and Azure OpenAI hosted provider integration for LegionIO
69
69
  LLM routing.
70
70
  email:
@@ -85,6 +85,10 @@ files:
85
85
  - lex-llm-azure-foundry.gemspec
86
86
  - lib/legion/extensions/llm/azure_foundry.rb
87
87
  - lib/legion/extensions/llm/azure_foundry/provider.rb
88
+ - lib/legion/extensions/llm/azure_foundry/registry_event_builder.rb
89
+ - lib/legion/extensions/llm/azure_foundry/registry_publisher.rb
90
+ - lib/legion/extensions/llm/azure_foundry/transport/exchanges/llm_registry.rb
91
+ - lib/legion/extensions/llm/azure_foundry/transport/messages/registry_event.rb
88
92
  - lib/legion/extensions/llm/azure_foundry/version.rb
89
93
  homepage: https://github.com/LegionIO/lex-llm-azure-foundry
90
94
  licenses: