lex-llm-vllm 0.4.5 → 0.4.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: bb587332ecdb4d2a8e4535d1b03250b3a63e7a8ce5171dc5198c11ad40549bef
4
- data.tar.gz: ec906ffff9f3d1a0496a1d0043d25902c08031d6cd613a239847ca97474e4def
3
+ metadata.gz: d68ddb8f9c969ef5991b7437e6d0d9cea05f2e4dfe9f39fbb9696ec513988b4f
4
+ data.tar.gz: 9fa21d69ed1c20795872db8648140c4d89b8155487a6def3f0fe7f121bf39e1e
5
5
  SHA512:
6
- metadata.gz: fd7215d64cda75590f936d6f6a4e39d3669826ee00639e765f426e602cf6a465c61a62bb0655126cb608552d70692eac2469def71a429bb4c885899cb9d9ce95
7
- data.tar.gz: 275be07c6a109418995c759a2c9fcf5a67b567cda41335a9bda5e3a0ca723f4cc9fe8436840b04edcdd0f403c1cb7864b475eb407644d22562b1ea64bba22384
6
+ metadata.gz: 5a372fff3346ff953fe4265073c19cefe72064a2592adb2d8313f2a61241d657424afd1eeb973284d3c15269500c04bcf47b16402317e9c240aebe2c213d62ad
7
+ data.tar.gz: f2ea87b7809e88965c53c19f412c3c37eb1036a3ffab1cf20bedf60c9c672c6cb918aee3aaf96ee1df513da7815fe287c84ccc8b09808ce6ef96ab5704cc5664
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.6] - 2026-08-19
4
+
5
+ ### Changed
6
+ - Publish the immutable four-component lane-weight pair from vLLM discovery and reconcile weight-only changes on the existing module-runner cadence.
7
+ - Serialize initial, recovery, replacement, removal, shutdown, and probe publication transitions behind one module-level mutex without adding a Settings callback or operator workflow.
8
+ - Track configured-but-unpublished weight keys on the ordinary discovery pass and log each dormant transition once.
9
+ - Raise the `lex-llm` dependency floor to 0.7.6; the existing `legion-settings` dependency remains unchanged.
10
+
11
+ ### Fixed
12
+ - Compare the offering catalog as a duplicate-preserving multiset of complete contracts while excluding only non-authoritative evidence observation timestamps, preventing both reorder-only churn and accidental duplicate suppression while retaining real evidence and weight changes.
13
+ - Complete startup discovery and weight validation before constructing a callable or claiming inventory, so malformed settings leave no orphaned initializing scope and the next valid cadence pass recovers without operator action.
14
+ - Use a real in-memory IO sink for the test logger so Ruby 4 cannot fall back to stdout during the file-only RSpec release gate.
15
+
16
+ ### Added
17
+ - Cover the complete writer lifecycle, publication races, failure atomicity, dormant-state cycle, and the actual callable's folded-system OpenAI-compatible wire payload.
18
+
3
19
  ## [0.4.5] - 2026-08-18
4
20
 
5
21
  ### Fixed
data/lex-llm-vllm.gemspec CHANGED
@@ -27,8 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency 'legion-logging', '>= 1.3.2'
28
28
  spec.add_dependency 'legion-settings', '>= 1.4.2'
29
29
  spec.add_dependency 'legion-transport', '>= 1.4.14'
30
- # 0.7.1 adds InstanceKey#physical_id (secondary physical identity) the
31
- # discovery runner claims with physical_id:, so the floor must not resolve
32
- # to a lex-llm without the field.
33
- spec.add_dependency 'lex-llm', '>= 0.7.1'
30
+ # 0.7.6 carries the write-time weight schema/reconciler and the immutable
31
+ # OfferingDraft/OfferingRecord/LaneRecord weight pair used by this writer.
32
+ spec.add_dependency 'lex-llm', '>= 0.7.6'
34
33
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/extensions/llm/inventory/evidence'
4
+ require 'legion/extensions/llm/inventory/weight_reconciler'
4
5
 
