lex-llm-openai 0.3.9 → 0.3.11

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: 808223596350fde35cbbf0145a411842ce1c0a3009d431edd354ce500732666c
4
- data.tar.gz: 9ed2580c07f4d4b1c35f9b90e8108b4ddbd9f74fa032998447b3639dda67e4db
3
+ metadata.gz: 570527e7fb80e2b480eafd36844264bbb59ccfbc11fc4424213cc57cc1e539ea
4
+ data.tar.gz: 9ec953777f2f1b2a0c91dd63c697c5760c9264b6577ded5a50d39ae6ced7d991
5
5
  SHA512:
6
- metadata.gz: a07fffb4a8a5652e3b14303102bc2811915efc374b32369f50e1d39a6397d44a8a2b7822aebedd300af37cecb1d447cbf3b517fdf8502138077de149c9e9e9ea
7
- data.tar.gz: 51f75db914319c8c086d8ca63a0127b6ed79d21ee92dd1a3c25c45bc9082ba0b1fde55792f8e6040e20c247baad09147634b079d953606e3a8031063422ad7f2
6
+ metadata.gz: 077412edd3903af264268d9863f7ce69f522940d12abc47fb63b9f948ef607d5b39da14a8d5bf42912fd2143494577882aaf34c2656fd4df6d91ed3dadf4d09d
7
+ data.tar.gz: f6b2c1f473e83012b548d80763320b8d66263ade5438b13743b35b7bd4716bb72f2bf333a03679608f79dd520682b5c6d3f1778c7c4197eecde8869aa0338f57
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.11 - 2026-06-05
4
+
5
+ - Fix missing top-level documentation comment in `DiscoveryRefresh` actor (RuboCop `Style/Documentation`).
6
+
7
+ ## 0.3.10 - 2026-05-21
8
+
9
+ - api_base reads from settings[:endpoint] fallback
10
+ - Identity headers included via base provider
11
+
12
+
3
13
  ## 0.3.9 - 2026-05-13
4
14
 
5
15
  - Change `default_model` from `gpt-4o` to `gpt-5.5` in provider default settings and instance discovery fallback.
data/README.md CHANGED
@@ -2,53 +2,138 @@
2
2
 
3
3
  LegionIO LLM provider extension for OpenAI.
4
4
 
5
- This gem lives under `Legion::Extensions::Llm::Openai` and depends on `lex-llm >= 0.4.3` for shared provider-neutral routing, response normalization, fleet envelopes, fleet responder execution, and schema primitives.
5
+ This gem provides the `:openai` provider family implementation, enabling LegionIO to route chat, streaming, embedding, moderation, image, and audio requests to OpenAI-compatible APIs through the shared `lex-llm` provider contract.
6
6
 
7
- Load it with `require 'legion/extensions/llm/openai'`.
7
+ **Namespace:** `Legion::Extensions::Llm::Openai`
8
+ **Version:** 0.3.11
9
+ **Load:** `require 'legion/extensions/llm/openai'`
10
+
11
+ ## Quick Start
12
+
13
+ ```ruby
14
+ require 'legion/extensions/llm/openai'
15
+
16
+ # Configure via the shared LLM configuration API
17
+ Legion::Extensions::Llm.configure do |config|
18
+ config.openai_api_key = ENV.fetch("OPENAI_API_KEY")
19
+ config.default_model = "gpt-5.5"
20
+ end
21
+
22
+ # Use through the standard provider interface
23
+ provider = Legion::Extensions::Llm::Openai::Provider.new
24
+ provider.chat(model: "gpt-5.5", messages: [{ role: "user", content: "Hello" }])
25
+ ```
8
26
 
9
27
  ## What It Provides
10
28
 
11
- - OpenAI provider discovery under the `:openai` provider family
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
- - Provider-owned fleet request handling through `Legion::Extensions::Llm::Fleet::ProviderResponder`
29
+ | Capability | Endpoint | Notes |
30
+ |---|---|---|
31
+ | Chat completions | `POST /v1/chat/completions` | Includes function calling, vision, structured output |
32
+ | Streaming chat | Same endpoint, `stream: true` | Token usage reported via `stream_usage_supported?` |
33
+ | Model listing | `GET /v1/models` | Enriched with static capability map; publishes registry events |
34
+ | Model retrieval | `GET /v1/models/{model}` | |
35
+ | Embeddings | `POST /v1/embeddings` | |
36
+ | Moderation | `POST /v1/moderations` | |
37
+ | Image generation | `POST /v1/images/generations` | |
38
+ | Image editing | `POST /v1/images/edits` | |
39
+ | Image variation | `POST /v1/images/variations` | |
40
+ | Audio transcription | `POST /v1/audio/transcriptions` | Whisper, gpt-4o-transcribe |
28
41
 
