|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +# ruby_llm-resilience — circuit breakers and fallback chains for LLM calls. |
| 4 | +# Docs: https://github.com/danielstpaul/ruby_llm-resilience |
| 5 | +# |
| 6 | +# Everything below is optional; the gem works with defaults out of the box |
| 7 | +# (per-process memory store, threshold 5, cooldown 120s). The two settings |
| 8 | +# most apps DO want are cache_store (multi-process correctness) and |
| 9 | +# fallback_models (the actual routing). |
| 10 | +RubyLLM::Resilience.configure do |config| |
| 11 | + # --- Store ----------------------------------------------------------- |
| 12 | + # REQUIRED for multi-process apps: the default MemoryStore is per-process, |
| 13 | + # so a breaker tripped in one worker stays closed in the others. Any store |
| 14 | + # with read / write(expires_in:, unless_exist:) / increment(expires_in:) / |
| 15 | + # delete / delete_multi works — Redis is the usual choice: |
| 16 | + # |
| 17 | + # config.cache_store = ActiveSupport::Cache::RedisCacheStore.new( |
| 18 | + # url: ENV["REDIS_URL"], namespace: "circuit_breaker" |
| 19 | + # ) |
| 20 | + |
| 21 | + # --- Breaker knobs ---------------------------------------------------- |
| 22 | + # config.failure_threshold = 5 # consecutive failures before trip |
| 23 | + # config.cooldown_seconds = 120 # open duration before a probe |
| 24 | + # config.failures_window_seconds = 3600 # failure-counter window |
| 25 | + # |
| 26 | + # Per-service overrides (a fast-recovery moderation endpoint shouldn't |
| 27 | + # share a cooldown with an expensive batch endpoint): |
| 28 | + # config.services = { |
| 29 | + # "api:openai:moderation" => { failure_threshold: 2, cooldown_seconds: 30 } |
| 30 | + # } |
| 31 | + |
| 32 | + # --- Fallback routing ------------------------------------------------- |
| 33 | + # One deliberate tier-hop per model is the recommended shape. Values may |
| 34 | + # be a single model or an array of hops. |
| 35 | + # config.fallback_models = { |
| 36 | + # "claude-haiku-4-5" => "claude-sonnet-4-6", |
| 37 | + # "claude-sonnet-4-6" => "claude-opus-4-7", |
| 38 | + # "gemini-3.5-flash" => "claude-sonnet-4-6" # cross-provider safety net |
| 39 | + # } |
| 40 | + |
| 41 | + # --- Telemetry (alert on the error, graph the gauge, trend the counter) — |
| 42 | + # config.on_error = ->(error, context) { |
| 43 | + # Rails.error.report(error, handled: true, context: context) |
| 44 | + # } |
| 45 | + # config.on_status = ->(service, state) { |
| 46 | + # Appsignal.set_gauge("circuit_breaker.state", state == :open ? 1 : 0, service: service) |
| 47 | + # } |
| 48 | + # config.on_fallback = ->(from:, to:, error:) { |
| 49 | + # Appsignal.increment_counter("llm.fallback", 1, |
| 50 | + # from: from[:service], to: to[:service], error: error.class.name) |
| 51 | + # } |
| 52 | + |
| 53 | + # --- Dashboard (mount RubyLLM::Resilience::Engine in routes.rb) -------- |
| 54 | + # DENY-BY-DEFAULT: every dashboard request 404s until you configure this. |
| 55 | + # config.dashboard_auth = ->(controller) { |
| 56 | + # controller.head :not_found unless controller.respond_to?(:current_user) && |
| 57 | + # controller.current_user&.admin? |
| 58 | + # } |
| 59 | + # |
| 60 | + # Show a static fleet (instead of only breakers seen since boot), with |
| 61 | + # descriptions for the About column: |
| 62 | + # config.dashboard_services = %w[api:anthropic:sonnet api:openai:moderation] |
| 63 | + # config.service_metadata = { |
| 64 | + # "api:anthropic:sonnet" => { description: "Coaching", consumers: "Chat" } |
| 65 | + # } |
| 66 | +end |
0 commit comments