Skip to content

Commit 0c424e9

Browse files
committed
ref
1 parent b27adba commit 0c424e9

File tree

7 files changed

+179
-101
lines changed

7 files changed

+179
-101
lines changed

docs/components/platform.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ This platform can also be configured when using the bundle::
510510
platforms:
511511
- 'ai.platform.ollama'
512512
- 'ai.platform.openai'
513-
rate_limiter: ''
513+
rate_limiter: 'limiter.failover_platform' # To define in config/packages/rate_limiter.yaml
514514

515515
.. note::
516516

src/ai-bundle/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"symfony/console": "^7.3|^8.0",
2929
"symfony/dependency-injection": "^7.3|^8.0",
3030
"symfony/framework-bundle": "^7.3|^8.0",
31+
"symfony/rate-limiter": "^7.3|^8.0",
3132
"symfony/string": "^7.3|^8.0"
3233
},
3334
"require-dev": {

src/ai-bundle/config/options.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,7 @@
122122
->arrayNode('platforms')
123123
->scalarPrototype()->end()
124124
->end()
125-
->integerNode('retry_period')
126-
->defaultValue(60)
127-
->end()
125+
->stringNode('rate_limiter')->cannotBeEmpty()->end()
128126
->end()
129127
->end()
130128
->end()

src/ai-bundle/src/AiBundle.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,8 +522,8 @@ private function processPlatformConfig(string $type, array $platform, ContainerB
522522
static fn (string $wrappedPlatform): Reference => new Reference($wrappedPlatform),
523523
$config['platforms'],
524524
),
525+
new Reference($config['rate_limiter']),
525526
new Reference(ClockInterface::class),
526-
$config['retry_period'],
527527
new Reference(LoggerInterface::class),
528528
])
529529
->addTag('proxy', ['interface' => PlatformInterface::class])

src/ai-bundle/tests/DependencyInjection/AiBundleTest.php

Lines changed: 16 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@
8787
use Symfony\Component\DependencyInjection\Reference;
8888
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
8989
use Symfony\Component\HttpClient\HttpClient;
90+
use Symfony\Component\RateLimiter\RateLimiterFactory;
91+
use Symfony\Component\RateLimiter\Storage\InMemoryStorage;
9092
use Symfony\Contracts\HttpClient\HttpClientInterface;
9193

9294
class AiBundleTest extends TestCase
@@ -4039,6 +4041,7 @@ public function testFailoverPlatformCanBeCreated()
40394041
'ai.platform.ollama',
40404042
'ai.platform.openai',
40414043
],
4044+
'rate_limiter' => 'limiter.failover_platform',
40424045
],
40434046
],
40444047
],
@@ -4059,56 +4062,9 @@ public function testFailoverPlatformCanBeCreated()
40594062
new Reference('ai.platform.openai'),
40604063
], $definition->getArgument(0));
40614064
$this->assertInstanceOf(Reference::class, $definition->getArgument(1));
4062-
$this->assertSame(ClockInterface::class, (string) $definition->getArgument(1));
4063-
$this->assertSame(60, $definition->getArgument(2));
4064-
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
4065-
$this->assertSame(LoggerInterface::class, (string) $definition->getArgument(3));
4066-
4067-
$this->assertTrue($definition->hasTag('proxy'));
4068-
$this->assertSame([['interface' => PlatformInterface::class]], $definition->getTag('proxy'));
4069-
$this->assertTrue($definition->hasTag('ai.platform'));
4070-
$this->assertSame([['name' => 'failover']], $definition->getTag('ai.platform'));
4071-
4072-
$this->assertTrue($container->hasAlias('Symfony\AI\Platform\PlatformInterface $main'));
4073-
4074-
$container = $this->buildContainer([
4075-
'ai' => [
4076-
'platform' => [
4077-
'ollama' => [
4078-
'host_url' => 'http://127.0.0.1:11434',
4079-
],
4080-
'openai' => [
4081-
'api_key' => 'sk-openai_key_full',
4082-
],
4083-
'failover' => [
4084-
'main' => [
4085-
'platforms' => [
4086-
'ai.platform.ollama',
4087-
'ai.platform.openai',
4088-
],
4089-
'retry_period' => 120,
4090-
],
4091-
],
4092-
],
4093-
],
4094-
]);
4095-
4096-
$this->assertTrue($container->hasDefinition('ai.platform.failover.main'));
4097-
4098-
$definition = $container->getDefinition('ai.platform.failover.main');
4099-
4100-
$this->assertTrue($definition->isLazy());
4101-
$this->assertSame(FailoverPlatform::class, $definition->getClass());
4102-
4103-
$this->assertCount(4, $definition->getArguments());
4104-
$this->assertCount(2, $definition->getArgument(0));
4105-
$this->assertEquals([
4106-
new Reference('ai.platform.ollama'),
4107-
new Reference('ai.platform.openai'),
4108-
], $definition->getArgument(0));
4109-
$this->assertInstanceOf(Reference::class, $definition->getArgument(1));
4110-
$this->assertSame(ClockInterface::class, (string) $definition->getArgument(1));
4111-
$this->assertSame(120, $definition->getArgument(2));
4065+
$this->assertSame('limiter.failover_platform', (string) $definition->getArgument(1));
4066+
$this->assertInstanceOf(Reference::class, $definition->getArgument(2));
4067+
$this->assertSame(ClockInterface::class, (string) $definition->getArgument(2));
41124068
$this->assertInstanceOf(Reference::class, $definition->getArgument(3));
41134069
$this->assertSame(LoggerInterface::class, (string) $definition->getArgument(3));
41144070

