Skip to content

Commit a745742

Browse files
committed
feature #640 [AI Bundle][Platform] Add ModelCatalog (OskarStark)
This PR was squashed before being merged into the main branch. Discussion ---------- [AI Bundle][Platform] Add `ModelCatalog` | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Docs? | yes | Issues | -- | License | MIT Recipe-PR: * symfony/recipes#1452 # ModelCatalog Architecture Refactoring ### BC Breaks * ⚠️ All model constants removed * ⚠️ Child model class constructors were changed and now have an `$capabilities` parameter * ⚠️ `PlatformFactory` constructor changed for all bridges to be able to receive a `ModelCatalogInterface` * ⚠️ `PlatformInterface` now has an additional `getModelCatalog` method * ⚠️ `PlatformInterface::__invoke` no receives an model as string (before `Model`) * ⚠️ `Vectorizer::__construct` no receives an model as string (before `Model`) * ⚠️ `Agent::__construct` no receives an model as string (before `Model`) ## Overview This PR refactors how models are defined and managed in the Symfony AI Platform, moving from constants in Model classes to centralized ModelCatalogs. ## Before (Old Configuration) ### Defining Models Models were defined as constants in Model classes: ```php // src/platform/src/Bridge/OpenAi/Gpt.php class Gpt extends Model { public const GPT_4O = 'gpt-4o'; public const GPT_4O_MINI = 'gpt-4o-mini'; // ... more constants public function __construct(string $name = self::GPT_4O, array $options = []) { $capabilities = [ Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, // ... capabilities determined in constructor ]; if (self::GPT_4O_AUDIO === $name) { $capabilities[] = Capability::INPUT_AUDIO; } // ... more logic parent::__construct($name, $capabilities, $options); } } ``` ### Using Models ```php use Symfony\AI\Platform\Bridge\OpenAi\Gpt; use Symfony\AI\Platform\Bridge\Voyage\Voyage; // Had to create Model objects $model = new Gpt('gpt-4o-mini'); $result = $platform->invoke($model, $input); // Or with constants $embeddings = new Voyage(Voyage::V3); $result = $platform->invoke($embeddings, $text); ``` ### Adding Custom Models There was no standardized way to add custom models. You had to: - Either extend a Model class and override the constructor - Or create a Model instance with hardcoded capabilities ## After (New Configuration) ### Defining Models Models are now defined in centralized ModelCatalog classes: ```php // src/platform/src/Bridge/OpenAi/ModelCatalog.php final class ModelCatalog extends AbstractModelCatalog { public function __construct(array $additionalModels = []) { $defaultModels = [ 'gpt-4o' => [ 'class' => Gpt::class, 'capabilities' => [ Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STREAMING, Capability::TOOL_CALLING, Capability::INPUT_IMAGE, Capability::OUTPUT_STRUCTURED, ], ], 'gpt-4o-mini' => [ 'class' => Gpt::class, 'capabilities' => [ Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STREAMING, Capability::TOOL_CALLING, Capability::INPUT_IMAGE, Capability::OUTPUT_STRUCTURED, ], ], // ... more models ]; $this->models = array_merge($defaultModels, $additionalModels); } } ``` ### Using Models ```php // Just pass model name as string - much simpler! $result = $platform->invoke('gpt-4o-mini', $input); // Works with any platform $result = $platform->invoke('voyage-3', $text); ``` ### Adding Custom Models #### Option 1: Via ModelCatalog Constructor ```php use Symfony\AI\Platform\Bridge\OpenAi\ModelCatalog; use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory; $customModels = [ 'custom-gpt-4' => [ 'class' => Gpt::class, 'capabilities' => [ Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STREAMING, Capability::TOOL_CALLING, Capability::OUTPUT_STRUCTURED, ], ], ]; $modelCatalog = new ModelCatalog($customModels); $platform = new Platform($modelClients, $resultConverters, $modelCatalog, $contract); // Use the custom model $result = $platform->invoke('custom-gpt-4', $input); ``` #### Option 2: Via Symfony Configuration (if using AI Bundle) ```yaml # config/packages/ai.yaml ai: platform: openai: models: custom-gpt-4: class: OpenAi\Gpt capabilities: - input_messages - output_text - output_streaming - tool_calling - output_structured ``` ## Benefits 1. **Cleaner API**: Use simple strings instead of Model objects 2. **Centralized Model Management**: All models defined in one place per platform 3. **Easier Custom Models**: Add custom models via constructor or configuration 4. **Better Testing**: Standardized ModelCatalogTestCase for all implementations 5. **No More Constants**: Model classes are simpler, no need for constants 6. **Flexible Capabilities**: Capabilities defined per model, not in constructor logic ## Migration Guide ### For Application Code ```diff - $result = $platform->invoke(new Gpt(Gpt::GPT_4O), $input); + $result = $platform->invoke('gpt-4o', $input); ``` ### For Custom Platforms ```php // Before: Had to extend Model class or handle in constructor class MyModel extends Model { public function __construct() { // Complex capability logic } } // After: Just add to ModelCatalog $catalog = new ModelCatalog([ 'my-model' => [ 'class' => MyModel::class, 'capabilities' => [...], ], ]); ``` ## Special Cases ### HuggingFace HuggingFace ModelCatalog accepts any model name since they support thousands of models: ```php final class ModelCatalog extends DynamicModelCatalog { // HuggingFace supports a wide range of models dynamically // Models are identified by repository/model format (e.g., "microsoft/DialoGPT-medium") } ``` ## Testing All ModelCatalogs can now be tested using the base `ModelCatalogTestCase`: ```php final class MyModelCatalogTest extends ModelCatalogTestCase { protected function createCatalog(): ModelCatalogInterface { return new MyModelCatalog(); } public static function modelsProvider(): iterable { yield 'model-name' => [ 'model-name', ModelClass::class, [Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT], ]; } } ``` Commits ------- 4f06377 [AI Bundle][Platform] Add `ModelCatalog`
2 parents 897085e + 4f06377 commit a745742

