lex-llm-openai 0.1.6 → 0.1.8

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: aab8c5aaa3a502b204f0f97f9eb28672a81d18464090f22eb1f7afd1e1d4a95e
4
- data.tar.gz: d4dfda3e0961f241fd71e8f0d3a1a8612ee5911efbe4aac0983111317e9b6481
3
+ metadata.gz: 923d77b5cf761e20eccb817d66dd36bec5310842f1216c2b403432bf84bf11fa
4
+ data.tar.gz: e84b863710db389adf98f1b06783c46e4e4b7797c51889026c62939a3e3ad9a6
5
5
  SHA512:
6
- metadata.gz: 9e0cca53fa0c55b5577da00780013abf66455259c6c996eee570ab68e910bf589c5cb00661281210d26779bd683a1489914cb45b2a8b8096c7e141399ad16a18
7
- data.tar.gz: 3a7869846152c7b902a9fdc08bee13f058dec8ebfa57a9ac4538901d07724e550248d0dae7542f4c62bbc0a917c88127f369cdfa2c19791dad83327052f3f27a
6
+ metadata.gz: 3ea2f8c543f3e8cbdf55a158bb0033d1711b34b3114ea8ebbccc530d398ac3931e95d3c729be869eac5070dac773edec62e3de1dc94b3c00a4560c6b6a998cbe
7
+ data.tar.gz: 5672bc7469d4a09a12c85fcd343c8008748ccb6a90f85dc54cfdf7feb9d81721a026aa91a11d555b826159fbe61a528732301cf19ab63470800068faa1971a42
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.8] - 2026-04-30
4
+ - Add Legion::Logging::Helper to all modules and classes for structured observability
5
+ - Replace bare rescue blocks with handle_exception for unified error telemetry
6
+ - Add info-level action logging for provider registration, model listing, model retrieval, and registry publishing
7
+ - Remove manual log_publish_failure helper in favor of handle_exception
8
+ - Update README to reflect current capabilities and architecture
9
+
10
+ ## [0.1.7] - 2026-04-30
11
+ - Enable stream_usage_supported? for streaming token usage reporting
12
+
3
13
  ## 0.1.6 - 2026-04-28
4
14
 
5
15
  - Publish best-effort `llm.registry` discovered-model availability events when transport is already loaded.
data/README.md CHANGED
@@ -9,19 +9,42 @@ Load it with `require 'legion/extensions/llm/openai'`.
9
9
  ## What It Provides
10
10
 
11
11
  - `Legion::Extensions::Llm::Provider` registration as `:openai`
12
- - chat completions through `POST /v1/chat/completions`
13
- - streaming chat completions through the same chat completions endpoint
14
- - model discovery through `GET /v1/models`
15
- - model retrieval through `GET /v1/models/{model}`
16
- - embeddings through `POST /v1/embeddings`
17
- - moderation through `POST /v1/moderations`
18
- - image generation through `POST /v1/images/generations`
19
- - image editing through `POST /v1/images/edits`
20
- - image variation endpoint helper for `POST /v1/images/variations`
21
- - audio transcription through `POST /v1/audio/transcriptions`
22
- - shared OpenAI-compatible request/response mapping via `Legion::Extensions::Llm::Provider::OpenAICompatible`
23
- - normalized chat, embedding, moderation, image, and audio capability mapping for discovered models
24
- - shared fleet/default settings via `Legion::Extensions::Llm.provider_settings`
12
+ - Chat completions via `POST /v1/chat/completions`
13
+ - Streaming chat completions (same endpoint, `stream: true`)
14
+ - Model discovery via `GET /v1/models`
15
+ - Model retrieval via `GET /v1/models/{model}`
16
+ - Embeddings via `POST /v1/embeddings`
17
+ - Moderation via `POST /v1/moderations`
18
+ - Image generation via `POST /v1/images/generations`
19
+ - Image editing via `POST /v1/images/edits`
20
+ - Image variation via `POST /v1/images/variations`
21
+ - Audio transcription via `POST /v1/audio/transcriptions`
22
+ - Streaming token usage reporting (`stream_usage_supported?`)
23
+ - Shared OpenAI-compatible request/response mapping via `Legion::Extensions::Llm::Provider::OpenAICompatible`
24
+ - Normalized chat, embedding, moderation, image, and audio capability mapping for discovered models
25
+ - Shared fleet/default settings via `Legion::Extensions::Llm.provider_settings`
26
+ - Best-effort `llm.registry` availability event publishing for discovered models
27
+
28
+ ## Architecture
29
+
30
+ ```
31
+ Legion::Extensions::Llm::Openai
32
+ ├── Provider # OpenAI provider implementation (chat, models, embeddings, etc.)
33
+ │ └── Capabilities # Model family capability predicates
34
+ ├── RegistryPublisher # Async llm.registry event publisher
35
+ ├── RegistryEventBuilder # Sanitized registry event envelope builder
36
+ └── Transport
37
+ ├── Exchanges::LlmRegistry # Topic exchange for llm.registry
38
+ └── Messages::RegistryEvent # AMQP message for registry events
39
+ ```
40
+
41
+ ## Observability
42
+
43
+ All classes include `Legion::Logging::Helper` (when available) providing:
44
+
45
+ - Structured `handle_exception` calls on every rescue block
46
+ - Info-level action logging for provider registration, model listing, model retrieval, and registry publishing
47
+ - Automatic log segment derivation and component type tagging
25
48
 
