symfony/ai-failover-platform
Symfony AI Failover Platform bridge that adds resilient fallback behavior across AI providers. Integrates with Symfony AI to automatically switch to alternate platforms on errors or outages, improving availability and reliability in production deployments.
Install Dependencies
composer require symfony/ai symfony/ai-failover-platform symfony/http-client
Note: Resolve conflicts with Laravel’s guzzlehttp/guzzle or illuminate/http via composer why-not and use package aliases if needed.
Configure Providers
Define AI providers and failover strategy in config/ai.php:
'ai' => [
'providers' => [
'openai' => [
'class' => \Symfony\Component\Ai\Provider\OpenAiProvider::class,
'api_key' => env('OPENAI_API_KEY'),
'priority' => 1,
],
'local' => [
'class' => \App\Providers\LocalAiProvider::class, // Custom fallback
'priority' => 2,
],
],
'failover' => [
'strategy' => 'priority', // or 'round-robin'
'max_attempts' => 3,
],
],
Bind Symfony Components
Register a service provider to bind Symfony’s FailoverClient to Laravel’s container:
// app/Providers/AiServiceProvider.php
use Symfony\Component\Ai\Failover\FailoverClient;
use Symfony\Component\Ai\Provider\ProviderRegistry;
public function register()
{
$this->app->singleton(ProviderRegistry::class, function ($app) {
return new ProviderRegistry($app['config']['ai.providers']);
});
$this->app->singleton(FailoverClient::class, function ($app) {
return new FailoverClient(
$app->make(ProviderRegistry::class),
$app['config']['ai.failover']
);
});
}
First Use Case: AI Response with Failover Replace direct API calls with the failover client:
use Symfony\Component\Ai\Failover\FailoverClient;
$response = app(FailoverClient::class)
->complete('User:', 'Generate a summary of the document.');
Symfony\Component\Ai\Provider\ProviderInterface for custom providers (e.g., local LLMs):
class LocalAiProvider implements ProviderInterface
{
public function complete(string $prompt, string $model): string
{
// Local logic (e.g., PHP-ML, Ollama)
return "Local response for: $prompt";
}
}
config/ai.php to define providers with priorities:
'providers' => [
'openai' => ['class' => \Symfony\Component\Ai\Provider\OpenAiProvider::class, 'priority' => 1],
'local' => ['class' => \App\Providers\LocalAiProvider::class, 'priority' => 2],
],
$client = new FailoverClient($registry, ['strategy' => 'priority']);
$client = new FailoverClient($registry, ['strategy' => 'round-robin']);
Symfony\Component\Ai\Failover\Strategy\StrategyInterface for advanced logic (e.g., cost-aware routing).use Illuminate\Support\Facades\Bus;
Bus::dispatch(new HandleAiRequest($prompt));
// HandleAiRequest.php
public function handle()
{
$response = app(FailoverClient::class)->complete($this->prompt, $this->model);
// Process response...
}
// app/Providers/EventServiceProvider.php
protected $listen = [
\Symfony\Component\Ai\Failover\Event\ProviderFailedEvent::class => [
\App\Listeners\LogFailover::class,
],
];
use Illuminate\Support\Facades\Cache;
$response = Cache::remember("ai_{$prompt}_{$model}", now()->addHours(1), function () {
return app(FailoverClient::class)->complete($prompt, $model);
});
use Symfony\Component\Ai\Provider\ProviderInterface;
$mockProvider = $this->createMock(ProviderInterface::class);
$mockProvider->method('complete')->willThrowException(new \RuntimeException('API Down'));
$registry = new ProviderRegistry(['mock' => $mockProvider]);
$client = new FailoverClient($registry, ['strategy' => 'priority']);
Replace Symfony’s HttpClient with Laravel’s Http facade:
// app/Adapters/LaravelHttpClient.php
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Illuminate\Support\Facades\Http;
class LaravelHttpClient implements HttpClientInterface
{
public function request(string $method, string $url, array $options = []): ResponseInterface
{
$response = Http::withOptions($options)->{$method}($url);
return new SymfonyHttpClientResponse($response);
}
}
Bind it in AiServiceProvider:
$this->app->singleton(HttpClientInterface::class, function () {
return new LaravelHttpClient();
});
Dynamically instantiate providers from config/ai.php:
// app/Providers/AiServiceProvider.php
public function register()
{
foreach ($this->app['config']['ai.providers'] as $name => $config) {
$this->app->singleton($name, function () use ($config) {
return new $config['class']($this->app['config']['ai.' . $name]);
});
}
}
Catch failover-specific exceptions and map them to Laravel’s error handler:
// app/Exceptions/Handler.php
public function render($request, Throwable $exception)
{
if ($exception instanceof \Symfony\Component\Ai\Failover\Exception\NoProviderAvailable) {
return response()->view('ai.fallback', [], 503);
}
return parent::render($request, $exception);
}
Create an Artisan command to simulate provider failures:
// app/Console/Commands/TestFailover.php
use Symfony\Component\Ai\Failover\FailoverClient;
public function handle()
{
$client = app(FailoverClient::class);
$response = $client->complete('Test prompt', 'gpt-3.5-turbo');
$this->info("Failover response: {$response}");
}
ServiceLocator may clash with Laravel’s container.
Fix: Use Laravel’s bind() method to override Symfony bindings:
$this->app->bind(\Symfony\Component\Ai\Failover\FailoverClient::class, function ($app) {
return new FailoverClient($app->make(\Symfony\Component\Ai\Provider\ProviderRegistry::class), $app['config']['ai.failover']);
});
HttpClient expects specific response formats; Laravel’s Http returns Illuminate\Http\Client\Response.
Fix: Create a response adapter:
class SymfonyHttpClientResponse implements \Symfony\Contracts\HttpClient\ResponseInterface
{
public function __construct(private \Illuminate\Http\Client\Response $response) {}
public function getStatusCode(): int
{
return $this->response->status();
}
public function getContent(false $throw = true): string
{
return $this->response->body();
}
}
$providers = collect($config['providers'])
How can I help you explore Laravel packages today?