Module: Legion::LLM::Router::OutcomeClassifier

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

Overview

Provider-neutral outcome classification (SSOT v3 §16). Consumes ONLY a Phase 1 normalized ProviderOutcome plus the exact AttemptContext; it never sees a provider name, HTTP status, or response body for branching.

Defined Under Namespace

Classes: Action, GlobalTransition

Constant Summary collapse

RETRYABLE =

Outcomes that retry against a different eligible target when attempts remain.

%i[
  instance_unavailable overloaded model_not_ready timeout connection_failure
  provider_error malformed_output tool_failure rate_limited
  authentication authorization billing model_missing context_rejected
].freeze
TERMINAL_REJECTION_KIND =

Terminal provider outcomes → best-fit Rejection kind. cancelled/client_disconnect preserve the outcome so the owner can skip HTTP rendering for a gone client.

{
  policy:            :policy_denied,
  invalid_request:   :invalid_request,
  safety_refusal:    :invalid_request,
  cancelled:         :invalid_request,
  client_disconnect: :invalid_request
}.freeze

Class Method Summary collapse

Class Method Details

.call(outcome:, attempt_context:, attempts_remaining:) ⇒ Object

Raises:

  • (ArgumentError)


89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/legion/llm/router/outcome_classifier.rb', line 89

def self.call(outcome:, attempt_context:, attempts_remaining:)
  kind = outcome.kind
  return Action.success(outcome: outcome) if kind == :success

  if kind == :instance_unavailable
    # The exact instance MUST be marked unavailable regardless of attempts;
    # attempts_exhausted (if any) is produced by RoutingSession#next_attempt's
    # counter guard on the following selection.
    return Action.retry(
      exclusions: [], outcome: outcome,
      global_transition: GlobalTransition.new(
        instance_key:       attempt_context.selection.instance_key,
        publisher_token_id: attempt_context.selection.publisher_token_id,
        reason:             outcome.reason
      )
    )
  end

  if RETRYABLE.include?(kind)
    return attempts_exhausted(outcome) unless attempts_remaining.positive?

    return Action.retry(exclusions: quota_exclusions(outcome), outcome: outcome)
  end

  rejection_kind = TERMINAL_REJECTION_KIND[kind]
  raise ArgumentError, "unclassifiable provider outcome kind: #{kind.inspect}" if rejection_kind.nil?

  Action.terminal(outcome: outcome, rejection: terminal_rejection(rejection_kind, outcome))
end