lex-llm-azure-foundry 0.1.4 → 0.1.5

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: 188c4529d8f3fb9fcde5080b22e3768fbe713b609fc86efb1264a1e8595c6bb5
4
- data.tar.gz: 3826e3576537bb8b1ba7d26bc4f91a061dbf023578bf21b232b90fe7269981c1
3
+ metadata.gz: 93b2b78c6462da9e924d9de921741cc21a406b68d47879a23c21d26df0478e88
4
+ data.tar.gz: a91ed47ace39e1a38db76d35a0475fb9332a60213cbf2bd4816a31fe988c58a6
5
5
  SHA512:
6
- metadata.gz: bec5704b0a9a1465b0e2404e0eeb436ab22026c3112d47a386b561f1b20c58a09326757d9c180aaf47fbc7a108f8f9f3267710639ae779803ff8a1bc17bea751
7
- data.tar.gz: 7b88529d1438f19a893e5d34839bea59b0a2c733a1e273ef0dcc0447fe929a7f44374316bd1377270dfd0036c2adcd24f10f6b8b42bd24a5a7ef98acab733c30
6
+ metadata.gz: 58ec3150d974660d8f573f282fbafa1c2d5716c817453da7124fa2f1125852dacac0d388e34e406535a43178da29c20e3b5ccd00f54d39c3581bde2bc949cd19
7
+ data.tar.gz: 1f53bccbe6444087c3269de7ad8d1a82ee00681dd4bf0670477b144dfb4994fe3a9f22c1738e067d38c0dcd4e1fe011ca243d6095adbeb28dc50ec9480d4a693
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.5] - 2026-04-30
4
+
5
+ - Audit all rescue blocks for handle_exception compliance across Provider, RegistryPublisher, and RegistryEventBuilder
6
+ - Add Legion::Logging::Helper to AzureFoundry module, RegistryPublisher, and RegistryEventBuilder
7
+ - Add info-level action logging for discover_offerings, health, readiness, list_models, chat, stream, embed, and registry publish
8
+ - Remove custom log_publish_failure in favour of standard handle_exception
9
+ - Update README to reflect current architecture, file map, and observability
10
+
3
11
  ## [0.1.4] - 2026-04-30
4
12
 
5
13
  - Enable stream_usage_supported? for streaming token usage reporting
data/README.md CHANGED
@@ -13,11 +13,49 @@ Load it with `require 'legion/extensions/llm/azure_foundry'`.
13
13
  - Azure AI Foundry model inference embeddings through `POST /models/embeddings?api-version=...`
14
14
  - Azure AI Foundry model info health check through `GET /models/info?api-version=...` when `live: true`
15
15
  - Azure OpenAI v1-compatible endpoint support through `/openai/v1/chat/completions` and `/openai/v1/embeddings`