5
6
  module Legion
6
7
  module Extensions
@@ -20,6 +21,13 @@ module Legion
20
21
  tier = @instance_cfg[:tier] || :direct
21
22
  embed_sup = embedding_supported?(model_data)
22
23
  max_output = model_data[:max_output_tokens] || model_data[:max_completion_tokens]
24
+ weight_inputs = Legion::Extensions::Llm::Inventory::WeightSchema.weight_inputs(
25
+ settings: Legion::Settings,
26
+ instance_key: instance_key,
27
+ provider_native_key: model_id,
28
+ model: model_id,
29
+ tier: tier
30
+ )
23
31
 
24
32
  Legion::Extensions::Llm::Inventory::OfferingDraft.new(
25
33
  provider_native_key: model_id,
@@ -34,7 +42,9 @@ module Legion
34
42
  tokenizer_evidence: build_tokenizer_evidence(model_data[:tokenizer]),
35
43
  quota_domains: {},
36
44
  metadata: build_metadata(model_data),
37
- publication_source: :provider_catalog
45
+ publication_source: :provider_catalog,
46
+ weight_inputs: weight_inputs,
47
+ base_weight: Legion::Extensions::Llm::Inventory::WeightSchema.base_weight(weight_inputs)
38
48
  )
39
49
  end
40
50
 
@@ -31,30 +31,32 @@ module Legion
31
31
  # (InstanceKey.instance_id); the derived host:port/ak string is
32
32
  # the secondary physical_id (dedup/diagnostics, never identity).
33
33
  def claim_and_activate_instance(name:, instance_cfg:)
34
+ instance_id = name.to_s
34
35
  instance_key = Legion::Extensions::Llm::Inventory::Identity::InstanceKey.new(
35
- provider_family: :vllm, instance_id: name,
36
+ provider_family: :vllm, instance_id: instance_id,
36
37
  physical_id: derive_physical_id(instance_cfg: instance_cfg)
37
38
  )
39
+ offerings = fetch_offerings(instance_cfg: instance_cfg, instance_key: instance_key)
38
40
  callable = Legion::Extensions::Llm::Vllm::VllmCallable.new(instance_cfg: instance_cfg, logger: log)
39
41
  probe_coordinator = Legion::Extensions::Llm::Inventory::ProbeCoordinator.new(
40
- instance_key: instance_key, enqueue: build_probe_enqueue(instance_id: name)
42
+ instance_key: instance_key, enqueue: build_probe_enqueue(instance_id: instance_id)
41
43
  )
42
44
  publisher_token = publisher.claim_instance(
43
- instance_id: name, physical_id: instance_key.physical_id,
45
+ instance_id: instance_id, physical_id: instance_key.physical_id,
44
46
  callable: callable, probe_request_handle: probe_coordinator
45
47
  )
46
-
47
- # Store state BEFORE readiness so a failed boot probe leaves a
48
- # recoverable :initializing entry for the next tick (D4).
49
48
  state = {
50
- name: name, instance_key: instance_key, instance_cfg: instance_cfg,
49
+ name: instance_id, instance_key: instance_key, instance_cfg: instance_cfg,
51
50
  callable: callable, probe_coordinator: probe_coordinator,
52
- publisher_token: publisher_token, sequence: 0, offerings: []
51
+ publisher_token: publisher_token, sequence: 0, offerings: offerings
53
52
  }
54
- instance_states[name] = state
55
-
56
- offerings = fetch_offerings(instance_cfg: instance_cfg, instance_key: instance_key)
57
- perform_readiness(instance_id: name, state: state, offerings: offerings)
53
+ Legion::Extensions::Llm::Inventory::WeightReconciler.track_initializing!(
54
+ states: instance_states,
55
+ state_key: instance_id,
56
+ state: state,
57
+ mutex: state_mutex
58
+ )
59
+ perform_readiness(instance_id: instance_id, state: state, offerings: offerings)
58
60
  end
59
61
 
60
62
  # Run a readiness probe and commit the outcome. While the instance
