Model Routing
Use model routing to choose a model for each agent invocation from a fixed set of candidates. It fits agents that need a backup model after failures or need request content to determine which model handles the work.
Configure ModelRouter as the agent’s model. The first candidate is the default,
and the routing strategy owns the opening choice and every decision after an
unclaimed model failure.
The examples use Amazon Bedrock. Before running them, configure AWS credentials and model access. Model routing accepts models from any supported provider, but each candidate must be stateless: conversation history stays with the agent instead of the provider.
How model routing works
Section titled “How model routing works”To reason about a routing decision, separate the components that the router coordinates:
- Candidate: A stateless model or nested
ModelRouteravailable for selection. - Default candidate: The first declared candidate, used when a strategy declines the opening choice.
- Routing strategy: Selects the opening candidate and decides whether to switch after an unclaimed model failure.
- Routing context: Supplies request messages, agent instructions, tool specifications, candidates, a read-only view of invocation state, and prior attempts to a strategy.
- Failure round: Starts with the opening candidate or a candidate that just succeeded. Routing selects each candidate at most once until the next success; same-model retries do not select it again.
Choose a routing strategy
Section titled “Choose a routing strategy”Choose the strategy that matches how your application should select and recover:
| Goal | Strategy |
|---|---|
| Try candidates in order, then favor healthier candidates | Default FallbackStrategy |
| Choose the opening candidate from the request and evidence | ClassifierStrategy |
| Define a custom candidate-selection policy | Custom RoutingStrategy |
ClassifierStrategy does not select another candidate after the serving model
fails. Implement a custom strategy when you need classifier-based opening selection
and fallback recovery together.
Configure model routing
Section titled “Configure model routing”To configure routing without changing how you invoke the agent:
- Create the stateless model instances that can serve the request.
- Wrap a model in
RoutingCandidatewhen a strategy needs a name, description, or metadata as selection evidence. - Choose the default fallback,
ClassifierStrategy, or a customRoutingStrategy. - Construct
ModelRouterwith the candidates and optional strategy. - Pass the router as the agent’s model. Do not attach it through the plugin list.
The agent invocation API stays the same. The following sections show each strategy with complete configuration.
Route failures to a backup model
Section titled “Route failures to a backup model”Use the default strategy when you want ordered fallback without a separate routing
call. On the first failure round of each invocation, FallbackStrategy walks the
candidates in declaration order: the first candidate opens the invocation, and
each unclaimed failure moves to the next candidate. After a model succeeds, later
failure rounds use health history. The strategy picks the untried candidate with
the fewest recorded failures and uses declaration order to break ties.
from strands import Agentfrom strands.models import BedrockModel, ModelRouter
primary_model = BedrockModel( model_id="us.amazon.nova-pro-v1:0",)backup_model = BedrockModel( model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0",)
router = ModelRouter( models=[primary_model, backup_model], max_switches=1,)agent = Agent(model=router)
agent("Summarize the tradeoffs of active-active deployment.")import { Agent, BedrockModel, ModelRouter } from '@strands-agents/sdk'
const primaryModel = new BedrockModel({ modelId: 'us.amazon.nova-pro-v1:0',})const backupModel = new BedrockModel({ modelId: 'us.anthropic.claude-haiku-4-5-20251001-v1:0',})
const router = new ModelRouter([primaryModel, backupModel], { maxSwitches: 1,})const agent = new Agent({ model: router })
await agent.invoke('Summarize the tradeoffs of active-active deployment.')Set max_switchesmaxSwitches
Route each request by content
Section titled “Route each request by content”Use ClassifierStrategy when request complexity, domain, cost, or latency should
choose the opening candidate. By default, it selects the least capable candidate
that can still deliver a complete and accurate result, reserving more capable
candidates for work that needs them.
Give each candidate a short name, a description, and optional metadata that explain where it fits. Metadata keys must be strings, and metadata values must be JSON-serializable. This example supplies metadata and overrides the default policy to prefer the lowest-latency candidate that satisfies every request requirement.
from strands import Agentfrom strands.models import ( BedrockModel, ClassifierStrategy, ModelRouter, RoutingCandidate,)
routing_policy = ( "Choose the lowest-latency candidate that satisfies every request requirement. " "Use candidate metadata as evidence.")classifier_model = BedrockModel( model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0", max_tokens=64, streaming=False, temperature=0,)routine_model = BedrockModel(model_id="us.amazon.nova-lite-v1:0")advanced_model = BedrockModel(model_id="us.amazon.nova-pro-v1:0")
router = ModelRouter( models=[ RoutingCandidate( routine_model, name="routine", description="Concise factual questions and routine requests.", metadata={"cost": "low", "latency": "low", "complexity": "routine"}, ), RoutingCandidate( advanced_model, name="advanced", description="Systems design with several interacting constraints.", metadata={"cost": "high", "latency": "medium", "complexity": "advanced"}, ), ], strategy=ClassifierStrategy( classifier_model, system_prompt=routing_policy, ),)agent = Agent(model=router)
agent( "Design a rollback-safe migration from regional to global idempotency keys.")import { Agent, BedrockModel, ClassifierStrategy, ModelRouter, RoutingCandidate,} from '@strands-agents/sdk'
const routingPolicy = 'Choose the lowest-latency candidate that satisfies every request requirement. ' + 'Use candidate metadata as evidence.'const classifierModel = new BedrockModel({ modelId: 'us.anthropic.claude-haiku-4-5-20251001-v1:0', maxTokens: 64, temperature: 0,})const routineModel = new BedrockModel({ modelId: 'us.amazon.nova-lite-v1:0',})const advancedModel = new BedrockModel({ modelId: 'us.amazon.nova-pro-v1:0',})
const router = new ModelRouter( [ new RoutingCandidate({ model: routineModel, name: 'routine', description: 'Concise factual questions and routine requests.', metadata: { cost: 'low', latency: 'low', complexity: 'routine' }, }), new RoutingCandidate({ model: advancedModel, name: 'advanced', description: 'Systems design with several interacting constraints.', metadata: { cost: 'high', latency: 'medium', complexity: 'advanced' }, }), ], { strategy: new ClassifierStrategy(classifierModel, { systemPrompt: routingPolicy, }), })const agent = new Agent({ model: router })
await agent.invoke( 'Design a rollback-safe migration from regional to global idempotency keys.')With two or more candidates, the classifier makes one additional model call before
the first serving call. Choose a classifier model that supports
Set system_promptsystemPrompt
The classifier receives a bounded representation of the latest request-bearing user message, textual agent instructions, and candidate names, descriptions, and metadata. Keep secrets out of that evidence because it can cross a model provider boundary.
The classifier prompt tells the model not to infer preference from declaration
order. If the classifier call times out, fails, or returns an invalid choice, the
router uses the first candidate. Candidate evidence that exceeds
max_candidate_charsmaxCandidateCharsValueErrorError
ClassifierStrategy only chooses the opening model. If the selected model fails and
no same-model retry claims the failure, the strategy declines further selection.
The router surfaces the selected model’s original error instead of switching
candidates.
Build a custom routing strategy
Section titled “Build a custom routing strategy”Implement RoutingStrategy, the select method. It receives a RoutingContext and returns one of
that context’s candidate instances or
Noneundefined
ModelRouter decides when to call select, applies the selected candidate, and
tracks switches and failure rounds. The strategy chooses a candidate or declines;
it does not perform the transition itself.
The example implements the contract by combining classifier-based opening selection with fallback after a failed serving attempt.
from typing import Any
from strands import Agentfrom strands.models import ( BedrockModel, ClassifierStrategy, FallbackStrategy, ModelRouter, RoutingCandidate, RoutingContext, RoutingStrategy,)
class ClassifyThenFallback: def __init__(self, classifier: ClassifierStrategy) -> None: self._classifier = classifier self._fallback = FallbackStrategy()
async def select( self, context: RoutingContext, **kwargs: Any, ) -> RoutingCandidate | None: if not context.attempts: return await self._classifier.select(context, **kwargs) return await self._fallback.select(context, **kwargs)
classifier_model = BedrockModel( model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0", max_tokens=64, streaming=False, temperature=0,)strategy: RoutingStrategy = ClassifyThenFallback( ClassifierStrategy(classifier_model))router = ModelRouter( models=[ RoutingCandidate( BedrockModel(model_id="us.amazon.nova-lite-v1:0"), name="routine", description="Concise factual questions and routine requests.", ), RoutingCandidate( BedrockModel(model_id="us.amazon.nova-pro-v1:0"), name="advanced", description="Systems design with several interacting constraints.", ), ], strategy=strategy,)agent = Agent(model=router)agent("Design a rollback-safe migration from regional to global idempotency keys.")import { Agent, BedrockModel, ClassifierStrategy, FallbackStrategy, ModelRouter, RoutingCandidate, type RoutingContext, type RoutingStrategy,} from '@strands-agents/sdk'
class ClassifyThenFallback implements RoutingStrategy { private readonly _classifier: ClassifierStrategy private readonly _fallback = new FallbackStrategy()
constructor(classifier: ClassifierStrategy) { this._classifier = classifier }
async select(context: RoutingContext): Promise<RoutingCandidate | undefined> { if (context.attempts.length === 0) { return this._classifier.select(context) } return this._fallback.select(context) }}
const classifierModel = new BedrockModel({ modelId: 'us.anthropic.claude-haiku-4-5-20251001-v1:0', maxTokens: 64, temperature: 0,})const strategy = new ClassifyThenFallback(new ClassifierStrategy(classifierModel))const router = new ModelRouter( [ new RoutingCandidate({ model: new BedrockModel({ modelId: 'us.amazon.nova-lite-v1:0', }), name: 'routine', description: 'Concise factual questions and routine requests.', }), new RoutingCandidate({ model: new BedrockModel({ modelId: 'us.amazon.nova-pro-v1:0', }), name: 'advanced', description: 'Systems design with several interacting constraints.', }), ], { strategy })const agent = new Agent({ model: router })await agent.invoke( 'Design a rollback-safe migration from regional to global idempotency keys.')A custom select method can inspect the request, tool specifications, candidate
metadata, read-only invocation state, and prior attempts. Routing strategies must
not mutate invocation state. The method must return one of the candidate instances
in its routing context. Returning a new, equivalent candidate is an error.
Understand retries and switches
Section titled “Understand retries and switches”To predict which model handles each call, separate same-model retries from candidate switches:
- The routing strategy chooses a candidate before the first model call.
- That model handles later calls in the same agent loop unless a call fails.
ModelRetryStrategygets the first chance to retry the same model.- If no retry claims the failure, the router asks its strategy for another candidate.
- A replacement model gets a fresh retry budget. A successful call starts a new failure round and makes the other candidates available again.
The next agent invocation starts with a new routing decision. Routing selects each candidate at most once per failure round, though the retry strategy can call the selected model more than once. This prevents routing cycles.
Work within current limits
Section titled “Work within current limits”Choose candidates with comparable context windows and tokenizers so context estimation stays aligned with the selected model.
- Model routing rejects stateful candidate models.
agent.modelremains the first candidate. Proactive context estimation also uses that default model, even when routing selects another candidate.- If a candidate fails after emitting stream events and its replacement completes, streaming consumers receive the failed candidate’s emitted events followed by the replacement’s complete stream.