Module: Legion::LLM::Routing::Filter

Includes:
Legion::Logging::Helper, Settings::Helper
Included in:
Legion::LLM::Router
Defined in:
lib/legion/llm/routing/filter.rb

Overview

Lane eligibility: a lane is eligible iff every applicable filter passes. Each filter is a pure (lane, fact) -> pass/fail check — stateless, individually RSpec-testable.

Instance Method Summary collapse

Instance Method Details

#body_model_hint_decision_for(body_model:, trusted_model:) ⇒ Object

The SOLE body-model hint decision (SSOT v3 §17.1 / D19). Given the untrusted request-body model value and any trusted explicit model, it returns one immutable BodyModelHintDecision. It never returns a lane, substitute model, or alias; only a :honored decision carries a model constraint. The Router calls this ONCE in initialize.



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/legion/llm/routing/filter.rb', line 285

def body_model_hint_decision_for(body_model:, trusted_model:, **)
  requested = normalize(body_model)
  router_cfg = Legion::Settings[:llm][:router]

  # 1. missing/blank body model → absent
  if requested.nil?
    return build_hint_decision(requested_model: nil, disposition: :absent,
                               settings_generation: 0)
  end

  # 2. body + trusted explicit model → trusted wins, body is metadata only
  unless normalize(trusted_model).nil?
    return build_hint_decision(requested_model: requested, disposition: :superseded_by_explicit_model,
                               settings_generation: 0)
  end

  # 3. auto-routing alias → auto (you-pick intent, no constraint)
  aliases = router_cfg[:auto_routing_model_aliases]
  if auto_alias?(requested, aliases)
    return build_hint_decision(requested_model: requested, disposition: :auto,
                               settings_generation: 0)
  end

  # 4. body hints globally disabled → ignored
  unless router_cfg[:allow_body_routing_hints]
    return build_hint_decision(requested_model: requested, disposition: :ignored_disabled,
                               settings_generation: 0)
  end

  whitelist = router_cfg[:body_model_hint_whitelist]
  blacklist = router_cfg[:body_model_hint_blacklist]

  # 5. nonempty whitelist with no match → ignored_not_whitelisted
  if !whitelist.empty? && substring_match(requested, whitelist).nil?
    return build_hint_decision(requested_model: requested, disposition: :ignored_not_whitelisted,
                               settings_generation: 0)
  end

  # 6. any blacklist match → ignored_blacklisted (blacklist wins over whitelist)
  matched_black = substring_match(requested, blacklist)
  unless matched_black.nil?
    return build_hint_decision(requested_model: requested, disposition: :ignored_blacklisted,
                               matched_blacklist: matched_black,
                               matched_whitelist: substring_match(requested, whitelist),
                               settings_generation: 0)
  end

  # 7. honored → exact body model becomes the model constraint
  build_hint_decision(requested_model: requested, disposition: :honored,
                      model_constraint: requested,
                      matched_whitelist: substring_match(requested, whitelist),
                      settings_generation: 0)
end

#evaluate_capabilities(lane:, required_capabilities:) ⇒ Object

§9.7 step 4 — capability reduction with the operator's enable_* routing override (fail-forward decision 2). All satisfied → :supported; any unknown → :unknown (highest priority); any not-ready with no unknown → :unsupported.



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/legion/llm/routing/filter.rb', line 147

def evaluate_capabilities(lane:, required_capabilities:, **)
  return :supported if required_capabilities.empty?

  any_unknown     = false
  any_unsupported = false

  required_capabilities.each do |cap|
    case resolved_capability_status(lane: lane, capability: cap)
    when :unknown     then any_unknown     = true
    when :unsupported then any_unsupported = true
    end
  end

  return :unknown     if any_unknown
  return :unsupported if any_unsupported

  :supported
end

#evaluate_context(lane:, budget:) ⇒ Object

§9.7 step 5 — context budget. Zero budget → :not_applicable (no context requirement). Authoritative limit: fits when budget <= (limit * headroom_ppm) / 1_000_000. Absent or unknown context evidence → :unknown.



170
171
172
173
174
175
176
177
178
179
# File 'lib/legion/llm/routing/filter.rb', line 170

def evaluate_context(lane:, budget:, **)
  return :not_applicable if budget.zero?

  ctx_ev = lane.context_evidence
  return :unknown unless ctx_ev.known?

  limit    = ctx_ev.value
  headroom = Legion::Settings[:llm][:router][:context_headroom_ppm]
  budget <= (limit * headroom) / 1_000_000 ? :fits : :rejected
end

