Class: Rubino::Agent::FallbackChain
- Inherits:
-
Object
- Object
- Rubino::Agent::FallbackChain
- Defined in:
- lib/rubino/agent/fallback_chain.rb
Overview
The provider/model fallback chain — a faithful port of the reference ‘_fallback_chain` + `try_activate_fallback` and the per-turn `_restore_primary_runtime`.
WHAT IT DOES. The primary backend is index 0; ‘agent.fallback_models` lists the ordered fallbacks. When the primary keeps failing — invalid/empty responses (eager fallback), rate-limit/overload, or an exhausted retry budget, or empty-after-retries — the runner / recovery ladder calls #activate_next! to rotate to the next backend and rebuild the adapter. At the TOP of each new turn ConversationLoop#run calls #restore_primary! so every turn gets a fresh attempt with the preferred model.
DEDUP. An entry that resolves to the CURRENT provider/model/base_url is skipped — falling back to the backend that just failed only loops the failure. We keep advancing past skipped entries in a single #activate_next! call, exactly like the reference recursive ‘return agent._try_activate_fallback()`.
GLOBAL-CONFIG ISOLATION (the heart of this slice). ‘RubyLLM.configure` is process-global; a naive provider swap would corrupt concurrent sessions on the API/server path. So fallback adapters are built with `isolate_config: true`: each scopes its provider config (base_url / api_key / timeout) into a per-adapter `RubyLLM::Context` and NEVER writes the global. The primary adapter is passed in as-is (it already configured the global at construction, exactly as before), so a single-provider setup — and the no-fallback case — is byte-identical to pre-Slice-7 behaviour.
NO-OP WHEN UNCONFIGURED. With an empty ‘fallback_models` the chain holds only the primary: #activate_next! is always false and #current_adapter is always the primary. Nothing is rebuilt, nothing is mutated.
Defined Under Namespace
Classes: Entry
Instance Method Summary collapse
-
#activate_next! ⇒ Object
Advance to the next usable, non-duplicate fallback and rebuild the adapter.
-
#active? ⇒ Boolean
True once a fallback has been activated this turn — lets callers emit the “switched to fallback” status only when something actually changed.
-
#current_adapter ⇒ Object
The adapter the loop/runner should issue calls against right now.
-
#initialize(primary_adapter:, config:, ui: nil, event_bus: nil, tool_executor: nil, cancel_token: nil, adapter_builder: LLM::AdapterFactory) ⇒ FallbackChain
constructor
primary_adapter : the already-built primary LLM adapter (index 0).
-
#restore_primary! ⇒ Object
Reset to the primary at the top of each turn.
Constructor Details
#initialize(primary_adapter:, config:, ui: nil, event_bus: nil, tool_executor: nil, cancel_token: nil, adapter_builder: LLM::AdapterFactory) ⇒ FallbackChain
primary_adapter : the already-built primary LLM adapter (index 0). The
chain never rebuilds it — restore just points back to it.
config : the live Configuration (reads agent.fallback_models and
the providers.* blocks the fallback entries inherit).
adapter_builder : injectable seam for tests; defaults to AdapterFactory.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/rubino/agent/fallback_chain.rb', line 50 def initialize(primary_adapter:, config:, ui: nil, event_bus: nil, tool_executor: nil, cancel_token: nil, adapter_builder: LLM::AdapterFactory) @primary = primary_adapter @config = config @ui = ui @event_bus = event_bus @tool_executor = tool_executor @cancel_token = cancel_token @adapter_builder = adapter_builder @entries = build_entries @index = 0 @active = @primary end |
Instance Method Details
#activate_next! ⇒ Object
Advance to the next usable, non-duplicate fallback and rebuild the adapter. Returns true if it actually switched, false when the chain is exhausted (or empty). Mirrors try_activate_fallback (helpers.py:1020): skip invalid entries and entries that resolve to the current backend, advancing past them within this one call.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/rubino/agent/fallback_chain.rb', line 82 def activate_next! loop do return false if @index >= @entries.size entry = @entries[@index] @index += 1 next unless entry.usable? next if duplicate_of_current?(entry) @active = build_adapter(entry) return true end end |
#active? ⇒ Boolean
True once a fallback has been activated this turn — lets callers emit the “switched to fallback” status only when something actually changed.
73 74 75 |
# File 'lib/rubino/agent/fallback_chain.rb', line 73 def active? @index.positive? end |
#current_adapter ⇒ Object
The adapter the loop/runner should issue calls against right now.
67 68 69 |
# File 'lib/rubino/agent/fallback_chain.rb', line 67 def current_adapter @active end |
#restore_primary! ⇒ Object
Reset to the primary at the top of each turn. No-op cost when we never left the primary; rebuilds nothing (the primary adapter is the one handed in at construction).
100 101 102 103 |
# File 'lib/rubino/agent/fallback_chain.rb', line 100 def restore_primary! @index = 0 @active = @primary end |