Class: RubyLLM::Resilience::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_llm/resilience/configuration.rb

Overview

All five injection seams live here. Configure once at boot, then the configuration is frozen — concurrency safety comes from immutability, not locks. (Use RubyLLM::Resilience.reset_configuration! in tests.)

Defined Under Namespace

Classes: Settings

Constant Summary collapse

DEFAULT_TRIPPABLE_NAMES =

Server-side unhealthiness — these TRIP the breaker. Client errors (4xx, auth, bad request) never do: those are your bug, not their outage. Resolved lazily so ruby_llm/faraday remain soft dependencies.

%w[
  RubyLLM::RateLimitError
  RubyLLM::ServerError
  RubyLLM::ServiceUnavailableError
  RubyLLM::OverloadedError
  Faraday::TimeoutError
  Faraday::ConnectionFailed
  Faraday::ServerError
].freeze
DEFAULT_FALLBACK_NAMES =

Errors that advance a fallback chain to its next step (without necessarily tripping). ModelNotFoundError is deliberate: it covers new-model rollout windows where a registry misses an ID. Programming bugs (NoMethodError, ArgumentError) are NOT here — those propagate so they surface in your error tracker instead of silently degrading.

%w[
  RubyLLM::Error
  RubyLLM::ModelNotFoundError
  Faraday::Error
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



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
103
104
105
# File 'lib/ruby_llm/resilience/configuration.rb', line 48

def initialize
  @cache_store = MemoryStore.new
  @failure_threshold = 5
  @cooldown_seconds = 120
  @failures_window_seconds = 3600
  # Model → fallback(s). Values may be a single model or an array of
  # hops, tried in order:
  #   c.fallback_models = {
  #     "claude-haiku-4-5"  => "claude-sonnet-4-6",                       # one hop
  #     "gemini-3.5-flash"  => ["claude-sonnet-4-6", "claude-opus-4-7"]   # multi-hop
  #   }
  # One deliberate hop is the recommended default (capacity incidents
  # are tier-correlated; chains of desperation compound quality drift) —
  # multi-hop is available, not encouraged.
  @fallback_models = {}
  @on_error = ->(_error, _context) {}
  @on_status = ->(_service, _state) {}

  # Fired every time a chain advances past a failed/skipped step —
  # the audit trail that keeps fallback an emergency, not an ambient
  # optimization:
  #   c.on_fallback = ->(from:, to:, error:) {
  #     Appsignal.increment_counter("llm.fallback", 1,
  #       from: from[:service], to: to[:service], error: error.class.name)
  #   }
  @on_fallback = ->(from:, to:, error:) {}
  @provider_resolver = default_provider_resolver
  @service_namer = TierNamer
  @trippable_errors = nil # nil => lazy defaults (see resolved_* below)
  @fallback_errors = nil

  # Per-service overrides for the three breaker knobs. A cheap
  # fast-recovery moderation endpoint and an expensive batch endpoint
  # shouldn't share one cooldown:
  #   c.services = { "api:openai:moderation" => { cooldown_seconds: 30 } }
  @services = {}

  # App knowledge for dashboards — descriptions belong in config, not
  # hardcoded in a controller:
  #   c.service_metadata = { "api:anthropic:sonnet" =>
  #     { description: "Coaching + generation", consumers: "Chat, Practice" } }
  @service_metadata = {}

  # Default service list for the dashboard engine. nil = the
  # per-process registry (services seen since boot). Apps with a known
  # static fleet should set this so the dashboard is complete from the
  # first request:
  #   c.dashboard_services = %w[api:anthropic:sonnet api:openai:gpt ...]
  @dashboard_services = nil

  # Auth hook for the mountable dashboard (see resilience/engine).
  # DENY BY DEFAULT: mounting without configuring this renders 404 on
  # every request. Override with e.g.
  #   c.dashboard_auth = ->(controller) {
  #     controller.head :not_found unless controller.current_user&.admin?
  #   }
  @dashboard_auth = ->(controller) { controller.head :not_found }
end

Instance Attribute Details

#cache_storeObject

Returns the value of attribute cache_store.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def cache_store
  @cache_store
end

#cooldown_secondsObject

Returns the value of attribute cooldown_seconds.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def cooldown_seconds
  @cooldown_seconds
end

#dashboard_authObject

Returns the value of attribute dashboard_auth.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def dashboard_auth
  @dashboard_auth
end

#dashboard_servicesObject

Returns the value of attribute dashboard_services.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def dashboard_services
  @dashboard_services
end

#failure_thresholdObject

Returns the value of attribute failure_threshold.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def failure_threshold
  @failure_threshold
end

#failures_window_secondsObject

Returns the value of attribute failures_window_seconds.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def failures_window_seconds
  @failures_window_seconds
end

#fallback_errorsObject

Returns the value of attribute fallback_errors.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def fallback_errors
  @fallback_errors
end

#fallback_modelsObject

Returns the value of attribute fallback_models.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def fallback_models
  @fallback_models
end

#on_errorObject

Returns the value of attribute on_error.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def on_error
  @on_error
end

#on_fallbackObject

Returns the value of attribute on_fallback.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def on_fallback
  @on_fallback
end

#on_statusObject

Returns the value of attribute on_status.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def on_status
  @on_status
end

#provider_resolverObject

Returns the value of attribute provider_resolver.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def provider_resolver
  @provider_resolver
end

#service_metadataObject

Returns the value of attribute service_metadata.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def 
  @service_metadata
end

#service_namerObject

Returns the value of attribute service_namer.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def service_namer
  @service_namer
end

#servicesObject

Returns the value of attribute services.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def services
  @services
end

#trippable_errorsObject

Returns the value of attribute trippable_errors.



33
34
35
# File 'lib/ruby_llm/resilience/configuration.rb', line 33

def trippable_errors
  @trippable_errors
end

Instance Method Details

#fallbacks_for(model_name) ⇒ Object

Normalized fallback hops for a model: always an Array (possibly empty).



41
42
43
# File 'lib/ruby_llm/resilience/configuration.rb', line 41

def fallbacks_for(model_name)
  Array(@fallback_models[model_name.to_s])
end

#metadata_for(service) ⇒ Object



116
117
118
# File 'lib/ruby_llm/resilience/configuration.rb', line 116

def (service)
  @service_metadata[service.to_s] || {}
end

#resolved_fallback_errorsObject



124
125
126
# File 'lib/ruby_llm/resilience/configuration.rb', line 124

def resolved_fallback_errors
  @fallback_errors || ErrorResolution.resolve(DEFAULT_FALLBACK_NAMES)
end

#resolved_trippable_errorsObject



120
121
122
# File 'lib/ruby_llm/resilience/configuration.rb', line 120

def resolved_trippable_errors
  @trippable_errors || ErrorResolution.resolve(DEFAULT_TRIPPABLE_NAMES)
end

#settings_for(service) ⇒ Object



107
108
109
110
111
112
113
114
# File 'lib/ruby_llm/resilience/configuration.rb', line 107

def settings_for(service)
  override = @services[service.to_s] || {}
  Settings.new(
    override[:failure_threshold] || failure_threshold,
    override[:cooldown_seconds] || cooldown_seconds,
    override[:failures_window_seconds] || failures_window_seconds
  )
end