Class: Legion::LLM::Router::HealthTracker

Inherits:
Object
  • Object
show all
Includes:
Legion::Logging::Helper
Defined in:
lib/legion/llm/router/health_tracker.rb

Constant Summary collapse

OPEN_PENALTY =
-50
LATENCY_THRESHOLD_MS =
5000
LATENCY_PENALTY_STEP =
-10

Instance Method Summary collapse

Constructor Details

#initialize(window_seconds: 300, failure_threshold: 3, cooldown_seconds: 60) ⇒ HealthTracker

Returns a new instance of HealthTracker.



14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/legion/llm/router/health_tracker.rb', line 14

def initialize(window_seconds: 300, failure_threshold: 3, cooldown_seconds: 60)
  @window_seconds    = window_seconds
  @failure_threshold = failure_threshold
  @cooldown_seconds  = cooldown_seconds

  @circuits       = {}
  @latency_window = {}
  @handlers       = {}
  @mutex          = Mutex.new

  register_default_handlers
end

Instance Method Details

#adjustment(provider, offering_id: nil) ⇒ Object

Returns total priority adjustment for a provider. Combines circuit-breaker penalty and latency penalty.



52
53
54
55
56
# File 'lib/legion/llm/router/health_tracker.rb', line 52

def adjustment(provider, offering_id: nil)
  key = health_key(provider, offering_id)
  key = provider if offering_id && !tracked?(key) && tracked?(provider)
  circuit_adjustment(key) + latency_adjustment(key)
end

#circuit_state(provider, offering_id: nil) ⇒ Object

Returns :closed, :open, or :half_open.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/legion/llm/router/health_tracker.rb', line 59

def circuit_state(provider, offering_id: nil)
  key = health_key(provider, offering_id)
  key = provider if offering_id && !tracked?(key) && tracked?(provider)
  circuit = @circuits[key]
  return :closed if circuit.nil?

  if circuit[:state] == :open
    elapsed = Time.now - circuit[:opened_at]
    if elapsed >= @cooldown_seconds
      log.warn("Circuit open->half_open for provider=#{key} (cooldown elapsed)")
      return :half_open
    end
  end

  circuit[:state]
end

#register_handler(signal, &block) ⇒ Object

Register a custom handler for a signal type.



28
29
30
# File 'lib/legion/llm/router/health_tracker.rb', line 28

def register_handler(signal, &block)
  @handlers[signal.to_sym] = block
end

#report(provider:, signal:, value:, metadata: {}, offering_id: nil) ⇒ Object

Thread-safe signal intake. Dispatches to the registered handler if one exists.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/legion/llm/router/health_tracker.rb', line 33

def report(provider:, signal:, value:, metadata: {}, offering_id: nil)
  sym     = signal.to_sym
  handler = @handlers[sym]
  return nil unless handler

  payload = {
    provider:    health_key(provider, offering_id),
    provider_id: provider,
    offering_id: offering_id,
    signal:      sym,
    value:       value,
    metadata:    ,
    at:          Time.now
  }
  @mutex.synchronize { handler.call(payload) }
end

#reset(provider, offering_id: nil) ⇒ Object

Clears circuit and latency data for a single provider.



77
78
79
80
81
82
83
# File 'lib/legion/llm/router/health_tracker.rb', line 77

def reset(provider, offering_id: nil)
  key = health_key(provider, offering_id)
  @mutex.synchronize do
    @circuits.delete(key)
    @latency_window.delete(key)
  end
end

#reset_allObject

Clears all state.



86
87
88
89
90
91
# File 'lib/legion/llm/router/health_tracker.rb', line 86

def reset_all
  @mutex.synchronize do
    @circuits.clear
    @latency_window.clear
  end
end