@@ -7115,6 +7071,15 @@ private function buildContainer(array $configuration): ContainerBuilder
71157071
$container->setParameter('kernel.build_dir', 'public');
71167072
$container->setDefinition(ClockInterface::class, new Definition(MonotonicClock::class));
71177073
$container->setDefinition(LoggerInterface::class, new Definition(NullLogger::class));
7074+
$container->setDefinition('limiter.failover_platform', new Definition(RateLimiterFactory::class, [
7075+
[
7076+
'policy' => 'sliding_window',
7077+
'id' => 'test',
7078+
'interval' => '60 seconds',
7079+
'limit' => 1,
7080+
],
7081+
new Definition(InMemoryStorage::class),
7082+
]));
71187083

71197084
$extension = (new AiBundle())->getContainerExtension();
71207085
$extension->load($configuration, $container);
@@ -7176,13 +7141,7 @@ private function getFullConfig(): array
71767141
'ai.platform.ollama',
71777142
'ai.platform.openai',
71787143
],
7179-
],
7180-
'main_with_custom_retry_period' => [
7181-
'platforms' => [
7182-
'ai.platform.ollama',
7183-
'ai.platform.openai',
7184-
],
7185-
'retry_period' => 120,
7144+
'rate_limiter' => 'limiter.failover_platform',
71867145
],
71877146
],
71887147
'gemini' => [

src/platform/src/FailoverPlatform.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,27 @@
1717
use Symfony\AI\Platform\Exception\RuntimeException;
1818
use Symfony\AI\Platform\ModelCatalog\ModelCatalogInterface;
1919
use Symfony\AI\Platform\Result\DeferredResult;
20+
use Symfony\Component\Clock\ClockInterface;
21+
use Symfony\Component\Clock\MonotonicClock;
2022
use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
2123

2224
/**
2325
* @author Guillaume Loulier <[email protected]>
2426
*/
2527
final class FailoverPlatform implements PlatformInterface
2628
{
29+
/**
30+
* @var \SplObjectStorage<PlatformInterface, int>
31+
*/
32+
private readonly \SplObjectStorage $failedPlatforms;
33+
2734
/**
2835
* @param PlatformInterface[] $platforms
2936
*/
3037
public function __construct(
3138
private readonly iterable $platforms,
3239
private readonly RateLimiterFactoryInterface $rateLimiterFactory,
40+
private readonly ClockInterface $clock = new MonotonicClock(),
3341
private readonly LoggerInterface $logger = new NullLogger(),
3442
) {
3543
if ([] === $platforms) {
@@ -39,6 +47,8 @@ public function __construct(
3947
if (!interface_exists(RateLimiterFactoryInterface::class)) {
4048
throw new RuntimeException('For using the FailoverPlatform, a symfony/rate-limiter implementation is required. Try running "composer require symfony/rate-limiter".');
4149
}
50+
51+
$this->failedPlatforms = new \SplObjectStorage();
4252
}
4353

4454
public function invoke(string $model, object|array|string $input, array $options = []): DeferredResult
@@ -56,14 +66,14 @@ private function do(\Closure $func): DeferredResult|ModelCatalogInterface
5666
foreach ($this->platforms as $platform) {
5767
$limiter = $this->rateLimiterFactory->create($platform::class);
5868

59-
if (!$limiter->consume()->isAccepted()) {
60-
continue;
61-
}
62-
6369
try {
70+
if ($limiter->consume()->isAccepted() && $this->failedPlatforms->offsetExists($platform)) {
71+
$this->failedPlatforms->offsetUnset($platform);
72+
}
73+
6474
return $func($platform);
6575
} catch (\Throwable $throwable) {
66-
$limiter->consume();
76+
$this->failedPlatforms->offsetSet($platform, $this->clock->now()->getTimestamp());
6777

6878
$this->logger->error('The {platform} platform due to an error/exception: {message}', [
6979
'platform' => $platform::class,

0 commit comments

Comments
 (0)