lex-llm-openai 0.1.8 → 0.3.0
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/.rubocop.yml +4 -0
- data/CHANGELOG.md +16 -0
- data/lex-llm-openai.gemspec +1 -1
- data/lib/legion/extensions/llm/openai/provider.rb +134 -16
- data/lib/legion/extensions/llm/openai/version.rb +1 -1
- data/lib/legion/extensions/llm/openai.rb +64 -19
- metadata +3 -7
- data/lib/legion/extensions/llm/openai/registry_event_builder.rb +0 -78
- data/lib/legion/extensions/llm/openai/registry_publisher.rb +0 -98
- data/lib/legion/extensions/llm/openai/transport/exchanges/llm_registry.rb +0 -24
- data/lib/legion/extensions/llm/openai/transport/messages/registry_event.rb +0 -42
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3cd48c5acd857910771717afe92892f537270d62a6f6d24d0f71e2b5805abb9d
|
|
4
|
+
data.tar.gz: c8f1a0644cb7cf1fe993f6d17b5aba3167e56d8f44f04fe0e0d6afe8d70a7146
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f5a9d6f495bb5fac304ef891d5f2f93353ff0d6909c54c506a448030e67791f7b79fbffb546e8b7cf94b5d8d8eef1f4b8122bd5df7115e47ff537baa33e8483
|
|
7
|
+
data.tar.gz: 7f40adfce35ab368b2db2d82a603fe48f2de269e13a093cab8adc15a946b80e2d02792fd97cf400895f55a18529f140d630e7dd4264d1f0799aa3328dd71908d
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - 2026-05-01
|
|
4
|
+
|
|
5
|
+
- Add auto-discovery via CredentialSources and AutoRegistration from lex-llm 0.3.0
|
|
6
|
+
- Self-register discovered instances into Call::Registry at require-time
|
|
7
|
+
- Require lex-llm >= 0.3.0
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - 2026-04-30
|
|
11
|
+
- **BREAKING**: Adopt base contract from lex-llm 0.1.9; require `lex-llm >= 0.1.9`
|
|
12
|
+
- Replace `provider_settings`-based `default_settings` with flat provider defaults (enabled, default_model, api_key, etc.)
|
|
13
|
+
- Remove deprecated `Provider.register` call; configuration options are now registered at class-load time
|
|
14
|
+
- Delete local `RegistryPublisher` and `RegistryEventBuilder`; use parameterized base classes from lex-llm
|
|
15
|
+
- Delete local `transport/` directory (exchanges, messages); use shared transport from lex-llm
|
|
16
|
+
- Add static `CAPABILITY_MAP` for known OpenAI model families; `list_models` now returns `Model::Info` structs directly
|
|
17
|
+
- `list_models` no longer delegates to `parse_list_models_response`; builds `Model::Info` via the static capability map
|
|
18
|
+
|
|
3
19
|
## [0.1.8] - 2026-04-30
|
|
4
20
|
- Add Legion::Logging::Helper to all modules and classes for structured observability
|
|
5
21
|
- Replace bare rescue blocks with handle_exception for unified error telemetry
|
data/lex-llm-openai.gemspec
CHANGED
|
@@ -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.
|
|
29
|
+
spec.add_dependency 'lex-llm', '>= 0.3.0'
|
|
30
30
|
end
|
|
@@ -9,7 +9,84 @@ 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
|
|
12
|
+
include Legion::Logging::Helper
|
|
13
|
+
|
|
14
|
+
# ── Static capability map for known OpenAI model families ──────
|
|
15
|
+
# Maps model-id prefixes to a set of capabilities and modality
|
|
16
|
+
# vectors. Used by list_models to build Model::Info structs from
|
|
17
|
+
# the raw /v1/models response.
|
|
18
|
+
CAPABILITY_MAP = {
|
|
19
|
+
'gpt-4o' => {
|
|
20
|
+
capabilities: %i[completion streaming function_calling vision structured_output],
|
|
21
|
+
modalities_input: %w[text image audio],
|
|
22
|
+
modalities_output: %w[text]
|
|
23
|
+
},
|
|
24
|
+
'gpt-4.1' => {
|
|
25
|
+
capabilities: %i[completion streaming function_calling vision structured_output],
|
|
26
|
+
modalities_input: %w[text image],
|
|
27
|
+
modalities_output: %w[text]
|
|
28
|
+
},
|
|
29
|
+
'gpt-4' => {
|
|
30
|
+
capabilities: %i[completion streaming function_calling vision],
|
|
31
|
+
modalities_input: %w[text image],
|
|
32
|
+
modalities_output: %w[text]
|
|
33
|
+
},
|
|
34
|
+
'gpt-5' => {
|
|
35
|
+
capabilities: %i[completion streaming function_calling vision structured_output reasoning],
|
|
36
|
+
modalities_input: %w[text image],
|
|
37
|
+
modalities_output: %w[text]
|
|
38
|
+
},
|
|
39
|
+
'o4' => {
|
|
40
|
+
capabilities: %i[completion streaming function_calling vision reasoning],
|
|
41
|
+
modalities_input: %w[text image],
|
|
42
|
+
modalities_output: %w[text]
|
|
43
|
+
},
|
|
44
|
+
'o3' => {
|
|
45
|
+
capabilities: %i[completion streaming function_calling vision reasoning],
|
|
46
|
+
modalities_input: %w[text image],
|
|
47
|
+
modalities_output: %w[text]
|
|
48
|
+
},
|
|
49
|
+
'o1' => {
|
|
50
|
+
capabilities: %i[completion streaming function_calling vision reasoning],
|
|
51
|
+
modalities_input: %w[text image],
|
|
52
|
+
modalities_output: %w[text]
|
|
53
|
+
},
|
|
54
|
+
'text-embedding-' => {
|
|
55
|
+
capabilities: %i[embedding],
|
|
56
|
+
modalities_input: %w[text],
|
|
57
|
+
modalities_output: %w[embeddings]
|
|
58
|
+
},
|
|
59
|
+
'omni-moderation' => {
|
|
60
|
+
capabilities: %i[moderation],
|
|
61
|
+
modalities_input: %w[text image],
|
|
62
|
+
modalities_output: %w[moderation]
|
|
63
|
+
},
|
|
64
|
+
'text-moderation' => {
|
|
65
|
+
capabilities: %i[moderation],
|
|
66
|
+
modalities_input: %w[text],
|
|
67
|
+
modalities_output: %w[moderation]
|
|
68
|
+
},
|
|
69
|
+
'gpt-image' => {
|
|
70
|
+
capabilities: %i[image_generation],
|
|
71
|
+
modalities_input: %w[text image],
|
|
72
|
+
modalities_output: %w[image]
|
|
73
|
+
},
|
|
74
|
+
'dall-e' => {
|
|
75
|
+
capabilities: %i[image_generation],
|
|
76
|
+
modalities_input: %w[text],
|
|
77
|
+
modalities_output: %w[image]
|
|
78
|
+
},
|
|
79
|
+
'whisper' => {
|
|
80
|
+
capabilities: %i[audio_transcription],
|
|
81
|
+
modalities_input: %w[audio],
|
|
82
|
+
modalities_output: %w[text]
|
|
83
|
+
},
|
|
84
|
+
'tts' => {
|
|
85
|
+
capabilities: %i[audio_generation],
|
|
86
|
+
modalities_input: %w[text],
|
|
87
|
+
modalities_output: %w[audio]
|
|
88
|
+
}
|
|
89
|
+
}.freeze
|
|
13
90
|
|
|
14
91
|
class << self
|
|
15
92
|
attr_writer :registry_publisher
|
|
@@ -30,7 +107,7 @@ module Legion
|
|
|
30
107
|
def capabilities = Capabilities
|
|
31
108
|
|
|
32
109
|
def registry_publisher
|
|
33
|
-
@registry_publisher ||= RegistryPublisher.new
|
|
110
|
+
@registry_publisher ||= Legion::Extensions::Llm::RegistryPublisher.new(provider_family: :openai)
|
|
34
111
|
end
|
|
35
112
|
end
|
|
36
113
|
|
|
@@ -95,32 +172,66 @@ module Legion
|
|
|
95
172
|
def images_url(with: nil, mask: nil) = super
|
|
96
173
|
|
|
97
174
|
def retrieve_model(model)
|
|
98
|
-
log.info("Retrieving model: #{model}")
|
|
175
|
+
log.info("Retrieving model: #{model}")
|
|
99
176
|
connection.get("#{models_url}/#{model}").body
|
|
100
177
|
rescue StandardError => e
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
operation: 'retrieve_model')
|
|
104
|
-
end
|
|
178
|
+
handle_exception(e, level: :error, handled: true,
|
|
179
|
+
operation: 'retrieve_model')
|
|
105
180
|
raise
|
|
106
181
|
end
|
|
107
182
|
|
|
108
183
|
def list_models
|
|
109
|
-
log.info('Listing OpenAI models')
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
184
|
+
log.info('Listing OpenAI models')
|
|
185
|
+
raw = connection.get(models_url)
|
|
186
|
+
models = build_model_infos(raw.body)
|
|
187
|
+
log.info("Discovered #{models.size} OpenAI models")
|
|
188
|
+
self.class.registry_publisher.publish_models_async(models, readiness: readiness(live: false))
|
|
189
|
+
models
|
|
114
190
|
rescue StandardError => e
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
operation: 'list_models')
|
|
118
|
-
end
|
|
191
|
+
handle_exception(e, level: :error, handled: true,
|
|
192
|
+
operation: 'list_models')
|
|
119
193
|
raise
|
|
120
194
|
end
|
|
121
195
|
|
|
122
196
|
private
|
|
123
197
|
|
|
198
|
+
def build_model_infos(body)
|
|
199
|
+
body.fetch('data', []).map do |raw_model|
|
|
200
|
+
id = raw_model.fetch('id')
|
|
201
|
+
cap_entry = capability_entry_for(id)
|
|
202
|
+
|
|
203
|
+
Legion::Extensions::Llm::Model::Info.new(
|
|
204
|
+
id: id,
|
|
205
|
+
name: id,
|
|
206
|
+
provider: :openai,
|
|
207
|
+
capabilities: cap_entry[:capabilities],
|
|
208
|
+
modalities_input: cap_entry[:modalities_input],
|
|
209
|
+
modalities_output: cap_entry[:modalities_output],
|
|
210
|
+
metadata: {
|
|
211
|
+
created_at: model_created_at(raw_model['created']),
|
|
212
|
+
raw: raw_model
|
|
213
|
+
}.compact
|
|
214
|
+
)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def capability_entry_for(model_id)
|
|
219
|
+
CAPABILITY_MAP.each do |prefix, entry|
|
|
220
|
+
return entry if model_id.start_with?(prefix)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Fallback for unknown models: assume chat-capable
|
|
224
|
+
{
|
|
225
|
+
capabilities: %i[completion streaming],
|
|
226
|
+
modalities_input: %w[text],
|
|
227
|
+
modalities_output: %w[text]
|
|
228
|
+
}
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def model_created_at(value)
|
|
232
|
+
value.is_a?(Numeric) ? Time.at(value).utc : value
|
|
233
|
+
end
|
|
234
|
+
|
|
124
235
|
def maybe_normalize_temperature(temperature, model)
|
|
125
236
|
model_id = model.id.to_s
|
|
126
237
|
return nil if model_id.include?('-search')
|
|
@@ -133,3 +244,10 @@ module Legion
|
|
|
133
244
|
end
|
|
134
245
|
end
|
|
135
246
|
end
|
|
247
|
+
|
|
248
|
+
# Register configuration options so Legion::Extensions::Llm::Configuration knows about them.
|
|
249
|
+
if Legion::Extensions::Llm::Configuration.respond_to?(:register_provider_options)
|
|
250
|
+
Legion::Extensions::Llm::Configuration.register_provider_options(
|
|
251
|
+
Legion::Extensions::Llm::Openai::Provider.configuration_options
|
|
252
|
+
)
|
|
253
|
+
end
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/llm'
|
|
4
|
-
require 'legion/extensions/llm/openai/registry_event_builder'
|
|
5
|
-
require 'legion/extensions/llm/openai/registry_publisher'
|
|
6
4
|
require 'legion/extensions/llm/openai/provider'
|
|
7
5
|
require 'legion/extensions/llm/openai/version'
|
|
8
6
|
|
|
@@ -12,34 +10,81 @@ module Legion
|
|
|
12
10
|
# Openai provider extension namespace.
|
|
13
11
|
module Openai
|
|
14
12
|
extend ::Legion::Extensions::Core if ::Legion::Extensions.const_defined?(:Core, false)
|
|
15
|
-
extend ::Legion::Logging::Helper
|
|
13
|
+
extend ::Legion::Logging::Helper
|
|
14
|
+
extend Legion::Extensions::Llm::AutoRegistration
|
|
16
15
|
|
|
17
16
|
PROVIDER_FAMILY = :openai
|
|
18
17
|
|
|
19
18
|
def self.default_settings
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
19
|
+
{
|
|
20
|
+
enabled: false,
|
|
21
|
+
default_model: 'gpt-4o',
|
|
22
|
+
api_key: nil,
|
|
23
|
+
organization_id: nil,
|
|
24
|
+
project_id: nil,
|
|
25
|
+
model_whitelist: [],
|
|
26
|
+
model_blacklist: [],
|
|
27
|
+
model_cache_ttl: 3600,
|
|
28
|
+
tls: { enabled: false, verify: :peer },
|
|
29
|
+
instances: {}
|
|
30
|
+
}
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def self.provider_class
|
|
34
34
|
Provider
|
|
35
35
|
end
|
|
36
|
+
|
|
37
|
+
def self.discover_instances # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
38
|
+
candidates = {}
|
|
39
|
+
|
|
40
|
+
# 1. OPENAI_API_KEY environment variable
|
|
41
|
+
env_key = CredentialSources.env('OPENAI_API_KEY')
|
|
42
|
+
candidates[:env] = { openai_api_key: env_key, tier: :frontier } if env_key
|
|
43
|
+
|
|
44
|
+
# 2. CODEX_API_KEY environment variable
|
|
45
|
+
codex_env_key = CredentialSources.env('CODEX_API_KEY')
|
|
46
|
+
candidates[:codex_env] = { openai_api_key: codex_env_key, tier: :frontier } if codex_env_key
|
|
47
|
+
|
|
48
|
+
# 3. Codex bearer token (~/.codex/auth.json chatgpt mode)
|
|
49
|
+
codex_tok = CredentialSources.codex_token
|
|
50
|
+
candidates[:codex] = { openai_api_key: codex_tok, tier: :frontier } if codex_tok
|
|
51
|
+
|
|
52
|
+
# 4. Codex OPENAI_API_KEY from ~/.codex/auth.json
|
|
53
|
+
codex_key = CredentialSources.codex_openai_key
|
|
54
|
+
candidates[:codex_key] = { openai_api_key: codex_key, tier: :frontier } if codex_key
|
|
55
|
+
|
|
56
|
+
# 5. Claude config openaiApiKey
|
|
57
|
+
claude_key = CredentialSources.claude_config_value(:openaiApiKey)
|
|
58
|
+
candidates[:claude] = { openai_api_key: claude_key, tier: :frontier } if claude_key
|
|
59
|
+
|
|
60
|
+
# 6. Extension settings
|
|
61
|
+
settings_config = CredentialSources.setting(:extensions, :llm, :openai)
|
|
62
|
+
if settings_config.is_a?(Hash) && !settings_config.empty?
|
|
63
|
+
settings_key = settings_config[:api_key] || settings_config['api_key']
|
|
64
|
+
if settings_key
|
|
65
|
+
candidates[:settings] = settings_config.merge(
|
|
66
|
+
openai_api_key: settings_key,
|
|
67
|
+
tier: :frontier
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# 7. Gateway instances from extension settings
|
|
73
|
+
gateways = CredentialSources.setting(:extensions, :llm, :openai, :gateways)
|
|
74
|
+
if gateways.is_a?(Hash)
|
|
75
|
+
gateways.each do |name, config|
|
|
76
|
+
next unless config.is_a?(Hash)
|
|
77
|
+
|
|
78
|
+
candidates[name.to_sym] = config.merge(tier: :openai_compat)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# 8. Dedup
|
|
83
|
+
CredentialSources.dedup_credentials(candidates)
|
|
84
|
+
end
|
|
36
85
|
end
|
|
37
86
|
end
|
|
38
87
|
end
|
|
39
88
|
end
|
|
40
89
|
|
|
41
|
-
Legion::Extensions::Llm::
|
|
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
|
|
90
|
+
Legion::Extensions::Llm::Openai.register_discovered_instances
|
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.
|
|
4
|
+
version: 0.3.0
|
|
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.
|
|
60
|
+
version: 0.3.0
|
|
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.
|
|
67
|
+
version: 0.3.0
|
|
68
68
|
description: OpenAI provider integration for the LegionIO LLM routing framework.
|
|
69
69
|
email:
|
|
70
70
|
- matthewdiverson@gmail.com
|
|
@@ -84,10 +84,6 @@ files:
|
|
|
84
84
|
- lex-llm-openai.gemspec
|
|
85
85
|
- lib/legion/extensions/llm/openai.rb
|
|
86
86
|
- lib/legion/extensions/llm/openai/provider.rb
|
|
87
|
-
- lib/legion/extensions/llm/openai/registry_event_builder.rb
|
|
88
|
-
- lib/legion/extensions/llm/openai/registry_publisher.rb
|
|
89
|
-
- lib/legion/extensions/llm/openai/transport/exchanges/llm_registry.rb
|
|
90
|
-
- lib/legion/extensions/llm/openai/transport/messages/registry_event.rb
|
|
91
87
|
- lib/legion/extensions/llm/openai/version.rb
|
|
92
88
|
homepage: https://github.com/LegionIO/lex-llm-openai
|
|
93
89
|
licenses:
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Legion
|
|
4
|
-
module Extensions
|
|
5
|
-
module Llm
|
|
6
|
-
module Openai
|
|
7
|
-
# Builds sanitized lex-llm registry envelopes for OpenAI provider state.
|
|
8
|
-
class RegistryEventBuilder
|
|
9
|
-
include Legion::Logging::Helper if defined?(Legion::Logging::Helper)
|
|
10
|
-
|
|
11
|
-
def model_available(model, readiness:)
|
|
12
|
-
registry_event_class.available(
|
|
13
|
-
model_offering(model),
|
|
14
|
-
runtime: runtime_metadata,
|
|
15
|
-
health: model_health(readiness),
|
|
16
|
-
metadata: model_metadata(model)
|
|
17
|
-
)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
def model_offering(model)
|
|
23
|
-
{
|
|
24
|
-
provider_family: :openai,
|
|
25
|
-
provider_instance: provider_instance,
|
|
26
|
-
transport: :http,
|
|
27
|
-
model: model.id,
|
|
28
|
-
usage_type: usage_type_for(model),
|
|
29
|
-
capabilities: Array(model.capabilities).map(&:to_sym),
|
|
30
|
-
limits: model_limits(model),
|
|
31
|
-
metadata: { lex: :llm_openai, model_name: model.name }.compact
|
|
32
|
-
}
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def model_health(readiness)
|
|
36
|
-
ready = readiness.fetch(:ready, true) == true
|
|
37
|
-
{ ready:, status: ready ? :available : :degraded }
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def model_metadata(model)
|
|
41
|
-
{ extension: :lex_llm_openai, provider: :openai, model_type: model.type }
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def runtime_metadata
|
|
45
|
-
{ node: provider_instance }
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def model_limits(model)
|
|
49
|
-
{
|
|
50
|
-
context_window: model.context_window,
|
|
51
|
-
max_output_tokens: model.max_output_tokens
|
|
52
|
-
}.compact
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def usage_type_for(model)
|
|
56
|
-
model.type == 'embedding' ? :embedding : :inference
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def provider_instance
|
|
60
|
-
configured_node = (::Legion::Settings.dig(:node, :canonical_name) if defined?(::Legion::Settings))
|
|
61
|
-
value = configured_node.to_s.strip
|
|
62
|
-
value.empty? ? :openai : value.to_sym
|
|
63
|
-
rescue StandardError => e
|
|
64
|
-
if respond_to?(:handle_exception)
|
|
65
|
-
handle_exception(e, level: :debug, handled: true,
|
|
66
|
-
operation: 'provider_instance')
|
|
67
|
-
end
|
|
68
|
-
:openai
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def registry_event_class
|
|
72
|
-
::Legion::Extensions::Llm::Routing::RegistryEvent
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Legion
|
|
4
|
-
module Extensions
|
|
5
|
-
module Llm
|
|
6
|
-
module Openai
|
|
7
|
-
# Best-effort publisher for OpenAI provider availability events.
|
|
8
|
-
class RegistryPublisher
|
|
9
|
-
include Legion::Logging::Helper if defined?(Legion::Logging::Helper)
|
|
10
|
-
|
|
11
|
-
APP_ID = 'lex-llm-openai'
|
|
12
|
-
|
|
13
|
-
def initialize(builder: RegistryEventBuilder.new)
|
|
14
|
-
@builder = builder
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def publish_models_async(models, readiness:)
|
|
18
|
-
log.info("Publishing #{Array(models).size} model(s) to llm.registry") if respond_to?(:log)
|
|
19
|
-
schedule do
|
|
20
|
-
Array(models).each do |model|
|
|
21
|
-
publish_event(@builder.model_available(model, readiness:))
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
private
|
|
27
|
-
|
|
28
|
-
def schedule(&)
|
|
29
|
-
return false unless publishing_available?
|
|
30
|
-
|
|
31
|
-
Thread.new do
|
|
32
|
-
Thread.current.abort_on_exception = false
|
|
33
|
-
yield
|
|
34
|
-
rescue StandardError => e
|
|
35
|
-
handle_exception(e, level: :debug, handled: true, operation: 'schedule') if respond_to?(:handle_exception)
|
|
36
|
-
end
|
|
37
|
-
rescue StandardError => e
|
|
38
|
-
handle_exception(e, level: :debug, handled: true, operation: 'schedule') if respond_to?(:handle_exception)
|
|
39
|
-
false
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def publish_event(event)
|
|
43
|
-
return false unless publishing_available?
|
|
44
|
-
|
|
45
|
-
message_class.new(event:, app_id: APP_ID).publish(spool: false)
|
|
46
|
-
rescue StandardError => e
|
|
47
|
-
if respond_to?(:handle_exception)
|
|
48
|
-
handle_exception(e, level: :warn, handled: true,
|
|
49
|
-
operation: 'publish_event')
|
|
50
|
-
end
|
|
51
|
-
false
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def publishing_available?
|
|
55
|
-
return false unless registry_event_available?
|
|
56
|
-
return false unless transport_message_available?
|
|
57
|
-
return true unless defined?(::Legion::Transport::Connection)
|
|
58
|
-
return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
|
|
59
|
-
|
|
60
|
-
::Legion::Transport::Connection.session_open?
|
|
61
|
-
rescue StandardError => e
|
|
62
|
-
if respond_to?(:handle_exception)
|
|
63
|
-
handle_exception(e, level: :debug, handled: true,
|
|
64
|
-
operation: 'publishing_available?')
|
|
65
|
-
end
|
|
66
|
-
false
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def registry_event_available?
|
|
70
|
-
defined?(::Legion::Extensions::Llm::Routing::RegistryEvent)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def transport_message_available?
|
|
74
|
-
return true if message_class_defined?
|
|
75
|
-
return false unless defined?(::Legion::Transport::Message) && defined?(::Legion::Transport::Exchange)
|
|
76
|
-
|
|
77
|
-
require 'legion/extensions/llm/openai/transport/messages/registry_event'
|
|
78
|
-
message_class_defined?
|
|
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
|
|
84
|
-
false
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def message_class_defined?
|
|
88
|
-
defined?(::Legion::Extensions::Llm::Openai::Transport::Messages::RegistryEvent)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def message_class
|
|
92
|
-
::Legion::Extensions::Llm::Openai::Transport::Messages::RegistryEvent
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Legion
|
|
4
|
-
module Extensions
|
|
5
|
-
module Llm
|
|
6
|
-
module Openai
|
|
7
|
-
module Transport
|
|
8
|
-
module Exchanges
|
|
9
|
-
# Topic exchange for OpenAI 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
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'legion/extensions/llm/openai/transport/exchanges/llm_registry'
|
|
4
|
-
|
|
5
|
-
module Legion
|
|
6
|
-
module Extensions
|
|
7
|
-
module Llm
|
|
8
|
-
module Openai
|
|
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
|