lex-llm-openai 0.3.10 → 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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +214 -59
- data/lib/legion/extensions/llm/openai/actors/discovery_refresh.rb +48 -0
- data/lib/legion/extensions/llm/openai/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 570527e7fb80e2b480eafd36844264bbb59ccfbc11fc4424213cc57cc1e539ea
|
|
4
|
+
data.tar.gz: 9ec953777f2f1b2a0c91dd63c697c5760c9264b6577ded5a50d39ae6ced7d991
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 077412edd3903af264268d9863f7ce69f522940d12abc47fb63b9f948ef607d5b39da14a8d5bf42912fd2143494577882aaf34c2656fd4df6d91ed3dadf4d09d
|
|
7
|
+
data.tar.gz: f6b2c1f473e83012b548d80763320b8d66263ade5438b13743b35b7bd4716bb72f2bf333a03679608f79dd520682b5c6d3f1778c7c4197eecde8869aa0338f57
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -2,53 +2,138 @@
|
|
|
2
2
|
|
|
3
3
|
LegionIO LLM provider extension for OpenAI.
|
|
4
4
|
|
|
5
|
-
This gem
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|--
|
|
34
|
-
|
|
|
35
|
-
|--
|
|
36
|
-
|
|
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
|
-
|
|
72
|
+
### Design Boundaries
|
|
40
73
|
|
|
41
|
-
|
|
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
|
-
|
|
79
|
+
## Instance Discovery
|
|
44
80
|
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
63
|
-
#
|
|
64
|
-
#
|
|
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
|
-
##
|
|
174
|
+
## Model Capability Map
|
|
71
175
|
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
|
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
|
|
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
|
|
121
|
-
bundle exec rubocop
|
|
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
|
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.
|
|
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
|