diff --git a/src/platform/doc/index.rst b/src/platform/doc/index.rst index 5a17f2990..6ec28740a 100644 --- a/src/platform/doc/index.rst +++ b/src/platform/doc/index.rst @@ -67,6 +67,24 @@ supports a specific feature, like ``Capability::INPUT_AUDIO`` or ``Capability::O **Options** are additional parameters that can be passed to the model, like ``temperature`` or ``max_tokens``, and are usually defined by the specific models and their documentation. +**Model Size Variants** + +For providers like Ollama, you can specify model size variants using a colon notation (e.g., ``qwen3:32b``, ``llama3:7b``). +If the exact model name with size variant is not found in the catalog, the system will automatically fall back to the base +model name (``qwen3``, ``llama3``) and use its capabilities while preserving the full model name for the provider. + +You can also combine size variants with query parameters:: + + use Symfony\AI\Platform\Bridge\Ollama\ModelCatalog; + + $catalog = new ModelCatalog(); + + // Get model with size variant + $model = $catalog->getModel('qwen3:32b'); + + // Get model with size variant and query parameters + $model = $catalog->getModel('qwen3:32b?temperature=0.5&top_p=0.9'); + **Supported Models & Platforms** * **Language Models** diff --git a/src/platform/src/ModelCatalog/AbstractModelCatalog.php b/src/platform/src/ModelCatalog/AbstractModelCatalog.php index e0a3caf0b..5475c3acd 100644 --- a/src/platform/src/ModelCatalog/AbstractModelCatalog.php +++ b/src/platform/src/ModelCatalog/AbstractModelCatalog.php @@ -32,15 +32,16 @@ public function getModel(string $modelName): Model throw new InvalidArgumentException('Model name cannot be empty.'); } - $parsed = self::parseModelName($modelName); + $parsed = $this->parseModelName($modelName); $actualModelName = $parsed['name']; + $catalogKey = $parsed['catalogKey']; $options = $parsed['options']; - if (!isset($this->models[$actualModelName])) { + if (!isset($this->models[$catalogKey])) { throw new ModelNotFoundException(\sprintf('Model "%s" not found.', $actualModelName)); } - $modelConfig = $this->models[$actualModelName]; + $modelConfig = $this->models[$catalogKey]; $modelClass = $modelConfig['class']; if (!class_exists($modelClass)) { @@ -65,12 +66,13 @@ public function getModels(): array /** * Extracts model name and options from a model name string that may contain query parameters. + * Also resolves size variants (e.g., "model:23b") to their base model for catalog lookup. * * @param string $modelName The model name, potentially with query parameters (e.g., "model-name?param=value&other=123") * - * @return array{name: string, options: array} An array containing the model name and parsed options + * @return array{name: string, catalogKey: string, options: array} An array containing the model name, catalog lookup key, and parsed options */ - protected static function parseModelName(string $modelName): array + protected function parseModelName(string $modelName): array { $options = []; $actualModelName = $modelName; @@ -87,8 +89,18 @@ protected static function parseModelName(string $modelName): array $options = self::convertNumericStrings($options); } + // Determine catalog key: try exact match first, then fall back to base model + $catalogKey = $actualModelName; + if (!isset($this->models[$actualModelName]) && str_contains($actualModelName, ':')) { + $baseModelName = explode(':', $actualModelName, 2)[0]; + if (isset($this->models[$baseModelName])) { + $catalogKey = $baseModelName; + } + } + return [ 'name' => $actualModelName, + 'catalogKey' => $catalogKey, 'options' => $options, ]; } 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