29
42
  ## Architecture
30
43
 
31
44
  ```
32
45
  Legion::Extensions::Llm::Openai
33
- |-- Provider # OpenAI provider implementation (chat, models, embeddings, etc.)
34
- | `-- Capabilities # Model family capability predicates
35
- |-- Actor::FleetWorker # Subscription actor gate for provider-owned fleet requests
36
- `-- Runners::FleetWorker # Delegates request execution to lex-llm ProviderResponder
46
+ |-- Openai # Root module settings, discovery, auto-registration
47
+ | |-- default_settings # Provider family defaults (endpoint, models, fleet)
48
+ | |-- discover_instances # Credential scanning across env, Codex, Claude, settings
49
+ | |-- normalize_instance_config # Normalizes generic keys to canonical OpenAI keys
50
+ | `-- sanitize_instance_config # Strips temporary credential fields
51
+ |
52
+ |-- Provider # OpenAI provider implementation
53
+ | |-- Capabilities # Model family predicates (chat?, streaming?, vision?, etc.)
54
+ | |-- CAPABILITY_MAP # Static capability matrix for 14 known model families
55
+ | |-- list_models # Enriches raw API response with capability metadata
56
+ | |-- retrieve_model # Fetches single model detail
57
+ | |-- chat_url, models_url, etc. # Endpoint builders
58
+ | `-- maybe_normalize_temperature # Adjusts temperature for o*/gpt-5 reasoning models
59
+ |
60
+ |-- Actor::FleetWorker # Subscription actor for fleet request consumption
61
+ | |-- enabled? # Checks if any instance has respond_to_requests: true
62
+ | `-- Delegates to lex-llm ProviderResponder
63
+ |
64
+ |-- Actor::DiscoveryRefresh # Periodic actor that refreshes the model discovery cache
65
+ | |-- time # Reads discovery_interval from settings (default 1800s)
66
+ | `-- Calls Legion::LLM::Discovery.refresh_discovered_models!
67
+ |
68
+ `-- Runners::FleetWorker # Execution entrypoint for fleet requests
69
+ `-- handle_fleet_request # Routes to lex-llm ProviderResponder.call
37
70
  ```
38
71
 
39
- Registry publishing, event envelope construction, fleet protocol handling, and fleet response/error transport live in `lex-llm`. This provider intentionally does not depend on `legion-llm` at runtime.
72
+ ### Design Boundaries
40
73
 
41
- ## Observability
74
+ - **Response normalization, request payload mapping** lives in `Lex-llm::Provider::OpenAICompatible` (mixed in)
75
+ - **Fleet responder logic** (ack/reject, response publication) lives in `Lex-llm::Fleet::ProviderResponder`
76
+ - **Registry event publishing** is delegated to `Lex-llm::RegistryPublisher`
77
+ - This extension depends **only** on `lex-llm` at runtime; it does not depend on `legion-llm`
42
78
 
43
- The provider and root extension namespace use `Legion::Logging::Helper` for:
79
+ ## Instance Discovery
44
80
 
45
- - Structured `handle_exception` calls on every rescue block
46
- - Info-level action logging for model listing, model retrieval, and registry publishing
47
- - Automatic log segment derivation and component type tagging
81
+ `Openai.discover_instances` scans 7 credential sources, deduplicates by key, and injects `default_model`:
48
82
 
