AI applications increasingly depend on external model providers. That dependency creates a reliability problem that is easy to overlook during development.
An application may work perfectly when its preferred model provider is available. But what happens when the provider experiences an outage, returns repeated errors, becomes temporarily unreachable, or cannot handle the current request?
For a production AI application, the answer should not simply be "try again."
A more resilient design can route requests to another compatible provider or model when the preferred provider cannot complete the request.
Microsoft.Extensions.AI now provides routing and failover components designed for this type of scenario. The Microsoft.Extensions.AI ecosystem includes abstractions such as IChatClient, while the newer routing capabilities provide mechanisms for semantic routing and failover between chat clients. Microsoft describes FailoverChatClient as a way to automatically move to another configured chat client when the current client fails.
This changes the reliability architecture of an AI application from:
Application
|
v
AI Provider
|
v
Response
to:
┌── Provider A
│
Application ──> Router
│
├── Provider B
│
└── Provider C
The important engineering question is not whether failover exists. It is how well it behaves under realistic failures and whether the additional complexity is justified.
What Is Microsoft.Extensions.AI?
Microsoft.Extensions.AI provides common abstractions for working with AI services in .NET applications.
Instead of coupling application code directly to a specific provider SDK, developers can work against abstractions such as IChatClient.
A simplified architecture looks like this:
public interface IChatClient
{
// Conceptual representation of a chat client abstraction.
}
In a real application, the concrete implementation might communicate with a hosted model provider.
The advantage is architectural flexibility.
Your application can depend on an abstraction while the underlying implementation changes.
ASP.NET Core Application
|
v
IChatClient
|
┌─────┴─────┐
│ │
Provider A Provider B
This abstraction becomes especially useful when an application needs to switch providers without rewriting the application layer.
Why AI Failover Matters
Traditional web applications have long used redundancy.
For example:
Web Server A
Web Server B
Database Replica
Cache Replica
AI applications have a similar dependency problem, but the failure modes can be different.
An AI provider can experience:
HTTP failures
Authentication problems
Timeouts
Rate limiting
Service interruptions
Temporary capacity problems
Model-specific failures
A resilient application should decide which of these conditions justify a retry and which should trigger failover.
That distinction matters.
A retry against the same unavailable service does not provide redundancy.
Failover does.
Understanding FailoverChatClient
The failover approach wraps multiple chat clients and attempts to use another client when a request fails.
Conceptually:
FailoverChatClient
|
+---- Primary Chat Client
|
+---- Secondary Chat Client
|
+---- Tertiary Chat Client
A request might follow this path:
Request
|
v
Provider A
|
X Failure
|
v
Provider B
|
v
Response
The application therefore does not need to manually implement provider selection throughout every business service.
That separation is valuable because resilience becomes part of the AI infrastructure layer instead of being duplicated throughout application code.
A Simplified .NET Configuration
The exact APIs should be checked against the version of Microsoft.Extensions.AI used by the application, but the conceptual setup looks like this:
using Microsoft.Extensions.AI;
var builder = WebApplication.CreateBuilder(args);
var primaryClient = new SomeProviderChatClient(
builder.Configuration["AI:PrimaryApiKey"]!);
var secondaryClient = new AnotherProviderChatClient(
builder.Configuration["AI:SecondaryApiKey"]!);
var failoverClient = new FailoverChatClient(
primaryClient,
secondaryClient);
builder.Services.AddSingleton<IChatClient>(failoverClient);
The important architectural idea is that application services receive IChatClient rather than directly depending on either provider.
For example:
public sealed class SupportAssistant
{
private readonly IChatClient _chatClient;
public SupportAssistant(IChatClient chatClient)
{
_chatClient = chatClient;
}
public async Task<string> AnswerAsync(
string question,
CancellationToken cancellationToken)
{
// Invoke the configured chat client.
// Failover is handled below the application layer.
return await GenerateAnswerAsync(
question,
cancellationToken);
}
private Task<string> GenerateAnswerAsync(
string question,
CancellationToken cancellationToken)
{
// Provider-specific implementation omitted.
throw new NotImplementedException();
}
}
The application does not need to know which provider ultimately handled the request.
Failover vs Retry
These concepts are related but not identical.
| Strategy | What Happens | Main Purpose |
|---|---|---|
| Retry | Repeat request against same dependency | Recover from transient failure |
| Failover | Move request to another dependency | Recover from provider failure |
| Load balancing | Distribute requests across providers | Capacity and performance |
| Semantic routing | Select provider based on request characteristics | Match workload to model |
A robust system may combine several of these.
For example:
Request
|
v
Primary Provider
|
+-- Temporary failure --> Retry
|
+-- Persistent failure --> Failover
|
v
Secondary Provider
The exact policy depends on the application's reliability requirements.
Building a Failover Benchmark
The most useful way to evaluate failover is to simulate failures instead of waiting for a real outage.
Create a controlled test environment with multiple fake or test chat clients.
For example:
public sealed class TestChatClient : IChatClient
{
private readonly bool _shouldFail;
public TestChatClient(bool shouldFail)
{
_shouldFail = shouldFail;
}
public async Task<string> CompleteAsync(
string prompt,
CancellationToken cancellationToken)
{
if (_shouldFail)
{
throw new HttpRequestException(
"Simulated provider failure.");
}
return "Successful response";
}
}
The exact interface implementation will depend on the version of Microsoft.Extensions.AI being tested.
The test should then measure:
Primary Available
Primary Fails
Primary Times Out
Secondary Available
Secondary Fails
All Providers Fail
Metrics Worth Measuring
A failover benchmark should measure more than whether the final request succeeded.
Join the conversation! Your thoughts help the community grow.