File tree

336 files changed

+5975
-2099
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

336 files changed

+5975
-2099
lines changed

demo/config/packages/ai.yaml

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,27 @@ ai:
44
api_key: '%env(OPENAI_API_KEY)%'
55
agent:
66
blog:
7-
model:
8-
class: 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'
9-
name: !php/const Symfony\AI\Platform\Bridge\OpenAi\Gpt::GPT_4O_MINI
7+
model: 'gpt-4o-mini'
108
tools:
119
- 'Symfony\AI\Agent\Toolbox\Tool\SimilaritySearch'
1210
- service: 'clock'
1311
name: 'clock'
1412
description: 'Provides the current date and time.'
1513
method: 'now'
1614
stream:
17-
model:
18-
class: 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'
19-
name: !php/const Symfony\AI\Platform\Bridge\OpenAi\Gpt::GPT_4O_MINI
15+
model: 'gpt-4o-mini'
2016
prompt: |
2117
You are an example chat application where messages from the LLM are streamed to the user using
2218
Server-Sent Events via `symfony/ux-turbo` / Turbo Streams. This example does not use any custom
2319
javascript and solely relies on the built-in `live` & `turbo_stream` Stimulus controllers.
2420
Whatever the user asks, tell them about the application & used technologies.
2521
tools: false
2622
youtube:
27-
model:
28-
class: 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'
29-
name: !php/const Symfony\AI\Platform\Bridge\OpenAi\Gpt::GPT_4O_MINI
23+
model: 'gpt-4o-mini'
3024
tools: false
3125
wikipedia:
3226
model:
33-
class: 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'
34-
name: !php/const Symfony\AI\Platform\Bridge\OpenAi\Gpt::GPT_4O_MINI
27+
name: 'gpt-4o-mini'
3528
options:
3629
temperature: 0.5
3730
prompt:
@@ -40,9 +33,7 @@ ai:
4033
tools:
4134
- 'Symfony\AI\Agent\Toolbox\Tool\Wikipedia'
4235
audio:
43-
model:
44-
class: 'Symfony\AI\Platform\Bridge\OpenAi\Gpt'
45-
name: 'gpt-4o-mini?temperature=1.0'
36+
model: 'gpt-4o-mini?temperature=1.0'
4637
prompt: 'You are a friendly chatbot that likes to have a conversation with users and asks them some questions.'
4738
tools:
4839
# Agent in agent 🤯
@@ -55,9 +46,7 @@ ai:
5546
collection: 'symfony_blog'
5647
vectorizer:
5748
openai:
58-
model:
59-
class: 'Symfony\AI\Platform\Bridge\OpenAi\Embeddings'
60-
name: !php/const Symfony\AI\Platform\Bridge\OpenAi\Embeddings::TEXT_ADA_002
49+
model: 'text-embedding-ada-002'
6150
indexer:
6251
blog:
6352
loader: 'Symfony\AI\Store\Document\Loader\RssFeedLoader'

examples/aimlapi/chat.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,19 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
use Symfony\AI\Platform\Bridge\AiMlApi\Completions;
1312
use Symfony\AI\Platform\Bridge\AiMlApi\PlatformFactory;
1413
use Symfony\AI\Platform\Message\Message;
1514
use Symfony\AI\Platform\Message\MessageBag;
1615

1716
require_once dirname(__DIR__).'/bootstrap.php';
1817

1918
$platform = PlatformFactory::create(env('AIMLAPI_API_KEY'), http_client());
20-
$model = new Completions(Completions::GEMINI_2_0_FLASH);
2119

2220
$messages = new MessageBag(
2321
Message::forSystem('You are a pirate and you write funny.'),
2422
Message::ofUser('What is the Symfony framework?'),
2523
);
26-
$result = $platform->invoke($model, $messages, [
24+
$result = $platform->invoke('gemini-2.0-flash', $messages, [
2725
'max_tokens' => 500, // specific options just for this call
2826
]);
2927

examples/aimlapi/image-input-binary.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,14 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
use Symfony\AI\Platform\Bridge\AiMlApi\Completions;
1312
use Symfony\AI\Platform\Bridge\AiMlApi\PlatformFactory;
14-
use Symfony\AI\Platform\Capability;
1513
use Symfony\AI\Platform\Message\Content\Image;
1614
use Symfony\AI\Platform\Message\Message;
1715
use Symfony\AI\Platform\Message\MessageBag;
1816

1917
require_once dirname(__DIR__).'/bootstrap.php';
2018

2119
$platform = PlatformFactory::create(env('AIMLAPI_API_KEY'), http_client());
22-
$model = new Completions(
23-
name: Completions::GOOGLE_GEMMA_3_27B_IT,
24-
capabilities: [...Completions::DEFAULT_CAPABILITIES, Capability::INPUT_IMAGE]
25-
);
2620

2721
$messages = new MessageBag(
2822
Message::forSystem('You are an image analyzer bot that helps identify the content of images.'),
@@ -31,6 +25,6 @@
3125
Image::fromFile(dirname(__DIR__, 2).'/fixtures/image.jpg'),
3226
),
3327
);
34-
$result = $platform->invoke($model, $messages);
28+
$result = $platform->invoke('google/gemma-3-27b-it', $messages);
3529

3630
echo $result->getResult()->getContent().\PHP_EOL;

examples/aimlapi/toolcall.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,18 @@
1313
use Symfony\AI\Agent\Toolbox\AgentProcessor;
1414
use Symfony\AI\Agent\Toolbox\Tool\Wikipedia;
1515
use Symfony\AI\Agent\Toolbox\Toolbox;
16-
use Symfony\AI\Platform\Bridge\AiMlApi\Completions;
1716
use Symfony\AI\Platform\Bridge\AiMlApi\PlatformFactory;
18-
use Symfony\AI\Platform\Capability;
1917
use Symfony\AI\Platform\Message\Message;
2018
use Symfony\AI\Platform\Message\MessageBag;
2119

2220
require_once dirname(__DIR__).'/bootstrap.php';
2321

2422
$platform = PlatformFactory::create(env('AIMLAPI_API_KEY'), http_client());
25-
$model = new Completions(
26-
name: Completions::GOOGLE_GEMINI_2_5_FLASH,
27-
capabilities: [...Completions::DEFAULT_CAPABILITIES, Capability::TOOL_CALLING]
28-
);
2923

3024
$wikipedia = new Wikipedia(http_client());
3125
$toolbox = new Toolbox([$wikipedia], logger: logger());
3226
$processor = new AgentProcessor($toolbox);
33-
$agent = new Agent($platform, $model, [$processor], [$processor], logger: logger());
27+
$agent = new Agent($platform, 'google/gemini-2.5-flash', [$processor], [$processor], logger: logger());
3428

3529
$messages = new MessageBag(Message::ofUser('Who is the current chancellor of Germany?'));
3630
$result = $agent->call($messages);

examples/aimlapi/vectorizing.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,14 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
use Symfony\AI\Platform\Bridge\AiMlApi\Embeddings;
1312
use Symfony\AI\Platform\Bridge\AiMlApi\PlatformFactory;
1413
use Symfony\AI\Store\Document\Vectorizer;
1514

1615
require_once dirname(__DIR__).'/bootstrap.php';
1716

1817
$platform = PlatformFactory::create(env('AIMLAPI_API_KEY'), http_client());
19-
$embeddings = new Embeddings(
20-
name: Embeddings::TEXT_EMBEDDING_3_SMALL
21-
);
2218

23-
$vectorizer = new Vectorizer($platform, $embeddings);
19+
$vectorizer = new Vectorizer($platform, 'text-embedding-3-small');
2420

2521
$string = 'Hello World';
2622
$vector = $vectorizer->vectorize($string);

examples/albert/chat.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,13 @@
1010
*/
1111

1212
use Symfony\AI\Platform\Bridge\Albert\PlatformFactory;
13-
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
1413
use Symfony\AI\Platform\Message\Message;
1514
use Symfony\AI\Platform\Message\MessageBag;
1615

1716
require_once dirname(__DIR__).'/bootstrap.php';
1817

1918
$platform = PlatformFactory::create(env('ALBERT_API_KEY'), env('ALBERT_API_URL'), http_client());
2019

21-
$model = new Gpt('gpt-4o');
22-
2320
$documentContext = <<<'CONTEXT'
2421
Document: AI Strategy of France
2522
@@ -42,6 +39,6 @@
4239
Message::ofUser('What are the main objectives of France\'s AI strategy?'),
4340
);
4441