49
- Fleet actor and runner code stays thin and delegates execution, ack/reject handling, and response publication to the shared `lex-llm` responder helper.
83
+ | Priority | Source | Key |
84
+ |---|---|---|
85
+ | 1 | `OPENAI_API_KEY` environment variable | `:env` |
86
+ | 2 | `CODEX_API_KEY` environment variable | `:codex_env` |
87
+ | 3 | Codex bearer token (`~/.codex/auth.json`) | `:codex` |
88
+ | 4 | Codex OpenAI key (`~/.codex/auth.json`) | `:codex_key` |
89
+ | 5 | Claude config (`openaiApiKey`) | `:claude` |
90
+ | 6 | Extension settings (`extensions.llm.openai`) | `:settings` |
91
+ | 7 | Named instances in extension settings | Named keys |
92
+
93
+ ## Configuration
94
+
95
+ ### Via Legion Settings (YAML)
96
+
97
+ ```yaml
98
+ extensions:
99
+ llm:
100
+ openai:
101
+ api_key: "sk-..."
102
+ default_model: "gpt-5.5"
103
+ endpoint: "https://api.openai.com"
104
+ discovery_interval: 1800 # Seconds between model list refresh (used by DiscoveryRefresh actor)
105
+ instances:
106
+ primary:
107
+ openai_api_key: "sk-..."
108
+ openai_api_base: "https://api.openai.com"
109
+ fleet:
110
+ enabled: true
111
+ respond_to_requests: true
112
+ capabilities:
113
+ - chat
114
+ - stream_chat
115
+ - embed
116
+ - image
117
+ ```
50
118
 
51
- ## Defaults
119
+ ### Via Ruby Configuration API
120
+
121
+ ```ruby
122
+ Legion::Extensions::Llm.configure do |config|
123
+ config.openai_api_key = ENV.fetch("OPENAI_API_KEY")
124
+ config.openai_api_base = nil # defaults to https://api.openai.com
125
+ config.openai_organization_id = nil # optional OpenAI-Organization header
126
+ config.openai_project_id = nil # optional OpenAI-Project header
127
+ config.openai_use_system_role = true # include system messages in requests
128
+ config.default_model = "gpt-5.5"
129
+ config.default_embedding_model = "text-embedding-3-small"
130
+ config.default_moderation_model = "omni-moderation-latest"
131
+ config.default_image_model = "gpt-image-1"
132
+ config.default_transcription_model = "gpt-4o-transcribe"
133
+ end
134
+ ```
135
+
136
+ ### Default Settings
52
137
 
53
138
  ```ruby
54
139
  Legion::Extensions::Llm::Openai.default_settings
@@ -57,45 +142,78 @@ Legion::Extensions::Llm::Openai.default_settings
57
142
  # instances: {
58
143
  # default: {
59
144
  # endpoint: "https://api.openai.com",
145
+ # default_model: "gpt-5.5",
60
146
  # tier: :frontier,
61
147
  # transport: :http,
62
- # credentials: { api_key: "env://OPENAI_API_KEY" },
63
- # usage: { inference: true, embedding: true, moderation: true, image: true, audio: true },
64
- # limits: { concurrency: 4 }
148
+ # credentials: {
149
+ # api_key: "env://OPENAI_API_KEY",
150
+ # organization_id: nil,
151
+ # project_id: nil
152
+ # },
153
+ # usage: {
154
+ # inference: true,
155
+ # embedding: true,
156
+ # moderation: true,
157
+ # image: true,
158
+ # audio: true
159
+ # },
160
+ # limits: { concurrency: 4 },
161
+ # fleet: {
162
+ # enabled: false,
163
+ # respond_to_requests: false,
164
+ # capabilities: [:chat, :stream_chat, :embed, :image],
165
+ # lanes: [],
166
+ # concurrency: 4,
167
+ # queue_suffix: nil
168
+ # }
65
169
  # }
66
170
  # }
67
171
  # }
68
172
  ```
69
173
 
70
- ## Configuration
174
+ ## Model Capability Map
71
175
 
72
- ```ruby
73
- Legion::Extensions::Llm.configure do |config|
74
- config.openai_api_key = ENV.fetch("OPENAI_API_KEY")
75
- config.openai_api_base = nil # defaults to https://api.openai.com
76
- config.openai_organization_id = nil # optional OpenAI-Organization header
77
- config.openai_project_id = nil # optional OpenAI-Project header
78
- config.default_model = "gpt-5.2"
79
- config.default_embedding_model = "text-embedding-3-small"
80
- config.default_moderation_model = "omni-moderation-latest"
81
- config.default_image_model = "gpt-image-1"
82
- config.default_transcription_model = "gpt-4o-transcribe"
83
- end
84
- ```
176
+ The provider maintains a static `CAPABILITY_MAP` covering 14 OpenAI model families. Each entry declares capabilities, input/output modalities, and context window size.
85
177
 