26
49
  ## Defaults
27
50
 
@@ -47,6 +70,9 @@ Legion::Extensions::Llm::Openai.default_settings
47
70
  ```ruby
48
71
  Legion::Extensions::Llm.configure do |config|
49
72
  config.openai_api_key = ENV.fetch("OPENAI_API_KEY")
73
+ config.openai_api_base = nil # defaults to https://api.openai.com
74
+ config.openai_organization_id = nil # optional OpenAI-Organization header
75
+ config.openai_project_id = nil # optional OpenAI-Project header
50
76
  config.default_model = "gpt-5.2"
51
77
  config.default_embedding_model = "text-embedding-3-small"
52
78
  config.default_moderation_model = "omni-moderation-latest"
@@ -54,3 +80,25 @@ Legion::Extensions::Llm.configure do |config|
54
80
  config.default_transcription_model = "gpt-4o-transcribe"
55
81
  end
56
82
  ```
83
+
84
+ ## Dependencies
85
+
86
+ | Gem | Purpose |
87
+ |-----|---------|
88
+ | `lex-llm` (>= 0.1.5) | Shared provider contract, fleet settings, routing |
89
+ | `legion-json` (>= 1.2.1) | JSON serialization |
90
+ | `legion-logging` (>= 1.3.2) | Structured logging via Helper |
91
+ | `legion-settings` (>= 1.3.14) | Configuration management |
92
+
93
+ ## Development
94
+
95
+ ```bash
96
+ bundle install
97
+ bundle exec rspec --format progress # all pass
98
+ bundle exec rubocop -A # auto-fix
99
+ bundle exec rubocop # lint check (0 offenses)
100
+ ```
101
+
102
+ ## License
103
+
104
+ MIT
@@ -9,6 +9,7 @@ module Legion
9
9
  # OpenAI provider implementation for the Legion::Extensions::Llm base provider contract.
10
10
  class Provider < Legion::Extensions::Llm::Provider
11
11
  include Legion::Extensions::Llm::Provider::OpenAICompatible
12
+ include Legion::Logging::Helper if defined?(Legion::Logging::Helper)
12
13
 
13
14
  class << self
14
15
  attr_writer :registry_publisher
@@ -73,6 +74,8 @@ module Legion
73
74
  end
74
75
  end
75
76
 
77
+ def stream_usage_supported? = true
78
+
76
79
  def api_base
77
80
  config.openai_api_base || 'https://api.openai.com'
78
81
  end
@@ -92,13 +95,28 @@ module Legion
92
95
  def images_url(with: nil, mask: nil) = super
93
96
 
94
97
  def retrieve_model(model)
98
+ log.info("Retrieving model: #{model}") if respond_to?(:log)
95
99
  connection.get("#{models_url}/#{model}").body
100
+ rescue StandardError => e
101
+ if respond_to?(:handle_exception)
102
+ handle_exception(e, level: :error, handled: true,
103
+ operation: 'retrieve_model')
104
+ end
105
+ raise
96
106
  end
97
107
 
98
108
  def list_models
109
+ log.info('Listing OpenAI models') if respond_to?(:log)
99
110
  super.tap do |models|
111
+ log.info("Discovered #{models.size} OpenAI models") if respond_to?(:log)
100
112
  self.class.registry_publisher.publish_models_async(models, readiness: readiness(live: false))
101
113
  end
114
+ rescue StandardError => e
115
+ if respond_to?(:handle_exception)
116
+ handle_exception(e, level: :error, handled: true,
117
+ operation: 'list_models')
118
+ end
119
+ raise
102
120
  end
103
121
 
104
122
  private
@@ -6,6 +6,8 @@ module Legion
6
6
  module Openai
7
7
  # Builds sanitized lex-llm registry envelopes for OpenAI provider state.
8
8
  class RegistryEventBuilder
9
+ include Legion::Logging::Helper if defined?(Legion::Logging::Helper)
10
+
9
11
  def model_available(model, readiness:)
