LLM Provider Fallback in PHP: Automatic Failover in Neuron AI Router
Building Resilient AI Agents: Provider-Level Failover Architecture in PHP
Current Situation Analysis
Large language model APIs operate as external, shared infrastructure. They are subject to the same operational realities as any distributed system: traffic spikes trigger rate limiting, backend deployments cause temporary error rate elevation, and regional outages introduce latency or complete unavailability. When building traditional web applications, a failed third-party API call is typically an isolated event. You log it, queue a retry, and the rest of the request lifecycle continues unaffected.
Agentic architectures invert this assumption. In an agent-driven workflow, the inference call is not a peripheral dependency; it is the execution core. A single user interaction rarely results in one API call. Modern agents iterate through tool execution, internal reasoning steps, context summarization, and multi-turn follow-ups. Each iteration is a discrete network request to a provider you do not control. If a provider experiences a five-minute degradation window, a single user request can trigger dozens of cascading failures. The compounding effect means that as agent capabilities increase, so does the surface area for provider instability.
The conventional response is to wrap inference calls in try/catch blocks with exponential backoff. This approach addresses transient network blips but fails catastrophically during provider-wide incidents. Retrying against the same endpoint during a regional outage simply delays failure. Switching providers manually requires branching application logic, duplicating agent configurations, or maintaining separate deployment environments. Because agent state lives deep within execution loops, catching failures at the application layer forces you to discard completed tool calls and reasoning steps, breaking conversation continuity.
The industry has largely outsourced this problem to external API gateways. While functional, this introduces additional network hops, vendor lock-in, and opaque cost tracking. The architectural gap remains: how do you achieve transparent provider failover without sacrificing state preservation, cost visibility, or deployment simplicity?
WOW Moment: Key Findings
The most effective resilience strategy operates at the provider boundary, not the application layer. By intercepting requests before they leave your codebase and classifying errors by type rather than HTTP status alone, you can achieve near-zero recovery time while maintaining complete state continuity.
| Strategy | Latency Overhead | State Preservation | Cost Visibility | Recovery Window |
|---|---|---|---|---|
| Naive Retry (Same Endpoint) | Low | High | High | Slow (repeated failures) |
| External API Gateway | Medium-High | Medium | Low (black box routing) | Fast |
| In-App Fallback Router | Negligible | Complete | Complete | Fast |
This finding matters because it decouples resilience from infrastructure complexity. An in-app fallback router eliminates the need for external proxy services, reduces request latency by removing network hops, and preserves the exact message payload across provider switches. More importantly, it enables cost-aware routing: you can track exactly when fallback activates, which provider handled the request, and how pricing models differ during failover events. This transforms provider instability from a silent production risk into a measurable, observable operational parameter.
Core Solution
The architecture relies on a proxy layer that implements the same contract as your base providers. In the Neuron AI ecosystem, this is the RouterProvider, which adheres to AIProviderInterface. Because it sits at the boundary between your agent and external APIs, it can intercept chat(), stream(), and structured() calls, evaluate the response, and route to an alternative provider without the agent layer ever detecting the switch.
Step 1: Boundary Interception Architecture
Instead of scattering error handling across agent methods, centralize provider communication through a single routing facade. This facade maintains an ordered fallback chain and evaluates every response against a transient error classifier.
declare(strict_types=1);
namespace App\AI;
use NeuronAI\Router\RouterProvider;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\Providers\OpenAI\OpenAI;
use NeuronAI\Providers\AIProviderInterface;
class ResilientModelFactory
{
public static function createFallbackChain(): AIProviderInterface
{
$primary = new Anthropic(
key: env('ANTHROPIC_API_KEY'),
model: 'claude-sonnet-4-20250514'
);
$secondary = new OpenAI(
key: env('OPENAI_API_KEY'),
model: 'gpt-4o'
);
return RouterProvider::make()
->addProvider('primary', $primary)
->addProvider('secondary', $secondary)
->setFallback(['primary', 'secondary']);
}
}
Why this works: The router normalizes the request payload once, then attempts execution against the first provider. If the response matches a transient error signature, the identical payload is forwarded to the next provider in the chain. The agent receives a single response object, unaware of the internal retry logic.
Step 2: Transient vs. Permanent Error Classification
Not all failures warrant failover. Sending a malformed request or an invalid API key to a second provider will produce identical results. The router distinguishes between:
- Transient errors: Rate limits (
429), gateway timeouts (504), overloaded backends (503), and network-level connection resets. - Permanent errors: Authentication failures (
401), validation errors (400), model deprecation notices, and quota exhaustion.
The fallback mechanism only triggers on transient classifications. This prevents infinite retry loops and preserves API quota during configuration mistakes.
Step 3: Composing Fallback with Routing Rules
Fallback chains operate independently of routing logic. Routing rules determine which provider handles a request under normal conditions. Fallback determines what happens when that provider becomes unavailable.
use NeuronAI\Router\Rules\MethodRule;
$router = RouterProvider::make()
->addProvider('claude', new Anthropic(key: env('ANTHROPIC_KEY'), model: 'claude-sonnet-4-20250514'))
->addProvider('gpt', new OpenAI(key: env('OPENAI_KEY'), model: 'gpt-4o'))
->addProvider('gemini', new Gemini(key: env('GEMINI_KEY'), model: 'gemini-2.0-flash'))
->setRule(
new MethodRule('claude')->structured('gpt')
)
->setFallback(['claude', 'gpt', 'gemini']);
Architecture rationale: The MethodRule routes structured output requests to OpenAI and standard chat to Anthropic. If either provider returns a transient error, the fallback chain takes over, attempting claude β gpt β gemini in sequence. This separation of concerns ensures that routing policies remain declarative while resilience remains automatic.
Step 4: Unified Payload Translation
The seamless failover is possible because of a normalized messaging layer. Every provider implementation translates internal message objects into provider-specific API formats. When the router intercepts a failed request, it does not need to re-parse or re-serialize the payload. The normalized message structure is passed directly to the next provider's translation layer. This eliminates serialization overhead and guarantees that tool definitions, system prompts, and conversation history remain intact across provider switches.
Pitfall Guide
1. Treating All HTTP Errors as Transient
Explanation: Blindly retrying on 400 or 401 errors wastes API quota and delays failure reporting. Invalid JSON schemas or revoked keys will fail identically across all providers.
Fix: Implement strict error classification. Only trigger fallback on 429, 502, 503, 504, and client-side timeout exceptions. Log permanent errors immediately and halt the chain.
2. Silent Cost Drift During Failover
Explanation: Fallback providers often have different pricing models. If your primary provider degrades for hours, your secondary provider absorbs all traffic, potentially tripling inference costs without triggering alerts.
Fix: Instrument fallback activation events. Emit metrics to your observability stack (e.g., llm.fallback.triggered{provider: "openai"}). Set billing alerts on fallback-specific tags, not just aggregate token usage.
3. Blocking Fallback in Streaming Contexts
Explanation: Streaming responses maintain open HTTP connections. If a provider drops mid-stream, naive fallback implementations attempt to restart the stream, causing duplicate tool calls or fragmented output. Fix: Configure the router to buffer streaming responses until the first token arrives. If the connection drops before the stream initializes, trigger fallback. Once streaming begins, allow the connection to fail and let the agent handle the interruption gracefully rather than attempting mid-stream provider switching.
4. Misordering the Fallback Chain
Explanation: Placing a cheaper or less capable model first in the fallback list degrades output quality during outages. Fallback order should reflect capability parity, not cost optimization. Fix: Order fallback providers by model capability tier, not pricing. Use routing rules for cost optimization during normal operations. Reserve fallback chains for capability-preserving failover.
5. Assuming Fallback Solves Rate Limiting
Explanation: Rate limits are often tied to account-level quotas or regional infrastructure. If your primary provider hits a global rate limit, your secondary provider may also be throttled if you share infrastructure or if the limit is account-wide. Fix: Treat fallback as an outage mitigation strategy, not a rate-limit bypass. Implement client-side token bucket rate limiting before requests reach the router. Use fallback only when providers are genuinely degraded, not when you are simply exceeding your own quota.
6. Losing Observability on Silent Failures
Explanation: Transparent failover is excellent for users but dangerous for engineering teams. If fallback activates silently, you lose visibility into provider health trends and cannot perform capacity planning. Fix: Inject a middleware hook that logs every fallback activation with provider name, error code, timestamp, and request ID. Correlate these logs with provider status pages to identify recurring degradation patterns.
7. Overcomplicating Agent-Level Retry Logic
Explanation: Developers often add retry loops inside agent tool handlers, creating nested retry mechanisms that conflict with router-level fallback. This causes exponential backoff collisions and unpredictable execution order.
Fix: Centralize all retry logic at the provider boundary. Remove try/catch retry blocks from agent methods. Let the router handle provider switching, and let the agent handle business logic retries only when explicitly required by domain rules.
Production Bundle
Action Checklist
- Define fallback chain order by capability tier, not pricing
- Implement transient error classifier to filter permanent failures
- Instrument fallback activation events in your observability stack
- Configure client-side rate limiting before requests reach the router
- Test fallback behavior with simulated provider outages in staging
- Set billing alerts on fallback-specific metric tags
- Remove nested retry logic from agent tool handlers
- Validate streaming fallback behavior under network degradation
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|---|---|---|
| Primary provider regional outage | In-app fallback router | Eliminates network hops, preserves state, immediate recovery | Neutral (shifts spend to secondary) |
| Account-wide rate limit exceeded | Client-side token bucket + queue | Fallback cannot bypass account quotas; queuing prevents waste | Low (delays execution, saves quota) |
| Cost optimization during normal ops | Routing rules with capability tiers | Routes structured output to cheaper models, keeps chat on premium | High reduction (up to 40% savings) |
| Multi-region compliance requirement | External gateway with geo-routing | In-app fallback cannot enforce data residency; gateway handles routing | Medium (adds proxy cost, ensures compliance) |
| Streaming-heavy agent workload | Buffered fallback + stream timeout | Prevents mid-stream provider switching, avoids duplicate tool calls | Neutral (slight latency increase for safety) |
Configuration Template
declare(strict_types=1);
namespace App\Providers;
use NeuronAI\Router\RouterProvider;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\Providers\OpenAI\OpenAI;
use NeuronAI\Providers\AIProviderInterface;
use Psr\Log\LoggerInterface;
class AIProviderRegistry
{
public function __construct(
private readonly LoggerInterface $logger
) {}
public function buildResilientRouter(): AIProviderInterface
{
$primary = new Anthropic(
key: config('services.anthropic.key'),
model: config('services.anthropic.model')
);
$secondary = new OpenAI(
key: config('services.openai.key'),
model: config('services.openai.model')
);
$router = RouterProvider::make()
->addProvider('anthropic', $primary)
->addProvider('openai', $secondary)
->setFallback(['anthropic', 'openai']);
// Optional: Attach observability hook
$router->onFallback(function (string $from, string $to, int $statusCode) {
$this->logger->warning('LLM fallback triggered', [
'from' => $from,
'to' => $to,
'status' => $statusCode,
'timestamp' => now()->toIso8601String()
]);
});
return $router;
}
}
Quick Start Guide
- Install the router package:
composer require neuron-core/router - Create a factory class that instantiates your primary and secondary providers with environment-backed credentials
- Chain providers using
RouterProvider::make()->addProvider()->setFallback() - Bind the router instance to your application container or pass it directly to your agent constructor
- Deploy and monitor fallback activation metrics in your observability dashboard
Mid-Year Sale β Unlock Full Article
Base plan from just $4.99/mo or $49/yr
Sign in to read the full article and unlock all tutorials.
Sign In / Register β Start Free Trial7-day free trial Β· Cancel anytime Β· 30-day money-back