45-
$result = $platform->invoke($model, $messages);
42+
$result = $platform->invoke('llama-3.3-70b-instruct', $messages);
4643

4744
echo $result->getResult()->getContent().\PHP_EOL;

examples/anthropic/chat.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,18 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
use Symfony\AI\Platform\Bridge\Anthropic\Claude;
1312
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory;
1413
use Symfony\AI\Platform\Message\Message;
1514
use Symfony\AI\Platform\Message\MessageBag;
1615

1716
require_once dirname(__DIR__).'/bootstrap.php';
1817

1918
$platform = PlatformFactory::create(env('ANTHROPIC_API_KEY'), httpClient: http_client());
20-
$model = new Claude(Claude::SONNET_37);
2119

2220
$messages = new MessageBag(
2321
Message::forSystem('You are a pirate and you write funny.'),
2422
Message::ofUser('What is the Symfony framework?'),
2523
);
26-
$result = $platform->invoke($model, $messages);
24+
$result = $platform->invoke('claude-3-5-sonnet-20241022', $messages);
2725

2826
echo $result->getResult()->getContent().\PHP_EOL;

examples/anthropic/image-input-binary.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
use Symfony\AI\Platform\Bridge\Anthropic\Claude;
1312
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory;
1413
use Symfony\AI\Platform\Message\Content\Image;
1514
use Symfony\AI\Platform\Message\Message;
@@ -18,7 +17,6 @@
1817
require_once dirname(__DIR__).'/bootstrap.php';
1918

2019
$platform = PlatformFactory::create(env('ANTHROPIC_API_KEY'), httpClient: http_client());
21-
$model = new Claude(Claude::SONNET_37);
2220

2321
$messages = new MessageBag(
2422
Message::forSystem('You are an image analyzer bot that helps identify the content of images.'),
@@ -27,6 +25,6 @@
2725
'Describe this image.',
2826
),
2927
);
30-
$result = $platform->invoke($model, $messages);
28+
$result = $platform->invoke('claude-3-5-sonnet-20241022', $messages);
3129

3230
echo $result->getResult()->getContent().\PHP_EOL;

examples/anthropic/image-input-url.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
use Symfony\AI\Platform\Bridge\Anthropic\Claude;
1312
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory;
1413
use Symfony\AI\Platform\Message\Content\ImageUrl;
1514
use Symfony\AI\Platform\Message\Message;
@@ -18,7 +17,6 @@
1817
require_once dirname(__DIR__).'/bootstrap.php';
1918

2019
$platform = PlatformFactory::create(env('ANTHROPIC_API_KEY'), httpClient: http_client());
21-
$model = new Claude(Claude::SONNET_37);
2220

2321
$messages = new MessageBag(
2422
Message::forSystem('You are an image analyzer bot that helps identify the content of images.'),
@@ -27,6 +25,6 @@
2725
'Describe this image.',
2826
),
2927
);
30-
$result = $platform->invoke($model, $messages);
28+
$result = $platform->invoke('claude-3-5-sonnet-20241022', $messages);
3129

3230
echo $result->getResult()->getContent().\PHP_EOL;

examples/anthropic/pdf-input-binary.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
use Symfony\AI\Platform\Bridge\Anthropic\Claude;
1312
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory;
1413
use Symfony\AI\Platform\Message\Content\Document;
1514
use Symfony\AI\Platform\Message\Message;
@@ -18,14 +17,13 @@
1817
require_once dirname(__DIR__).'/bootstrap.php';
1918

2019
$platform = PlatformFactory::create(env('ANTHROPIC_API_KEY'), httpClient: http_client());
21-
$model = new Claude(Claude::SONNET_37);
2220

2321
$messages = new MessageBag(
2422
Message::ofUser(
2523
Document::fromFile(dirname(__DIR__, 2).'/fixtures/document.pdf'),
2624
'What is this document about?',
2725
),
2826
);
29-
$result = $platform->invoke($model, $messages);
27+
$result = $platform->invoke('claude-3-5-sonnet-20241022', $messages);
3028

3129
echo $result->getResult()->getContent().\PHP_EOL;

0 commit comments

Comments
 (0)