lex-llm 0.1.3 → 0.1.4

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: 375f971150ba508862d136d724dd61f99ffb49bf3076d6a5debe0e8e12dfe86b
4
- data.tar.gz: '092859a51545b6408d0b9342065fcabf32d77184fd7ebd9b2e5739e415c7a43f'
3
+ metadata.gz: acd86ca9d14268a164c9492297186706a93770c93f4e312f45d6e54cec8bd565
4
+ data.tar.gz: b78f52a9e0cebfedb102b7809aaf3affc20440e1fcd68a9f815e00f4a129b440
5
5
  SHA512:
6
- metadata.gz: fead7c175af6e409b349ac8c6654d2c8ddbc8ed66ac2a158483b2bdd4f78898881e4e5b6aa15728515dcfa46d8ce0c601a8c81d99eeabb87f681f93828e3ce31
7
- data.tar.gz: 69df8e7c7b0b09917d23b0de90d518dc9132dc9221a1ff9eef5dd1b30dc585beb0c1682d930a57335b29a9424a0b21f17b8994745e39515c1331dc9ff9a198ce
6
+ metadata.gz: cb2b53cfc698777af6dbfbd735a8d12c0ed5eb94a3dbea88fb91a4951946ac2c3f42790b316bfa34e9f56f822ce3c1be6a07cdc8298525f8d317895269fb2348
7
+ data.tar.gz: b7392fe404a9bee6f2cde122522016b43f193c4df546a69732f9d238161f61a8a6dcb93b685fc2de5a1f9703ecf2c14c433e1a790039d8489b302afbb8cce2ef
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4 - 2026-04-28
4
+
5
+ - Add non-live provider readiness metadata for routing without expensive health or model calls by default.
6
+ - Map OpenAI-compatible model listings to normalized capabilities and modalities for routing.
7
+
3
8
  ## 0.1.3 - 2026-04-27
4
9
 
5
10
  - Convert the gem to a standard Legion extension runtime under `Legion::Extensions::Llm`.
data/README.md CHANGED
@@ -48,7 +48,7 @@ gem 'lex-llm'
48
48
  Provider extensions should declare `lex-llm` as a gemspec dependency:
49
49
 
50
50
  ```ruby
51
- spec.add_dependency 'lex-llm', '>= 0.1.0'
51
+ spec.add_dependency 'lex-llm', '>= 0.1.4'
52
52
  ```
53
53
 
54
54
  For local development across LegionIO repos, prefer a local path override in the app or test `Gemfile`, not a permanent git dependency in the gemspec.
@@ -233,6 +233,8 @@ At minimum, a provider extension should define:
233
233
 
234
234
  Provider extensions should avoid duplicating shared classes, schema logic, fleet lane construction, JSON handling, or common request/response objects.
235
235
 
236
+ All providers inherit `#readiness(live: false)`, which returns configured state, provider locality, API base, endpoint helpers, and non-live health metadata without probing remote services. Providers with a cheap health endpoint can pass `live: true` to include that endpoint response. OpenAI-compatible providers also inherit shared model-list parsing that maps discovered models into normalized capabilities and modalities for Legion routing.
237
+
236
238
  ## Schema Status
237
239
 
238
240
  `lex-llm` still depends on `ruby_llm-schema` because the current schema bridge exposes:
@@ -171,18 +171,54 @@ module Legion
171
171
  {}
172
172
  end
173
173
 
174
- def parse_list_models_response(response, provider, _capabilities)
174
+ def parse_list_models_response(response, provider, capabilities)
175
175
  response.body.fetch('data', []).map do |model|
176
+ critical_capabilities = critical_capabilities_for(capabilities, model)
176
177
  Legion::Extensions::Llm::Model::Info.new(
177
178
  id: model.fetch('id'),
178
179
  name: model['id'],
179
180
  provider: provider,
180
- created_at: model['created'],
181
+ created_at: model_created_at(model['created']),
182
+ capabilities: critical_capabilities,
183
+ modalities: modalities_for_capabilities(critical_capabilities),
181
184
  metadata: model
182
185
  )
183
186
  end
184
187
  end
185
188
 
189
+ def model_created_at(value)
190
+ value.is_a?(Numeric) ? Time.at(value).utc : value
191
+ end
192
+
193
+ def critical_capabilities_for(capabilities, model)
194
+ return [] unless capabilities
195
+ return capabilities.critical_capabilities_for(model) if capabilities.respond_to?(:critical_capabilities_for)
196
+
197
+ {
198
+ 'streaming' => :streaming?,
199
+ 'function_calling' => :functions?,
200
+ 'vision' => :vision?,
201
+ 'embeddings' => :embeddings?,
202
+ 'moderation' => :moderation?,
203
+ 'image' => :images?,
204
+ 'audio_transcription' => :audio_transcription?
205
+ }.filter_map do |capability, predicate|
206
+ capability if capabilities.respond_to?(predicate) && capabilities.public_send(predicate, model)
207
+ end
208
+ end
209
+
210
+ def modalities_for_capabilities(capabilities)
211
+ if capabilities.include?('embeddings') && (capabilities - ['embeddings']).empty?
212
+ { input: %w[text], output: %w[embeddings] }
213
+ elsif capabilities.include?('image')
214
+ { input: %w[text image], output: %w[image] }
215
+ elsif capabilities.include?('audio_transcription')
216
+ { input: %w[audio], output: %w[text] }
217
+ else
218
+ { input: %w[text image], output: %w[text] }
219
+ end
220
+ end
221
+
186
222
  def render_embedding_payload(text, model:, dimensions:)
187
223
  { model: model, input: text, dimensions: dimensions }.compact
188
224
  end
@@ -113,6 +113,38 @@ module Legion
113
113
  self.class.assume_models_exist?
114
114
  end
115
115
 
116
+ def readiness(live: false)
117
+ metadata = {
118
+ provider: slug.to_sym,
119
+ name: name,
120
+ configured: configured?,
121
+ ready: configured?,
122
+ local: local?,
123
+ remote: remote?,
124
+ api_base: api_base,
125
+ endpoints: endpoint_manifest,
126
+ live: live
127
+ }
128
+
129
+ return metadata.merge(health: { checked: false }) unless live && metadata[:endpoints][:health]
130
+
131
+ response = @connection.get(metadata[:endpoints][:health])
132
+ metadata.merge(ready: configured? && health_ready?(response.body), health: response.body)
133
+ rescue StandardError => e
134
+ metadata.merge(ready: false, health: { error: e.class.name, message: e.message })
135
+ end
136
+
137
+ def endpoint_manifest
138
+ endpoint_methods.each_with_object({}) do |(key, method_name), result|
139
+ next unless respond_to?(method_name)
140
+
141
+ value = public_send(method_name)
142
+ result[key] = value unless value.nil?
143
+ rescue ArgumentError, NotImplementedError
144
+ next
145
+ end
146
+ end
147
+
116
148
  def parse_error(response)
117
149
  return if response.body.empty?
118
150
 
@@ -270,6 +302,29 @@ module Legion
270
302
  temperature
271
303
  end
272
304
 
305
+ def endpoint_methods
306
+ {
307
+ completion: :completion_url,
308
+ stream: :stream_url,
309
+ models: :models_url,
310
+ embeddings: :embedding_url,
311
+ moderation: :moderation_url,
312
+ images: :images_url,
313
+ transcription: :transcription_url,
314
+ health: :health_url,
315
+ version: :version_url
316
+ }
317
+ end
318
+
319
+ def health_ready?(body)
320
+ return body unless body.is_a?(Hash)
321
+
322
+ status = body['status'] || body[:status] || body['state'] || body[:state]
323
+ return true if status.nil?
324
+
325
+ %w[ok ready healthy running].include?(status.to_s.downcase)
326
+ end
327
+
273
328
  def sync_response(connection, payload, additional_headers = {})
274
329
  response = connection.post completion_url, payload do |req|
275
330
  req.headers = additional_headers.merge(req.headers) unless additional_headers.empty?
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Llm
6
- VERSION = '0.1.3'
6
+ VERSION = '0.1.4'
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO