lex-llm-mlx 0.3.13 → 0.5.2
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 +8 -0
- data/CHANGELOG.md +73 -0
- data/Gemfile +2 -0
- data/lex-llm-mlx.gemspec +6 -2
- data/lib/legion/extensions/llm/mlx/actors/discovery_refresh.rb +788 -94
- data/lib/legion/extensions/llm/mlx/actors/fleet_worker.rb +6 -1
- data/lib/legion/extensions/llm/mlx/provider.rb +169 -150
- data/lib/legion/extensions/llm/mlx/runners/fleet_worker.rb +23 -4
- data/lib/legion/extensions/llm/mlx/version.rb +1 -1
- data/lib/legion/extensions/llm/mlx.rb +59 -25
- metadata +19 -5
|
@@ -20,14 +20,19 @@ module Legion
|
|
|
20
20
|
module Actor
|
|
21
21
|
# Subscription actor for MLX fleet request consumption.
|
|
22
22
|
class FleetWorker < Legion::Extensions::Actors::Subscription
|
|
23
|
+
# The runner module as a constant: the Subscription dispatch
|
|
24
|
+
# path calls `runner_class.send(runner_function, **message)`,
|
|
25
|
+
# which a String cannot receive.
|
|
23
26
|
def runner_class
|
|
24
|
-
|
|
27
|
+
Legion::Extensions::Llm::Mlx::Runners::FleetWorker
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
def runner_function
|
|
28
31
|
'handle_fleet_request'
|
|
29
32
|
end
|
|
30
33
|
|
|
34
|
+
# Call the runner module directly — no task record per
|
|
35
|
+
# inference hop.
|
|
31
36
|
def use_runner?
|
|
32
37
|
false
|
|
33
38
|
end
|
|
@@ -6,119 +6,113 @@ module Legion
|
|
|
6
6
|
module Extensions
|
|
7
7
|
module Llm
|
|
8
8
|
module Mlx
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
# Conservative capability predicates for local MLX OpenAI-compatible servers.
|
|
10
|
+
module Capabilities
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def chat?(model) = !embeddings?(model)
|
|
14
|
+
def streaming?(model) = chat?(model)
|
|
15
|
+
def vision?(model) = model_id(model).match?(/vlm|vision|llava|pixtral|qwen.*vl/i)
|
|
16
|
+
def functions?(model) = chat?(model)
|
|
17
|
+
def embeddings?(model) = model_id(model).match?(/embed|bge|e5|nomic/i)
|
|
18
|
+
|
|
19
|
+
def critical_capabilities_for(model)
|
|
20
|
+
[
|
|
21
|
+
('streaming' if streaming?(model)),
|
|
22
|
+
('function_calling' if functions?(model)),
|
|
23
|
+
('vision' if vision?(model)),
|
|
24
|
+
('embeddings' if embeddings?(model))
|
|
25
|
+
].compact
|
|
26
|
+
end
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
end
|
|
28
|
+
def model_id(model)
|
|
29
|
+
model.respond_to?(:id) ? model.id.to_s : model.to_s
|
|
27
30
|
end
|
|
31
|
+
end
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
# Capability config helpers — mix into Provider.
|
|
34
|
+
module CapabilityConfig
|
|
35
|
+
private
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
def provider_capability_config
|
|
38
|
+
conf = Legion::Extensions::Llm::CredentialSources.setting(:extensions, :llm, :mlx)
|
|
39
|
+
conf.is_a?(Hash) ? conf.to_h.except(:instances, 'instances') : {}
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
handle_exception(e, level: :warn, handled: true, operation: 'mlx.provider_capability_config')
|
|
42
|
+
{}
|
|
43
|
+
end
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
end
|
|
45
|
+
def instance_capability_config
|
|
46
|
+
cfg = config
|
|
47
|
+
result = {}
|
|
48
|
+
%i[capabilities enable_thinking enable_tools enable_streaming enable_vision enable_embeddings
|
|
49
|
+
thinking_flag tools_flag streaming_flag vision_flag embedding_flag embeddings_flag
|
|
50
|
+
tool_flag images_flag image_flag].each do |key|
|
|
51
|
+
next unless cfg.respond_to?(key)
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
val = cfg.send(key)
|
|
54
|
+
result[key] = val unless val.nil?
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
handle_exception(e, level: :warn, handled: true, operation: 'mlx.provider.instance_capability_config')
|
|
57
|
+
next
|
|
50
58
|
end
|
|
59
|
+
result
|
|
51
60
|
end
|
|
52
61
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
def model_capability_config(model_id)
|
|
63
|
+
models_conf = fetch_models_config
|
|
64
|
+
return {} unless models_conf
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
hash = models_conf.to_h
|
|
67
|
+
hash[model_id.to_s] || hash[model_id.to_sym] || {}
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
handle_exception(e, level: :warn, handled: true, operation: 'mlx.model_capability_config')
|
|
70
|
+
{}
|
|
59
71
|
end
|
|
60
72
|
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
def fetch_models_config
|
|
74
|
+
conf = config.models if config.respond_to?(:models)
|
|
75
|
+
conf ||= config[:models] if config.respond_to?(:[])
|
|
76
|
+
conf if conf.respond_to?(:to_h)
|
|
77
|
+
rescue StandardError => e
|
|
78
|
+
handle_exception(e, level: :warn, handled: true, operation: 'mlx.fetch_models_config')
|
|
79
|
+
nil
|
|
66
80
|
end
|
|
81
|
+
end
|
|
67
82
|
|
|
68
|
-
|
|
83
|
+
# Health payload helpers — mix into Provider.
|
|
84
|
+
module HealthHelpers
|
|
85
|
+
private
|
|
69
86
|
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
health_payload(raw)
|
|
74
|
-
rescue StandardError => e
|
|
75
|
-
handle_exception(e, level: :warn, handled: true, operation: 'mlx.provider.health')
|
|
87
|
+
def health_payload(raw)
|
|
88
|
+
ready = health_ready?(raw)
|
|
89
|
+
status = health_status(ready)
|
|
76
90
|
{
|
|
77
91
|
provider: :mlx,
|
|
78
92
|
instance_id: provider_instance_id,
|
|
79
|
-
status:
|
|
80
|
-
ready:
|
|
81
|
-
circuit_state:
|
|
82
|
-
|
|
83
|
-
message: e.message
|
|
93
|
+
status: status,
|
|
94
|
+
ready: ready,
|
|
95
|
+
circuit_state: circuit_state(status),
|
|
96
|
+
raw: raw
|
|
84
97
|
}
|
|
85
98
|
end
|
|
86
99
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
super.tap do |metadata|
|
|
90
|
-
self.class.registry_publisher.publish_readiness_async(metadata) if live
|
|
91
|
-
end
|
|
100
|
+
def health_ready?(raw)
|
|
101
|
+
raw.is_a?(Hash) ? raw.fetch('ready', raw.fetch(:ready, true)) : true
|
|
92
102
|
end
|
|
93
103
|
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
super.tap do |models|
|
|
97
|
-
log.info("Discovered #{Array(models).size} MLX models")
|
|
98
|
-
self.class.registry_publisher.publish_models_async(models, readiness: readiness(live: false))
|
|
99
|
-
end
|
|
104
|
+
def health_status(ready)
|
|
105
|
+
ready ? 'healthy' : 'unhealthy'
|
|
100
106
|
end
|
|
101
107
|
|
|
102
|
-
def
|
|
103
|
-
|
|
104
|
-
ctx = model_info.respond_to?(:context_length) ? model_info.context_length : nil
|
|
105
|
-
|
|
106
|
-
Legion::Extensions::Llm::Routing::ModelOffering.new(
|
|
107
|
-
provider_family: :mlx,
|
|
108
|
-
instance_id: config.respond_to?(:instance_id) ? config.instance_id : :default,
|
|
109
|
-
transport: offering_transport,
|
|
110
|
-
tier: offering_tier,
|
|
111
|
-
model: model_info.id,
|
|
112
|
-
canonical_model_alias: model_info.respond_to?(:name) ? model_info.name : nil,
|
|
113
|
-
model_family: model_info.respond_to?(:family) ? model_info.family : nil,
|
|
114
|
-
usage_type: embedding_model?(model_info.id) ? :embedding : :inference,
|
|
115
|
-
capabilities: policy[:capabilities],
|
|
116
|
-
capability_sources: policy[:sources],
|
|
117
|
-
limits: { context_window: ctx }.compact,
|
|
118
|
-
health: health,
|
|
119
|
-
metadata: offering_metadata_for(model_info).merge(capability_sources: policy[:sources])
|
|
120
|
-
)
|
|
108
|
+
def circuit_state(status)
|
|
109
|
+
status == 'healthy' ? 'closed' : 'open'
|
|
121
110
|
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Capability resolution helpers — mix into Provider.
|
|
114
|
+
module CapabilityResolution
|
|
115
|
+
private
|
|
122
116
|
|
|
123
117
|
def resolve_capability_policy(model_info)
|
|
124
118
|
Legion::Extensions::Llm::CapabilityPolicy.resolve(
|
|
@@ -142,13 +136,10 @@ module Legion
|
|
|
142
136
|
caps.is_a?(Hash) ? caps : {}
|
|
143
137
|
end
|
|
144
138
|
|
|
145
|
-
def extract_catalog_capabilities(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
caps[:vision] = true if model_id.match?(/vlm|vision|llava|pixtral|qwen.*vl/i)
|
|
150
|
-
caps[:streaming] = true unless caps[:embeddings]
|
|
151
|
-
caps
|
|
139
|
+
def extract_catalog_capabilities(_model_info)
|
|
140
|
+
# Regex-based name matching is not authoritative evidence.
|
|
141
|
+
# Unverified capability support is unknown, not promoted to supported.
|
|
142
|
+
{}
|
|
152
143
|
end
|
|
153
144
|
|
|
154
145
|
def embedding_model?(model_id)
|
|
@@ -156,84 +147,112 @@ module Legion
|
|
|
156
147
|
end
|
|
157
148
|
|
|
158
149
|
def provider_envelope_capabilities
|
|
159
|
-
|
|
150
|
+
# No capabilities are advertised at the envelope level without endpoint evidence.
|
|
151
|
+
{}
|
|
160
152
|
end
|
|
161
153
|
|
|
162
|
-
def
|
|
163
|
-
ready = health_ready?(raw)
|
|
164
|
-
status = health_status(ready)
|
|
165
|
-
|
|
154
|
+
def offering_metadata_for(model_info)
|
|
166
155
|
{
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
circuit_state: circuit_state(status),
|
|
172
|
-
raw: raw
|
|
173
|
-
}
|
|
156
|
+
raw_model: model_info.id,
|
|
157
|
+
parameter_count: model_info.respond_to?(:parameter_count) ? model_info.parameter_count : nil,
|
|
158
|
+
quantization: model_info.respond_to?(:quantization) ? model_info.quantization : nil
|
|
159
|
+
}.compact
|
|
174
160
|
end
|
|
161
|
+
end
|
|
175
162
|
|
|
176
|
-
|
|
177
|
-
|
|
163
|
+
# MLX provider implementation for local OpenAI-compatible servers.
|
|
164
|
+
class Provider < Legion::Extensions::Llm::Provider
|
|
165
|
+
include Legion::Extensions::Llm::Provider::OpenAICompatible
|
|
166
|
+
include CapabilityConfig
|
|
167
|
+
include HealthHelpers
|
|
168
|
+
include CapabilityResolution
|
|
169
|
+
|
|
170
|
+
class << self
|
|
171
|
+
def slug = 'mlx'
|
|
172
|
+
def local? = true
|
|
173
|
+
def default_transport = :http
|
|
174
|
+
def default_tier = :local
|
|
175
|
+
def configuration_options = %i[mlx_api_base mlx_api_key]
|
|
176
|
+
def configuration_requirements = []
|
|
177
|
+
def capabilities = Capabilities
|
|
178
178
|
end
|
|
179
179
|
|
|
180
|
-
def
|
|
181
|
-
|
|
180
|
+
def settings
|
|
181
|
+
Mlx.default_settings
|
|
182
182
|
end
|
|
183
183
|
|
|
184
|
-
def
|
|
185
|
-
|
|
184
|
+
def api_base
|
|
185
|
+
normalize_url(config.mlx_api_base || settings[:instances][:default][:endpoint])
|
|
186
186
|
end
|
|
187
187
|
|
|
188
|
-
def
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
{}
|
|
188
|
+
def headers
|
|
189
|
+
hdrs = identity_headers
|
|
190
|
+
token = config.mlx_api_key
|
|
191
|
+
hdrs['Authorization'] = "Bearer #{token}" unless token.nil? || token.to_s.empty?
|
|
192
|
+
hdrs
|
|
194
193
|
end
|
|
195
194
|
|
|
196
|
-
def
|
|
197
|
-
cfg = config
|
|
198
|
-
result = {}
|
|
199
|
-
%i[capabilities enable_thinking enable_tools enable_streaming enable_vision enable_embeddings
|
|
200
|
-
thinking_flag tools_flag streaming_flag vision_flag embedding_flag embeddings_flag
|
|
201
|
-
tool_flag images_flag image_flag].each do |key|
|
|
202
|
-
next unless cfg.respond_to?(key)
|
|
195
|
+
def health_url = '/health'
|
|
203
196
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
197
|
+
def health(live: false)
|
|
198
|
+
log.info("Checking MLX health live=#{live} at #{api_base}#{health_url}")
|
|
199
|
+
raw = connection.get(health_url).body
|
|
200
|
+
health_payload(raw)
|
|
201
|
+
rescue StandardError => e
|
|
202
|
+
handle_exception(e, level: :warn, handled: true, operation: 'mlx.provider.health')
|
|
203
|
+
{
|
|
204
|
+
provider: :mlx,
|
|
205
|
+
instance_id: provider_instance_id,
|
|
206
|
+
status: 'unhealthy',
|
|
207
|
+
ready: false,
|
|
208
|
+
circuit_state: 'open',
|
|
209
|
+
error: e.class.name,
|
|
210
|
+
message: e.message
|
|
211
|
+
}
|
|
210
212
|
end
|
|
211
213
|
|
|
212
|
-
def
|
|
213
|
-
|
|
214
|
-
|
|
214
|
+
def readiness(live: false)
|
|
215
|
+
log.info("Checking MLX readiness (live=#{live})")
|
|
216
|
+
super
|
|
217
|
+
end
|
|
215
218
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
def list_models(**)
|
|
220
|
+
log.info('Listing available MLX models')
|
|
221
|
+
models = super
|
|
222
|
+
log.info("Discovered #{Array(models).size} MLX models")
|
|
223
|
+
models
|
|
221
224
|
end
|
|
222
225
|
|
|
223
|
-
def
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
nil
|
|
226
|
+
def offering_from_model(model_info, health: {})
|
|
227
|
+
policy = resolve_capability_policy(model_info)
|
|
228
|
+
Legion::Extensions::Llm::Routing::ModelOffering.new(**build_offering_kwargs(
|
|
229
|
+
model_info: model_info, policy: policy, health: health
|
|
230
|
+
))
|
|
229
231
|
end
|
|
230
232
|
|
|
231
|
-
|
|
233
|
+
private
|
|
234
|
+
|
|
235
|
+
def build_offering_kwargs(model_info:, policy:, health:)
|
|
232
236
|
{
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
+
provider_family: :mlx,
|
|
238
|
+
instance_id: provider_instance_id,
|
|
239
|
+
transport: offering_transport,
|
|
240
|
+
tier: offering_tier,
|
|
241
|
+
model: model_info.id,
|
|
242
|
+
canonical_model_alias: model_info.respond_to?(:name) ? model_info.name : nil,
|
|
243
|
+
model_family: model_info.respond_to?(:family) ? model_info.family : nil,
|
|
244
|
+
usage_type: embedding_model?(model_info.id) ? :embedding : :inference,
|
|
245
|
+
capabilities: policy[:capabilities],
|
|
246
|
+
capability_sources: policy[:sources],
|
|
247
|
+
limits: extract_model_limits(model_info),
|
|
248
|
+
health: health,
|
|
249
|
+
metadata: offering_metadata_for(model_info).merge(capability_sources: policy[:sources])
|
|
250
|
+
}
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def extract_model_limits(model_info)
|
|
254
|
+
ctx = model_info.respond_to?(:context_length) ? model_info.context_length : nil
|
|
255
|
+
{ context_window: ctx }.compact
|
|
237
256
|
end
|
|
238
257
|
end
|
|
239
258
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/llm/fleet/provider_responder'
|
|
4
4
|
require 'legion/extensions/llm/mlx'
|
|
5
|
+
require 'legion/logging'
|
|
5
6
|
|
|
6
7
|
module Legion
|
|
7
8
|
module Extensions
|
|
@@ -9,17 +10,35 @@ module Legion
|
|
|
9
10
|
module Mlx
|
|
10
11
|
module Runners
|
|
11
12
|
# Runner entrypoint for MLX fleet request execution.
|
|
13
|
+
# Delegates to the shared ProviderResponder with exact-offering
|
|
14
|
+
# registry support for SSOT v3 execution contracts.
|
|
15
|
+
#
|
|
16
|
+
# The fleet Subscription actor dispatches this as
|
|
17
|
+
# `handle_fleet_request(**message)`, where message is the decoded
|
|
18
|
+
# request envelope merged with transport metadata, so the
|
|
19
|
+
# signature is kwargs-only. Ack/reject is owned by the
|
|
20
|
+
# Subscription actor, so the responder is called with nil
|
|
21
|
+
# delivery/properties.
|
|
12
22
|
module FleetWorker
|
|
23
|
+
include Legion::Logging::Helper
|
|
24
|
+
extend Legion::Logging::Helper
|
|
25
|
+
|
|
13
26
|
module_function
|
|
14
27
|
|
|
15
|
-
def handle_fleet_request(
|
|
28
|
+
def handle_fleet_request(**message)
|
|
29
|
+
log.debug do
|
|
30
|
+
"handling MLX fleet request request_id=#{message[:request_id].inspect} " \
|
|
31
|
+
"provider_instance=#{message[:provider_instance].inspect} " \
|
|
32
|
+
"operation=#{message[:operation].inspect}"
|
|
33
|
+
end
|
|
16
34
|
Legion::Extensions::Llm::Fleet::ProviderResponder.call(
|
|
17
|
-
payload:
|
|
35
|
+
payload: message,
|
|
18
36
|
provider_family: Mlx::PROVIDER_FAMILY,
|
|
19
37
|
provider_class: Mlx::Provider,
|
|
20
38
|
provider_instances: -> { Mlx.discover_instances },
|
|
21
|
-
|
|
22
|
-
|
|
39
|
+
registry: Legion::Extensions::Llm::Inventory::Registry,
|
|
40
|
+
delivery: nil,
|
|
41
|
+
properties: nil
|
|
23
42
|
)
|
|
24
43
|
end
|
|
25
44
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require 'legion/extensions/llm'
|
|
4
4
|
require 'legion/extensions/llm/mlx/provider'
|
|
5
5
|
require 'legion/extensions/llm/mlx/version'
|
|
6
|
-
|
|
6
|
+
require 'legion/extensions/llm/mlx/actors/discovery_refresh'
|
|
7
7
|
|
|
8
8
|
module Legion
|
|
9
9
|
module Extensions
|
|
@@ -11,7 +11,6 @@ module Legion
|
|
|
11
11
|
# Mlx provider extension namespace.
|
|
12
12
|
module Mlx
|
|
13
13
|
extend Legion::Logging::Helper
|
|
14
|
-
extend ::Legion::Extensions::Core if ::Legion::Extensions.const_defined?(:Core, false)
|
|
15
14
|
extend Legion::Extensions::Llm::AutoRegistration
|
|
16
15
|
|
|
17
16
|
PROVIDER_FAMILY = :mlx
|
|
@@ -39,48 +38,83 @@ module Legion
|
|
|
39
38
|
Provider
|
|
40
39
|
end
|
|
41
40
|
|
|
41
|
+
# Single source of truth for MLX instance discovery. The SSOT
|
|
42
|
+
# discovery actor and the fleet responder both read this: only
|
|
43
|
+
# operator-configured instances. No port-scanning, no fabricated
|
|
44
|
+
# instances, no tier override.
|
|
42
45
|
def self.discover_instances
|
|
46
|
+
configured_instances
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Only instances the operator actually configured are claimable.
|
|
50
|
+
# The synthetic instances.default section (provider_settings nests
|
|
51
|
+
# the extension's own instance defaults there at boot) is skipped
|
|
52
|
+
# with a once-per-boot warn while it is still the unmodified
|
|
53
|
+
# extension default — an unconfigured phantom must never be
|
|
54
|
+
# auto-registered, and a localhost endpoint is never a fallback
|
|
55
|
+
# identity.
|
|
56
|
+
def self.configured_instances
|
|
57
|
+
provider_cfg = Legion::Settings.dig(:extensions, :llm, :mlx) || {}
|
|
43
58
|
instances = {}
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
cfg_instances = provider_cfg[:instances]
|
|
60
|
+
if cfg_instances.is_a?(Hash)
|
|
61
|
+
cfg_instances.each do |name, config|
|
|
62
|
+
normalized = normalize_instance_config(config)
|
|
63
|
+
instances[name.to_sym] = normalized
|
|
64
|
+
end
|
|
65
|
+
end
|
|
46
66
|
instances
|
|
47
67
|
end
|
|
48
68
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
# The synthetic default is the extension's OWN registered instance
|
|
70
|
+
# defaults (endpoint http://localhost:8000 + fleet/limits blocks),
|
|
71
|
+
# deep-merged into instances.default by provider_settings. It is
|
|
72
|
+
# "configured" only when the operator changed something — a
|
|
73
|
+
# configured 'default' passes the provider layer and reaches the
|
|
74
|
+
# claim path (v2 parity: 'default' is a plain instance label). ONE
|
|
75
|
+
# predicate, TWO consumers: the actor's claim path (tick_refresh
|
|
76
|
+
# iterates configured_instances into claim_and_activate_instance)
|
|
77
|
+
# and the fleet responder (discover_instances → configured_instances)
|
|
78
|
+
# both reach it through this single filter point — no drift.
|
|
79
|
+
def self.unconfigured_default?(name:, normalized:)
|
|
80
|
+
name.to_sym == :default && normalized == normalized_synthetic_default_instance
|
|
57
81
|
end
|
|
58
82
|
|
|
59
|
-
def self.
|
|
60
|
-
|
|
61
|
-
|
|
83
|
+
def self.normalized_synthetic_default_instance
|
|
84
|
+
@normalized_synthetic_default_instance ||= normalize_instance_config(
|
|
85
|
+
default_settings.dig(:instances, :default) || {}
|
|
86
|
+
)
|
|
87
|
+
end
|
|
62
88
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
89
|
+
def self.normalize_instance_config(config)
|
|
90
|
+
normalized = config.to_h.transform_keys(&:to_sym)
|
|
91
|
+
promote_api_base_aliases(normalized)
|
|
92
|
+
normalized[:mlx_api_base] = normalize_api_base(normalized[:mlx_api_base]) if normalized[:mlx_api_base]
|
|
93
|
+
normalized[:mlx_api_key] ||= normalized.delete(:api_key)
|
|
94
|
+
resolve_instance_credentials(normalized)
|
|
95
|
+
normalized[:tier] ||= :local
|
|
96
|
+
normalized.compact
|
|
66
97
|
end
|
|
67
98
|
|
|
68
|
-
def self.
|
|
69
|
-
normalized = config.to_h.transform_keys { |key| key.respond_to?(:to_sym) ? key.to_sym : key }
|
|
99
|
+
def self.promote_api_base_aliases(normalized)
|
|
70
100
|
normalized[:mlx_api_base] ||= normalized.delete(:base_url)
|
|
71
101
|
normalized[:mlx_api_base] ||= normalized.delete(:api_base)
|
|
72
102
|
normalized[:mlx_api_base] ||= normalized.delete(:endpoint)
|
|
73
|
-
normalized[:mlx_api_key] ||= normalized.delete(:api_key)
|
|
74
|
-
normalized[:mlx_api_base] = normalize_api_base(normalized[:mlx_api_base]) if normalized[:mlx_api_base]
|
|
75
|
-
normalized.compact
|
|
76
103
|
end
|
|
77
104
|
|
|
78
105
|
def self.normalize_api_base(url)
|
|
79
106
|
url.to_s.sub(%r{/v1/?\z}, '')
|
|
80
107
|
end
|
|
81
108
|
|
|
82
|
-
|
|
83
|
-
|
|
109
|
+
def self.resolve_instance_credentials(normalized)
|
|
110
|
+
creds = normalized.delete(:credentials)
|
|
111
|
+
return unless creds.is_a?(Hash)
|
|
112
|
+
|
|
113
|
+
normalized[:mlx_api_key] ||= creds.transform_keys(&:to_sym)[:api_key]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private_class_method :normalize_instance_config, :promote_api_base_aliases, :normalize_api_base,
|
|
117
|
+
:resolve_instance_credentials
|
|
84
118
|
|
|
85
119
|
Legion::Extensions::Llm::Configuration.register_provider_options(Provider.configuration_options)
|
|
86
120
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-llm-mlx
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LegionIO
|
|
@@ -9,6 +9,20 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 1.10.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 1.10.0
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: legion-json
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -43,14 +57,14 @@ dependencies:
|
|
|
43
57
|
requirements:
|
|
44
58
|
- - ">="
|
|
45
59
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 1.
|
|
60
|
+
version: 1.4.2
|
|
47
61
|
type: :runtime
|
|
48
62
|
prerelease: false
|
|
49
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
64
|
requirements:
|
|
51
65
|
- - ">="
|
|
52
66
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: 1.
|
|
67
|
+
version: 1.4.2
|
|
54
68
|
- !ruby/object:Gem::Dependency
|
|
55
69
|
name: legion-transport
|
|
56
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -71,14 +85,14 @@ dependencies:
|
|
|
71
85
|
requirements:
|
|
72
86
|
- - ">="
|
|
73
87
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: 0.
|
|
88
|
+
version: 0.7.1
|
|
75
89
|
type: :runtime
|
|
76
90
|
prerelease: false
|
|
77
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
92
|
requirements:
|
|
79
93
|
- - ">="
|
|
80
94
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: 0.
|
|
95
|
+
version: 0.7.1
|
|
82
96
|
description: MLX provider integration for the LegionIO LLM routing framework.
|
|
83
97
|
email:
|
|
84
98
|
- matthewdiverson@gmail.com
|