Skip to content

Conversation

OskarStark
Copy link
Contributor

Q A
License MIT
Doc issue/PR Needs symfony/ai#640

@OskarStark OskarStark self-assigned this Sep 23, 2025
@symfony-recipes-bot symfony-recipes-bot enabled auto-merge (squash) September 23, 2025 13:40
Copy link

github-actions bot commented Sep 23, 2025

Thanks for the PR 😍

How to test these changes in your application

  1. Define the SYMFONY_ENDPOINT environment variable:

    # On Unix-like (BSD, Linux and macOS)
    export SYMFONY_ENDPOINT=https://raw.githubusercontent.com/symfony/recipes/flex/pull-1452/index.json
    # On Windows
    SET SYMFONY_ENDPOINT=https://raw.githubusercontent.com/symfony/recipes/flex/pull-1452/index.json
  2. Install the package(s) related to this recipe:

    composer req symfony/flex
    composer req 'symfony/ai-bundle:^0.1'
  3. Don't forget to unset the SYMFONY_ENDPOINT environment variable when done:

    # On Unix-like (BSD, Linux and macOS)
    unset SYMFONY_ENDPOINT
    # On Windows
    SET SYMFONY_ENDPOINT=

Diff between recipe versions

In order to help with the review stage, I'm in charge of computing the diff between the various versions of patched recipes.
I'm going keep this comment up to date with any updates of the attached patch.

chr-hertel added a commit to symfony/ai that referenced this pull request Sep 28, 2025
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`
@OskarStark
Copy link
Contributor Author

symfony/ai#640 is now merged.

@symfony-recipes-bot symfony-recipes-bot merged commit 52fd1d8 into symfony:main Sep 29, 2025
1 of 2 checks passed
devmatt000 pushed a commit to devmatt000/dev_ai that referenced this pull request Oct 2, 2025
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
-------

4f063776 [AI Bundle][Platform] Add `ModelCatalog`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants