lex-llm-mlx 0.3.0 → 0.3.5

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: 38cb4fb34920395c6af9d7d86f8fbc104c92b632cb886acc601a80815fd64d02
4
- data.tar.gz: 29ef77ad4cc5ff70720aa9ce298f6d5fbdde58e7dc4836369e4a60bbaf1e70d8
3
+ metadata.gz: 5c591beb817516c542e92352ffd580449e5b966f290a3440a753dbcbb45ea9b6
4
+ data.tar.gz: 05c1a37ec94439268940e2e883136661eec0fdf37ac82111aea0503e037a81c1
5
5
  SHA512:
6
- metadata.gz: c8a71c4d81eaaa3d1aeb66256ae163b922d1ef139317a540fbc62423bbf5d2edc67ca9f9def102420bd9da2d862097ed493dd028a740d58d14ca523f36068ef4
7
- data.tar.gz: '049f44b3ec25a8ceffa50b6228a7d5a5e20941fa3a54cc142127caa93b075931ad3613ea39dc23bff469fc24c31f5ff5256adc0153a2f53926894403a5c49a34'
6
+ metadata.gz: 2a81d1aca40035296633d2b37af08b464d8fe7ecb4e7f210bc0dd856e10f1599d88bb43a2998557413ad53e31923fb6ca6d44fb6c95a919561ca405daf398644
7
+ data.tar.gz: 3dcfc07aab6db3e5972f5b18313d864f54fc64aa3b81527c3f5b1f4d306b8f33327acb5359c300a824ccce24e87705b66be4ce4fb7af80d1fb3e816e433fb99f
@@ -8,8 +8,20 @@ jobs:
8
8
  ci:
9
9
  uses: LegionIO/.github/.github/workflows/ci.yml@main
10
10
 
11
+ excluded-files:
12
+ uses: LegionIO/.github/.github/workflows/excluded-files.yml@main
13
+
14
+ security:
15
+ uses: LegionIO/.github/.github/workflows/security-scan.yml@main
16
+
17
+ version-changelog:
18
+ uses: LegionIO/.github/.github/workflows/version-changelog.yml@main
19
+
20
+ dependency-review:
21
+ uses: LegionIO/.github/.github/workflows/dependency-review.yml@main
22
+
11
23
  release:
12
- needs: ci
24
+ needs: [ci, excluded-files, security]
13
25
  if: github.event_name == 'push' && github.ref == 'refs/heads/main'
14
26
  uses: LegionIO/.github/.github/workflows/release.yml@main
15
27
  secrets:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.5 - 2026-05-06
4
+
5
+ - Load provider-owned fleet actors through the LegionIO subscription base and the canonical MLX provider root.
6
+ - Keep fleet runners anchored on the provider root namespace so provider constants and instance discovery are always loaded.
7
+ - Gate release publishing on the shared security workflow.
8
+
9
+ ## 0.3.4 - 2026-05-06
10
+
11
+ - Use the shared `lex-llm` fleet provider responder helper for provider-owned fleet workers.
12
+ - Remove the runtime `legion-llm` dependency and require `lex-llm >= 0.4.3` for responder-side fleet execution.
13
+
14
+ ## 0.3.3 - 2026-05-06
15
+
16
+ - Remove require-time provider self-registration; `legion-llm` now owns adapter creation and registry writes from loaded provider discovery metadata.
17
+ - Bump dependency floors to `lex-llm >= 0.4.1` and `legion-llm >= 0.9.1`.
18
+
19
+ ## 0.3.2 - 2026-05-06
20
+
21
+ - Enforce the shared keyword-only `lex-llm` provider contract and accept `health(live:)`.
22
+ - Move MLX defaults back to `Legion::Extensions::Llm.provider_settings` with instance-level fleet responder settings.
23
+ - Add provider-owned fleet responder actor and runner backed by `legion-llm` fleet policy execution.
24
+ - Bump the transport dependency floor to `legion-transport >= 1.4.14`.
25
+
26
+ ## 0.3.1 - 2026-05-03
27
+
28
+ - Normalize generic settings keys to MLX provider config keys during instance discovery.
29
+ - Strip trailing `/v1` from configured OpenAI-compatible MLX API roots.
30
+
3
31
  ## 0.3.0 - 2026-05-01
