From d238fb2a31f5a00cd1d4b348557aa532b536ffd8 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Sun, 28 Sep 2025 23:13:57 +0200 Subject: [PATCH 1/4] Refactor OpenAI model catalog to use external models.php file - Extract model definitions to src/Bridge/OpenAi/Resources/models.php - Update ModelCatalog to load models from external file at runtime - Improve maintainability and organization - All tests passing (28 ModelCatalog tests, 131 OpenAI bridge tests) --- .../src/Bridge/OpenAi/ModelCatalog.php | 233 +--------------- .../src/Bridge/OpenAi/Resources/models.php | 254 ++++++++++++++++++ 2 files changed, 255 insertions(+), 232 deletions(-) create mode 100644 src/platform/src/Bridge/OpenAi/Resources/models.php diff --git a/src/platform/src/Bridge/OpenAi/ModelCatalog.php b/src/platform/src/Bridge/OpenAi/ModelCatalog.php index 0fae198cf..885c4cd77 100644 --- a/src/platform/src/Bridge/OpenAi/ModelCatalog.php +++ b/src/platform/src/Bridge/OpenAi/ModelCatalog.php @@ -24,238 +24,7 @@ final class ModelCatalog extends AbstractModelCatalog */ public function __construct(array $additionalModels = []) { - $defaultModels = [ - 'gpt-3.5-turbo' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - ], - ], - 'gpt-3.5-turbo-instruct' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - ], - ], - 'gpt-4' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - ], - ], - 'gpt-4-turbo' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - Capability::INPUT_IMAGE, - ], - ], - '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, - ], - ], - 'gpt-4o-audio-preview' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - Capability::INPUT_AUDIO, - Capability::INPUT_IMAGE, - Capability::OUTPUT_STRUCTURED, - ], - ], - 'o1-mini' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - Capability::INPUT_IMAGE, - ], - ], - 'o1-preview' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - Capability::INPUT_IMAGE, - ], - ], - 'o3-mini' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - Capability::INPUT_IMAGE, - Capability::OUTPUT_STRUCTURED, - ], - ], - 'o3-mini-high' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - ], - ], - 'gpt-4.5-preview' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - Capability::INPUT_IMAGE, - Capability::OUTPUT_STRUCTURED, - ], - ], - 'gpt-4.1' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - Capability::INPUT_IMAGE, - Capability::OUTPUT_STRUCTURED, - ], - ], - 'gpt-4.1-mini' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - Capability::INPUT_IMAGE, - Capability::OUTPUT_STRUCTURED, - ], - ], - 'gpt-4.1-nano' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - Capability::INPUT_IMAGE, - Capability::OUTPUT_STRUCTURED, - ], - ], - 'gpt-5' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - Capability::INPUT_IMAGE, - Capability::OUTPUT_STRUCTURED, - ], - ], - 'gpt-5-chat-latest' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::INPUT_IMAGE, - ], - ], - 'gpt-5-mini' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - Capability::INPUT_IMAGE, - Capability::OUTPUT_STRUCTURED, - ], - ], - 'gpt-5-nano' => [ - 'class' => Gpt::class, - 'capabilities' => [ - Capability::INPUT_MESSAGES, - Capability::OUTPUT_TEXT, - Capability::OUTPUT_STREAMING, - Capability::TOOL_CALLING, - Capability::INPUT_IMAGE, - Capability::OUTPUT_STRUCTURED, - ], - ], - 'text-embedding-ada-002' => [ - 'class' => Embeddings::class, - 'capabilities' => [Capability::INPUT_TEXT], - ], - 'text-embedding-3-large' => [ - 'class' => Embeddings::class, - 'capabilities' => [Capability::INPUT_TEXT], - ], - 'text-embedding-3-small' => [ - 'class' => Embeddings::class, - 'capabilities' => [Capability::INPUT_TEXT], - ], - 'whisper-1' => [ - 'class' => Whisper::class, - 'capabilities' => [ - Capability::INPUT_AUDIO, - Capability::OUTPUT_TEXT, - ], - ], - 'dall-e-2' => [ - 'class' => DallE::class, - 'capabilities' => [ - Capability::INPUT_TEXT, - Capability::OUTPUT_IMAGE, - ], - ], - 'dall-e-3' => [ - 'class' => DallE::class, - 'capabilities' => [ - Capability::INPUT_TEXT, - Capability::OUTPUT_IMAGE, - ], - ], - ]; + $defaultModels = require __DIR__.'/Resources/models.php'; $this->models = array_merge($defaultModels, $additionalModels); } diff --git a/src/platform/src/Bridge/OpenAi/Resources/models.php b/src/platform/src/Bridge/OpenAi/Resources/models.php new file mode 100644 index 000000000..5a22b61c1 --- /dev/null +++ b/src/platform/src/Bridge/OpenAi/Resources/models.php @@ -0,0 +1,254 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\AI\Platform\Bridge\OpenAi\DallE; +use Symfony\AI\Platform\Bridge\OpenAi\Embeddings; +use Symfony\AI\Platform\Bridge\OpenAi\Gpt; +use Symfony\AI\Platform\Bridge\OpenAi\Whisper; +use Symfony\AI\Platform\Capability; + +/** + * OpenAI Model Definitions + * + * @return array}> + */ +return [ + 'gpt-3.5-turbo' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + ], + ], + 'gpt-3.5-turbo-instruct' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + ], + ], + 'gpt-4' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + ], + ], + 'gpt-4-turbo' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + Capability::INPUT_IMAGE, + ], + ], + '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, + ], + ], + 'gpt-4o-audio-preview' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + Capability::INPUT_AUDIO, + Capability::INPUT_IMAGE, + Capability::OUTPUT_STRUCTURED, + ], + ], + 'o1-mini' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + Capability::INPUT_IMAGE, + ], + ], + 'o1-preview' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + Capability::INPUT_IMAGE, + ], + ], + 'o3-mini' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + Capability::INPUT_IMAGE, + Capability::OUTPUT_STRUCTURED, + ], + ], + 'o3-mini-high' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + ], + ], + 'gpt-4.5-preview' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + Capability::INPUT_IMAGE, + Capability::OUTPUT_STRUCTURED, + ], + ], + 'gpt-4.1' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + Capability::INPUT_IMAGE, + Capability::OUTPUT_STRUCTURED, + ], + ], + 'gpt-4.1-mini' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + Capability::INPUT_IMAGE, + Capability::OUTPUT_STRUCTURED, + ], + ], + 'gpt-4.1-nano' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + Capability::INPUT_IMAGE, + Capability::OUTPUT_STRUCTURED, + ], + ], + 'gpt-5' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + Capability::INPUT_IMAGE, + Capability::OUTPUT_STRUCTURED, + ], + ], + 'gpt-5-chat-latest' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::INPUT_IMAGE, + ], + ], + 'gpt-5-mini' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + Capability::INPUT_IMAGE, + Capability::OUTPUT_STRUCTURED, + ], + ], + 'gpt-5-nano' => [ + 'class' => Gpt::class, + 'capabilities' => [ + Capability::INPUT_MESSAGES, + Capability::OUTPUT_TEXT, + Capability::OUTPUT_STREAMING, + Capability::TOOL_CALLING, + Capability::INPUT_IMAGE, + Capability::OUTPUT_STRUCTURED, + ], + ], + 'text-embedding-ada-002' => [ + 'class' => Embeddings::class, + 'capabilities' => [Capability::INPUT_TEXT], + ], + 'text-embedding-3-large' => [ + 'class' => Embeddings::class, + 'capabilities' => [Capability::INPUT_TEXT], + ], + 'text-embedding-3-small' => [ + 'class' => Embeddings::class, + 'capabilities' => [Capability::INPUT_TEXT], + ], + 'whisper-1' => [ + 'class' => Whisper::class, + 'capabilities' => [ + Capability::INPUT_AUDIO, + Capability::OUTPUT_TEXT, + ], + ], + 'dall-e-2' => [ + 'class' => DallE::class, + 'capabilities' => [ + Capability::INPUT_TEXT, + Capability::OUTPUT_IMAGE, + ], + ], + 'dall-e-3' => [ + 'class' => DallE::class, + 'capabilities' => [ + Capability::INPUT_TEXT, + Capability::OUTPUT_IMAGE, + ], + ], +]; From 568688761653b3d69d24c4e25b138501e7c0907c Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Thu, 2 Oct 2025 16:01:15 +0200 Subject: [PATCH 2/4] Add JSON schema and enhanced documentation for OpenAI models - Add models.schema.json with complete JSON Schema definition - Enhance PHPDoc with detailed capability documentation - Reference schema file for IDE support and validation - All model classes and capabilities documented inline --- .../src/Bridge/OpenAi/Resources/models.php | 28 ++++++- .../OpenAi/Resources/models.schema.json | 77 +++++++++++++++++++ 2 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 src/platform/src/Bridge/OpenAi/Resources/models.schema.json diff --git a/src/platform/src/Bridge/OpenAi/Resources/models.php b/src/platform/src/Bridge/OpenAi/Resources/models.php index 5a22b61c1..b512cb85a 100644 --- a/src/platform/src/Bridge/OpenAi/Resources/models.php +++ b/src/platform/src/Bridge/OpenAi/Resources/models.php @@ -17,8 +17,32 @@ /** * OpenAI Model Definitions - * - * @return array}> + * + * This file returns an array of model configurations for OpenAI models. + * Each model is keyed by its identifier and contains: + * - class: The fully qualified class name handling this model type + * - capabilities: An array of Capability enum values defining what the model can do + * + * Available model classes: + * - Gpt::class: For chat/completion models (GPT-3.5, GPT-4, GPT-5, O1, O3) + * - Embeddings::class: For embedding models + * - Whisper::class: For speech-to-text models + * - DallE::class: For image generation models + * + * Available capabilities: + * - Capability::INPUT_MESSAGES: Can process message input + * - Capability::INPUT_TEXT: Can process text input + * - Capability::INPUT_IMAGE: Can process image input + * - Capability::INPUT_AUDIO: Can process audio input + * - Capability::OUTPUT_TEXT: Can generate text output + * - Capability::OUTPUT_IMAGE: Can generate image output + * - Capability::OUTPUT_STREAMING: Supports streaming responses + * - Capability::OUTPUT_STRUCTURED: Supports structured output + * - Capability::TOOL_CALLING: Supports function/tool calling + * + * @see models.schema.json JSON Schema for validation and IDE support + * + * @return array}> */ return [ 'gpt-3.5-turbo' => [ diff --git a/src/platform/src/Bridge/OpenAi/Resources/models.schema.json b/src/platform/src/Bridge/OpenAi/Resources/models.schema.json new file mode 100644 index 000000000..1a921cd8c --- /dev/null +++ b/src/platform/src/Bridge/OpenAi/Resources/models.schema.json @@ -0,0 +1,77 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://symfony.com/schema/ai/openai-models.json", + "title": "OpenAI Model Catalog Schema", + "description": "JSON Schema for OpenAI model definitions in models.php", + "type": "object", + "patternProperties": { + "^[a-z0-9-]+$": { + "$ref": "#/definitions/model" + } + }, + "additionalProperties": false, + "definitions": { + "model": { + "type": "object", + "required": ["class", "capabilities"], + "properties": { + "class": { + "type": "string", + "enum": [ + "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Gpt", + "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Embeddings", + "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Whisper", + "Symfony\\AI\\Platform\\Bridge\\OpenAi\\DallE" + ], + "description": "The fully qualified class name that handles this model type" + }, + "capabilities": { + "type": "array", + "items": { + "$ref": "#/definitions/capability" + }, + "minItems": 1, + "uniqueItems": true, + "description": "Array of capabilities supported by this model" + } + }, + "additionalProperties": false + }, + "capability": { + "type": "string", + "enum": [ + "INPUT_MESSAGES", + "INPUT_TEXT", + "INPUT_IMAGE", + "INPUT_AUDIO", + "OUTPUT_TEXT", + "OUTPUT_IMAGE", + "OUTPUT_STREAMING", + "OUTPUT_STRUCTURED", + "TOOL_CALLING" + ], + "description": "A capability that the model supports" + } + }, + "examples": [ + { + "gpt-4o": { + "class": "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Gpt", + "capabilities": [ + "INPUT_MESSAGES", + "OUTPUT_TEXT", + "OUTPUT_STREAMING", + "TOOL_CALLING", + "INPUT_IMAGE", + "OUTPUT_STRUCTURED" + ] + }, + "text-embedding-3-large": { + "class": "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Embeddings", + "capabilities": [ + "INPUT_TEXT" + ] + } + } + ] +} From c99626c38416b0c8b410f639c013a395a79e40e4 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Thu, 2 Oct 2025 16:03:49 +0200 Subject: [PATCH 3/4] Add Model constants and PHPStan types for IDE autocompletion - Create Model class with constants for all OpenAI model identifiers - Add @phpstan-type OpenAiModelName with literal union types - Override getModel() with detailed documentation - Add tests to verify constants match catalog models - Enable full IDE autocompletion for model names in PhpStorm --- src/platform/src/Bridge/OpenAi/Model.php | 102 ++++++++++++++++++ .../src/Bridge/OpenAi/ModelCatalog.php | 16 +++ .../tests/Bridge/OpenAi/ModelTest.php | 49 +++++++++ 3 files changed, 167 insertions(+) create mode 100644 src/platform/src/Bridge/OpenAi/Model.php create mode 100644 src/platform/tests/Bridge/OpenAi/ModelTest.php diff --git a/src/platform/src/Bridge/OpenAi/Model.php b/src/platform/src/Bridge/OpenAi/Model.php new file mode 100644 index 000000000..c6c965a2f --- /dev/null +++ b/src/platform/src/Bridge/OpenAi/Model.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Bridge\OpenAi; + +/** + * OpenAI model identifiers. + * + * These constants provide IDE autocompletion and type safety when working with OpenAI models. + * Use these with ModelCatalog::getModel() for better developer experience. + * + * @author Oskar Stark + */ +final class Model +{ + // GPT-3.5 Models + public const GPT_3_5_TURBO = 'gpt-3.5-turbo'; + public const GPT_3_5_TURBO_INSTRUCT = 'gpt-3.5-turbo-instruct'; + + // GPT-4 Models + public const GPT_4 = 'gpt-4'; + public const GPT_4_TURBO = 'gpt-4-turbo'; + public const GPT_4O = 'gpt-4o'; + public const GPT_4O_MINI = 'gpt-4o-mini'; + public const GPT_4O_AUDIO_PREVIEW = 'gpt-4o-audio-preview'; + + // O1 Reasoning Models + public const O1_MINI = 'o1-mini'; + public const O1_PREVIEW = 'o1-preview'; + + // O3 Reasoning Models + public const O3_MINI = 'o3-mini'; + public const O3_MINI_HIGH = 'o3-mini-high'; + + // GPT-4.5 Preview Models + public const GPT_4_5_PREVIEW = 'gpt-4.5-preview'; + + // GPT-4.1 Models + public const GPT_4_1 = 'gpt-4.1'; + public const GPT_4_1_MINI = 'gpt-4.1-mini'; + public const GPT_4_1_NANO = 'gpt-4.1-nano'; + + // GPT-5 Models + public const GPT_5 = 'gpt-5'; + public const GPT_5_CHAT_LATEST = 'gpt-5-chat-latest'; + public const GPT_5_MINI = 'gpt-5-mini'; + public const GPT_5_NANO = 'gpt-5-nano'; + + // Embedding Models + public const TEXT_EMBEDDING_ADA_002 = 'text-embedding-ada-002'; + public const TEXT_EMBEDDING_3_LARGE = 'text-embedding-3-large'; + public const TEXT_EMBEDDING_3_SMALL = 'text-embedding-3-small'; + + // Audio Models + public const WHISPER_1 = 'whisper-1'; + + // Image Generation Models + public const DALL_E_2 = 'dall-e-2'; + public const DALL_E_3 = 'dall-e-3'; + + /** + * @return list + */ + public static function all(): array + { + return [ + self::GPT_3_5_TURBO, + self::GPT_3_5_TURBO_INSTRUCT, + self::GPT_4, + self::GPT_4_TURBO, + self::GPT_4O, + self::GPT_4O_MINI, + self::GPT_4O_AUDIO_PREVIEW, + self::O1_MINI, + self::O1_PREVIEW, + self::O3_MINI, + self::O3_MINI_HIGH, + self::GPT_4_5_PREVIEW, + self::GPT_4_1, + self::GPT_4_1_MINI, + self::GPT_4_1_NANO, + self::GPT_5, + self::GPT_5_CHAT_LATEST, + self::GPT_5_MINI, + self::GPT_5_NANO, + self::TEXT_EMBEDDING_ADA_002, + self::TEXT_EMBEDDING_3_LARGE, + self::TEXT_EMBEDDING_3_SMALL, + self::WHISPER_1, + self::DALL_E_2, + self::DALL_E_3, + ]; + } +} diff --git a/src/platform/src/Bridge/OpenAi/ModelCatalog.php b/src/platform/src/Bridge/OpenAi/ModelCatalog.php index 885c4cd77..559784f85 100644 --- a/src/platform/src/Bridge/OpenAi/ModelCatalog.php +++ b/src/platform/src/Bridge/OpenAi/ModelCatalog.php @@ -16,6 +16,8 @@ /** * @author Oskar Stark + * + * @phpstan-type OpenAiModelName 'gpt-3.5-turbo'|'gpt-3.5-turbo-instruct'|'gpt-4'|'gpt-4-turbo'|'gpt-4o'|'gpt-4o-mini'|'gpt-4o-audio-preview'|'o1-mini'|'o1-preview'|'o3-mini'|'o3-mini-high'|'gpt-4.5-preview'|'gpt-4.1'|'gpt-4.1-mini'|'gpt-4.1-nano'|'gpt-5'|'gpt-5-chat-latest'|'gpt-5-mini'|'gpt-5-nano'|'text-embedding-ada-002'|'text-embedding-3-large'|'text-embedding-3-small'|'whisper-1'|'dall-e-2'|'dall-e-3' */ final class ModelCatalog extends AbstractModelCatalog { @@ -28,4 +30,18 @@ public function __construct(array $additionalModels = []) $this->models = array_merge($defaultModels, $additionalModels); } + + /** + * Get an OpenAI model by name. + * + * For better IDE autocompletion, use the Model class constants: + * + * $model = $catalog->getModel(Model::GPT_4O); + * + * @param OpenAiModelName $modelName The model identifier (e.g., 'gpt-4o', 'gpt-4o-mini') + */ + public function getModel(string $modelName): \Symfony\AI\Platform\Model + { + return parent::getModel($modelName); + } } diff --git a/src/platform/tests/Bridge/OpenAi/ModelTest.php b/src/platform/tests/Bridge/OpenAi/ModelTest.php new file mode 100644 index 000000000..e3a31cb1e --- /dev/null +++ b/src/platform/tests/Bridge/OpenAi/ModelTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Tests\Bridge\OpenAi; + +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\OpenAi\Model; +use Symfony\AI\Platform\Bridge\OpenAi\ModelCatalog; + +final class ModelTest extends TestCase +{ + public function testAllConstantsMatchCatalogModels() + { + $catalog = new ModelCatalog(); + $catalogModels = array_keys($catalog->getModels()); + $constants = Model::all(); + + sort($catalogModels); + sort($constants); + + self::assertSame($catalogModels, $constants, 'Model constants must match the models defined in the catalog'); + } + + public function testConstantsCanBeUsedWithModelCatalog() + { + $catalog = new ModelCatalog(); + + // Test a few representative constants + $model = $catalog->getModel(Model::GPT_4O); + self::assertSame('gpt-4o', $model->getName()); + + $model = $catalog->getModel(Model::GPT_4O_MINI); + self::assertSame('gpt-4o-mini', $model->getName()); + + $model = $catalog->getModel(Model::TEXT_EMBEDDING_3_LARGE); + self::assertSame('text-embedding-3-large', $model->getName()); + + $model = $catalog->getModel(Model::DALL_E_3); + self::assertSame('dall-e-3', $model->getName()); + } +} From 2c611364674a88f7916e4b81fb3afa7dd41ec151 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Thu, 2 Oct 2025 16:06:29 +0200 Subject: [PATCH 4/4] Add script to auto-generate model schemas - Create bin/generate-model-schema.php to generate JSON schema from models.php - Automatically extract classes and capabilities from models - Update schema pattern to support dots in model names (e.g., gpt-4.1) - Script can be run manually or integrated into CI/CD --- src/platform/bin/generate-model-schema.php | 156 ++++++++++++++++++ .../OpenAi/Resources/models.schema.json | 148 +++++++++-------- 2 files changed, 232 insertions(+), 72 deletions(-) create mode 100755 src/platform/bin/generate-model-schema.php diff --git a/src/platform/bin/generate-model-schema.php b/src/platform/bin/generate-model-schema.php new file mode 100755 index 000000000..eef0a540c --- /dev/null +++ b/src/platform/bin/generate-model-schema.php @@ -0,0 +1,156 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Generates models.schema.json from models.php for OpenAI bridge. + * + * This script extracts model definitions and generates a JSON Schema + * that can be used for validation and IDE support. + */ + +// Bootstrap autoloader +$autoloadFiles = [ + __DIR__.'/../vendor/autoload.php', + __DIR__.'/../../../../vendor/autoload.php', +]; + +$autoloaded = false; +foreach ($autoloadFiles as $autoloadFile) { + if (file_exists($autoloadFile)) { + require $autoloadFile; + $autoloaded = true; + break; + } +} + +if (!$autoloaded) { + fwrite(STDERR, "Error: Could not find vendor/autoload.php\n"); + exit(1); +} + +$modelsFile = __DIR__.'/../src/Bridge/OpenAi/Resources/models.php'; +$schemaFile = __DIR__.'/../src/Bridge/OpenAi/Resources/models.schema.json'; + +if (!file_exists($modelsFile)) { + fwrite(STDERR, "Error: models.php not found at: {$modelsFile}\n"); + exit(1); +} + +// Load the models array +$models = require $modelsFile; + +if (!is_array($models)) { + fwrite(STDERR, "Error: models.php must return an array\n"); + exit(1); +} + +// Extract unique classes +$classes = []; +foreach ($models as $model) { + if (isset($model['class'])) { + $classes[$model['class']] = true; + } +} +$classes = array_keys($classes); +sort($classes); + +// Define capability enum values +$capabilities = [ + 'INPUT_MESSAGES', + 'INPUT_TEXT', + 'INPUT_IMAGE', + 'INPUT_AUDIO', + 'OUTPUT_TEXT', + 'OUTPUT_IMAGE', + 'OUTPUT_STREAMING', + 'OUTPUT_STRUCTURED', + 'TOOL_CALLING', +]; + +// Create example from first few models +$exampleModels = array_slice($models, 0, 2, true); +$examples = []; +foreach ($exampleModels as $name => $config) { + $capabilityNames = []; + foreach ($config['capabilities'] as $capability) { + $capabilityNames[] = $capability->name; + } + $examples[$name] = [ + 'class' => $config['class'], + 'capabilities' => $capabilityNames, + ]; +} + +// Build the schema +$schema = [ + '$schema' => 'http://json-schema.org/draft-07/schema#', + '$id' => 'https://symfony.com/schema/ai/openai-models.json', + 'title' => 'OpenAI Model Catalog Schema', + 'description' => 'JSON Schema for OpenAI model definitions in models.php', + 'type' => 'object', + 'patternProperties' => [ + '^[a-z0-9.-]+$' => [ + '$ref' => '#/definitions/model', + ], + ], + 'additionalProperties' => false, + 'definitions' => [ + 'model' => [ + 'type' => 'object', + 'required' => ['class', 'capabilities'], + 'properties' => [ + 'class' => [ + 'type' => 'string', + 'enum' => $classes, + 'description' => 'The fully qualified class name that handles this model type', + ], + 'capabilities' => [ + 'type' => 'array', + 'items' => [ + '$ref' => '#/definitions/capability', + ], + 'minItems' => 1, + 'uniqueItems' => true, + 'description' => 'Array of capabilities supported by this model', + ], + ], + 'additionalProperties' => false, + ], + 'capability' => [ + 'type' => 'string', + 'enum' => $capabilities, + 'description' => 'A capability that the model supports', + ], + ], + 'examples' => [$examples], +]; + +// Generate JSON with pretty printing +$json = json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + +if (false === $json) { + fwrite(STDERR, "Error: Failed to encode JSON schema\n"); + exit(1); +} + +// Write to file +if (false === file_put_contents($schemaFile, $json."\n")) { + fwrite(STDERR, "Error: Failed to write schema file to: {$schemaFile}\n"); + exit(1); +} + +echo "✓ Successfully generated models.schema.json\n"; +echo " Models: ".count($models)."\n"; +echo " Classes: ".count($classes)."\n"; +echo " Output: {$schemaFile}\n"; + +exit(0); diff --git a/src/platform/src/Bridge/OpenAi/Resources/models.schema.json b/src/platform/src/Bridge/OpenAi/Resources/models.schema.json index 1a921cd8c..d1388dadb 100644 --- a/src/platform/src/Bridge/OpenAi/Resources/models.schema.json +++ b/src/platform/src/Bridge/OpenAi/Resources/models.schema.json @@ -1,77 +1,81 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://symfony.com/schema/ai/openai-models.json", - "title": "OpenAI Model Catalog Schema", - "description": "JSON Schema for OpenAI model definitions in models.php", - "type": "object", - "patternProperties": { - "^[a-z0-9-]+$": { - "$ref": "#/definitions/model" - } - }, - "additionalProperties": false, - "definitions": { - "model": { - "type": "object", - "required": ["class", "capabilities"], - "properties": { - "class": { - "type": "string", - "enum": [ - "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Gpt", - "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Embeddings", - "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Whisper", - "Symfony\\AI\\Platform\\Bridge\\OpenAi\\DallE" - ], - "description": "The fully qualified class name that handles this model type" + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://symfony.com/schema/ai/openai-models.json", + "title": "OpenAI Model Catalog Schema", + "description": "JSON Schema for OpenAI model definitions in models.php", + "type": "object", + "patternProperties": { + "^[a-z0-9.-]+$": { + "$ref": "#/definitions/model" + } + }, + "additionalProperties": false, + "definitions": { + "model": { + "type": "object", + "required": [ + "class", + "capabilities" + ], + "properties": { + "class": { + "type": "string", + "enum": [ + "Symfony\\AI\\Platform\\Bridge\\OpenAi\\DallE", + "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Embeddings", + "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Gpt", + "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Whisper" + ], + "description": "The fully qualified class name that handles this model type" + }, + "capabilities": { + "type": "array", + "items": { + "$ref": "#/definitions/capability" + }, + "minItems": 1, + "uniqueItems": true, + "description": "Array of capabilities supported by this model" + } + }, + "additionalProperties": false }, - "capabilities": { - "type": "array", - "items": { - "$ref": "#/definitions/capability" - }, - "minItems": 1, - "uniqueItems": true, - "description": "Array of capabilities supported by this model" + "capability": { + "type": "string", + "enum": [ + "INPUT_MESSAGES", + "INPUT_TEXT", + "INPUT_IMAGE", + "INPUT_AUDIO", + "OUTPUT_TEXT", + "OUTPUT_IMAGE", + "OUTPUT_STREAMING", + "OUTPUT_STRUCTURED", + "TOOL_CALLING" + ], + "description": "A capability that the model supports" } - }, - "additionalProperties": false }, - "capability": { - "type": "string", - "enum": [ - "INPUT_MESSAGES", - "INPUT_TEXT", - "INPUT_IMAGE", - "INPUT_AUDIO", - "OUTPUT_TEXT", - "OUTPUT_IMAGE", - "OUTPUT_STREAMING", - "OUTPUT_STRUCTURED", - "TOOL_CALLING" - ], - "description": "A capability that the model supports" - } - }, - "examples": [ - { - "gpt-4o": { - "class": "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Gpt", - "capabilities": [ - "INPUT_MESSAGES", - "OUTPUT_TEXT", - "OUTPUT_STREAMING", - "TOOL_CALLING", - "INPUT_IMAGE", - "OUTPUT_STRUCTURED" - ] - }, - "text-embedding-3-large": { - "class": "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Embeddings", - "capabilities": [ - "INPUT_TEXT" - ] - } - } - ] + "examples": [ + { + "gpt-3.5-turbo": { + "class": "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Gpt", + "capabilities": [ + "INPUT_MESSAGES", + "OUTPUT_TEXT", + "OUTPUT_STREAMING", + "TOOL_CALLING" + ] + }, + "gpt-3.5-turbo-instruct": { + "class": "Symfony\\AI\\Platform\\Bridge\\OpenAi\\Gpt", + "capabilities": [ + "INPUT_MESSAGES", + "OUTPUT_TEXT", + "OUTPUT_STREAMING", + "TOOL_CALLING" + ] + } + } + ] }