16
- - deployment-name-preserving routing offerings for hosted Azure deployments
17
- - explicit `model_family` and `canonical_model_alias` metadata for deployments whose base model cannot be proven from Azure metadata
18
- - offline-first discovery from configured deployments
19
- - shared OpenAI-compatible request and response mapping via `Legion::Extensions::Llm::Provider::OpenAICompatible`
20
- - conservative token-counting metadata when no portable Azure token-counting REST endpoint is configured
16
+ - Deployment-name-preserving routing offerings for hosted Azure deployments
17
+ - Explicit `model_family` and `canonical_model_alias` metadata for deployments whose base model cannot be proven from Azure metadata
18
+ - Offline-first discovery from configured deployments
19
+ - Shared OpenAI-compatible request and response mapping via `Legion::Extensions::Llm::Provider::OpenAICompatible`
20
+ - Conservative token-counting metadata when no portable Azure token-counting REST endpoint is configured
21
+ - Best-effort `llm.registry` event publishing for readiness and model availability via AMQP when transport is available
22
+
23
+ ## Architecture
24
+
25
+ ```
26
+ Legion::Extensions::Llm::AzureFoundry
27
+ ├── Provider # Azure AI Foundry and Azure OpenAI hosted provider surface
28
+ │ └── Capabilities # Capability predicates inferred from deployment metadata and model naming
29
+ ├── RegistryPublisher # Best-effort async publisher for llm.registry availability events
30
+ ├── RegistryEventBuilder # Builds sanitized lex-llm registry envelopes for provider state
31
+ ├── Transport/
32
+ │ ├── Messages::RegistryEvent # AMQP message for llm.registry events
33
+ │ └── Exchanges::LlmRegistry # Topic exchange for provider availability events
34
+ └── VERSION
35
+ ```
36
+
37
+ ## File Map
38
+
39
+ | Path | Purpose |
40
+ |------|---------|
41
+ | `lib/legion/extensions/llm/azure_foundry.rb` | Entry point, provider registration, default settings |
42
+ | `lib/legion/extensions/llm/azure_foundry/provider.rb` | Provider implementation with chat, stream, embed, health, readiness, discovery |
43
+ | `lib/legion/extensions/llm/azure_foundry/registry_publisher.rb` | Async registry event publishing with transport guards |
44
+ | `lib/legion/extensions/llm/azure_foundry/registry_event_builder.rb` | Sanitized registry envelope construction |
45
+ | `lib/legion/extensions/llm/azure_foundry/transport/messages/registry_event.rb` | AMQP message class for registry events |
46
+ | `lib/legion/extensions/llm/azure_foundry/transport/exchanges/llm_registry.rb` | Topic exchange definition for llm.registry |
47
+ | `lib/legion/extensions/llm/azure_foundry/version.rb` | `VERSION` constant |
48
+
49
+ ## Observability
50
+
51
+ Every class and module uses `Legion::Logging::Helper`:
52
+
53
+ - **AzureFoundry** module: `extend Legion::Logging::Helper`
54
+ - **Provider**: inherits `include Legion::Logging::Helper` from `Legion::Extensions::Llm::Provider`
55
+ - **RegistryPublisher**: `include Legion::Logging::Helper`
56
+ - **RegistryEventBuilder**: `include Legion::Logging::Helper`
57
+
58
+ All rescue blocks call `handle_exception(e, level:, handled:, operation:)` for structured exception reporting. Key actions emit info-level log lines including discover_offerings, health checks, readiness, model listing, chat, stream, embed, and registry publish operations.
21
59
 
22
60
  ## API Contract
23
61
 