4
32
 
5
33
  - Add auto-discovery via CredentialSources and AutoRegistration from lex-llm 0.3.0
data/Gemfile CHANGED
@@ -4,6 +4,8 @@ source 'https://rubygems.org'
4
4
 
5
5
  group :test do
6
6
  llm_base_path = ENV.fetch('LEX_LLM_PATH', File.expand_path('../lex-llm', __dir__))
7
+ transport_path = ENV.fetch('LEGION_TRANSPORT_PATH', File.expand_path('../../legion-transport', __dir__))
8
+ gem 'legion-transport', path: transport_path if File.directory?(transport_path)
7
9
  gem 'lex-llm', path: llm_base_path if File.directory?(llm_base_path)
8
10
  end
9
11
 
data/README.md CHANGED
@@ -2,42 +2,47 @@
2
2
 
3
3
  LegionIO LLM provider extension for MLX-backed OpenAI-compatible servers on Apple Silicon.
4
4
 
5
- This gem lives under `Legion::Extensions::Llm::Mlx` and depends on `lex-llm` for shared provider-neutral routing, fleet, and schema primitives.
5
+ This gem lives under `Legion::Extensions::Llm::Mlx` and depends on `lex-llm >= 0.4.3` for shared provider-neutral routing, response normalization, fleet envelopes, fleet responder execution, and schema primitives.
6
6
 
7
7
  Load it with `require 'legion/extensions/llm/mlx'`.
8
8
 
9
9
  ## What It Provides
10
10
 
11
- - `Legion::Extensions::Llm::Mlx::Provider`, registered as `:mlx`.
11
+ - `Legion::Extensions::Llm::Mlx::Provider`, exposed to `legion-llm` as the `:mlx` provider family.
12
12
  - OpenAI-compatible chat, streaming, model listing, and embeddings endpoint wrappers.
13
13
  - Heuristic chat, embedding, and vision capability mapping for discovered local models.
14
- - Local-first defaults for MLX servers running on MacBook, Mac Studio, or local Apple Silicon hosts.
15
- - Best-effort `llm.registry` event publishing for readiness and model availability when transport is available.
14
+ - Local-first defaults for MLX servers running on Apple Silicon hosts.
15
+ - Best-effort `llm.registry` event publishing through shared `lex-llm` registry helpers when transport is available.
16
+ - Provider-owned fleet request actor and runner backed by `lex-llm`.
16
17
  - Shared Legion settings, JSON, and logging dependencies with full `Legion::Logging::Helper` integration.
17
18
 
18
19
  ## Architecture
19
20
 
20
21
  ```
21
22
  Legion::Extensions::Llm::Mlx
22
- Mlx (module) # Extension namespace, provider registration, default settings
23
- Provider # MLX provider — health, readiness, model listing, OpenAI-compatible adapter
24
- RegistryPublisher # Async publisher for llm.registry readiness/model availability events
25
- RegistryEventBuilder # Builds sanitized lex-llm registry envelopes for MLX provider state
26
- Transport::
27
- Messages::RegistryEvent # AMQP message for llm.registry exchange
28
- Exchanges::LlmRegistry # Topic exchange definition for llm.registry
23
+ Mlx # Extension namespace, discovery metadata, default settings
24
+ Provider # Health, readiness, model listing, OpenAI-compatible adapter
25
+ Actor::FleetWorker # Subscription actor enabled by provider instance fleet settings
26
+ Runners::FleetWorker # Delegates fleet execution to Legion::Extensions::Llm::Fleet::ProviderResponder
27
+ (shared from lex-llm)
28
+ RegistryPublisher # Async llm.registry event publishing
29
+ RegistryEventBuilder # Sanitized registry envelope construction
29
30
  ```
30
31
 
32
+ The extension no longer writes provider adapters into the registry at require time. Loaded provider discovery metadata is consumed by `legion-llm`, which owns adapter creation and registry writes.
33
+
31
34
  ## Default Settings
32
35
 
33
36
  ```ruby
34
37
  Legion::Extensions::Llm::Mlx.default_settings
35
38
  ```
36
39
 
37
- Defaults target `http://localhost:8000`, mark the provider as `:local`, and allow one concurrent local request. Fleet participation stays disabled unless the host opts in through `Legion::Settings`.
40
+ Defaults target `http://localhost:8000`, mark the default instance as `:local`, allow one concurrent local request, and keep fleet participation disabled until a host opts in through extension settings.
38
41
 
39
42
  ## Configuration
40
43
 
44
+ The provider accepts the shared `lex-llm` configuration options:
45
+
41
46
  ```ruby
42
47
  Legion::Extensions::Llm.configure do |config|
43
48
  config.mlx_api_base = 'http://localhost:8000'
@@ -47,6 +52,47 @@ end
47
52
 
48
53
  `mlx_api_key` is optional because most local MLX servers run without authentication. Set it when a proxy or hosted MLX gateway requires bearer authentication.
49
54
 
55
+ Provider discovery also reads named instances from `extensions.llm.mlx.instances`. Generic keys are normalized for the MLX provider:
56
+
57
+ ```yaml
58
+ extensions:
59
+ llm:
60
+ mlx:
61
+ instances:
62
+ local:
63
+ base_url: http://localhost:8000
64
+ api_key: null
65
+ fleet:
66
+ enabled: false
67
+ respond_to_requests: false
68
+ capabilities:
69
+ - chat
70
+ - stream_chat
71
+ - embed
72
+ ```
73
+
74
+ Accepted instance URL keys are `base_url`, `api_base`, `endpoint`, or `mlx_api_base`. A trailing `/v1` is stripped because the shared OpenAI-compatible adapter appends endpoint paths itself.
75
+
76
+ ## Fleet Responder
77
+
78
+ 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`.
79
+
80
+ ```yaml
81
+ extensions:
82
+ llm:
83
+ mlx:
84
+ instances:
85
+ local:
86
+ base_url: http://localhost:8000
87
+ fleet:
88
+ enabled: true
89
+ respond_to_requests: true
90
+ capabilities:
91
+ - chat
92
+ - stream_chat
93
+ - embed
94
+ ```
95
+
50
96
  ## Endpoint Helpers
51
97
 
52
98
  - `completion_url` and `stream_url`: `/v1/chat/completions`
@@ -65,6 +111,13 @@ When `Legion::Transport` and `lex-llm` routing are available, the provider publi
65
111
 
66
112
  Publishing is fire-and-forget in background threads; failures never block the provider.
67
113
 
114
+ ## Failure Modes
115
+
116
+ - `readiness(live: true)` calls the MLX `/health` endpoint and publishes readiness metadata only when the live check succeeds.
117
+ - `list_models` expects an OpenAI-compatible `/v1/models` response and publishes discovered model availability through the shared registry publisher.
118
+ - Fleet request handling is disabled unless at least one discovered instance opts in with `fleet.respond_to_requests: true`.
119
+ - Local instance discovery checks `localhost:8080`; explicitly configured instances can point at any OpenAI-compatible MLX endpoint.
120
+
68
121
  ## Dependencies
69
122
 
70
123
  | Gem | Required | Purpose |
@@ -72,13 +125,13 @@ Publishing is fire-and-forget in background threads; failures never block the pr
72
125
  | `legion-json` (>= 1.2.1) | Yes | JSON serialization |
73
126
  | `legion-logging` (>= 1.3.2) | Yes | Structured logging via Helper |
74
127
  | `legion-settings` (>= 1.3.14) | Yes | Configuration |
75
- | `lex-llm` (>= 0.1.5) | Yes | Shared provider base, routing, fleet |
128
+ | `lex-llm` (>= 0.4.3) | Yes | Shared provider base, response normalization, routing, fleet envelopes, and fleet responder execution |
129
+ | `legion-transport` (>= 1.4.14) | Yes | AMQP subscriptions and replies |
76
130
 
77
131
  ## Development
78
132
 
79
133
  ```bash
80
134
  bundle install
81
- bundle exec rspec # 0 failures
82
- bundle exec rubocop -A # auto-fix
83
- bundle exec rubocop # lint check
135
+ bundle exec rspec --format json --out tmp/rspec_results.json --format progress --out tmp/rspec_progress.txt
136
+ bundle exec rubocop -A
84
137
  ```
data/lex-llm-mlx.gemspec CHANGED
@@ -26,5 +26,6 @@ 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.3.0'
29
+ spec.add_dependency 'legion-transport', '>= 1.4.14'
30
+ spec.add_dependency 'lex-llm', '>= 0.4.3'
30
31
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'legion/extensions/actors/subscription'
5
+ rescue LoadError => e
6
+ warn(e.message) if $VERBOSE
7
+ end
8
+
9
+ unless defined?(Legion::Extensions::Actors::Subscription)
10
+ raise LoadError, 'LegionIO actor runtime is required for MLX fleet worker'
11
+ end
12
+
13
+ require 'legion/extensions/llm/mlx'
14
+ require 'legion/extensions/llm/fleet/provider_responder'
15
+
16
+ module Legion
17
+ module Extensions
18
+ module Llm
19
+ module Mlx
20
+ module Actor
21
+ # Subscription actor for MLX fleet request consumption.
22
+ class FleetWorker < Legion::Extensions::Actors::Subscription
23
+ def runner_class
24
+ 'Legion::Extensions::Llm::Mlx::Runners::FleetWorker'
25
+ end
26
+
27
+ def runner_function
28
+ 'handle_fleet_request'
29
+ end
30
+
31
+ def use_runner?
32
+ false
33
+ end
34
+
35
+ def enabled?
36
+ Legion::Extensions::Llm::Fleet::ProviderResponder.enabled_for?(Mlx.discover_instances)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -65,8 +65,8 @@ module Legion
65
65
 
66
66
  def health_url = '/health'
67
67
 
68
- def health
69
- log.info("Checking MLX health at #{api_base}#{health_url}")
68
+ def health(live: false)
69
+ log.info("Checking MLX health live=#{live} at #{api_base}#{health_url}")
70
70
  connection.get(health_url).body
71
71
  end