10
12
  registry_event_class.available(
11
13
  model_offering(model),
@@ -58,7 +60,11 @@ module Legion
58
60
  configured_node = (::Legion::Settings.dig(:node, :canonical_name) if defined?(::Legion::Settings))
59
61
  value = configured_node.to_s.strip
60
62
  value.empty? ? :openai : value.to_sym
61
- rescue StandardError
63
+ rescue StandardError => e
64
+ if respond_to?(:handle_exception)
65
+ handle_exception(e, level: :debug, handled: true,
66
+ operation: 'provider_instance')
67
+ end
62
68
  :openai
63
69
  end
64
70
 
@@ -6,6 +6,8 @@ module Legion
6
6
  module Openai
7
7
  # Best-effort publisher for OpenAI provider availability events.
8
8
  class RegistryPublisher
9
+ include Legion::Logging::Helper if defined?(Legion::Logging::Helper)
10
+
9
11
  APP_ID = 'lex-llm-openai'
10
12
 
11
13
  def initialize(builder: RegistryEventBuilder.new)
@@ -13,6 +15,7 @@ module Legion
13
15
  end
14
16
 
15
17
  def publish_models_async(models, readiness:)
18
+ log.info("Publishing #{Array(models).size} model(s) to llm.registry") if respond_to?(:log)
16
19
  schedule do
17
20
  Array(models).each do |model|
18
21
  publish_event(@builder.model_available(model, readiness:))
@@ -29,10 +32,10 @@ module Legion
29
32
  Thread.current.abort_on_exception = false
30
33
  yield
31
34
  rescue StandardError => e
32
- log_publish_failure(e, level: :debug)
35
+ handle_exception(e, level: :debug, handled: true, operation: 'schedule') if respond_to?(:handle_exception)
33
36
  end
34
37
  rescue StandardError => e
35
- log_publish_failure(e, level: :debug)
38
+ handle_exception(e, level: :debug, handled: true, operation: 'schedule') if respond_to?(:handle_exception)
36
39
  false
37
40
  end
38
41
 
@@ -41,7 +44,10 @@ module Legion
41
44
 
42
45
  message_class.new(event:, app_id: APP_ID).publish(spool: false)
43
46
  rescue StandardError => e
44
- log_publish_failure(e)
47
+ if respond_to?(:handle_exception)
48
+ handle_exception(e, level: :warn, handled: true,
49
+ operation: 'publish_event')
50
+ end
45
51
  false
46
52
  end
47
53
 
@@ -52,7 +58,11 @@ module Legion
52
58
  return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
53
59
 
54
60
  ::Legion::Transport::Connection.session_open?
55
- rescue StandardError
61
+ rescue StandardError => e
62
+ if respond_to?(:handle_exception)
63
+ handle_exception(e, level: :debug, handled: true,
64
+ operation: 'publishing_available?')
65
+ end
56
66
  false
57
67
  end
58
68
 
@@ -66,7 +76,11 @@ module Legion
66
76
 
67
77
  require 'legion/extensions/llm/openai/transport/messages/registry_event'
68
78
  message_class_defined?
69
- rescue LoadError
79
+ rescue LoadError => e
80
+ if respond_to?(:handle_exception)
81
+ handle_exception(e, level: :debug, handled: true,
82
+ operation: 'transport_message_available?')
83
+ end
70
84
  false
71
85
  end
72
86
 
@@ -77,18 +91,6 @@ module Legion
77
91
  def message_class
78
92
  ::Legion::Extensions::Llm::Openai::Transport::Messages::RegistryEvent
79
93
  end
80
-
81
- def log_publish_failure(error, level: :warn)
82
- message = "[lex-llm-openai] 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
94
  end
93
95
  end
94
96
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Openai
7
- VERSION = '0.1.6'
7
+ VERSION = '0.1.8'
8
8
  end
9
9
  end
10
10
  end
@@ -12,6 +12,7 @@ module Legion
12
12
  # Openai provider extension namespace.
13
13
  module Openai
14
14
  extend ::Legion::Extensions::Core if ::Legion::Extensions.const_defined?(:Core, false)
15
+ extend ::Legion::Logging::Helper if defined?(::Legion::Logging::Helper)
15
16
 
16
17
  PROVIDER_FAMILY = :openai
17
18
 
@@ -39,3 +40,6 @@ end
39
40
 
40
41
  Legion::Extensions::Llm::Provider.register(Legion::Extensions::Llm::Openai::PROVIDER_FAMILY,
41
42
  Legion::Extensions::Llm::Openai::Provider)
43
+ if defined?(Legion::Logging::Helper) && Legion::Extensions::Llm::Openai.respond_to?(:log)
44
+ Legion::Extensions::Llm::Openai.log.info('Registered OpenAI provider for :openai family')
45
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-openai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO