lex-llm-ollama 0.1.6 → 0.1.7

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: a9b4ccf479be5fe7a785b15c5a8f5cc328c3e916736d75b22584af2a7e3488b5
4
- data.tar.gz: 8ee2de3a028a8a877a4055e83019652feb8208f1d359db91584b9a795b5b20ed
3
+ metadata.gz: '04731865b67b0980013a81e3c1c076e39daa788b7e1136b78c7bb25ec5f3ac8b'
4
+ data.tar.gz: 7dad2cff87b97058266b5a96c8b7bdd5600f3436ca73115fd81c0db517bf2d0d
5
5
  SHA512:
6
- metadata.gz: de11691a812a9a45ec23ee8b2391cdc902c77043acd368f6b48d3ace4e53acd20da23a8d8d2b66d90a6878542ec186d6b2bf809a86e660b545ff5fe2aeed8577
7
- data.tar.gz: 8080ef3565c62e8c587036d70fce6f08ac15c4890b3d5b4316a1cf8f85ac9407483c0638292d1bd24857451490a9a49d4287db40c75f5a921d1a4d5dfffa6a6e
6
+ metadata.gz: 45071306f61626d92ff3037f713dab35426170010bdecdf26dee092553a2d07d52595e44e26d7c3ec692a350f024c01df2e56e5c6f2445a9cdfbedfeba8030c7
7
+ data.tar.gz: bc489708640e30d27c306433d5f1e2f82d335550c66bb758a49cfc0a02183a048f3667a1007240f3ff7ee8b2f69192bbb22e4cdf4f0ec4a62e57cae4eef36fb3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.7 - 2026-04-30
4
+
5
+ - Provider contract overhaul: adopt lex-llm >= 0.1.9 base contract.
6
+ - Replace `default_settings` with full schema (enabled, base_url, default_model, whitelist/blacklist, model_cache_ttl, tls, instances).
7
+ - Remove `Provider.register` call; providers are now discovered via the extension registry.
8
+ - Delete local `RegistryPublisher`, `RegistryEventBuilder`, and `Transport/` directory; use shared classes from lex-llm base.
9
+ - Replace `config.ollama_api_base` with `resolve_base_url` multi-host resolution from the base provider contract.
10
+ - Enrich `parse_list_models_response` to infer embedding capabilities and modalities from model name and family.
11
+ - Add `settings` and `config_base_url` accessors to the provider for whitelist/blacklist and base URL resolution support.
12
+
3
13
  ## 0.1.6 - 2026-04-30
4
14
 
5
15
  - Add `Legion::Logging::Helper` to Ollama module, RegistryPublisher, and RegistryEventBuilder.
data/README.md CHANGED
@@ -2,22 +2,21 @@
2
2
 
3
3
  LegionIO LLM provider extension for [Ollama](https://ollama.ai).
4
4
 
5
- This gem lives under `Legion::Extensions::Llm::Ollama` and depends on `lex-llm` for shared provider-neutral routing, fleet, and schema primitives.
5
+ This gem lives under `Legion::Extensions::Llm::Ollama` and depends on `lex-llm` (>= 0.1.9) for shared provider-neutral routing, fleet, transport, and registry primitives.
6
6
 
7
7
  Load it with `require 'legion/extensions/llm/ollama'`.
8
8
 
9
9
  ## What It Provides
10
10
 
11
- - `Legion::Extensions::Llm::Provider` registration as `:ollama`
12
11
  - Ollama-native chat requests through `POST /api/chat`
13
12
  - Streaming chat support
14
- - Model discovery through `GET /api/tags`
13
+ - Model discovery through `GET /api/tags` with automatic embedding capability inference
15
14
  - Running model inspection through `GET /api/ps`
16
15
  - Model details through `POST /api/show`
17
16
  - Model download helper through `POST /api/pull`
18
17
  - Embeddings through `POST /api/embed`
19
- - Best-effort `llm.registry` availability events from readiness and model discovery when Legion Transport is loaded
20
- - Shared fleet/default settings via `Legion::Extensions::Llm.provider_settings`
18
+ - Best-effort `llm.registry` availability events via the shared `Legion::Extensions::Llm::RegistryPublisher`
19
+ - Full settings schema with model whitelist/blacklist, TLS, and multi-host base URL resolution
21
20
  - Full `Legion::Logging::Helper` integration with structured `handle_exception` in every rescue block
22
21
 
23
22
  ## Architecture
@@ -25,11 +24,10 @@ Load it with `require 'legion/extensions/llm/ollama'`.
25
24
  ```
26
25
  Legion::Extensions::Llm::Ollama
27
26
  ├── Provider # Ollama provider (chat, stream, embed, models, readiness)
28
- ├── RegistryPublisher # Best-effort async llm.registry event publishing
29
- ├── RegistryEventBuilder # Sanitized lex-llm registry envelope construction
30
- └── Transport/
31
- ├── Exchanges::LlmRegistry # Topic exchange for llm.registry
32
- └── Messages::RegistryEvent # AMQP message wrapper for registry events
27
+ └── (shared from lex-llm)
28
+ ├── RegistryPublisher # Best-effort async llm.registry event publishing
29
+ ├── RegistryEventBuilder # Sanitized registry envelope construction
30
+ └── Transport/ # Shared exchange and message classes
33
31
  ```
34
32
 
35
33
  ## Defaults
@@ -37,16 +35,14 @@ Legion::Extensions::Llm::Ollama
37
35
  ```ruby
38
36
  Legion::Extensions::Llm::Ollama.default_settings
39
37
  # {
40
- # provider_family: :ollama,
41
- # instances: {
42
- # default: {
43
- # endpoint: "http://localhost:11434",
44
- # tier: :local,
45
- # transport: :http,
46
- # usage: { inference: true, embedding: true },
47
- # limits: { concurrency: 1 }
48
- # }
49
- # }
38
+ # enabled: false,
39
+ # base_url: '127.0.0.1:11434',
40
+ # default_model: 'qwen3.5:latest',
41
+ # model_whitelist: [],
42
+ # model_blacklist: [],
43
+ # model_cache_ttl: 60,
44
+ # tls: { enabled: false, verify: :peer },
45
+ # instances: {}
50
46
  # }
51
47
  ```
52
48
 
@@ -54,7 +50,6 @@ Legion::Extensions::Llm::Ollama.default_settings
54
50
 
55
51
  ```ruby
56
52
  Legion::Extensions::Llm.configure do |config|
57
- config.ollama_api_base = "http://localhost:11434"
58
53
  config.default_model = "qwen3.6:27b"
59
54
  config.default_embedding_model = "nomic-embed-text:latest"
60
55
  end
@@ -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.1.5'
29
+ spec.add_dependency 'lex-llm', '>= 0.1.9'
30
30
  end
@@ -13,12 +13,11 @@ module Legion
13
13
 
14
14
  def slug = 'ollama'
15
15
  def local? = true
16
- def configuration_options = %i[ollama_api_base ollama_keep_alive]
17
16
  def configuration_requirements = []
18
17
  def capabilities = Capabilities
19
18
 
20
19
  def registry_publisher
21
- @registry_publisher ||= RegistryPublisher.new
20
+ @registry_publisher ||= Ollama.registry_publisher
22
21
  end
23
22
  end
24
23
 
@@ -33,8 +32,16 @@ module Legion
33
32
  def embeddings?(_model) = true
34
33
  end
35
34
 
35
+ def settings
36
+ Ollama.default_settings
37
+ end
38
+
36
39
  def api_base
37
- config.ollama_api_base || 'http://localhost:11434'
40
+ resolve_base_url || normalize_url(settings[:base_url] || '127.0.0.1:11434')
41
+ end
42
+
43
+ def config_base_url
44
+ settings[:base_url]
38
45
  end
39
46
 
40
47
  def completion_url = '/api/chat'
@@ -87,13 +94,17 @@ module Legion
87
94
 
88
95
  private
89
96
 
97
+ def ollama_keep_alive
98
+ settings[:keep_alive]
99
+ end
100
+
90
101
  def render_payload(messages, tools:, temperature:, model:, stream:, schema:, thinking:, tool_prefs:) # rubocop:disable Metrics/ParameterLists
91
102
  {
92
103
  model: model.id,
93
104
  messages: format_messages(messages),
94
105
  stream: stream,
95
106
  think: thinking ? true : nil,
96
- keep_alive: config.ollama_keep_alive,
107
+ keep_alive: ollama_keep_alive,
97
108
  format: schema_format(schema),
98
109
  options: { temperature: temperature }.compact,
99
110
  tools: format_tools(tools),
@@ -193,17 +204,36 @@ module Legion
193
204
 
194
205
  def parse_list_models_response(response, provider, _capabilities)
195
206
  response.body.fetch('models', []).map do |model|
207
+ family = model.dig('details', 'family')
208
+ caps = infer_capabilities(model.fetch('name'), family, Array(model['capabilities']))
209
+ output_mods = embedding_model?(model.fetch('name'), family) ? [:embeddings] : [:text]
210
+
196
211
  Legion::Extensions::Llm::Model::Info.new(
197
212
  id: model.fetch('name'),
198
213
  name: model.fetch('name'),
199
214
  provider: provider,
200
- created_at: model['modified_at'],
201
- capabilities: Array(model['capabilities']),
202
- metadata: model
215
+ family: family,
216
+ capabilities: caps,
217
+ modalities_output: output_mods,
218
+ metadata: model.merge('created_at' => model['modified_at'])
203
219
  )
204
220
  end
205
221
  end
206
222
 
223
+ def infer_capabilities(name, family, api_caps)
224
+ return api_caps.map(&:to_sym) unless api_caps.empty?
225
+
226
+ if embedding_model?(name, family)
227
+ [:embedding]
228
+ else
229
+ %i[completion streaming tools vision]
230
+ end
231
+ end
232
+
233
+ def embedding_model?(name, family)
234
+ name.to_s.match?(/embed|embedding/i) || family.to_s.match?(/bert|nomic/i)
235
+ end
236
+
207
237
  def render_embedding_payload(text, model:, dimensions:)
208
238
  { model: model, input: text, dimensions: dimensions }.compact
209
239
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Ollama
7
- VERSION = '0.1.6'
7
+ VERSION = '0.1.7'
8
8
  end
9
9
  end
10
10
  end
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/extensions/llm'
4
- require 'legion/extensions/llm/ollama/registry_event_builder'
5
4
  require 'legion/extensions/llm/ollama/provider'
6
- require 'legion/extensions/llm/ollama/registry_publisher'
7
5
  require 'legion/extensions/llm/ollama/version'
8
6
 
9
7
  module Legion
@@ -17,25 +15,26 @@ module Legion
17
15
  PROVIDER_FAMILY = :ollama
18
16
 
19
17
  def self.default_settings
20
- ::Legion::Extensions::Llm.provider_settings(
21
- family: PROVIDER_FAMILY,
22
- instance: {
23
- endpoint: 'http://localhost:11434',
24
- tier: :local,
25
- transport: :http,
26
- usage: { inference: true, embedding: true },
27
- limits: { concurrency: 1 }
28
- }
29
- )
18
+ {
19
+ enabled: false,
20
+ base_url: '127.0.0.1:11434',
21
+ default_model: 'qwen3.5:latest',
22
+ model_whitelist: [],
23
+ model_blacklist: [],
24
+ model_cache_ttl: 60,
25
+ tls: { enabled: false, verify: :peer },
26
+ instances: {}
27
+ }
30
28
  end
31
29
 
32
30
  def self.provider_class
33
31
  Provider
34
32
  end
33
+
34
+ def self.registry_publisher
35
+ @registry_publisher ||= Legion::Extensions::Llm::RegistryPublisher.new(provider_family: PROVIDER_FAMILY)
36
+ end
35
37
  end
36
38
  end
37
39
  end
38
40
  end
39
-
40
- Legion::Extensions::Llm::Provider.register(Legion::Extensions::Llm::Ollama::PROVIDER_FAMILY,
41
- Legion::Extensions::Llm::Ollama::Provider)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-ollama
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
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.1.5
60
+ version: 0.1.9
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.1.5
67
+ version: 0.1.9
68
68
  description: Ollama 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-ollama.gemspec
85
85
  - lib/legion/extensions/llm/ollama.rb
86
86
  - lib/legion/extensions/llm/ollama/provider.rb
87
- - lib/legion/extensions/llm/ollama/registry_event_builder.rb
88
- - lib/legion/extensions/llm/ollama/registry_publisher.rb
89
- - lib/legion/extensions/llm/ollama/transport/exchanges/llm_registry.rb
90
- - lib/legion/extensions/llm/ollama/transport/messages/registry_event.rb
91
87
  - lib/legion/extensions/llm/ollama/version.rb
92
88
  homepage: https://github.com/LegionIO/lex-llm-ollama
93
89
  licenses:
@@ -1,146 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- module Ollama
7
- # Builds sanitized lex-llm registry envelopes for Ollama provider state.
8
- class RegistryEventBuilder # rubocop:disable Metrics/ClassLength
9
- include Legion::Logging::Helper
10
-
11
- def readiness(readiness)
12
- registry_event_class.public_send(
13
- readiness[:ready] ? :available : :unavailable,
14
- provider_offering(readiness),
15
- runtime: runtime_metadata,
16
- health: readiness_health(readiness),
17
- metadata: readiness_metadata(readiness)
18
- )
19
- end
20
-
21
- def model_available(model, readiness:)
22
- registry_event_class.available(
23
- model_offering(model),
24
- runtime: runtime_metadata,
25
- health: model_health(readiness),
26
- metadata: model_metadata(model)
27
- )
28
- end
29
-
30
- private
31
-
32
- def provider_offering(readiness)
33
- {
34
- provider_family: :ollama,
35
- provider_instance: provider_instance,
36
- transport: :http,
37
- model: 'provider-readiness',
38
- usage_type: :inference,
39
- capabilities: [],
40
- health: readiness_health(readiness),
41
- metadata: { lex: :llm_ollama, provider_readiness: true }
42
- }
43
- end
44
-
45
- def model_offering(model)
46
- {
47
- provider_family: :ollama,
48
- provider_instance: provider_instance,
49
- transport: :http,
50
- model: model.id,
51
- usage_type: usage_type_for(model),
52
- capabilities: capabilities_for(model),
53
- limits: model_limits(model),
54
- metadata: model_metadata(model)
55
- }
56
- end
57
-
58
- def readiness_health(readiness)
59
- health = {
60
- ready: readiness[:ready] == true,
61
- status: readiness[:ready] ? :available : :unavailable,
62
- checked: readiness.dig(:health, :checked) != false
63
- }
64
- add_readiness_error(health, readiness[:health])
65
- end
66
-
67
- def add_readiness_error(health, source)
68
- error = source.is_a?(Hash) ? source : {}
69
- error_class = error[:error] || error['error']
70
- error_message = error[:message] || error['message']
71
- health[:error_class] = error_class if error_class
72
- health[:error] = error_message if error_message
73
- health
74
- end
75
-
76
- def model_health(readiness)
77
- ready = readiness.fetch(:ready, true) == true
78
- { ready:, status: ready ? :available : :degraded }
79
- end
80
-
81
- def readiness_metadata(readiness)
82
- {
83
- extension: :lex_llm_ollama,
84
- provider: :ollama,
85
- configured: readiness[:configured] == true,
86
- live: readiness[:live] == true
87
- }
88
- end
89
-
90
- def model_metadata(model)
91
- metadata = model.metadata || {}
92
- {
93
- extension: :lex_llm_ollama,
94
- provider: :ollama,
95
- model_name: model.name,
96
- family: metadata.dig('details', 'family'),
97
- parameter_size: metadata.dig('details', 'parameter_size'),
98
- quantization_level: metadata.dig('details', 'quantization_level')
99
- }.compact
100
- end
101
-
102
- def runtime_metadata
103
- { node: provider_instance }
104
- end
105
-
106
- def model_limits(model)
107
- context_window = model.metadata&.dig('model_info', 'general.context_length') ||
108
- model.metadata&.dig('model_info', "#{model_family(model)}.context_length")
109
- { context_window: context_window }.compact
110
- end
111
-
112
- def capabilities_for(model)
113
- configured = Array(model.capabilities).map(&:to_sym)
114
- return configured unless configured.empty?
115
-
116
- usage_type_for(model) == :embedding ? [:embedding] : %i[chat streaming tools vision]
117
- end
118
-
119
- def usage_type_for(model)
120
- return :embedding if model.id.to_s.match?(/embed|embedding/i)
121
-
122
- family = model_family(model).to_s
123
- family.match?(/bert|nomic/i) ? :embedding : :inference
124
- end
125
-
126
- def model_family(model)
127
- model.metadata&.dig('details', 'family') || model.metadata&.dig(:details, :family)
128
- end
129
-
130
- def provider_instance
131
- configured_node = (::Legion::Settings.dig(:node, :canonical_name) if defined?(::Legion::Settings))
132
- value = configured_node.to_s.strip
133
- value.empty? ? :ollama : value.to_sym
134
- rescue StandardError => e
135
- handle_exception(e, level: :debug, handled: true, operation: 'ollama.registry.provider_instance')
136
- :ollama
137
- end
138
-
139
- def registry_event_class
140
- ::Legion::Extensions::Llm::Routing::RegistryEvent
141
- end
142
- end
143
- end
144
- end
145
- end
146
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- module Ollama
7
- # Best-effort publisher for Ollama provider availability events.
8
- class RegistryPublisher
9
- include Legion::Logging::Helper
10
-
11
- APP_ID = 'lex-llm-ollama'
12
-
13
- def initialize(builder: RegistryEventBuilder.new)
14
- @builder = builder
15
- end
16
-
17
- def publish_readiness_async(readiness)
18
- log.info { 'publishing readiness event to llm.registry' }
19
- schedule { publish_event(@builder.readiness(readiness)) }
20
- end
21
-
22
- def publish_models_async(models, readiness:)
23
- log.info { "publishing #{Array(models).size} model event(s) to llm.registry" }
24
- schedule do
25
- Array(models).each do |model|
26
- publish_event(@builder.model_available(model, readiness:))
27
- end
28
- end
29
- end
30
-
31
- private
32
-
33
- def schedule(&)
34
- return false unless publishing_available?
35
-
36
- Thread.new do
37
- Thread.current.abort_on_exception = false
38
- yield
39
- rescue StandardError => e
40
- handle_exception(e, level: :debug, handled: true, operation: 'ollama.registry.schedule_thread')
41
- end
42
- rescue StandardError => e
43
- handle_exception(e, level: :debug, handled: true, operation: 'ollama.registry.schedule')
44
- false
45
- end
46
-
47
- def publish_event(event)
48
- return false unless publishing_available?
49
-
50
- message_class.new(event:, app_id: APP_ID).publish(spool: false)
51
- rescue StandardError => e
52
- handle_exception(e, level: :warn, handled: true, operation: 'ollama.registry.publish_event')
53
- false
54
- end
55
-
56
- def publishing_available?
57
- return false unless registry_event_available?
58
- return false unless transport_message_available?
59
- return true unless defined?(::Legion::Transport::Connection)
60
- return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
61
-
62
- ::Legion::Transport::Connection.session_open?
63
- rescue StandardError => e
64
- handle_exception(e, level: :debug, handled: true, operation: 'ollama.registry.publishing_available?')
65
- false
66
- end
67
-
68
- def registry_event_available?
69
- defined?(::Legion::Extensions::Llm::Routing::RegistryEvent)
70
- end
71
-
72
- def transport_message_available?
73
- return true if message_class_defined?
74
- return false unless defined?(::Legion::Transport::Message) && defined?(::Legion::Transport::Exchange)
75
-
76
- require 'legion/extensions/llm/ollama/transport/messages/registry_event'
77
- message_class_defined?
78
- rescue LoadError => e
79
- handle_exception(e, level: :debug, handled: true, operation: 'ollama.registry.transport_load')
80
- false
81
- end
82
-
83
- def message_class_defined?
84
- defined?(::Legion::Extensions::Llm::Ollama::Transport::Messages::RegistryEvent)
85
- end
86
-
87
- def message_class
88
- ::Legion::Extensions::Llm::Ollama::Transport::Messages::RegistryEvent
89
- end
90
- end
91
- end
92
- end
93
- end
94
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Llm
6
- module Ollama
7
- module Transport
8
- module Exchanges
9
- # Topic exchange for Ollama 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/ollama/transport/exchanges/llm_registry'
4
-
5
- module Legion
6
- module Extensions
7
- module Llm
8
- module Ollama
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