lex-llm-mlx 0.1.3 → 0.1.6

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: c278a29838c21de426034f06f316f4e9e119317f2551ef9040522f9bb304d605
4
- data.tar.gz: 25151910ac50c46cb5276e1b78a0a2d37a65aaf2d2b536493369b0ffd8136985
3
+ metadata.gz: 6305c1739c33374352cd5bda241303bd3e4dbfa742a162a7b11276be46eccb97
4
+ data.tar.gz: f50421895c2cf1472393b0687aed78e8ae98b27a130fd95a2038060faa588377
5
5
  SHA512:
6
- metadata.gz: 4bd51bf199594f179c820344d11bb9c81d24eb553cfffc8a9766f3baf693cde1f3479b64fd88805fa7d501a3d336bdec8e9bc64f3d8b4b21b446770de9f10336
7
- data.tar.gz: d9c87b7aac38d4e62bbbe8afd3cc7ecc78aeb2886ecfd6d41a9f5d3f6840df06193262614f0885f87e30a391fb5011518cedd180d30d4ee97840cc5ca98e2b30
6
+ metadata.gz: 86b833f891fe19fbb9f8e136a0da466d3a1cd1dc4c6af02eb5cab62fe3fffb2f8684c20c9f6a978859f44f756c4dbbb51004effe42d120d117f02ef13e3545de
7
+ data.tar.gz: d3f422a95aac94dcd185c754cd5f1806edd86d77ff73b6a429c838dbd6369f51298a7ff1f38019ccc455509b66a9525a2cb30893cd5f7fad0ff8499f2cfebb77
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.6 - 2026-04-28
4
+
5
+ - Publish best-effort `llm.registry` live readiness and discovered-model availability events using `lex-llm` registry envelopes when transport is already available.
6
+
7
+ ## 0.1.5 - 2026-04-28
8
+
9
+ - Require current shared Legion JSON, logging, settings, and `lex-llm >= 0.1.5` runtime dependencies.
10
+
11
+ ## 0.1.4 - 2026-04-28
12
+
13
+ - Require `lex-llm >= 0.1.4` so OpenAI-compatible model discovery exposes normalized capabilities and modalities.
14
+ - Add explicit chat and embedding model capability mapping for MLX routing metadata.
15
+
3
16
  ## 0.1.3 - 2026-04-28
4
17
 
5
18
  - Remove the leftover compatibility entrypoint outside the Legion namespace.
data/README.md CHANGED
@@ -10,6 +10,7 @@ Load it with `require 'legion/extensions/llm/mlx'`.
10
10
 
11
11
  - `Legion::Extensions::Llm::Mlx::Provider`, registered as `:mlx`.
12
12
  - OpenAI-compatible chat, streaming, model listing, and embeddings endpoint wrappers.
13
+ - Heuristic chat, embedding, and vision capability mapping for discovered local models.
13
14
  - Local-first defaults for MLX servers running on MacBook, Mac Studio, or local Apple Silicon hosts.
14
15
  - Shared Legion settings, JSON, and logging dependencies.
15
16
 
data/lex-llm-mlx.gemspec CHANGED
@@ -23,5 +23,8 @@ Gem::Specification.new do |spec|
23
23
  spec.files = `git ls-files -z`.split("\x0").reject { |file| file.match(%r{^(spec|test|features|tmp|coverage)/}) }
24
24
  spec.require_paths = ['lib']
25
25
 
26
- spec.add_dependency 'lex-llm', '>= 0.1.3'
26
+ spec.add_dependency 'legion-json', '>= 1.2.1'
27
+ spec.add_dependency 'legion-logging', '>= 1.3.2'
28
+ spec.add_dependency 'legion-settings', '>= 1.3.14'
29
+ spec.add_dependency 'lex-llm', '>= 0.1.5'
27
30
  end
@@ -11,11 +11,17 @@ module Legion
11
11
  include Legion::Extensions::Llm::Provider::OpenAICompatible
12
12
 
13
13
  class << self
14
+ attr_writer :registry_publisher
15
+
14
16
  def slug = 'mlx'
15
17
  def local? = true
16
18
  def configuration_options = %i[mlx_api_base mlx_api_key]
17
19
  def configuration_requirements = []
18
20
  def capabilities = Capabilities
21
+
22
+ def registry_publisher
23
+ @registry_publisher ||= RegistryPublisher.new
24
+ end
19
25
  end
20
26
 
21
27
  # Conservative capability predicates for local MLX OpenAI-compatible servers.
@@ -25,9 +31,18 @@ module Legion
25
31
  def chat?(model) = !embeddings?(model)
26
32
  def streaming?(model) = chat?(model)
27
33
  def vision?(model) = model_id(model).match?(/vlm|vision|llava|pixtral|qwen.*vl/i)
28
- def functions?(_model) = true
34
+ def functions?(model) = chat?(model)
29
35
  def embeddings?(model) = model_id(model).match?(/embed|bge|e5|nomic/i)
30
36
 
37
+ def critical_capabilities_for(model)
38
+ [
39
+ ('streaming' if streaming?(model)),
40
+ ('function_calling' if functions?(model)),
41
+ ('vision' if vision?(model)),
42
+ ('embeddings' if embeddings?(model))
43
+ ].compact
44
+ end
45
+
31
46
  def model_id(model)
32
47
  model.respond_to?(:id) ? model.id.to_s : model.to_s
33
48
  end
@@ -49,6 +64,18 @@ module Legion
49
64
  def health
50
65
  connection.get(health_url).body
51
66
  end
67
+
68
+ def readiness(live: false)
69
+ super.tap do |metadata|
70
+ self.class.registry_publisher.publish_readiness_async(metadata) if live
71
+ end
72
+ end
73
+
74
+ def list_models
75
+ super.tap do |models|
76
+ self.class.registry_publisher.publish_models_async(models, readiness: readiness(live: false))
77
+ end
78
+ end
52
79
  end
53
80
  end
54
81
  end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module Mlx
7
+ # Builds sanitized lex-llm registry envelopes for MLX provider state.
8
+ class RegistryEventBuilder
9
+ def readiness(readiness)
10
+ registry_event_class.public_send(
11
+ readiness[:ready] ? :available : :unavailable,
12
+ provider_offering(readiness),
13
+ runtime: runtime_metadata,
14
+ health: readiness_health(readiness),
15
+ metadata: readiness_metadata(readiness)
16
+ )
17
+ end
18
+
19
+ def model_available(model, readiness:)
20
+ registry_event_class.available(
21
+ model_offering(model),
22
+ runtime: runtime_metadata,
23
+ health: model_health(readiness),
24
+ metadata: model_metadata(model)
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ def provider_offering(readiness)
31
+ {
32
+ provider_family: :mlx,
33
+ provider_instance: provider_instance,
34
+ transport: :http,
35
+ model: 'provider-readiness',
36
+ usage_type: :inference,
37
+ capabilities: [],
38
+ health: readiness_health(readiness),
39
+ metadata: { lex: :llm_mlx, provider_readiness: true }
40
+ }
41
+ end
42
+
43
+ def model_offering(model)
44
+ {
45
+ provider_family: :mlx,
46
+ provider_instance: provider_instance,
47
+ transport: :http,
48
+ model: model.id,
49
+ usage_type: usage_type_for(model),
50
+ capabilities: Array(model.capabilities).map(&:to_sym),
51
+ limits: model_limits(model),
52
+ metadata: { lex: :llm_mlx, model_name: model.name }.compact
53
+ }
54
+ end
55
+
56
+ def readiness_health(readiness)
57
+ health = {
58
+ ready: readiness[:ready] == true,
59
+ status: readiness[:ready] ? :available : :unavailable,
60
+ checked: readiness.dig(:health, :checked) != false
61
+ }
62
+ add_readiness_error(health, readiness[:health])
63
+ end
64
+
65
+ def add_readiness_error(health, source)
66
+ error = source.is_a?(Hash) ? source : {}
67
+ error_class = error[:error] || error['error']
68
+ error_message = error[:message] || error['message']
69
+ health[:error_class] = error_class if error_class
70
+ health[:error] = error_message if error_message
71
+ health
72
+ end
73
+
74
+ def model_health(readiness)
75
+ ready = readiness.fetch(:ready, true) == true
76
+ { ready:, status: ready ? :available : :degraded }
77
+ end
78
+
79
+ def readiness_metadata(readiness)
80
+ {
81
+ extension: :lex_llm_mlx,
82
+ provider: :mlx,
83
+ configured: readiness[:configured] == true,
84
+ live: readiness[:live] == true
85
+ }
86
+ end
87
+
88
+ def model_metadata(model)
89
+ { extension: :lex_llm_mlx, provider: :mlx, model_type: model.type }
90
+ end
91
+
92
+ def runtime_metadata
93
+ { node: provider_instance }
94
+ end
95
+
96
+ def model_limits(model)
97
+ {
98
+ context_window: model.context_window,
99
+ max_output_tokens: model.max_output_tokens
100
+ }.compact
101
+ end
102
+
103
+ def usage_type_for(model)
104
+ model.type == 'embedding' ? :embedding : :inference
105
+ end
106
+
107
+ def provider_instance
108
+ :mlx
109
+ end
110
+
111
+ def registry_event_class
112
+ ::Legion::Extensions::Llm::Routing::RegistryEvent
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module Mlx
7
+ # Best-effort publisher for MLX provider availability events.
8
+ class RegistryPublisher
9
+ APP_ID = 'lex-llm-mlx'
10
+
11
+ def initialize(builder: RegistryEventBuilder.new)
12
+ @builder = builder
13
+ end
14
+
15
+ def publish_readiness_async(readiness)
16
+ schedule { publish_event(@builder.readiness(readiness)) }
17
+ end
18
+
19
+ def publish_models_async(models, readiness:)
20
+ schedule do
21
+ Array(models).each do |model|
22
+ publish_event(@builder.model_available(model, readiness:))
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def schedule(&)
30
+ return false unless publishing_available?
31
+
32
+ Thread.new do
33
+ Thread.current.abort_on_exception = false
34
+ yield
35
+ rescue StandardError => e
36
+ log_publish_failure(e, level: :debug)
37
+ end
38
+ rescue StandardError => e
39
+ log_publish_failure(e, level: :debug)
40
+ false
41
+ end
42
+
43
+ def publish_event(event)
44
+ return false unless publishing_available?
45
+
46
+ message_class.new(event:, app_id: APP_ID).publish(spool: false)
47
+ rescue StandardError => e
48
+ log_publish_failure(e)
49
+ false
50
+ end
51
+
52
+ def publishing_available?
53
+ return false unless registry_event_available?
54
+ return false unless transport_message_available?
55
+ return true unless defined?(::Legion::Transport::Connection)
56
+ return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
57
+
58
+ ::Legion::Transport::Connection.session_open?
59
+ rescue StandardError
60
+ false
61
+ end
62
+
63
+ def registry_event_available?
64
+ defined?(::Legion::Extensions::Llm::Routing::RegistryEvent)
65
+ end
66
+
67
+ def transport_message_available?
68
+ return true if message_class_defined?
69
+ return false unless defined?(::Legion::Transport::Message) && defined?(::Legion::Transport::Exchange)
70
+
71
+ require 'legion/extensions/llm/mlx/transport/messages/registry_event'
72
+ message_class_defined?
73
+ rescue LoadError
74
+ false
75
+ end
76
+
77
+ def message_class_defined?
78
+ defined?(::Legion::Extensions::Llm::Mlx::Transport::Messages::RegistryEvent)
79
+ end
80
+
81
+ def message_class
82
+ ::Legion::Extensions::Llm::Mlx::Transport::Messages::RegistryEvent
83
+ end
84
+
85
+ def log_publish_failure(error, level: :warn)
86
+ message = "[lex-llm-mlx] llm.registry publish failed: #{error.class}: #{error.message}"
87
+ logger = ::Legion::Extensions::Llm.logger if defined?(::Legion::Extensions::Llm)
88
+ if logger.respond_to?(level)
89
+ logger.public_send(level, message)
90
+ elsif logger.respond_to?(:debug)
91
+ logger.debug(message)
92
+ end
93
+ rescue StandardError
94
+ nil
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module Mlx
7
+ module Transport
8
+ module Exchanges
9
+ # Topic exchange for MLX 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
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/llm/mlx/transport/exchanges/llm_registry'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Llm
8
+ module Mlx
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
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Mlx
7
- VERSION = '0.1.3'
7
+ VERSION = '0.1.6'
8
8
  end
9
9
  end
10
10
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'legion/extensions/llm'
4
4
  require 'legion/extensions/llm/mlx/provider'
5
+ require 'legion/extensions/llm/mlx/registry_event_builder'
6
+ require 'legion/extensions/llm/mlx/registry_publisher'
5
7
  require 'legion/extensions/llm/mlx/version'
6
8
 
7
9
  module Legion
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.1.3
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -9,20 +9,62 @@ 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: legion-json
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 1.2.1
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 1.2.1
26
+ - !ruby/object:Gem::Dependency
27
+ name: legion-logging
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.3.2
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 1.3.2
40
+ - !ruby/object:Gem::Dependency
41
+ name: legion-settings
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 1.3.14
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.3.14
12
54
  - !ruby/object:Gem::Dependency
13
55
  name: lex-llm
14
56
  requirement: !ruby/object:Gem::Requirement
15
57
  requirements:
16
58
  - - ">="
17
59
  - !ruby/object:Gem::Version
18
- version: 0.1.3
60
+ version: 0.1.5
19
61
  type: :runtime
20
62
  prerelease: false
21
63
  version_requirements: !ruby/object:Gem::Requirement
22
64
  requirements:
23
65
  - - ">="
24
66
  - !ruby/object:Gem::Version
25
- version: 0.1.3
67
+ version: 0.1.5
26
68
  description: MLX provider integration for the LegionIO LLM routing framework.
27
69
  email:
28
70
  - matthewdiverson@gmail.com
@@ -42,6 +84,10 @@ files:
42
84
  - lex-llm-mlx.gemspec
43
85
  - lib/legion/extensions/llm/mlx.rb
44
86
  - lib/legion/extensions/llm/mlx/provider.rb
87
+ - lib/legion/extensions/llm/mlx/registry_event_builder.rb
88
+ - lib/legion/extensions/llm/mlx/registry_publisher.rb
89
+ - lib/legion/extensions/llm/mlx/transport/exchanges/llm_registry.rb
90
+ - lib/legion/extensions/llm/mlx/transport/messages/registry_event.rb
45
91
  - lib/legion/extensions/llm/mlx/version.rb
46
92
  homepage: https://github.com/LegionIO/lex-llm-mlx
47
93
  licenses: