Module: Legion::LLM::Router::Availability

Extended by:
Legion::Logging::Helper
Defined in:
lib/legion/llm/router/availability.rb

Class Method Summary collapse

Class Method Details

.available_capabilities(resolution) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/legion/llm/router/availability.rb', line 138

def available_capabilities(resolution)
  offerings = inventory_offerings_for(resolution)
  if offerings&.any?
    offering = offerings.find { |o| model_matches_offering?(resolution.model, o) }
    if offering
      return Capabilities.merge(
        offering[:capabilities],
        offering['capabilities']
      )
    end
  end

  Capabilities.merge(
    resolution.[:model_capabilities],
    resolution.['model_capabilities'],
    resolution.[:capabilities],
    resolution.['capabilities']
  )
end

.context_headroomObject



158
159
160
161
# File 'lib/legion/llm/router/availability.rb', line 158

def context_headroom
  routing = Legion::Settings[:llm][:routing] || {}
  (routing[:context_headroom] || 0.9).to_f
end

.discovery_enabled?Boolean

Returns:

  • (Boolean)


129
130
131
132
133
134
135
136
# File 'lib/legion/llm/router/availability.rb', line 129

def discovery_enabled?
  llm = Legion::Settings[:llm] || {}
  discovery = llm[:discovery] || llm['discovery'] || {}
  enabled = if discovery.is_a?(Hash)
              discovery.key?(:enabled) ? discovery[:enabled] : discovery['enabled']
            end
  enabled != false
end

.discovery_status_for(resolution) ⇒ Object



121
122
123
124
125
126
127
# File 'lib/legion/llm/router/availability.rb', line 121

def discovery_status_for(resolution)
  return :unknown unless defined?(Discovery)

  return :unknown unless discovery_enabled?

  Discovery.discovery_status(provider: resolution.provider, instance: resolution.instance)
end

.filter_resolutions(resolutions, estimated_tokens: nil, required_capabilities: []) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/legion/llm/router/availability.rb', line 15

def filter_resolutions(resolutions, estimated_tokens: nil, required_capabilities: [])
  req_caps = Array(required_capabilities)
  @last_rejection_reasons = []
  resolutions.filter_map do |resolution|
    reason = rejection_reason(
      resolution,
      estimated_tokens:      estimated_tokens,
      required_capabilities: req_caps
    )
    if reason
      @last_rejection_reasons << reason
      detail = if reason == :missing_capability
                 caps = available_capabilities(resolution)
                 missing = Capabilities.normalize(req_caps) - Capabilities.normalize(caps)
                 " required=#{req_caps} available=#{caps} missing=#{missing}"
               else
                 ''
               end
      log.info "[llm][router] action=resolution_unavailable provider=#{resolution.provider} " \
               "instance=#{resolution.instance || 'default'} model=#{resolution.model} " \
               "reason=#{reason}#{detail}"
      next
    end
    resolution
  end
end

.instance_resolution_required?(resolution, discovery_state) ⇒ Boolean

Instance resolution is only required for discoverable local/fleet providers when discovery has confirmed the provider has multiple instances. During cold boot or for providers with a single instance, nil instance is fine.

Returns:

  • (Boolean)


166
167
168
169
170
171
172
173
174
# File 'lib/legion/llm/router/availability.rb', line 166

def instance_resolution_required?(resolution, discovery_state)
  return false unless %i[ok].include?(discovery_state)
  return false unless Discovery::RuleGenerator::DISCOVERABLE_PROVIDERS.include?(resolution.provider&.to_sym)

  # Only require instance if the provider actually has multiple discovered instances
  models = Array(Discovery.cached_discovered_models).select { |m| m[:provider] == resolution.provider }
  instances = models.map { |m| m[:instance] }.compact.uniq
  instances.size > 1
end

.inventory_offerings_for(resolution) ⇒ Object



104
105
106
107
108
109
110
111
112
113
# File 'lib/legion/llm/router/availability.rb', line 104

def inventory_offerings_for(resolution)
  return nil unless defined?(Inventory)

  # Query by provider only — instance is a routing hint, not an availability gate.
  # A model being offered by ANY instance of the provider confirms availability.
  Inventory.offerings(provider: resolution.provider)
rescue StandardError => e
  handle_exception(e, level: :debug, handled: true, operation: 'router.availability.inventory_lookup')
  nil
end

.last_rejection_reasonsObject



42
43
44
# File 'lib/legion/llm/router/availability.rb', line 42

def last_rejection_reasons
  @last_rejection_reasons || []
end

.model_matches_offering?(model, offering) ⇒ Boolean

Returns:

  • (Boolean)


115
116
117
118
119
# File 'lib/legion/llm/router/availability.rb', line 115

def model_matches_offering?(model, offering)
  offering_model = (offering[:model] || offering[:canonical_model_alias]).to_s
  model_s = model.to_s
  offering_model == model_s || offering_model.start_with?("#{model_s}:")
end

.rejection_reason(resolution, estimated_tokens: nil, required_capabilities: []) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/legion/llm/router/availability.rb', line 46

def rejection_reason(resolution, estimated_tokens: nil, required_capabilities: [])
  state = Router.health_tracker.circuit_state(resolution.provider, instance: resolution.instance)
  return :circuit_open if state == :open
  return :model_denied if Router.health_tracker.model_denied?(provider: resolution.provider,
                                                              model:    resolution.model,
                                                              instance: resolution.instance)

  discovery_state = discovery_status_for(resolution)
  return :instance_unresolved if resolution.instance.nil? && instance_resolution_required?(resolution, discovery_state)

  # Discovery :unreachable/:error only blocks discoverable local/fleet providers.
  # Cloud/frontier providers don't rely on discovery for availability.
  if %i[unreachable error].include?(discovery_state) &&
     Discovery::RuleGenerator::DISCOVERABLE_PROVIDERS.include?(resolution.provider&.to_sym)
    return :discovery_unavailable
  end

  # Model existence is checked against Inventory — the single source of truth
  # (settings + native-adapter static catalogs + discovery), already
  # whitelist/blacklist-filtered. The invariant: the daemon never dispatches a
  # (provider, instance, model) the catalog doesn't list — for EVERY provider,
  # cloud/frontier included. This is what blocks anthropic+qwen (qwen is not an
  # anthropic offering) and any policy-excluded model (filtered out of the
  # catalog upstream).
  #
  # Empty/nil catalog handling stays permissive so we never block on a cold
  # boot: an Inventory lookup error (nil) is permissive; an empty catalog is
  # authoritative ONLY for discoverable local/fleet providers with discovery on
  # (an empty live catalog means the instance genuinely serves nothing) —
  # otherwise an empty catalog is a not-yet-populated signal, not absence.
  discoverable = Discovery::RuleGenerator::DISCOVERABLE_PROVIDERS.include?(resolution.provider&.to_sym)
  offerings = inventory_offerings_for(resolution)
  unless offerings.nil?
    if offerings.empty?
      return :provider_instance_has_no_models if discoverable && discovery_enabled?
    else
      offering = offerings.find { |o| model_matches_offering?(resolution.model, o) }
      return :model_not_offered if offering.nil?

      if estimated_tokens&.positive?
        ctx = (offering.dig(:limits, :context_window) || offering[:context_length] || 0).to_i
        return :context_too_small if ctx.positive? && estimated_tokens > (ctx * context_headroom).to_i
      end
    end
  end

  if required_capabilities.any?
    caps = available_capabilities(resolution)
    return :missing_capability if caps.any? && !Capabilities.include_all?(caps, required_capabilities)
  end

  nil
rescue StandardError => e
  handle_exception(e, level: :warn, handled: true, operation: 'router.availability',
                      provider: resolution.provider)
  nil
end