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.
-
#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.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/rubino/agent/fallback_chain.rb', line 76 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 |
#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).
94 95 96 97 |
# File 'lib/rubino/agent/fallback_chain.rb', line 94 def restore_primary! @index = 0 @active = @primary end |