86
- ## Dependencies
178
+ | Prefix | Capabilities | Input | Output | Context |
179
+ |---|---|---|---|---|
180
+ | `gpt-4o` | completion, streaming, function_calling, vision, structured_output | text, image, audio | text | 128K |
181
+ | `gpt-4.1` | completion, streaming, function_calling, vision, structured_output | text, image | text | 1M |
182
+ | `gpt-4` | completion, streaming, function_calling, vision | text, image | text | 128K |
183
+ | `gpt-5` | completion, streaming, function_calling, vision, structured_output, reasoning | text, image | text | 1M |
184
+ | `o4` | completion, streaming, function_calling, vision, reasoning | text, image | text | 200K |
185
+ | `o3` | completion, streaming, function_calling, vision, reasoning | text, image | text | 200K |
186
+ | `o1` | completion, streaming, function_calling, vision, reasoning | text, image | text | 200K |
187
+ | `text-embedding-*` | embedding | text | embeddings | 8K |
188
+ | `omni-moderation` | moderation | text, image | moderation | - |
189
+ | `text-moderation` | moderation | text | moderation | - |
190
+ | `gpt-image` | image_generation | text, image | image | - |
191
+ | `dall-e` | image_generation | text | image | - |
192
+ | `whisper` | audio_transcription | audio | text | - |
193
+ | `tts` | audio_generation | text | audio | - |
87
194
 
88
- | Gem | Purpose |
89
- |-----|---------|
90
- | `lex-llm` (>= 0.4.3) | Shared provider contract, response normalization, fleet settings, routing, and fleet responder execution |
91
- | `legion-transport` (>= 1.4.14) | AMQP subscriptions and replies |
92
- | `legion-json` (>= 1.2.1) | JSON serialization |
93
- | `legion-logging` (>= 1.3.2) | Structured logging via Helper |
94
- | `legion-settings` (>= 1.3.14) | Configuration management |
195
+ Unknown models default to `{ capabilities: [:completion, :streaming], modalities: { input: ["text"], output: ["text"] } }`.
196
+
197
+ ## Capability Predicates
198
+
199
+ `Provider::Capabilities` provides module functions for model routing decisions:
200
+
201
+ | Method | Matches |
202
+ |---|---|
203
+ | `chat?(model)` | Any model that is not embedding, moderation, image, audio, tts, realtime, or sora |
204
+ | `streaming?(model)` | Same as `chat?` |
205
+ | `functions?(model)` | Models starting with `gpt` or `o\d` |
206
+ | `vision?(model)` | Models starting with `gpt`, `o\d`, or `omni-moderation` |
207
+ | `embeddings?(model)` | Models starting with `text-embedding-` |
208
+ | `moderation?(model)` | Models containing `moderation` |
209
+ | `images?(model)` | Models starting with `gpt-image` or `dall-e` |
210
+ | `audio_transcription?(model)` | Models matching `gpt-4o.*transcribe` or `whisper` |
95
211
 
96
212
  ## Fleet Responder
97
213
 
98
- Provider instances can opt in to consuming Legion LLM fleet requests. The provider-owned fleet actor only starts when at least one configured instance enables `respond_to_requests`, and the runner delegates execution to `Legion::Extensions::Llm::Fleet::ProviderResponder` from `lex-llm`.
214
+ Provider instances can opt in to consuming Legion LLM fleet requests via the shared `ProviderResponder`. The fleet actor starts automatically when any instance has `respond_to_requests: true`.
215
+
216
+ ### Fleet YAML Configuration
99
217
 
100
218
  ```yaml
101
219
  extensions:
@@ -113,12 +231,49 @@ extensions:
113
231
  - image
114
232
  ```
115
233
 
234
+ ### Fleet Components
235
+
236
+ | Class | Role |
237
+ |---|---|
238
+ | `Actor::FleetWorker` | Subscription actor; checks `enabled?` against discovered instances |
239
+ | `Runners::FleetWorker.handle_fleet_request` | Execution entrypoint; delegates to `ProviderResponder.call` |
240
+
241
+ ## Observability
242
+
243
+ All classes include `Legion::Logging::Helper`:
244
+
245
+ - **Structured error handling:** Every `rescue` calls `handle_exception` with operation context
246
+ - **Debug-level request telemetry:** Model listing, retrieval, fleet dispatch, discovery refresh
247
+ - **Info-level action logging:** Registry publishing, instance discovery results
248
+ - **Automatic segment derivation:** Log lines tagged with provider family and component type
249
+
250
+ ## Dependencies
251
+
252
+ | Gem | Purpose |
253
+ |-----|---------|
254
+ | `lex-llm` (>= 0.4.3) | Provider contract, OpenAICompatible mixin, fleet responder, registry publisher |
255
+ | `legion-transport` (>= 1.4.14) | AMQP subscriptions and replies |
256
+ | `legion-json` (>= 1.2.1) | JSON serialization |
257
+ | `legion-logging` (>= 1.3.2) | Structured logging |
258
+ | `legion-settings` (>= 1.3.14) | Configuration management |
259
+
260
+ ## Key Files
261
+
262
+ | File | Purpose |
263
+ |------|---------|
264
+ | `lib/legion/extensions/llm/openai.rb` | Root module, settings, instance discovery, auto-registration |
265
+ | `lib/legion/extensions/llm/openai/provider.rb` | Provider implementation, capability map, API methods |
266
+ | `lib/legion/extensions/llm/openai/actors/discovery_refresh.rb` | Periodic model cache refresh actor |
267
+ | `lib/legion/extensions/llm/openai/actors/fleet_worker.rb` | Fleet request subscription actor |
268
+ | `lib/legion/extensions/llm/openai/runners/fleet_worker.rb` | Fleet request execution runner |
269
+ | `lib/legion/extensions/llm/openai/version.rb` | Version constant |
270
+
116
271
  ## Development
117
272
 
118
273
  ```bash
119
274
  bundle install
120
- bundle exec rspec --format json --out tmp/rspec_results.json --format progress --out tmp/rspec_progress.txt
121
- bundle exec rubocop -A
275
+ bundle exec rspec
276
+ bundle exec rubocop
122
277
  ```
123
278
 
124
279
  ## License
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'legion/extensions/actors/every'
5
+ rescue LoadError => e
6
+ warn(e.message) if $VERBOSE
7
+ end
8
+
9
+ return unless defined?(Legion::Extensions::Actors::Every)
10
+
11
+ module Legion
12
+ module Extensions
13
+ module Llm
14
+ module Openai
15
+ module Actor
16
+ # Periodically refreshes the OpenAI model discovery cache.
17
+ class DiscoveryRefresh < Legion::Extensions::Actors::Every
18
+ include Legion::Logging::Helper
19
+
20
+ REFRESH_INTERVAL = 1800
21
+
22
+ def runner_class = self.class
23
+ def runner_function = 'manual'
24
+ def run_now? = true
25
+ def use_runner? = false
26
+ def check_subtask? = false
27
+ def generate_task? = false
28
+
29
+ def time
30
+ return REFRESH_INTERVAL unless defined?(Legion::Settings)
31
+
32
+ Legion::Settings.dig(:extensions, :llm, :openai, :discovery_interval) || REFRESH_INTERVAL
33
+ end
34
+
35
+ def manual
36
+ log.debug('[openai][discovery_refresh] refreshing model list')
37
+ return unless defined?(Legion::LLM::Discovery)
38
+
39
+ Legion::LLM::Discovery.refresh_discovered_models!(provider: :openai)
40
+ rescue StandardError => e
41
+ handle_exception(e, level: :warn, handled: true, operation: 'openai.actor.discovery_refresh')
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -161,16 +161,20 @@ module Legion
161
161
 
162
162
  def stream_usage_supported? = true
163
163
 
164
+ def settings
165
+ Openai.default_settings
166
+ end
167
+
164
168
  def api_base
165
- config.openai_api_base || 'https://api.openai.com'
169
+ config.openai_api_base || settings[:endpoint] || 'https://api.openai.com'
166
170
  end
167
171
 
168
172
  def headers
169
- {
173
+ identity_headers.merge({
170
174
  'Authorization' => "Bearer #{config.openai_api_key}",
171
175
  'OpenAI-Organization' => config.openai_organization_id,
172
176
  'OpenAI-Project' => config.openai_project_id
173
- }.compact
177
+ }.compact)
174
178
  end
175
179
 
176
180
  def chat_url = completion_url
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Openai
7
- VERSION = '0.3.9'
7
+ VERSION = '0.3.11'
8
8
  end
9
9
  end
10
10
  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.3.9
4
+ version: 0.3.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -97,6 +97,7 @@ files:
97
97
  - README.md
98
98
  - lex-llm-openai.gemspec
99
99
  - lib/legion/extensions/llm/openai.rb
100
+ - lib/legion/extensions/llm/openai/actors/discovery_refresh.rb
100
101
  - lib/legion/extensions/llm/openai/actors/fleet_worker.rb
101
102
  - lib/legion/extensions/llm/openai/provider.rb
102
103
  - lib/legion/extensions/llm/openai/runners/fleet_worker.rb