Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d8b15fa

Browse files
committed
[Platform] Introduce Provider abstraction and model routing layer
1 parent 8f9e9c4 commit d8b15fa

7 files changed

Lines changed: 69 additions & 42 deletions

File tree

BridgeResolver.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111

1212
namespace Symfony\AI\Platform\Bridge\ModelsDev;
1313

14-
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory as AnthropicPlatformFactory;
15-
use Symfony\AI\Platform\Bridge\Bedrock\PlatformFactory as BedrockPlatformFactory;
16-
use Symfony\AI\Platform\Bridge\Gemini\PlatformFactory as GeminiPlatformFactory;
17-
use Symfony\AI\Platform\Bridge\VertexAi\PlatformFactory as VertexAiPlatformFactory;
14+
use Symfony\AI\Platform\Bridge\Anthropic\Factory as AnthropicFactory;
15+
use Symfony\AI\Platform\Bridge\Bedrock\Factory as BedrockFactory;
16+
use Symfony\AI\Platform\Bridge\Gemini\Factory as GeminiFactory;
17+
use Symfony\AI\Platform\Bridge\VertexAi\Factory as VertexAiFactory;
1818

1919
/**
2020
* Resolves which Symfony AI bridge to use based on the provider's NPM package.
@@ -33,30 +33,30 @@ final class BridgeResolver
3333
*/
3434
private const BRIDGE_MAPPING = [
3535
'@ai-sdk/anthropic' => [
36-
'factory' => AnthropicPlatformFactory::class,
36+
'factory' => AnthropicFactory::class,
3737
'package' => 'symfony/ai-anthropic-platform',
3838
'routable' => true, // Compatible factory signature
3939
'completionsModelClass' => 'Symfony\AI\Platform\Bridge\Anthropic\Claude',
4040
],
4141
'@ai-sdk/google' => [
42-
'factory' => GeminiPlatformFactory::class,
42+
'factory' => GeminiFactory::class,
4343
'package' => 'symfony/ai-gemini-platform',
4444
'routable' => true, // Compatible factory signature
4545
'completionsModelClass' => 'Symfony\AI\Platform\Bridge\Gemini\Gemini',
4646
'embeddingsModelClass' => 'Symfony\AI\Platform\Bridge\Gemini\Embeddings',
4747
],
4848
'@ai-sdk/google-vertex' => [
49-
'factory' => VertexAiPlatformFactory::class,
49+
'factory' => VertexAiFactory::class,
5050
'package' => 'symfony/ai-vertex-ai-platform',
5151
'routable' => false, // Requires location and projectId
5252
],
5353
'@ai-sdk/google-vertex/anthropic' => [
54-
'factory' => VertexAiPlatformFactory::class,
54+
'factory' => VertexAiFactory::class,
5555
'package' => 'symfony/ai-vertex-ai-platform',
5656
'routable' => false, // Requires location and projectId
5757
],
5858
'@ai-sdk/amazon-bedrock' => [
59-
'factory' => BedrockPlatformFactory::class,
59+
'factory' => BedrockFactory::class,
6060
'package' => 'symfony/ai-bedrock-platform',
6161
'routable' => false, // Requires BedrockRuntimeClient
6262
],

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
---------
33

4+
0.8
5+
---
6+
7+
* [BC BREAK] Rename `PlatformFactory` to `Factory` with explicit `createProvider()` and `createPlatform()` methods
8+
49
0.6
510
---
611

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313

1414
use Symfony\AI\Platform\Bridge\Generic\CompletionsModel;
1515
use Symfony\AI\Platform\Bridge\Generic\EmbeddingsModel;
16-
use Symfony\AI\Platform\Bridge\Generic\PlatformFactory as GenericPlatformFactory;
16+
use Symfony\AI\Platform\Bridge\Generic\Factory as GenericFactory;
1717
use Symfony\AI\Platform\Contract;
1818
use Symfony\AI\Platform\Exception\InvalidArgumentException;
19+
use Symfony\AI\Platform\ModelRouter\CatalogBasedModelRouter;
20+
use Symfony\AI\Platform\ModelRouterInterface;
1921
use Symfony\AI\Platform\Platform;
22+
use Symfony\AI\Platform\ProviderInterface;
2023
use Symfony\Component\HttpClient\EventSourceHttpClient;
2124
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
2225
use Symfony\Contracts\HttpClient\HttpClientInterface;
@@ -26,7 +29,7 @@
2629
*
2730
* @author Fabien Potencier <[email protected]>
2831
*/
29-
final class PlatformFactory
32+
final class Factory
3033
{
3134
/**
3235
* Well-known API base URLs for providers whose models.dev entry
@@ -47,15 +50,15 @@ final class PlatformFactory
4750
'@ai-sdk/xai' => 'https://api.x.ai',
4851
];
4952

50-
public static function create(
53+
public static function createProvider(
5154
string $provider,
5255
#[\SensitiveParameter] ?string $apiKey = null,
5356
?string $baseUrl = null,
5457
?string $dataPath = null,
5558
?Contract $contract = null,
5659
?HttpClientInterface $httpClient = null,
5760
?EventDispatcherInterface $eventDispatcher = null,
58-
): Platform {
61+
): ProviderInterface {
5962
$httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
6063

6164
$data = DataLoader::load($dataPath);
@@ -76,7 +79,7 @@ public static function create(
7679
throw new InvalidArgumentException(\sprintf('Provider "%s" requires a specialized bridge (%s); install it with composer require "%s".', $provider, $npmPackage, $package));
7780
}
7881
if (!BridgeResolver::isRoutable($npmPackage)) {
79-
throw new InvalidArgumentException(\sprintf('Provider "%s" requires "%s" which has a different factory signature; use "%s::create()" directly.', $provider, $package, $factoryClass));
82+
throw new InvalidArgumentException(\sprintf('Provider "%s" requires "%s" which has a different factory signature; use "%s::createProvider()" directly.', $provider, $package, $factoryClass));
8083
}
8184

8285
$modelCatalog = new ModelCatalog(
@@ -86,12 +89,13 @@ public static function create(
8689
embeddingsModelClass: BridgeResolver::getEmbeddingsModelClass($npmPackage),
8790
);
8891

89-
return $factoryClass::create(
92+
return $factoryClass::createProvider(
9093
apiKey: $apiKey,
9194
modelCatalog: $modelCatalog,
9295
contract: $contract,
9396
httpClient: $httpClient,
9497
eventDispatcher: $eventDispatcher,
98+
name: $provider,
9599
);
96100
}
97101

@@ -128,7 +132,7 @@ public static function create(
128132
}
129133
}
130134

131-
return GenericPlatformFactory::create(
135+
return GenericFactory::createProvider(
132136
baseUrl: $baseUrl,
133137
apiKey: $apiKey,
134138
modelCatalog: $modelCatalog,
@@ -137,6 +141,24 @@ public static function create(
137141
eventDispatcher: $eventDispatcher,
138142
supportsCompletions: $supportsCompletions,
139143
supportsEmbeddings: $supportsEmbeddings,
144+
name: $provider,
145+
);
146+
}
147+
148+
public static function createPlatform(
149+
string $provider,
150+
#[\SensitiveParameter] ?string $apiKey = null,
151+
?string $baseUrl = null,
152+
?string $dataPath = null,
153+
?Contract $contract = null,
154+
?HttpClientInterface $httpClient = null,
155+
?EventDispatcherInterface $eventDispatcher = null,
156+
?ModelRouterInterface $modelRouter = null,
157+
): Platform {
158+
return new Platform(
159+
[self::createProvider($provider, $apiKey, $baseUrl, $dataPath, $contract, $httpClient, $eventDispatcher)],
160+
$modelRouter ?? new CatalogBasedModelRouter(),
161+
$eventDispatcher,
140162
);
141163
}
142164
}

Tests/BridgeResolverTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
namespace Symfony\AI\Platform\Bridge\ModelsDev\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15-
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory as AnthropicPlatformFactory;
16-
use Symfony\AI\Platform\Bridge\Gemini\PlatformFactory as GeminiPlatformFactory;
15+
use Symfony\AI\Platform\Bridge\Anthropic\Factory as AnthropicFactory;
16+
use Symfony\AI\Platform\Bridge\Gemini\Factory as GeminiFactory;
1717
use Symfony\AI\Platform\Bridge\ModelsDev\BridgeResolver;
1818

1919
/**
@@ -31,8 +31,8 @@ public function testRequiresSpecializedBridge()
3131

3232
public function testGetBridgeFactory()
3333
{
34-
$this->assertSame(AnthropicPlatformFactory::class, BridgeResolver::getBridgeFactory('@ai-sdk/anthropic'));
35-
$this->assertSame(GeminiPlatformFactory::class, BridgeResolver::getBridgeFactory('@ai-sdk/google'));
34+
$this->assertSame(AnthropicFactory::class, BridgeResolver::getBridgeFactory('@ai-sdk/anthropic'));
35+
$this->assertSame(GeminiFactory::class, BridgeResolver::getBridgeFactory('@ai-sdk/google'));
3636
$this->assertNull(BridgeResolver::getBridgeFactory('@ai-sdk/openai'));
3737
}
3838

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,21 @@
1212
namespace Symfony\AI\Platform\Bridge\ModelsDev\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15-
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory as AnthropicPlatformFactory;
16-
use Symfony\AI\Platform\Bridge\Gemini\PlatformFactory as GeminiPlatformFactory;
17-
use Symfony\AI\Platform\Bridge\ModelsDev\PlatformFactory;
18-
use Symfony\AI\Platform\Bridge\VertexAi\PlatformFactory as VertexAiPlatformFactory;
15+
use Symfony\AI\Platform\Bridge\Anthropic\Factory as AnthropicFactory;
16+
use Symfony\AI\Platform\Bridge\Gemini\Factory as GeminiFactory;
17+
use Symfony\AI\Platform\Bridge\ModelsDev\Factory;
18+
use Symfony\AI\Platform\Bridge\VertexAi\Factory as VertexAiFactory;
1919
use Symfony\AI\Platform\Exception\InvalidArgumentException;
2020
use Symfony\AI\Platform\Platform;
2121

2222
/**
2323
* @author Fabien Potencier <[email protected]>
2424
*/
25-
final class PlatformFactoryTest extends TestCase
25+
final class FactoryTest extends TestCase
2626
{
2727
public function testCreateWithProviderThatHasApiBaseUrl()
2828
{
29-
$platform = PlatformFactory::create(
29+
$platform = Factory::createPlatform(
3030
provider: 'deepseek',
3131
apiKey: 'test-key',
3232
);
@@ -36,7 +36,7 @@ public function testCreateWithProviderThatHasApiBaseUrl()
3636

3737
public function testCreateWithExplicitBaseUrl()
3838
{
39-
$platform = PlatformFactory::create(
39+
$platform = Factory::createPlatform(
4040
provider: 'openai',
4141
apiKey: 'test-key',
4242
baseUrl: 'https://api.openai.com/v1',
@@ -47,7 +47,7 @@ public function testCreateWithExplicitBaseUrl()
4747

4848
public function testCreateWithWellKnownNpmPackageProvider()
4949
{
50-
$platform = PlatformFactory::create(
50+
$platform = Factory::createPlatform(
5151
provider: 'openai',
5252
apiKey: 'test-key',
5353
);
@@ -60,15 +60,15 @@ public function testCreateThrowsWhenNoBaseUrlAvailable()
6060
$this->expectException(InvalidArgumentException::class);
6161
$this->expectExceptionMessage('does not have a known API base URL');
6262

63-
PlatformFactory::create(
63+
Factory::createPlatform(
6464
provider: 'azure',
6565
apiKey: 'test-key',
6666
);
6767
}
6868

6969
public function testCreateWithProviderHavingApiUrl()
7070
{
71-
$platform = PlatformFactory::create(
71+
$platform = Factory::createPlatform(
7272
provider: 'deepseek',
7373
apiKey: 'test-key',
7474
);
@@ -79,11 +79,11 @@ public function testCreateWithProviderHavingApiUrl()
7979
public function testProviderWithAnthropicBridgeRoutesAutomatically()
8080
{
8181
// If symfony/ai-anthropic-platform is installed, it should route automatically
82-
if (!class_exists(AnthropicPlatformFactory::class)) {
82+
if (!class_exists(AnthropicFactory::class)) {
8383
$this->markTestSkipped('Anthropic bridge not installed');
8484
}
8585

86-
$platform = PlatformFactory::create(
86+
$platform = Factory::createPlatform(
8787
provider: 'anthropic',
8888
apiKey: 'test-key',
8989
);
@@ -94,11 +94,11 @@ public function testProviderWithAnthropicBridgeRoutesAutomatically()
9494
public function testProviderUsingAnthropicApiRoutesAutomatically()
9595
{
9696
// Providers like minimax use Anthropic's API format
97-
if (!class_exists(AnthropicPlatformFactory::class)) {
97+
if (!class_exists(AnthropicFactory::class)) {
9898
$this->markTestSkipped('Anthropic bridge not installed');
9999
}
100100

101-
$platform = PlatformFactory::create(
101+
$platform = Factory::createPlatform(
102102
provider: 'minimax',
103103
apiKey: 'test-key',
104104
);
@@ -109,11 +109,11 @@ public function testProviderUsingAnthropicApiRoutesAutomatically()
109109
public function testProviderWithGeminiBridgeRoutesAutomatically()
110110
{
111111
// If symfony/ai-gemini-platform is installed, it should route automatically
112-
if (!class_exists(GeminiPlatformFactory::class)) {
112+
if (!class_exists(GeminiFactory::class)) {
113113
$this->markTestSkipped('Gemini bridge not installed');
114114
}
115115

116-
$platform = PlatformFactory::create(
116+
$platform = Factory::createPlatform(
117117
provider: 'google',
118118
apiKey: 'test-key',
119119
);
@@ -129,13 +129,13 @@ public function testProviderWithNonRoutableBridgeShowsHelpfulError()
129129

130130
$this->expectException(InvalidArgumentException::class);
131131

132-
if (class_exists(VertexAiPlatformFactory::class)) {
132+
if (class_exists(VertexAiFactory::class)) {
133133
$this->expectExceptionMessage('Provider "google-vertex" requires "symfony/ai-vertex-ai-platform" which has a different factory signature');
134134
} else {
135135
$this->expectExceptionMessage('Provider "google-vertex" requires a specialized bridge (@ai-sdk/google-vertex); install it with composer require "symfony/ai-vertex-ai-platform".');
136136
}
137137

138-
PlatformFactory::create(
138+
Factory::createPlatform(
139139
provider: 'google-vertex',
140140
apiKey: 'test-key',
141141
);
@@ -144,7 +144,7 @@ public function testProviderWithNonRoutableBridgeShowsHelpfulError()
144144
public function testAutoDetectsCompletionsAndEmbeddingsSupport()
145145
{
146146
// OpenAI has both completions and embeddings models
147-
$platform = PlatformFactory::create(
147+
$platform = Factory::createPlatform(
148148
provider: 'openai',
149149
apiKey: 'test-key',
150150
baseUrl: 'https://api.openai.com/v1',
@@ -153,7 +153,7 @@ public function testAutoDetectsCompletionsAndEmbeddingsSupport()
153153
$this->assertInstanceOf(Platform::class, $platform);
154154

155155
// DeepSeek also has both types
156-
$platform = PlatformFactory::create(
156+
$platform = Factory::createPlatform(
157157
provider: 'deepseek',
158158
apiKey: 'test-key',
159159
);

Tests/ModelCatalogTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public function testUnknownProviderThrowsException()
7777
public function testProvidersWithSpecializedBridgesCanCreateCatalog()
7878
{
7979
// ModelCatalog can be created for all providers, including those with specialized bridges
80-
// The routing to specialized bridges happens in PlatformFactory
80+
// The routing to specialized bridges happens in Factory
8181
$catalog = new ModelCatalog('anthropic');
8282

8383
$this->assertNotEmpty($catalog->getModels());

Tests/ProviderRegistryTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public function testGetProviderIds()
6464
$this->assertContains('mistral', $ids);
6565
$this->assertContains('deepseek', $ids);
6666

67-
// Providers with specialized bridges are also listed (routing handled by PlatformFactory)
67+
// Providers with specialized bridges are also listed (routing handled by Factory)
6868
$this->assertContains('anthropic', $ids);
6969
$this->assertContains('google', $ids);
7070
$this->assertContains('minimax', $ids);

0 commit comments

Comments
 (0)