@@ -62,7 +64,9 @@ module Legion
62
64
  # once activated it reports success/failure against the
63
65
  # availability fact.
64
66
  def perform_readiness(instance_id:, state:, offerings:)
65
- state[:offerings] = offerings
67
+ reconcile_weight_snapshot(
68
+ instance_id: instance_id, state: state, discovered_offerings: offerings
69
+ )
66
70
  probe_token = publisher.readiness_probe_started(
67
71
  instance_id: instance_id, publisher_token: state[:publisher_token]
68
72
  )
@@ -75,23 +79,43 @@ module Legion
75
79
  end
76
80
 
77
81
  def report_probe_result(instance_id:, state:, probe_token:, readiness:)
78
- status = publisher.snapshot.publication_status(instance_key: state[:instance_key])
79
- if readiness.ready? && status.state == :initializing
80
- publisher.activate_instance_snapshot(
81
- instance_id: instance_id, publisher_token: state[:publisher_token],
82
- offerings: state[:offerings], sequence: state[:sequence], probe_token: probe_token
83
- )
84
- elsif readiness.ready?
85
- publisher.readiness_succeeded(instance_id: instance_id, probe_token: probe_token)
86
- else
87
- publisher.readiness_failed(
88
- instance_id: instance_id, probe_token: probe_token, reason: readiness.reason
89
- )
90
- end
91
- write_instance_health(state)
82
+ committed = if readiness.ready? && tracked_unpublished?(instance_id: instance_id, state: state)
83
+ activate_tracked_state(
84
+ instance_id: instance_id, state: state, probe_token: probe_token
85
+ )
86
+ else
87
+ report_tracked_readiness(
88
+ instance_id: instance_id, state: state,
89
+ probe_token: probe_token, readiness: readiness
90
+ )
91
+ end
92
+ write_instance_health(state) if committed
93
+ committed
92
94
  rescue StandardError => e
93
95
  handle_exception(e, level: :warn, operation: 'vllm.runner.discovery.report_probe',
94
96
  instance_id: instance_id)
97
+ false
98
+ end
99
+
100
+ def tracked_unpublished?(instance_id:, state:)
101
+ state_mutex.synchronize do
102
+ instance_states[instance_id].equal?(state) && !state.fetch(:published)
103
+ end
104
+ end
105
+
106
+ def report_tracked_readiness(instance_id:, state:, probe_token:, readiness:)
107
+ state_mutex.synchronize do
108
+ return false unless instance_states[instance_id].equal?(state)
109
+
110
+ if readiness.ready?
111
+ publisher.readiness_succeeded(instance_id: instance_id, probe_token: probe_token)
112
+ else
113
+ publisher.readiness_failed(
114
+ instance_id: instance_id, probe_token: probe_token, reason: readiness.reason
115
+ )
116
+ end
117
+ true
118
+ end
95
119
  end
96
120
  end
97
121
  end
@@ -15,7 +15,7 @@ module Legion
15
15
  snapshot = publisher.snapshot
16
16
  display = display_fact(instance_key: instance_key, snapshot: snapshot)
17
17
 
18
- instance_settings = settings.dig(:instances, state[:name])
18
+ instance_settings = settings.dig(:instances, state[:name].to_sym)
19
19
  return unless instance_settings.is_a?(Hash)
20
20
 
21
21
  instance_settings[:health] = health_hash(display)
@@ -26,7 +26,7 @@ module Legion
26
26
  end
27
27
 
28
28
  def clear_settings_health(name:)
29
- instance_settings = settings.dig(:instances, name)
29
+ instance_settings = settings.dig(:instances, name.to_sym)
30
30
  return unless instance_settings.is_a?(Hash)
31
31
 
32
32
  instance_settings.delete(:health)
@@ -31,28 +31,36 @@ module Legion
31
31
  # names pointing at the same endpoint remain distinct instances.
32
32
  def reconcile_instances
33
33
  configured = Vllm.discover_instances
34
+ configured_ids = configured.keys.map(&:to_s)
34
35
 
35
- instance_states.each_key do |instance_id|
36
- next if configured.key?(instance_id)
36
+ tracked_ids = state_mutex.synchronize { instance_states.keys }
37
+ tracked_ids.each do |instance_id|
38
+ next if configured_ids.include?(instance_id)
37
39
 
38
40
  remove_instance_state(instance_id)
39
41
  end
40
42
 
41
43
  configured.each do |name, instance_cfg|
42
- update_instance(name: name, instance_cfg: instance_cfg)
44
+ update_instance(name: name.to_s, instance_cfg: instance_cfg)
43
45
  rescue StandardError => e
44
46
  handle_exception(e, level: :warn, operation: 'vllm.runner.discovery.instance',
45
47
  instance_name: name.to_s)
46
48
  end
49
+ observe_dormant_weights
47
50
  end
48
51
 
49
52
  def remove_all_instances(**)
50
- instance_states.each_key { |instance_id| remove_instance_state(instance_id) }
53
+ tracked_ids = state_mutex.synchronize { instance_states.keys }
54
+ tracked_ids.each { |instance_id| remove_instance_state(instance_id) }
55
+ state_mutex.synchronize do
56
+ instance_states.clear
57
+ dormant_weight_tracker.clear!
58
+ end
51
59
  { success: true }
52
60
  end
53
61
 
54
62
  def update_instance(name:, instance_cfg:)
55
- state = instance_states[name]
63
+ state = state_mutex.synchronize { instance_states[name] }
56
64
  if state && physical_id_changed?(state, instance_cfg)
57
65
  # The physical target moved (endpoint or API key changed) —
58
66
  # the captured callable points at the old endpoint, so drop
@@ -71,10 +79,18 @@ module Legion
71
79
  end
72
80
 
73
81
  def remove_instance_state(instance_id)
74
- state = instance_states.delete(instance_id)
82
+ state = state_mutex.synchronize do
83
+ tracked = instance_states[instance_id]
84
+ next unless tracked
85
+
86
+ publisher.remove_instance(
87
+ instance_id: instance_id, publisher_token: tracked[:publisher_token]
88
+ )
89
+ instance_states.delete(instance_id) if instance_states[instance_id].equal?(tracked)
90
+ tracked
91
+ end
75
92
  return unless state
76
93
 
77
- publisher.remove_instance(instance_id: instance_id, publisher_token: state[:publisher_token])
78
94
  clear_settings_health(name: state[:name])
79
95
  rescue StandardError => e
80
96
  handle_exception(e, level: :warn, operation: 'vllm.runner.discovery.remove_instance',
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module Vllm
7
+ module Runners
8
+ module DiscoveryRefresh
9
+ # vLLM rebuilds fresh evidence timestamps on every catalog pass.
10
+ # Compare the complete draft contract while excluding only that
11
+ # non-authoritative observation telemetry.
12
+ module OfferingComparison
13
+ SCALAR_EVIDENCE_FIELDS = %i[
14
+ context_evidence max_output_evidence embedding_dimensions_evidence
15
+ model_revision_evidence tokenizer_evidence
16
+ ].freeze
17
+
18
+ def offerings_equivalent?(previous, current)
19
+ offering_comparison_multiset(previous) == offering_comparison_multiset(current)
20
+ end
21
+
22
+ private
23
+
24
+ def offering_comparison_multiset(offerings)
25
+ Array(offerings).map { |draft| offering_comparison_state(draft) }.tally
26
+ end
27
+
28
+ def offering_comparison_state(draft)
29
+ state = draft.to_h
30
+ state[:operation_evidence] = comparison_evidence_map(draft.operation_evidence)
31
+ state[:capability_evidence] = comparison_evidence_map(draft.capability_evidence)
32
+ SCALAR_EVIDENCE_FIELDS.each do |field|
33
+ state[field] = comparison_evidence(draft.public_send(field))
34
+ end
35
+ state
36
+ end
37
+
38
+ def comparison_evidence_map(evidence)
39
+ evidence.transform_values { |entry| comparison_evidence(entry) }
40
+ end
41
+
42
+ def comparison_evidence(evidence)
43
+ evidence.to_h.except(:observed_at)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -10,7 +10,7 @@ module Legion
10
10
  # discovery runner. Mixed into DiscoveryRefresh.
11
11
  module Probing
12
12
  def refresh_instance(instance_id:, instance_cfg:)
13
- state = instance_states[instance_id]
13
+ state = state_mutex.synchronize { instance_states[instance_id] }
14
14
  return unless state
15
15
 
16
16
  status = publisher.snapshot.publication_status(instance_key: state[:instance_key])
@@ -29,15 +29,10 @@ module Legion
29
29
 
30
30
  def replace_if_changed(instance_id:, state:, instance_cfg:)
31
31
  new_offerings = fetch_offerings(instance_cfg: instance_cfg, instance_key: state[:instance_key])
32
- return if new_offerings == state[:offerings]
33
-
34
- state[:sequence] += 1
35
- publisher.replace_instance_snapshot(
36
- instance_id: instance_id, publisher_token: state[:publisher_token],
37
- offerings: new_offerings, sequence: state[:sequence]
32
+ changed = reconcile_weight_snapshot(
33
+ instance_id: instance_id, state: state, discovered_offerings: new_offerings
38
34
  )
39
- state[:offerings] = new_offerings
40
- write_instance_health(state)
35
+ write_instance_health(state) if changed
41
36
  end
42
37
 
43
38
  def run_cadence_probe(instance_id:, state:)
@@ -58,7 +53,7 @@ module Legion
58
53
  end
59
54
 
60
55
  def handle_reactive_probe(instance_id:, request:)
61
- state = instance_states[instance_id]
56
+ state = state_mutex.synchronize { instance_states[instance_id] }
62
57
  return unless state
63
58
 
64
59
  coordinator = state[:probe_coordinator]
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Llm
6
+ module Vllm
7
+ module Runners
8
+ module DiscoveryRefresh
9
+ # Module-runner bindings for the shared writer reconciler. The
10
+ # existing discovery cadence remains the only execution path.
11
+ module WeightPublication
12
+ def reconcile_weight_snapshot(instance_id:, state:, discovered_offerings:)
13
+ Legion::Extensions::Llm::Inventory::WeightReconciler.commit_if_changed!(
14
+ settings: Legion::Settings,
15
+ instance_id: instance_id,
16
+ state: state,
17
+ discovered_offerings: discovered_offerings,
18
+ mutex: state_mutex,
19
+ equivalent: lambda do |previous, current|
20
+ !instance_states[instance_id].equal?(state) ||
21
+ offerings_equivalent?(previous, current)
22
+ end,
23
+ replace: method(:replace_weight_snapshot)
24
+ )
25
+ end
26
+
27
+ def replace_weight_snapshot(instance_id:, state:, offerings:, sequence:)
28
+ publisher.replace_instance_snapshot(
29
+ instance_id: instance_id,
30
+ publisher_token: state.fetch(:publisher_token),
31
+ offerings: offerings,
32
+ sequence: sequence
33
+ )
34
+ end
35
+
36
+ def activate_weight_snapshot(instance_id:, state:, offerings:, sequence:, probe_token:)
37
+ publisher.activate_instance_snapshot(
38
+ instance_id: instance_id,
39
+ publisher_token: state.fetch(:publisher_token),
40
+ offerings: offerings,
41
+ sequence: sequence,
42
+ probe_token: probe_token
43
+ )
44
+ end
45
+
46
+ def activate_tracked_state(instance_id:, state:, probe_token:)
47
+ Legion::Extensions::Llm::Inventory::WeightReconciler.activate_tracked!(
48
+ settings: Legion::Settings,
49
+ instance_id: instance_id,
50
+ state_key: state.fetch(:name),
51
+ state: state,
52
+ states: instance_states,
53
+ mutex: state_mutex,
54
+ probe_token: probe_token,
55
+ activate: method(:activate_weight_snapshot),
56
+ activation_sequence: ->(tracked) { tracked.fetch(:sequence) }
57
+ )
58
+ end
59
+
60
+ def observe_dormant_weights
61
+ Legion::Extensions::Llm::Inventory::WeightReconciler.observe_dormant!(
62
+ settings: Legion::Settings,
63
+ provider_family: :vllm,
64
+ states: instance_states,
65
+ mutex: state_mutex,
66
+ tracker: dormant_weight_tracker,
67
+ dormant_logger: lambda do |key|
68
+ log.info(
69
+ "[llm][vllm] action=dormant_weight weight_key=#{key.inspect} no_lane_published=true"
70
+ )
71
+ end
72
+ )
73
+ end
74
+
75
+ def state_mutex
76
+ @state_mutex ||= Mutex.new
77
+ end
78
+
79
+ def dormant_weight_tracker
80
+ @dormant_weight_tracker ||=
81
+ Legion::Extensions::Llm::Inventory::DormantWeightTracker.new
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -7,6 +7,8 @@ require 'legion/logging'
7
7
  require 'legion/extensions/llm/vllm'
8
8
  require 'legion/extensions/llm/inventory/records'
9
9
 
10
+ require 'legion/extensions/llm/vllm/runners/discovery_refresh/offering_comparison'
11
+ require 'legion/extensions/llm/vllm/runners/discovery_refresh/weight_publication'
10
12
  require 'legion/extensions/llm/vllm/runners/discovery_refresh/instance_lifecycle'
11
13
  require 'legion/extensions/llm/vllm/runners/discovery_refresh/claim_activation'
12
14
  require 'legion/extensions/llm/vllm/runners/discovery_refresh/probing'
@@ -45,6 +47,8 @@ module Legion
45
47
  # Http — model/health HTTP, instance identity
46
48
  module DiscoveryRefresh
47
49
  include Legion::Extensions::Helpers::Lex
50
+ include OfferingComparison
51
+ include WeightPublication
48
52
  include InstanceLifecycle
49
53
  include ClaimActivation
50
54
  include Probing
@@ -60,7 +64,10 @@ module Legion
60
64
  end
61
65
 
62
66
  def self.reset_instance_states!
63
- @instance_states.clear
67
+ state_mutex.synchronize do
68
+ @instance_states.clear
69
+ dormant_weight_tracker.clear!
70
+ end
64
71
  end
65
72
  end
66
73
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Vllm
7
- VERSION = '0.4.5'
7
+ VERSION = '0.4.6'
8
8
  end
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-vllm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.5
4
+ version: 0.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - LegionIO
@@ -71,14 +71,14 @@ dependencies:
71
71
  requirements:
72
72
  - - ">="
73
73
  - !ruby/object:Gem::Version
74
- version: 0.7.1
74
+ version: 0.7.6
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - ">="
80
80
  - !ruby/object:Gem::Version
81
- version: 0.7.1
81
+ version: 0.7.6
82
82
  description: vLLM provider integration for the LegionIO LLM routing framework.
83
83
  email:
84
84
  - matthewdiverson@gmail.com
@@ -107,7 +107,9 @@ files:
107
107
  - lib/legion/extensions/llm/vllm/runners/discovery_refresh/health_display.rb
108
108
  - lib/legion/extensions/llm/vllm/runners/discovery_refresh/http.rb
109
109
  - lib/legion/extensions/llm/vllm/runners/discovery_refresh/instance_lifecycle.rb
110
+ - lib/legion/extensions/llm/vllm/runners/discovery_refresh/offering_comparison.rb
110
111
  - lib/legion/extensions/llm/vllm/runners/discovery_refresh/probing.rb
112
+ - lib/legion/extensions/llm/vllm/runners/discovery_refresh/weight_publication.rb
111
113
  - lib/legion/extensions/llm/vllm/runners/fleet_worker.rb
112
114
  - lib/legion/extensions/llm/vllm/translator.rb
113
115
  - lib/legion/extensions/llm/vllm/version.rb