72
72
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/llm/fleet/provider_responder'
4
+ require 'legion/extensions/llm/mlx'
5
+
6
+ module Legion
7
+ module Extensions
8
+ module Llm
9
+ module Mlx
10
+ module Runners
11
+ # Runner entrypoint for MLX fleet request execution.
12
+ module FleetWorker
13
+ module_function
14
+
15
+ def handle_fleet_request(payload, delivery: nil, properties: nil)
16
+ Legion::Extensions::Llm::Fleet::ProviderResponder.call(
17
+ payload: payload,
18
+ provider_family: Mlx::PROVIDER_FAMILY,
19
+ provider_class: Mlx::Provider,
20
+ provider_instances: -> { Mlx.discover_instances },
21
+ delivery: delivery,
22
+ properties: properties
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Mlx
7
- VERSION = '0.3.0'
7
+ VERSION = '0.3.5'
8
8
  end
9
9
  end
10
10
  end
@@ -16,17 +16,25 @@ module Legion
16
16
  PROVIDER_FAMILY = :mlx
17
17
 
18
18
  def self.default_settings
19
- {
20
- enabled: false,
21
- base_url: 'localhost:8000',
22
- default_model: nil,
23
- api_key: nil,
24
- model_whitelist: [],
25
- model_blacklist: [],
26
- model_cache_ttl: 60,
27
- tls: { enabled: false, verify: :peer },
28
- instances: {}
29
- }
19
+ ::Legion::Extensions::Llm.provider_settings(
20
+ family: PROVIDER_FAMILY,
21
+ instance: {
22
+ endpoint: 'http://localhost:8000',
23
+ tier: :local,
24
+ transport: :http,
25
+ credentials: { api_key: nil },
26
+ usage: { inference: true, embedding: true, image: false },
27
+ limits: { concurrency: 1 },
28
+ fleet: {
29
+ enabled: false,
30
+ respond_to_requests: false,
31
+ capabilities: %i[chat stream_chat embed],
32
+ lanes: [],
33
+ concurrency: 1,
34
+ queue_suffix: nil
35
+ }
36
+ }
37
+ )
30
38
  end
31
39
 
32
40
  def self.provider_class
@@ -55,18 +63,30 @@ module Legion
55
63
  return unless cfg.is_a?(Hash)
56
64
 
57
65
  cfg.each do |name, config|
58
- instances[name.to_sym] = config.merge(tier: :direct)
66
+ instances[name.to_sym] = normalize_instance_config(config).merge(tier: :direct)
59
67
  end
60
68
  end
61
69
 
62
- private_class_method :discover_local_instance, :discover_settings_instances
70
+ def self.normalize_instance_config(config) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
71
+ normalized = config.to_h.transform_keys { |key| key.respond_to?(:to_sym) ? key.to_sym : key }
72
+ normalized[:mlx_api_base] ||= normalized.delete(:base_url)
73
+ normalized[:mlx_api_base] ||= normalized.delete(:api_base)
74
+ normalized[:mlx_api_base] ||= normalized.delete(:endpoint)
75
+ normalized[:mlx_api_key] ||= normalized.delete(:api_key)
76
+ normalized[:mlx_api_base] = normalize_api_base(normalized[:mlx_api_base]) if normalized[:mlx_api_base]
77
+ normalized.compact
78
+ end
79
+
80
+ def self.normalize_api_base(url)
81
+ url.to_s.sub(%r{/v1/?\z}, '')
82
+ end
83
+
84
+ private_class_method :discover_local_instance, :discover_settings_instances,
85
+ :normalize_instance_config, :normalize_api_base
86
+
87
+ Legion::Extensions::Llm::Configuration.register_provider_options(Provider.configuration_options) if
88
+ Legion::Extensions::Llm::Configuration.respond_to?(:register_provider_options)
63
89
  end
64
90
  end
65
91
  end
66
92
  end
67
-
68
- Legion::Extensions::Llm::Configuration.register_provider_options(
69
- Legion::Extensions::Llm::Mlx::Provider.configuration_options
70
- )
71
-
72
- Legion::Extensions::Llm::Mlx.register_discovered_instances
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.3.0
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -51,20 +51,34 @@ dependencies:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: 1.3.14
54
+ - !ruby/object:Gem::Dependency
55
+ name: legion-transport
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.4.14
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 1.4.14
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: lex-llm
56
70
  requirement: !ruby/object:Gem::Requirement
57
71
  requirements:
58
72
  - - ">="
59
73
  - !ruby/object:Gem::Version
60
- version: 0.3.0
74
+ version: 0.4.3
61
75
  type: :runtime
62
76
  prerelease: false
63
77
  version_requirements: !ruby/object:Gem::Requirement
64
78
  requirements:
65
79
  - - ">="
66
80
  - !ruby/object:Gem::Version
67
- version: 0.3.0
81
+ version: 0.4.3
68
82
  description: MLX provider integration for the LegionIO LLM routing framework.
69
83
  email:
70
84
  - matthewdiverson@gmail.com
@@ -83,7 +97,9 @@ files:
83
97
  - README.md
84
98
  - lex-llm-mlx.gemspec
85
99
  - lib/legion/extensions/llm/mlx.rb
100
+ - lib/legion/extensions/llm/mlx/actors/fleet_worker.rb
86
101
  - lib/legion/extensions/llm/mlx/provider.rb
102
+ - lib/legion/extensions/llm/mlx/runners/fleet_worker.rb
87
103
  - lib/legion/extensions/llm/mlx/version.rb
88
104
  homepage: https://github.com/LegionIO/lex-llm-mlx
89
105
  licenses: