-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfallback.exs
More file actions
152 lines (126 loc) · 5.87 KB
/
Copy pathfallback.exs
File metadata and controls
152 lines (126 loc) · 5.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/usr/bin/env elixir
# Nous AI - Fallback Chains
# Built-in automatic provider/model failover via the :fallback option
IO.puts("=== Nous AI - Fallback Chains ===\n")
# ============================================================================
# Declaring a Fallback Chain
# ============================================================================
IO.puts("--- Declaring a Fallback Chain ---")
IO.puts("""
Pass :fallback to Nous.new/2 (also works on Nous.LLM helpers). It takes an
ordered list of "provider:model" strings and/or %Nous.Model{} structs:
agent =
Nous.new("openai:gpt-4o",
instructions: "You are a helpful assistant.",
fallback: [
"anthropic:claude-sonnet-4-5-20250929",
"lmstudio:qwen3"
]
)
Then just run normally. If the primary fails with a ProviderError or
ModelError (outage, rate limit, 5xx, auth, timeout), Nous transparently
retries the request against the next model in the chain. The first model
that succeeds wins; if all fail, the last error is returned.
""")
# ============================================================================
# Runnable Demo
# ============================================================================
#
# To keep this runnable offline-ish, the primary points at an unreachable
# local server (port 1 never has anything listening), so its request fails
# with a ProviderError. Nous then falls back to a local LM Studio model.
#
# Run a model in LM Studio (http://localhost:1234) to see the fallback
# actually succeed; otherwise both legs fail and you'll see the last error.
IO.puts("--- Runnable Demo ---\n")
primary = "openai:gpt-4o@http://localhost:1"
fallback_model = "lmstudio:qwen3"
IO.puts("Primary (intentionally unreachable): #{primary}")
IO.puts("Fallback (local): #{fallback_model}\n")
agent =
Nous.new(primary,
instructions: "Be concise.",
# api_key required so the unreachable primary is attempted at the
# provider layer (a missing key surfaces as a terminal ConfigurationError,
# which is NOT fallback-eligible).
api_key: "sk-not-a-real-key",
fallback: [fallback_model]
)
case Nous.run(agent, "What is 2+2? Answer with just the number.") do
{:ok, result} ->
IO.puts("Output: #{result.output}")
# The model that actually served the run is recorded in the run context as
# `deps[:active_model]` (set when a fallback takes over). It differs from
# `primary` only when failover happened. You can also observe failover via
# the `[:nous, :agent, :fallback, :used]` telemetry event.
served = result.deps[:active_model] || primary
if served != primary, do: IO.puts("(served by fallback model: #{inspect(served)})")
{:error, %Nous.Errors.ProviderError{} = err} ->
# Every model in the chain failed at the provider layer.
IO.puts("All providers unavailable: #{Exception.message(err)}")
{:error, reason} ->
# A non-eligible error short-circuited the chain (e.g. ValidationError).
IO.inspect(reason, label: "non-fallback error")
end
IO.puts("")
# ============================================================================
# Observing Failover with Telemetry
# ============================================================================
#
# Two distinct events let you watch failover:
#
# [:nous, :fallback, :activated]
# Emitted by Nous.Fallback.with_fallback/3 on EACH hop from one model to
# the next (one event per step in the chain). Metadata: failed_provider,
# failed_model, next_provider, next_model, reason.
#
# [:nous, :agent, :fallback, :used]
# Emitted ONCE by the agent runner when a run actually completed on a
# non-primary model ("sticky fallback" - the promoted model is reused for
# the rest of that run). Metadata: agent_name, original_provider,
# original_model, active_provider, active_model.
IO.puts("--- Telemetry ---")
:telemetry.attach_many(
"nous-fallback-demo-logger",
[
[:nous, :fallback, :activated],
[:nous, :agent, :fallback, :used]
],
fn event, _measurements, metadata, _config ->
IO.inspect({event, metadata}, label: "fallback telemetry")
end,
nil
)
# Re-run so the attached handlers fire on the hop above.
case Nous.run(agent, "Say hello.") do
{:ok, result} -> IO.puts("Output: #{String.slice(result.output, 0, 60)}")
{:error, reason} -> IO.puts("Error: #{inspect(reason)}")
end
:telemetry.detach("nous-fallback-demo-logger")
IO.puts("")
# ============================================================================
# Built-in :fallback vs. the Manual Loop in error_handling.exs
# ============================================================================
#
# examples/advanced/error_handling.exs hand-rolls failover: it loops over a
# list of provider configs and calls Nous.run/2 on each in turn. That manual
# approach falls over on ANY {:error, _} (including tool and validation
# errors), re-runs the whole agent each time, and needs you to instrument
# telemetry yourself.
#
# The built-in :fallback chain only fails over on ProviderError/ModelError
# (transport/provider-layer failures), is sticky within a run, threads stream
# init through the chain, and emits the telemetry events shown above for free.
# Reach for the manual loop only when you need per-provider options (distinct
# API keys, instructions, base URLs) or failover on errors the built-in path
# intentionally treats as terminal.
IO.puts("""
--- :fallback vs. manual loop (error_handling.exs) ---
Aspect | Manual loop | :fallback chain
-----------------|-----------------------------|---------------------------
Error filtering | Any {:error, _} | ProviderError/ModelError
Telemetry | You instrument it | Built-in activated + used
Sticky in a run | No (re-runs whole agent) | Yes (promoted model reused)
Streaming | Manual | Stream init uses the chain
Prefer :fallback for resilience against provider outages.
""")