From 853d90256c34e347e590c1268e8c9725a99f6b04 Mon Sep 17 00:00:00 2001 From: Nikolay Mikhaylov Date: Wed, 1 Oct 2025 00:12:18 +0400 Subject: [PATCH 1/6] [Platform][OLLAMA] Add support for models with size in model catalog --- .../src/Bridge/Ollama/ModelCatalog.php | 34 +++++++++++++++++++ .../tests/Bridge/Ollama/ModelCatalogTest.php | 2 ++ 2 files changed, 36 insertions(+) diff --git a/src/platform/src/Bridge/Ollama/ModelCatalog.php b/src/platform/src/Bridge/Ollama/ModelCatalog.php index 41869a3d2..9320402b7 100644 --- a/src/platform/src/Bridge/Ollama/ModelCatalog.php +++ b/src/platform/src/Bridge/Ollama/ModelCatalog.php @@ -12,6 +12,9 @@ namespace Symfony\AI\Platform\Bridge\Ollama; use Symfony\AI\Platform\Capability; +use Symfony\AI\Platform\Exception\InvalidArgumentException; +use Symfony\AI\Platform\Exception\ModelNotFoundException; +use Symfony\AI\Platform\Model; use Symfony\AI\Platform\ModelCatalog\AbstractModelCatalog; /** @@ -210,4 +213,35 @@ public function __construct(array $additionalModels = []) $this->models = array_merge($defaultModels, $additionalModels); } + + public function getModel(string $modelName): Model + { + if ('' === $modelName) { + throw new InvalidArgumentException('Model name cannot be empty.'); + } + + $parsed = self::parseModelName($modelName); + $actualModelName = $parsed['name']; + $options = $parsed['options']; + + $modelNameWithoutSize = explode(':', $actualModelName, 2)[0]; + + if (!isset($this->models[$modelNameWithoutSize])) { + throw new ModelNotFoundException(\sprintf('Model "%s" not found.', $modelNameWithoutSize)); + } + + $modelConfig = $this->models[$modelNameWithoutSize]; + $modelClass = $modelConfig['class']; + + if (!class_exists($modelClass)) { + throw new InvalidArgumentException(\sprintf('Model class "%s" does not exist.', $modelClass)); + } + + $model = new $modelClass($actualModelName, $modelConfig['capabilities'], $options); + if (!$model instanceof Model) { + throw new InvalidArgumentException(\sprintf('Model class "%s" must extend "%s".', $modelClass, Model::class)); + } + + return $model; + } } diff --git a/src/platform/tests/Bridge/Ollama/ModelCatalogTest.php b/src/platform/tests/Bridge/Ollama/ModelCatalogTest.php index 9e1c3fad5..dac4e8b97 100644 --- a/src/platform/tests/Bridge/Ollama/ModelCatalogTest.php +++ b/src/platform/tests/Bridge/Ollama/ModelCatalogTest.php @@ -30,6 +30,7 @@ public static function modelsProvider(): iterable yield 'llama3' => ['llama3', Ollama::class, [Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STRUCTURED, Capability::TOOL_CALLING]]; yield 'mistral' => ['mistral', Ollama::class, [Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STRUCTURED, Capability::TOOL_CALLING]]; yield 'qwen3' => ['qwen3', Ollama::class, [Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STRUCTURED, Capability::TOOL_CALLING]]; + yield 'qwen3:32b' => ['qwen3:32b', Ollama::class, [Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STRUCTURED, Capability::TOOL_CALLING]]; yield 'qwen' => ['qwen', Ollama::class, [Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STRUCTURED, Capability::TOOL_CALLING]]; yield 'qwen2' => ['qwen2', Ollama::class, [Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STRUCTURED, Capability::TOOL_CALLING]]; yield 'qwen2.5' => ['qwen2.5', Ollama::class, [Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STRUCTURED, Capability::TOOL_CALLING]]; @@ -45,6 +46,7 @@ public static function modelsProvider(): iterable yield 'nomic-embed-text' => ['nomic-embed-text', Ollama::class, [Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STRUCTURED, Capability::INPUT_MULTIPLE]]; yield 'bge-m3' => ['bge-m3', Ollama::class, [Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STRUCTURED, Capability::INPUT_MULTIPLE]]; yield 'all-minilm' => ['all-minilm', Ollama::class, [Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STRUCTURED, Capability::INPUT_MULTIPLE]]; + yield 'all-minilm:33m' => ['all-minilm:33m', Ollama::class, [Capability::INPUT_MESSAGES, Capability::OUTPUT_TEXT, Capability::OUTPUT_STRUCTURED, Capability::INPUT_MULTIPLE]]; } protected function createModelCatalog(): ModelCatalogInterface From ea58a093b74cd92ba13d4930aef79e139cfcc7ec Mon Sep 17 00:00:00 2001 From: Nikolay Mikhaylov Date: Wed, 1 Oct 2025 00:22:44 +0400 Subject: [PATCH 2/6] [Platform][Ollama] Fix codestyle --- src/platform/src/Bridge/Ollama/ModelCatalog.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/src/Bridge/Ollama/ModelCatalog.php b/src/platform/src/Bridge/Ollama/ModelCatalog.php index 9320402b7..3c2545889 100644 --- a/src/platform/src/Bridge/Ollama/ModelCatalog.php +++ b/src/platform/src/Bridge/Ollama/ModelCatalog.php @@ -23,7 +23,7 @@ final class ModelCatalog extends AbstractModelCatalog { /** - * @param array, capabilities: list}> $additionalModels + * @param array, capabilities: list}> $additionalModels */ public function __construct(array $additionalModels = []) { From 6b5ca9255ae8b2fc5d8eb6857b6fece3c97a7fe3 Mon Sep 17 00:00:00 2001 From: Nikolay Mikhaylov Date: Wed, 1 Oct 2025 11:17:23 +0400 Subject: [PATCH 3/6] [Platform] Add support for models with size in abstract model catalog --- .../src/Bridge/Ollama/ModelCatalog.php | 36 +------------------ .../src/ModelCatalog/AbstractModelCatalog.php | 12 ++++--- .../ModelCatalog/AbstractModelCatalogTest.php | 26 ++++++++++++++ 3 files changed, 34 insertions(+), 40 deletions(-) diff --git a/src/platform/src/Bridge/Ollama/ModelCatalog.php b/src/platform/src/Bridge/Ollama/ModelCatalog.php index 3c2545889..41869a3d2 100644 --- a/src/platform/src/Bridge/Ollama/ModelCatalog.php +++ b/src/platform/src/Bridge/Ollama/ModelCatalog.php @@ -12,9 +12,6 @@ namespace Symfony\AI\Platform\Bridge\Ollama; use Symfony\AI\Platform\Capability; -use Symfony\AI\Platform\Exception\InvalidArgumentException; -use Symfony\AI\Platform\Exception\ModelNotFoundException; -use Symfony\AI\Platform\Model; use Symfony\AI\Platform\ModelCatalog\AbstractModelCatalog; /** @@ -23,7 +20,7 @@ final class ModelCatalog extends AbstractModelCatalog { /** - * @param array, capabilities: list}> $additionalModels + * @param array, capabilities: list}> $additionalModels */ public function __construct(array $additionalModels = []) { @@ -213,35 +210,4 @@ public function __construct(array $additionalModels = []) $this->models = array_merge($defaultModels, $additionalModels); } - - public function getModel(string $modelName): Model - { - if ('' === $modelName) { - throw new InvalidArgumentException('Model name cannot be empty.'); - } - - $parsed = self::parseModelName($modelName); - $actualModelName = $parsed['name']; - $options = $parsed['options']; - - $modelNameWithoutSize = explode(':', $actualModelName, 2)[0]; - - if (!isset($this->models[$modelNameWithoutSize])) { - throw new ModelNotFoundException(\sprintf('Model "%s" not found.', $modelNameWithoutSize)); - } - - $modelConfig = $this->models[$modelNameWithoutSize]; - $modelClass = $modelConfig['class']; - - if (!class_exists($modelClass)) { - throw new InvalidArgumentException(\sprintf('Model class "%s" does not exist.', $modelClass)); - } - - $model = new $modelClass($actualModelName, $modelConfig['capabilities'], $options); - if (!$model instanceof Model) { - throw new InvalidArgumentException(\sprintf('Model class "%s" must extend "%s".', $modelClass, Model::class)); - } - - return $model; - } } diff --git a/src/platform/src/ModelCatalog/AbstractModelCatalog.php b/src/platform/src/ModelCatalog/AbstractModelCatalog.php index e0a3caf0b..691da01db 100644 --- a/src/platform/src/ModelCatalog/AbstractModelCatalog.php +++ b/src/platform/src/ModelCatalog/AbstractModelCatalog.php @@ -35,12 +35,13 @@ public function getModel(string $modelName): Model $parsed = self::parseModelName($modelName); $actualModelName = $parsed['name']; $options = $parsed['options']; + $modelBaseName = $parsed['baseName']; - if (!isset($this->models[$actualModelName])) { + if (!isset($this->models[$actualModelName]) && !isset($this->models[$modelBaseName])) { throw new ModelNotFoundException(\sprintf('Model "%s" not found.', $actualModelName)); } - $modelConfig = $this->models[$actualModelName]; + $modelConfig = $this->models[$modelName] ?? $this->models[$modelBaseName]; $modelClass = $modelConfig['class']; if (!class_exists($modelClass)) { @@ -64,11 +65,11 @@ public function getModels(): array } /** - * Extracts model name and options from a model name string that may contain query parameters. + * Extracts model name, base model name (without size suffix) and options from a model name string that may contain query parameters. * - * @param string $modelName The model name, potentially with query parameters (e.g., "model-name?param=value&other=123") + * @param string $modelName The model name, potentially with size suffix and query parameters (e.g., "model-name:32b?param=value&other=123") * - * @return array{name: string, options: array} An array containing the model name and parsed options + * @return array{name: string, baseName: string, options: array} An array containing the model name and parsed options */ protected static function parseModelName(string $modelName): array { @@ -89,6 +90,7 @@ protected static function parseModelName(string $modelName): array return [ 'name' => $actualModelName, + 'baseName' => explode(':', $actualModelName, 2)[0], 'options' => $options, ]; } diff --git a/src/platform/tests/ModelCatalog/AbstractModelCatalogTest.php b/src/platform/tests/ModelCatalog/AbstractModelCatalogTest.php index 7f5f5cce8..c2c54dfcc 100644 --- a/src/platform/tests/ModelCatalog/AbstractModelCatalogTest.php +++ b/src/platform/tests/ModelCatalog/AbstractModelCatalogTest.php @@ -125,6 +125,32 @@ public function testNumericStringsAreConvertedRecursively() $this->assertIsInt($options['a']['e']); } + public function testGetModelWithSizeSuffixResolvesToBaseModel() + { + $catalog = $this->createTestCatalog(); + $model = $catalog->getModel('test-model:32b'); + + $this->assertSame('test-model:32b', $model->getName()); + $this->assertSame([], $model->getOptions()); + } + + public function testGetModelWithSizeSuffixAndQueryParameters() + { + $catalog = $this->createTestCatalog(); + $model = $catalog->getModel('test-model:64m?max_tokens=100&temperature=0.5'); + + $this->assertSame('test-model:64m', $model->getName()); + + $options = $model->getOptions(); + $this->assertArrayHasKey('max_tokens', $options); + $this->assertIsInt($options['max_tokens']); + $this->assertSame(100, $options['max_tokens']); + + $this->assertArrayHasKey('temperature', $options); + $this->assertIsFloat($options['temperature']); + $this->assertSame(0.5, $options['temperature']); + } + private function createTestCatalog(): AbstractModelCatalog { return new class extends AbstractModelCatalog { From a2da027643fc308b501951e17616f9aada85e014 Mon Sep 17 00:00:00 2001 From: Nikolay Mikhaylov Date: Wed, 1 Oct 2025 17:10:33 +0400 Subject: [PATCH 4/6] Revert "[Platform] Add support for models with size in abstract model catalog" This reverts commit 6b5ca9255ae8b2fc5d8eb6857b6fece3c97a7fe3. --- .../src/Bridge/Ollama/ModelCatalog.php | 36 ++++++++++++++++++- .../src/ModelCatalog/AbstractModelCatalog.php | 12 +++---- .../ModelCatalog/AbstractModelCatalogTest.php | 26 -------------- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/platform/src/Bridge/Ollama/ModelCatalog.php b/src/platform/src/Bridge/Ollama/ModelCatalog.php index 41869a3d2..3c2545889 100644 --- a/src/platform/src/Bridge/Ollama/ModelCatalog.php +++ b/src/platform/src/Bridge/Ollama/ModelCatalog.php @@ -12,6 +12,9 @@ namespace Symfony\AI\Platform\Bridge\Ollama; use Symfony\AI\Platform\Capability; +use Symfony\AI\Platform\Exception\InvalidArgumentException; +use Symfony\AI\Platform\Exception\ModelNotFoundException; +use Symfony\AI\Platform\Model; use Symfony\AI\Platform\ModelCatalog\AbstractModelCatalog; /** @@ -20,7 +23,7 @@ final class ModelCatalog extends AbstractModelCatalog { /** - * @param array, capabilities: list}> $additionalModels + * @param array, capabilities: list}> $additionalModels */ public function __construct(array $additionalModels = []) { @@ -210,4 +213,35 @@ public function __construct(array $additionalModels = []) $this->models = array_merge($defaultModels, $additionalModels); } + + public function getModel(string $modelName): Model + { + if ('' === $modelName) { + throw new InvalidArgumentException('Model name cannot be empty.'); + } + + $parsed = self::parseModelName($modelName); + $actualModelName = $parsed['name']; + $options = $parsed['options']; + + $modelNameWithoutSize = explode(':', $actualModelName, 2)[0]; + + if (!isset($this->models[$modelNameWithoutSize])) { + throw new ModelNotFoundException(\sprintf('Model "%s" not found.', $modelNameWithoutSize)); + } + + $modelConfig = $this->models[$modelNameWithoutSize]; + $modelClass = $modelConfig['class']; + + if (!class_exists($modelClass)) { + throw new InvalidArgumentException(\sprintf('Model class "%s" does not exist.', $modelClass)); + } + + $model = new $modelClass($actualModelName, $modelConfig['capabilities'], $options); + if (!$model instanceof Model) { + throw new InvalidArgumentException(\sprintf('Model class "%s" must extend "%s".', $modelClass, Model::class)); + } + + return $model; + } } diff --git a/src/platform/src/ModelCatalog/AbstractModelCatalog.php b/src/platform/src/ModelCatalog/AbstractModelCatalog.php index 691da01db..e0a3caf0b 100644 --- a/src/platform/src/ModelCatalog/AbstractModelCatalog.php +++ b/src/platform/src/ModelCatalog/AbstractModelCatalog.php @@ -35,13 +35,12 @@ public function getModel(string $modelName): Model $parsed = self::parseModelName($modelName); $actualModelName = $parsed['name']; $options = $parsed['options']; - $modelBaseName = $parsed['baseName']; - if (!isset($this->models[$actualModelName]) && !isset($this->models[$modelBaseName])) { + if (!isset($this->models[$actualModelName])) { throw new ModelNotFoundException(\sprintf('Model "%s" not found.', $actualModelName)); } - $modelConfig = $this->models[$modelName] ?? $this->models[$modelBaseName]; + $modelConfig = $this->models[$actualModelName]; $modelClass = $modelConfig['class']; if (!class_exists($modelClass)) { @@ -65,11 +64,11 @@ public function getModels(): array } /** - * Extracts model name, base model name (without size suffix) and options from a model name string that may contain query parameters. + * Extracts model name and options from a model name string that may contain query parameters. * - * @param string $modelName The model name, potentially with size suffix and query parameters (e.g., "model-name:32b?param=value&other=123") + * @param string $modelName The model name, potentially with query parameters (e.g., "model-name?param=value&other=123") * - * @return array{name: string, baseName: string, options: array} An array containing the model name and parsed options + * @return array{name: string, options: array} An array containing the model name and parsed options */ protected static function parseModelName(string $modelName): array { @@ -90,7 +89,6 @@ protected static function parseModelName(string $modelName): array return [ 'name' => $actualModelName, - 'baseName' => explode(':', $actualModelName, 2)[0], 'options' => $options, ]; } diff --git a/src/platform/tests/ModelCatalog/AbstractModelCatalogTest.php b/src/platform/tests/ModelCatalog/AbstractModelCatalogTest.php index c2c54dfcc..7f5f5cce8 100644 --- a/src/platform/tests/ModelCatalog/AbstractModelCatalogTest.php +++ b/src/platform/tests/ModelCatalog/AbstractModelCatalogTest.php @@ -125,32 +125,6 @@ public function testNumericStringsAreConvertedRecursively() $this->assertIsInt($options['a']['e']); } - public function testGetModelWithSizeSuffixResolvesToBaseModel() - { - $catalog = $this->createTestCatalog(); - $model = $catalog->getModel('test-model:32b'); - - $this->assertSame('test-model:32b', $model->getName()); - $this->assertSame([], $model->getOptions()); - } - - public function testGetModelWithSizeSuffixAndQueryParameters() - { - $catalog = $this->createTestCatalog(); - $model = $catalog->getModel('test-model:64m?max_tokens=100&temperature=0.5'); - - $this->assertSame('test-model:64m', $model->getName()); - - $options = $model->getOptions(); - $this->assertArrayHasKey('max_tokens', $options); - $this->assertIsInt($options['max_tokens']); - $this->assertSame(100, $options['max_tokens']); - - $this->assertArrayHasKey('temperature', $options); - $this->assertIsFloat($options['temperature']); - $this->assertSame(0.5, $options['temperature']); - } - private function createTestCatalog(): AbstractModelCatalog { return new class extends AbstractModelCatalog { From 7995b46d0342ac12272884031f26194140a87b9f Mon Sep 17 00:00:00 2001 From: Nikolay Mikhaylov Date: Wed, 1 Oct 2025 17:12:31 +0400 Subject: [PATCH 5/6] [Platform][Ollama] Use base model name as fallback for resolving config --- src/platform/src/Bridge/Ollama/ModelCatalog.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/src/Bridge/Ollama/ModelCatalog.php b/src/platform/src/Bridge/Ollama/ModelCatalog.php index 3c2545889..65aaa0331 100644 --- a/src/platform/src/Bridge/Ollama/ModelCatalog.php +++ b/src/platform/src/Bridge/Ollama/ModelCatalog.php @@ -224,13 +224,13 @@ public function getModel(string $modelName): Model $actualModelName = $parsed['name']; $options = $parsed['options']; - $modelNameWithoutSize = explode(':', $actualModelName, 2)[0]; + $baseModelName = explode(':', $actualModelName, 2)[0]; - if (!isset($this->models[$modelNameWithoutSize])) { - throw new ModelNotFoundException(\sprintf('Model "%s" not found.', $modelNameWithoutSize)); + if (!isset($this->models[$actualModelName]) && !isset($this->models[$baseModelName])) { + throw new ModelNotFoundException(\sprintf('Model "%s" not found.', $baseModelName)); } - $modelConfig = $this->models[$modelNameWithoutSize]; + $modelConfig = $this->models[$actualModelName] ?? $this->models[$baseModelName]; $modelClass = $modelConfig['class']; if (!class_exists($modelClass)) { From 3e9e62764611da83108af0772db2a0d8e3489ee1 Mon Sep 17 00:00:00 2001 From: Nikolay Mikhaylov Date: Wed, 1 Oct 2025 17:41:57 +0400 Subject: [PATCH 6/6] Use actual model name in exception message --- src/platform/src/Bridge/Ollama/ModelCatalog.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/src/Bridge/Ollama/ModelCatalog.php b/src/platform/src/Bridge/Ollama/ModelCatalog.php index 65aaa0331..f3b791ac9 100644 --- a/src/platform/src/Bridge/Ollama/ModelCatalog.php +++ b/src/platform/src/Bridge/Ollama/ModelCatalog.php @@ -227,7 +227,7 @@ public function getModel(string $modelName): Model $baseModelName = explode(':', $actualModelName, 2)[0]; if (!isset($this->models[$actualModelName]) && !isset($this->models[$baseModelName])) { - throw new ModelNotFoundException(\sprintf('Model "%s" not found.', $baseModelName)); + throw new ModelNotFoundException(\sprintf('Model "%s" not found.', $actualModelName)); } $modelConfig = $this->models[$actualModelName] ?? $this->models[$baseModelName];