@@ -97,6 +135,8 @@ provider = Legion::Extensions::Llm::AzureFoundry.provider_class.new(Legion::Exte
97
135
  provider.discover_offerings(live: false)
98
136
  provider.offering_for(model: "gpt-4o-prod", model_family: :openai, canonical_model_alias: "gpt-4o")
99
137
  provider.health(live: false)
138
+ provider.readiness(live: false)
139
+ provider.list_models
100
140
  provider.chat(messages, model: "gpt-4o-prod")
101
141
  provider.stream(messages, model: "gpt-4o-prod") { |chunk| puts chunk.content }
102
142
  provider.embed(["hello"], model: "embedding-prod")
@@ -144,12 +144,14 @@ module Legion
144
144
  def health_url = models_url
145
145
 
146
146
  def discover_offerings(live: false, **filters)
147
+ log.info { "discovering offerings live=#{live} from #{api_base}" }
147
148
  offerings = configured_deployments.filter_map { |deployment| offering_from_config(deployment) }
148
149
  return filter_offerings(offerings, **filters) unless live
149
150
 
150
151
  filter_offerings(offerings, **filters).map do |offering|
151
152
  with_live_metadata(offering)
152
153
  rescue StandardError => e
154
+ handle_exception(e, level: :warn, handled: true, operation: 'azure_foundry.discover_offerings')
153
155
  with_health(offering, ready: false, checked: true, error: e)
154
156
  end
155
157
  end
@@ -172,43 +174,43 @@ module Legion
172
174
  end
173
175
 
174
176
  def health(live: false)
175
- baseline = {
176
- provider: :azure_foundry,
177
- configured: configured?,
178
- ready: configured?,
179
- live: live,
180
- api_base: api_base,
181
- surface: surface
182
- }
177
+ log.info { "checking health live=#{live} at #{api_base}" }
178
+ baseline = health_baseline(live)
183
179
  return baseline.merge(checked: false) unless live
184
180
 
185
181
  response = connection.get(health_url)
186
182
  baseline.merge(checked: true, model_info: response.body)
187
183
  rescue StandardError => e
184
+ handle_exception(e, level: :warn, handled: true, operation: 'azure_foundry.health')
188
185
  baseline.merge(checked: true, ready: false, error: e.class.name, message: e.message)
189
186
  end
190
187
 
191
188
  def readiness(live: false)
189
+ log.info { "checking readiness live=#{live} at #{api_base}" }
192
190
  health(live: live).merge(local: false, remote: true, endpoints: endpoint_manifest).tap do |metadata|
193
191
  self.class.registry_publisher.publish_readiness_async(metadata) if live
194
192
  end
195
193
  end
196
194
 
197
195
  def list_models
196
+ log.info { "listing configured deployment models from #{api_base}" }
198
197
  models = discover_offerings(live: false).map { |offering| model_info_from_offering(offering) }
199
198
  self.class.registry_publisher.publish_models_async(models, readiness: readiness(live: false))
200
199
  models
201
200
  end
202
201
 
203
202
  def chat(messages, model:, temperature: nil, max_tokens: nil, tools: {}, tool_prefs: nil, params: {}) # rubocop:disable Metrics/ParameterLists
203
+ log.info { "chat request model=#{model} messages=#{messages.size}" }
204
204
  complete(messages, tools:, temperature:, model: model_info(model, max_tokens:), params:, tool_prefs:)
205
205
  end
206
206
 
207
207
  def stream(messages, model:, temperature: nil, max_tokens: nil, tools: {}, tool_prefs: nil, params: {}, &) # rubocop:disable Metrics/ParameterLists
208
+ log.info { "stream request model=#{model} messages=#{messages.size}" }
208
209
  complete(messages, tools:, temperature:, model: model_info(model, max_tokens:), params:, tool_prefs:, &)
209
210
  end
210
211
 
211
212
  def embed(text, model:, dimensions: nil, input_type: nil)
213
+ log.info { "embed request model=#{model}" }
212
214
  payload = render_embedding_payload(text, model: model_id(model), dimensions:)
213
215
  payload[:input_type] = input_type if input_type
214
216
  response = connection.post(embedding_url(model:), payload)
@@ -231,6 +233,17 @@ module Legion
231
233
  (config.azure_foundry_surface || MODEL_INFERENCE_SURFACE).to_sym
232
234
  end
233
235
 
236
+ def health_baseline(live)
237
+ {
238
+ provider: :azure_foundry,
239
+ configured: configured?,
240
+ ready: configured?,
241
+ live: live,
242
+ api_base: api_base,
243
+ surface: surface
244
+ }
245
+ end
246
+
234
247
  def model_info_from_offering(offering)
235
248
  capabilities = offering.capabilities.map(&:to_s)
236
249
  Legion::Extensions::Llm::Model::Info.new(
@@ -6,6 +6,8 @@ module Legion
6
6
  module AzureFoundry
7
7
  # Builds sanitized lex-llm registry envelopes for Azure Foundry provider state.
8
8
  class RegistryEventBuilder
9
+ include Legion::Logging::Helper
10
+
9
11
  def readiness(readiness)
10
12
  registry_event_class.public_send(
11
13
  readiness[:ready] ? :available : :unavailable,
@@ -108,6 +110,12 @@ module Legion
108
110
  end
109
111
 
110
112
  def provider_instance
113
+ configured_node = (::Legion::Settings.dig(:node, :canonical_name) if defined?(::Legion::Settings))
114
+ value = configured_node.to_s.strip
115
+ value.empty? ? :azure_foundry : value.to_sym
116
+ rescue StandardError => e
117
+ handle_exception(e, level: :debug, handled: true,
118
+ operation: 'azure_foundry.registry.provider_instance')
111
119
  :azure_foundry
112
120
  end
113
121
 
@@ -6,6 +6,8 @@ module Legion
6
6
  module AzureFoundry
7
7
  # Best-effort publisher for Azure Foundry provider availability events.
8
8
  class RegistryPublisher
9
+ include Legion::Logging::Helper
10
+
9
11
  APP_ID = 'lex-llm-azure-foundry'
10
12
 
11
13
  def initialize(builder: RegistryEventBuilder.new)
@@ -13,10 +15,12 @@ module Legion
13
15
  end
14
16
 
15
17
  def publish_readiness_async(readiness)
18
+ log.info { 'publishing readiness event to llm.registry' }
16
19
  schedule { publish_event(@builder.readiness(readiness)) }
17
20
  end
18
21
 
19
22
  def publish_models_async(models, readiness:)
23
+ log.info { "publishing #{Array(models).size} model event(s) to llm.registry" }
20
24
  schedule do
21
25
  Array(models).each do |model|
22
26
  publish_event(@builder.model_available(model, readiness:))
@@ -33,10 +37,10 @@ module Legion
33
37
  Thread.current.abort_on_exception = false
34
38
  yield
35
39
  rescue StandardError => e
36
- log_publish_failure(e, level: :debug)
40
+ handle_exception(e, level: :debug, handled: true, operation: 'azure_foundry.registry.schedule_thread')
37
41
  end
38
42
  rescue StandardError => e
39
- log_publish_failure(e, level: :debug)
43
+ handle_exception(e, level: :debug, handled: true, operation: 'azure_foundry.registry.schedule')
40
44
  false
41
45
  end
42
46
 
@@ -45,7 +49,7 @@ module Legion
45
49
 
46
50
  message_class.new(event:, app_id: APP_ID).publish(spool: false)
47
51
  rescue StandardError => e
48
- log_publish_failure(e)
52
+ handle_exception(e, level: :warn, handled: true, operation: 'azure_foundry.registry.publish_event')
49
53
  false
50
54
  end
51
55
 
@@ -56,7 +60,9 @@ module Legion
56
60
  return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
57
61
 
58
62
  ::Legion::Transport::Connection.session_open?
59
- rescue StandardError
63
+ rescue StandardError => e
64
+ handle_exception(e, level: :debug, handled: true,
65
+ operation: 'azure_foundry.registry.publishing_available?')
60
66
  false
61
67
  end
62
68
 
@@ -70,7 +76,8 @@ module Legion
70
76
 
71
77
  require 'legion/extensions/llm/azure_foundry/transport/messages/registry_event'
72
78
  message_class_defined?
73
- rescue LoadError
79
+ rescue LoadError => e
80
+ handle_exception(e, level: :debug, handled: true, operation: 'azure_foundry.registry.transport_load')
74
81
  false
75
82
  end
76
83
 
@@ -81,18 +88,6 @@ module Legion
81
88
  def message_class
82
89
  ::Legion::Extensions::Llm::AzureFoundry::Transport::Messages::RegistryEvent
83
90
  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
91
  end
97
92
  end
98
93
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module AzureFoundry
7
- VERSION = '0.1.4'
7
+ VERSION = '0.1.5'
8
8
  end
9
9
  end
10
10
  end
@@ -12,6 +12,7 @@ module Legion
12
12
  # Azure AI Foundry provider extension namespace.
13
13
  module AzureFoundry
14
14
  extend ::Legion::Extensions::Core if ::Legion::Extensions.const_defined?(:Core, false)
15
+ extend Legion::Logging::Helper
15
16
 
16
17
  PROVIDER_FAMILY = :azure_foundry
17
18
 
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.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO