Module: Legion::Extensions::Llm::Fleet::WorkerExecution
- Extended by:
- Logging::Helper
- Includes:
- Logging::Helper
- Defined in:
- lib/legion/extensions/llm/fleet/worker_execution.rb
Overview
Applies responder-side policy and executes a fleet request against the captured registry callable. Protocol v3: exact execution only — one dispatch path (06 W1/W3), no provider-object topology.
Defined Under Namespace
Classes: PolicyError
Constant Summary collapse
- ERRORS =
Legion::Extensions::Llm::Inventory::Errors
- IDENTITY =
Legion::Extensions::Llm::Inventory::Identity
- MAX_IDEMPOTENCY_ENTRIES =
100_000
Class Method Summary collapse
- .auth_required? ⇒ Boolean
- .available_record!(snapshot, instance_key) ⇒ Object
-
.call(envelope:, registry:) ⇒ Object
The single dispatch entry (06 W1).
-
.dispatch!(envelope:, registry:) ⇒ Object
The one dispatcher (06 W3): the exact resolution chain (W2).
- .dispatch_audio(callable, operation, model, params) ⇒ Object
- .dispatch_image(callable, model, params) ⇒ Object
- .dispatch_operation(callable, operation, model, params) ⇒ Object
- .dispatch_speak(callable, model, params) ⇒ Object
- .evict_oldest_idempotency_entries! ⇒ Object
-
.exact_lane!(snapshot, record, envelope, operation) ⇒ Object
The exact lane the claim names: the 5-tuple claim value must parse and validate, resolve in the snapshot, and agree with the activated instance, the envelope's operation type, and the envelope's model.
-
.exact_operation(envelope) ⇒ Object
06 §5: an unknown operation is a contract error at the worker.
-
.exact_params(envelope) ⇒ Object
W5 params strictness: no :model key (the Selection-derived model is untouchable), no duplicate param spellings.
- .idempotency_ttl_seconds ⇒ Object
- .mark_idempotency_success!(key) ⇒ Object
- .purge_idempotency_cache! ⇒ Object
-
.rehydrate_wire_messages(value) ⇒ Object
W4 — the named rehydration boundary: the ONLY place serialized wire messages become Canonical::Message.
- .release_idempotency!(key) ⇒ Object
- .release_replay!(claims) ⇒ Object
- .require_matching_model!(lane, envelope) ⇒ Object
- .require_matching_operation!(lane, operation) ⇒ Object
- .require_param!(params, key, operation) ⇒ Object
- .reserve_idempotency_key!(key) ⇒ Object
- .reset_idempotency_cache! ⇒ Object
- .responder_setting(key, default:) ⇒ Object
- .validate_idempotency!(envelope) ⇒ Object
- .validate_identity!(envelope) ⇒ Object
-
.validate_policy!(_envelope) ⇒ Object
W6 fail-closed: require_policy with no policy engine configured RAISES.
Class Method Details
.auth_required? ⇒ Boolean
304 305 306 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 304 def auth_required? Settings.value(:fleet, :auth, :require_signed_token, default: true) != false end |
.available_record!(snapshot, instance_key) ⇒ Object
106 107 108 109 110 111 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 106 def available_record!(snapshot, instance_key) record = snapshot.instance(instance_key: instance_key) raise ERRORS::ExactOfferingMismatchError, 'instance is absent, initializing, or unavailable' unless record && record.availability.state == :available record end |
.call(envelope:, registry:) ⇒ Object
The single dispatch entry (06 W1).
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 33 def call(envelope:, registry:) raise PolicyError, 'WorkerExecution.call requires registry' if registry.nil? envelope = FleetEnvelope.wrap(envelope) claims = nil idempotency_key = nil claims = validate_identity!(envelope) validate_policy!(envelope) idempotency_key = validate_idempotency!(envelope) response = dispatch!(envelope:, registry:) mark_idempotency_success!(idempotency_key) if idempotency_key TokenValidator.mark_replay!(claims[:jti]) if claims.is_a?(Hash) response rescue TokenError => e handle_exception(e, level: :warn, handled: false, operation: 'llm.fleet.worker_execution.identity') release_idempotency!(idempotency_key) if idempotency_key release_replay!(claims) raise PolicyError, e. rescue StandardError => e handle_exception(e, level: :warn, handled: false, operation: 'llm.fleet.worker_execution.call') release_idempotency!(idempotency_key) if idempotency_key release_replay!(claims) raise end |
.dispatch!(envelope:, registry:) ⇒ Object
The one dispatcher (06 W3): the exact resolution chain (W2). The claim's offering_id value is the lane's 5 tuple (field name kept for protocol continuity, D4); the claim resolves a lane exactly. The REQUESTED operation (envelope) is what gets dispatched — the lane constrains type/model/instance, the operation is a request property.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 90 def dispatch!(envelope:, registry:) snapshot = registry.snapshot instance_key = IDENTITY::InstanceKey.new( provider_family: envelope.provider, instance_id: envelope.provider_instance ) record = available_record!(snapshot, instance_key) operation = exact_operation(envelope) lane = exact_lane!(snapshot, record, envelope, operation) lease = registry.acquire(callable_handle: lane.callable_handle) begin dispatch_operation(lease.callable, operation, lane.model, exact_params(envelope)) ensure lease.release end end |
.dispatch_audio(callable, operation, model, params) ⇒ Object
220 221 222 223 224 225 226 227 228 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 220 def dispatch_audio(callable, operation, model, params) require_param!(params, :audio_file, operation) raise ContractError, "#{operation} requires the language key" unless params.key?(:language) callable.public_send( operation, params[:audio_file], model: model, language: params[:language], **params.except(:audio_file, :model, :language) ) end |
.dispatch_image(callable, model, params) ⇒ Object
214 215 216 217 218 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 214 def dispatch_image(callable, model, params) require_param!(params, :prompt, :image) require_param!(params, :size, :image) callable.image(prompt: params[:prompt], model: model, **params.except(:prompt)) end |
.dispatch_operation(callable, operation, model, params) ⇒ Object
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 186 def dispatch_operation(callable, operation, model, params) case operation when :chat callable.chat((require_param!(params, :messages, operation)), model: model, **params.except(:messages)) when :stream_chat callable.stream_chat((require_param!(params, :messages, operation)), model: model, **params.except(:messages)) when :count_tokens callable.count_tokens(messages: (require_param!(params, :messages, operation)), model: model, **params.except(:messages)) when :embed callable.(text: require_param!(params, :text, operation), model: model, **params.except(:text)) when :image dispatch_image(callable, model, params) when :transcribe dispatch_audio(callable, :transcribe, model, params) when :translate dispatch_audio(callable, :translate, model, params) when :speak dispatch_speak(callable, model, params) when :moderate callable.moderate(input: require_param!(params, :input, operation), model: model, **params.except(:input)) else raise ContractError, "unsupported exact operation: #{operation}" end end |
.dispatch_speak(callable, model, params) ⇒ Object
230 231 232 233 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 230 def dispatch_speak(callable, model, params) require_param!(params, :text, :speak) callable.speak(params[:text], model: model, voice: params[:voice], **params.except(:text, :model, :voice)) end |
.evict_oldest_idempotency_entries! ⇒ Object
274 275 276 277 278 279 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 274 def evict_oldest_idempotency_entries! sorted = @idempotency_keys.each_pair.sort_by { |_key, entry| entry[:expires_at] } sorted.first(@idempotency_keys.size - MAX_IDEMPOTENCY_ENTRIES).each_key do |key| @idempotency_keys.delete(key) end end |
.exact_lane!(snapshot, record, envelope, operation) ⇒ Object
The exact lane the claim names: the 5-tuple claim value must parse and validate, resolve in the snapshot, and agree with the activated instance, the envelope's operation type, and the envelope's model. The operation is a request property matched against the lane type — the lane's operation member is the representative of its type, not the identity.
119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 119 def exact_lane!(snapshot, record, envelope, operation) IDENTITY.validate_lane_id!(value: envelope.offering_id) lane = snapshot.lane(lane_id: envelope.offering_id) raise ERRORS::ExactOfferingMismatchError, 'lane_id not on the activated instance' if lane.nil? unless lane.instance_key == record.instance_key && lane.callable_handle.equal?(record.callable_handle) raise ERRORS::ExactOfferingMismatchError, 'lane does not belong to the activated instance' end require_matching_operation!(lane, operation) require_matching_model!(lane, envelope) lane end |
.exact_operation(envelope) ⇒ Object
06 §5: an unknown operation is a contract error at the worker.
133 134 135 136 137 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 133 def exact_operation(envelope) Legion::Extensions::Llm::Taxonomies.normalize_operation(value: envelope.operation) rescue Legion::Extensions::Llm::Inventory::Errors::ValidationError raise ContractError, "unsupported exact operation: #{envelope.operation}" end |
.exact_params(envelope) ⇒ Object
W5 params strictness: no :model key (the Selection-derived model is untouchable), no duplicate param spellings. Shape violations are contract errors (06 §5), not offering mismatches.
156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 156 def exact_params(envelope) raw = envelope.params || {} params = {} raw.each do |key, value| sym = key.respond_to?(:to_sym) ? key.to_sym : key raise ContractError, "duplicate param spelling for #{sym}" if params.key?(sym) params[sym] = value end raise ContractError, 'params must not contain model' if params.key?(:model) params end |
.idempotency_ttl_seconds ⇒ Object
291 292 293 294 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 291 def idempotency_ttl_seconds ttl = responder_setting(:idempotency_ttl_seconds, default: 600).to_i ttl.positive? ? ttl : 600 end |
.mark_idempotency_success!(key) ⇒ Object
246 247 248 249 250 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 246 def mark_idempotency_success!(key) @idempotency_mutex.synchronize do @idempotency_keys[key.to_s] = { state: :complete, expires_at: Time.now.to_i + idempotency_ttl_seconds } end end |
.purge_idempotency_cache! ⇒ Object
264 265 266 267 268 269 270 271 272 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 264 def purge_idempotency_cache! @idempotency_mutex.synchronize do now = Time.now.to_i @idempotency_keys.each_pair do |key, entry| @idempotency_keys.delete(key) if entry[:expires_at] <= now end evict_oldest_idempotency_entries! if @idempotency_keys.size > MAX_IDEMPOTENCY_ENTRIES end end |
.rehydrate_wire_messages(value) ⇒ Object
W4 — the named rehydration boundary: the ONLY place serialized wire messages become Canonical::Message. Non-Hash/non-Message elements are contract errors.
173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 173 def (value) Array(value).map do || next if .is_a?(Canonical::Message) unless .is_a?(Hash) raise ContractError, "fleet wire message must be a serialized Canonical::Message, got #{.class}" end Canonical::Message.from_hash() end end |
.release_idempotency!(key) ⇒ Object
252 253 254 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 252 def release_idempotency!(key) @idempotency_mutex.synchronize { @idempotency_keys.delete(key.to_s) } end |
.release_replay!(claims) ⇒ Object
256 257 258 259 260 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 256 def release_replay!(claims) return unless claims.is_a?(Hash) && claims[:jti] TokenValidator.release_replay!(claims[:jti]) end |
.require_matching_model!(lane, envelope) ⇒ Object
146 147 148 149 150 151 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 146 def require_matching_model!(lane, envelope) model = IDENTITY.normalize_text(value: envelope.model, field: :model) raise ERRORS::ExactOfferingMismatchError, 'model does not match the lane' unless lane.model == model model end |
.require_matching_operation!(lane, operation) ⇒ Object
139 140 141 142 143 144 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 139 def require_matching_operation!(lane, operation) lane_type = Legion::Extensions::Llm::Taxonomies.lane_type_for(operation: lane.operation) return if Legion::Extensions::Llm::Taxonomies.lane_type_for(operation: operation) == lane_type raise ERRORS::ExactOfferingMismatchError, "operation #{operation} does not match the claimed lane type" end |
.require_param!(params, key, operation) ⇒ Object
235 236 237 238 239 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 235 def require_param!(params, key, operation) raise ContractError, "#{operation} requires the #{key} param" unless params.key?(key) params[key] end |
.reserve_idempotency_key!(key) ⇒ Object
281 282 283 284 285 286 287 288 289 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 281 def reserve_idempotency_key!(key) @idempotency_mutex.synchronize do now = Time.now.to_i existing = @idempotency_keys[key] raise PolicyError, 'duplicate fleet idempotency key' if existing && existing[:expires_at] > now @idempotency_keys[key] = { state: :inflight, expires_at: now + idempotency_ttl_seconds } end end |
.reset_idempotency_cache! ⇒ Object
241 242 243 244 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 241 def reset_idempotency_cache! @idempotency_keys = Concurrent::Map.new @idempotency_mutex = Mutex.new end |
.responder_setting(key, default:) ⇒ Object
296 297 298 299 300 301 302 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 296 def responder_setting(key, default:) value = Settings.value(:fleet, :responder, key, default: nil) return auth_required? if key == :require_auth && value.nil? return default if value.nil? value end |
.validate_idempotency!(envelope) ⇒ Object
72 73 74 75 76 77 78 79 80 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 72 def validate_idempotency!(envelope) return nil unless responder_setting(:require_idempotency, default: true) key = envelope.idempotency_key raise PolicyError, 'fleet idempotency_key is required' if key.to_s.empty? reserve_idempotency_key!(key.to_s) key.to_s end |
.validate_identity!(envelope) ⇒ Object
58 59 60 61 62 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 58 def validate_identity!(envelope) return true unless responder_setting(:require_auth, default: true) TokenValidator.validate!(token: envelope.signed_token, envelope: envelope.to_h) end |
.validate_policy!(_envelope) ⇒ Object
W6 fail-closed: require_policy with no policy engine configured RAISES. The v2 warn-and-allow is deleted.
66 67 68 69 70 |
# File 'lib/legion/extensions/llm/fleet/worker_execution.rb', line 66 def validate_policy!(_envelope) return true unless responder_setting(:require_policy, default: false) raise PolicyError, 'require_policy is enabled but no policy engine is configured' end |