#evaluate_dimensions(lane:, requested_dimensions:) ⇒ Object

§9.7 step 6 — embedding dimensions. Nil requested → :not_applicable. Authoritative evidence is a sorted Array of positive Integers. :match when the requested dimension appears in the supported set; :rejected when the set is known but excludes the requested value. Unknown evidence → :unknown.



187
188
189
190
191
192
193
194
# File 'lib/legion/llm/routing/filter.rb', line 187

def evaluate_dimensions(lane:, requested_dimensions:, **)
  return :not_applicable if requested_dimensions.nil?

  dim_ev = lane.embedding_dimensions_evidence
  return :unknown unless dim_ev.known?

  Array(dim_ev.value).include?(requested_dimensions) ? :match : :rejected
end

#filter_availability(instance:) ⇒ Object

Availability: the exact-instance availability state.



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/legion/llm/routing/filter.rb', line 74

def filter_availability(instance:, **)
  return :unknown if instance.nil?

  avail = instance.availability
  return :unknown if avail.nil?

  case avail.state
  when :available   then :available
  when :unavailable then :unavailable
  else                   :unknown
  end
end

#filter_capability(**opts) ⇒ Object

Capability constraint: the capabilities the request requires, or nil when none.



56
57
58
59
# File 'lib/legion/llm/routing/filter.rb', line 56

def filter_capability(**opts)
  caps = Array(opts[:capabilities]).compact
  caps.empty? ? nil : caps.freeze
end

#filter_context(**opts) ⇒ Object

Context constraint: the required context size (tokens), or nil when unconstrained.



62
63
64
65
# File 'lib/legion/llm/routing/filter.rb', line 62

def filter_context(**opts)
  context = opts[:context]
  context if context.is_a?(Integer)
end

#filter_embedding_dimensions(**opts) ⇒ Object

Embedding-dimension constraint: the requested dimensionality, or nil when unconstrained.



68
69
70
71
# File 'lib/legion/llm/routing/filter.rb', line 68

def filter_embedding_dimensions(**opts)
  dims = opts[:embedding_dimensions]
  dims if dims.is_a?(Integer)
end

#filter_exclusions(lane:, exclusions:) ⇒ Object

§9.7 step 8 — typed exclusions. attempt_target compares (provider_family, instance_id, model) only. :lane and :offering both name the 5-tuple lane id (D2).



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/legion/llm/routing/filter.rb', line 199

def filter_exclusions(lane:, exclusions:, **)
  return :clear if exclusions.empty?

  ik    = lane.instance_key
  pf    = ik.provider_family
  iid   = ik.instance_id
  model = lane.model

  excluded = exclusions.any? do |excl|
    case excl.target_kind
    when :attempt_target
      t = excl.target
      t.provider_family == pf && t.instance_id == iid && t.model == model
    when :instance
      excl.target == ik
    when :lane, :offering
      excl.target == lane.lane_id
    when :model
      excl.target == model
    when :provider
      excl.target == pf
    when :quota_domain
      qd = lane.quota_domain
      qd && excl.target == qd
    else
      false
    end
  end

  excluded ? :excluded : :clear
end

#filter_fleet(lane:) ⇒ Object

Fleet contract: the fleet execution contract (fleet tier only).



88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/legion/llm/routing/filter.rb', line 88

def filter_fleet(lane:, **)
  return :not_applicable unless lane.tier == :fleet

  contract = lane.[:fleet_execution_contract]
  if contract == 'exact_offering_v1'
    :supported
  elsif contract.nil? || contract.to_s.empty?
    :legacy
  else
    :unknown
  end
end

#filter_instance(**opts) ⇒ Object

Instance constraint: the instance the request asks for, or nil when unconstrained.



33
34
35
36
# File 'lib/legion/llm/routing/filter.rb', line 33

def filter_instance(**opts)
  instance = opts[:instance]
  instance if instance.is_a?(String)
end

#filter_operation(lane:, operation:) ⇒ Object

§9.7 step 1 — operation axis: requested operation's coarse type vs lane type. The registry publishes lanes only for supported operations, so a type match is :supported and a type miss is :unsupported.



123
124
125
126
127
# File 'lib/legion/llm/routing/filter.rb', line 123

def filter_operation(lane:, operation:, **)
  requested_type = OPS.lane_type_for(operation: operation)
  lane_type = OPS.lane_type_for(operation: lane.operation)
  lane_type == requested_type ? :supported : :unsupported
end

#filter_pins(lane:, provider_pin: nil, instance_pin: nil, model_pin: nil, tier_constraint: nil) ⇒ Object

§9.7 step 2 — provider/instance/model/tier pins. :match when all configured pins equal the lane's fields. :mismatch when any configured pin differs.



132
133
134
135
136
137
138
139
140
141
# File 'lib/legion/llm/routing/filter.rb', line 132

def filter_pins(lane:, provider_pin: nil, instance_pin: nil, model_pin: nil, tier_constraint: nil, **)
  ik = lane.instance_key

  return :mismatch if provider_pin   && provider_pin   != ik.provider_family
  return :mismatch if instance_pin   && instance_pin   != ik.instance_id
  return :mismatch if model_pin      && model_pin      != lane.model
  return :mismatch if tier_constraint && tier_constraint != lane.tier

  :match
end

#filter_policy(lane:, whitelist:, blacklist:) ⇒ Object

Policy: model whitelist/blacklist (substring, case-insensitive); blacklist wins.



45
46
47
48
49
50
51
52
53
# File 'lib/legion/llm/routing/filter.rb', line 45

def filter_policy(lane:, whitelist:, blacklist:, **)
  model_lc = lane.model.downcase
  return :denied if Array(blacklist).any? { |e| model_lc.include?(e.to_s.downcase) }

  wl = Array(whitelist)
  return :denied if wl.any? && wl.none? { |e| model_lc.include?(e.to_s.downcase) }

  :allowed
end

#filter_provider(**opts) ⇒ Object

Provider constraint: the provider the request asks for, or nil when unconstrained.



27
28
29
30
# File 'lib/legion/llm/routing/filter.rb', line 27

def filter_provider(**opts)
  provider = opts[:provider]
  provider if provider.is_a?(Symbol)
end

#filter_tier(**opts) ⇒ Object

Tier constraint: the tier the request asks for, or nil when unconstrained.



39
40
41
42
# File 'lib/legion/llm/routing/filter.rb', line 39

def filter_tier(**opts)
  tier = opts[:tier]
  tier if tier.is_a?(Symbol)
end

#filter_type(**opts) ⇒ Object

Type constraint: the lane type/modality the request asks for, or nil when unconstrained.



21
22
23
24
# File 'lib/legion/llm/routing/filter.rb', line 21

def filter_type(**opts)
  type = opts[:type]
  type if type.is_a?(Symbol)
end

#filter_weight(lane:) ⇒ Object

Weight: the stored write-time weight; any zero component disables the lane.



102
103
104
105
106
107
108
109
# File 'lib/legion/llm/routing/filter.rb', line 102

def filter_weight(lane:, **)
  inputs = lane.weight_inputs
  if inputs.nil? || inputs.values.any?(&:zero?)
    :disabled
  else
    :enabled
  end
end

#model_policy_for(lane:) ⇒ Object

Returns { whitelist: Array, blacklist: Array } using the §9.5 specificity cascade: exact provider+instance → provider → global. "First scope whose key EXISTS, including explicit empty Array."



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/legion/llm/routing/filter.rb', line 238

def model_policy_for(lane:, **)
  ext_llm = Legion::Settings[:extensions][:llm] || {}
  ik  = lane.instance_key
  pf  = ik.provider_family
  iid = ik.instance_id

  prov = ext_llm[pf] || {}
  instances = prov[:instances] || {}
  inst = instances[iid.to_sym] || instances[iid] || {}

  wl = first_existing_policy(inst, prov, ext_llm, :model_whitelist)
  bl = first_existing_policy(inst, prov, ext_llm, :model_blacklist)

  { whitelist: Array(wl).freeze, blacklist: Array(bl).freeze }.freeze
end

#preferred_context_range_for(lane:) ⇒ Object

Returns { min: Integer_or_nil, max: Integer_or_nil } or nil when no preferred range is configured. Resolved through the lex-llm 3-level cascade (provider -> instance -> model, most-specific-first) keyed by the config name.



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/legion/llm/routing/filter.rb', line 258

def preferred_context_range_for(lane:, **)
  ext_llm = Legion::Settings[:extensions][:llm] || {}
  pf  = lane.instance_key.provider_family
  iid = lane.instance_key.instance_id

  min_v = CASCADE.resolve_from(
    llm_conf: ext_llm, provider_family: pf, instance: iid,
    key: :preferred_min_context_tokens, model: lane.model
  )
  max_v = CASCADE.resolve_from(
    llm_conf: ext_llm, provider_family: pf, instance: iid,
    key: :preferred_max_context_tokens, model: lane.model
  )
  return nil unless min_v || max_v

  { min: min_v, max: max_v }.freeze
end