From 3adacba07d9490cd26b8cc74815f537249e87453 Mon Sep 17 00:00:00 2001 From: Guillaume Loulier Date: Thu, 9 Oct 2025 17:52:57 +0200 Subject: [PATCH 1/2] ref(platform): OllamaCatalog --- src/ai-bundle/config/options.php | 1 + src/ai-bundle/src/AiBundle.php | 18 +++- .../DependencyInjection/AiBundleTest.php | 40 +++++++++ .../src/Bridge/Ollama/OllamaApiCatalog.php | 83 +++++++++++++++++++ .../src/Bridge/Ollama/OllamaClient.php | 17 +--- src/platform/src/Capability.php | 5 ++ .../Bridge/Ollama/OllamaApiCatalogTest.php | 38 +++++++++ .../tests/Bridge/Ollama/OllamaClientTest.php | 14 ++-- 8 files changed, 191 insertions(+), 25 deletions(-) create mode 100644 src/platform/src/Bridge/Ollama/OllamaApiCatalog.php create mode 100644 src/platform/tests/Bridge/Ollama/OllamaApiCatalogTest.php diff --git a/src/ai-bundle/config/options.php b/src/ai-bundle/config/options.php index 0ad9e775f..5e41906da 100644 --- a/src/ai-bundle/config/options.php +++ b/src/ai-bundle/config/options.php @@ -160,6 +160,7 @@ ->defaultValue('http_client') ->info('Service ID of the HTTP client to use') ->end() + ->booleanNode('use_api_as_catalog')->end() ->end() ->end() ->arrayNode('cerebras') diff --git a/src/ai-bundle/src/AiBundle.php b/src/ai-bundle/src/AiBundle.php index c7decae7a..50b07748c 100644 --- a/src/ai-bundle/src/AiBundle.php +++ b/src/ai-bundle/src/AiBundle.php @@ -55,6 +55,7 @@ use Symfony\AI\Platform\Bridge\HuggingFace\PlatformFactory as HuggingFacePlatformFactory; use Symfony\AI\Platform\Bridge\LmStudio\PlatformFactory as LmStudioPlatformFactory; use Symfony\AI\Platform\Bridge\Mistral\PlatformFactory as MistralPlatformFactory; +use Symfony\AI\Platform\Bridge\Ollama\OllamaApiCatalog; use Symfony\AI\Platform\Bridge\Ollama\PlatformFactory as OllamaPlatformFactory; use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory as OpenAiPlatformFactory; use Symfony\AI\Platform\Bridge\OpenRouter\PlatformFactory as OpenRouterPlatformFactory; @@ -564,11 +565,21 @@ private function processPlatformConfig(string $type, array $platform, ContainerB } if ('ollama' === $type) { - $platformId = 'ai.platform.ollama'; + if (\array_key_exists('use_api_as_catalog', $platform)) { + $catalogDefinition = (new Definition(OllamaApiCatalog::class)) + ->setDecoratedService('ai.platform.model_catalog.ollama') + ->setArguments([ + $platform['host_url'], + new Reference('http_client'), + new Reference('.inner'), + ]); + + $container->setDefinition('ai.platform.model_catalog.ollama', $catalogDefinition); + } + $definition = (new Definition(Platform::class)) ->setFactory(OllamaPlatformFactory::class.'::create') ->setLazy(true) - ->addTag('proxy', ['interface' => PlatformInterface::class]) ->setArguments([ $platform['host_url'], new Reference($platform['http_client'], ContainerInterface::NULL_ON_INVALID_REFERENCE), @@ -576,9 +587,10 @@ private function processPlatformConfig(string $type, array $platform, ContainerB new Reference('ai.platform.contract.ollama'), new Reference('event_dispatcher'), ]) + ->addTag('proxy', ['interface' => PlatformInterface::class]) ->addTag('ai.platform', ['name' => 'ollama']); - $container->setDefinition($platformId, $definition); + $container->setDefinition('ai.platform.ollama', $definition); return; } diff --git a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php index dfcfbda76..fa6a74a45 100644 --- a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php +++ b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php @@ -23,6 +23,7 @@ use Symfony\AI\AiBundle\AiBundle; use Symfony\AI\Chat\ChatInterface; use Symfony\AI\Chat\MessageStoreInterface; +use Symfony\AI\Platform\Bridge\Ollama\OllamaApiCatalog; use Symfony\AI\Store\Document\Filter\TextContainsFilter; use Symfony\AI\Store\Document\Loader\InMemoryLoader; use Symfony\AI\Store\Document\Transformer\TextTrimTransformer; @@ -579,6 +580,45 @@ public function testConfigurationWithUseAttributeAsKeyWorksWithoutNormalizeKeys( $this->assertTrue($container->hasDefinition('ai.store.mongodb.Production_DB-v3')); } + public function testOllamaCanBeCreatedWithCatalogFromApi() + { + $container = $this->buildContainer([ + 'ai' => [ + 'platform' => [ + 'ollama' => [ + 'use_api_as_catalog' => true, + ], + ], + ], + ]); + + $this->assertTrue($container->hasDefinition('ai.platform.ollama')); + $this->assertTrue($container->hasDefinition('ai.platform.model_catalog.ollama')); + + $ollamaDefinition = $container->getDefinition('ai.platform.ollama'); + + $this->assertCount(4, $ollamaDefinition->getArguments()); + $this->assertSame('http://127.0.0.1:11434', $ollamaDefinition->getArgument(0)); + $this->assertInstanceOf(Reference::class, $ollamaDefinition->getArgument(1)); + $this->assertSame('http_client', (string) $ollamaDefinition->getArgument(1)); + $this->assertInstanceOf(Reference::class, $ollamaDefinition->getArgument(2)); + $this->assertSame('ai.platform.model_catalog.ollama', (string) $ollamaDefinition->getArgument(2)); + $this->assertInstanceOf(Reference::class, $ollamaDefinition->getArgument(3)); + $this->assertSame('ai.platform.contract.ollama', (string) $ollamaDefinition->getArgument(3)); + + $this->assertTrue($ollamaDefinition->isLazy()); + + $ollamaCatalogDefinition = $container->getDefinition('ai.platform.model_catalog.ollama'); + + $this->assertSame(OllamaApiCatalog::class, $ollamaCatalogDefinition->getClass()); + $this->assertCount(3, $ollamaCatalogDefinition->getArguments()); + $this->assertSame('http://127.0.0.1:11434', $ollamaCatalogDefinition->getArgument(0)); + $this->assertInstanceOf(Reference::class, $ollamaCatalogDefinition->getArgument(1)); + $this->assertSame('http_client', (string) $ollamaCatalogDefinition->getArgument(1)); + $this->assertInstanceOf(Reference::class, $ollamaCatalogDefinition->getArgument(2)); + $this->assertSame('.inner', (string) $ollamaCatalogDefinition->getArgument(2)); + } + /** * Tests that processor tags use the full agent ID (ai.agent.my_agent) instead of just the agent name (my_agent). * This regression test prevents issues where processors would not be correctly associated with their agents. diff --git a/src/platform/src/Bridge/Ollama/OllamaApiCatalog.php b/src/platform/src/Bridge/Ollama/OllamaApiCatalog.php new file mode 100644 index 000000000..1559cfcd1 --- /dev/null +++ b/src/platform/src/Bridge/Ollama/OllamaApiCatalog.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Bridge\Ollama; + +use Symfony\AI\Platform\Capability; +use Symfony\AI\Platform\Exception\InvalidArgumentException; +use Symfony\AI\Platform\Model; +use Symfony\AI\Platform\ModelCatalog\FallbackModelCatalog; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Guillaume Loulier + */ +final class OllamaApiCatalog extends FallbackModelCatalog +{ + public function __construct( + private readonly string $host, + private readonly HttpClientInterface $httpClient, + private readonly ModelCatalog $inner, + ) { + parent::__construct(); + } + + public function getModel(string $modelName): Model + { + $model = parent::getModel($modelName); + + if (\array_key_exists($model->getName(), $this->models)) { + $finalModel = $this->models[$model->getName()]; + + return new $finalModel['class']( + $model->getName(), + $finalModel['capabilities'], + $model->getOptions(), + ); + } + + try { + $response = $this->httpClient->request('POST', \sprintf('%s/api/show', $this->host), [ + 'json' => [ + 'model' => $model->getName(), + ], + ]); + + $payload = $response->toArray(); + + if ([] === $payload['capabilities'] ?? []) { + throw new InvalidArgumentException('The model information could not be retrieved from the Ollama API. Your Ollama server might be too old. Try upgrade it.'); + } + + $capabilities = array_map( + static fn (string $capability): Capability => match ($capability) { + 'embeddings' => Capability::EMBEDDINGS, + 'completion' => Capability::INPUT_TEXT, + 'tools' => Capability::TOOLS, + 'vision' => Capability::INPUT_IMAGE, + default => throw new InvalidArgumentException(\sprintf('The "%s" capability is not supported', $capability)), + }, + $payload['capabilities'], + ); + + $finalModel = new Ollama($model->getName(), $capabilities, $model->getOptions()); + + $this->models[$finalModel->getName()] = [ + 'class' => Ollama::class, + 'capabilities' => $finalModel->getCapabilities(), + ]; + + return $finalModel; + } catch (\Throwable) { + return $this->inner->getModel($modelName); + } + } +} diff --git a/src/platform/src/Bridge/Ollama/OllamaClient.php b/src/platform/src/Bridge/Ollama/OllamaClient.php index 34238a8db..bae2b3461 100644 --- a/src/platform/src/Bridge/Ollama/OllamaClient.php +++ b/src/platform/src/Bridge/Ollama/OllamaClient.php @@ -11,6 +11,7 @@ namespace Symfony\AI\Platform\Bridge\Ollama; +use Symfony\AI\Platform\Capability; use Symfony\AI\Platform\Exception\InvalidArgumentException; use Symfony\AI\Platform\Model; use Symfony\AI\Platform\ModelClientInterface; @@ -35,21 +36,9 @@ public function supports(Model $model): bool public function request(Model $model, array|string $payload, array $options = []): RawHttpResult { - $response = $this->httpClient->request('POST', \sprintf('%s/api/show', $this->hostUrl), [ - 'json' => [ - 'model' => $model->getName(), - ], - ]); - - $capabilities = $response->toArray()['capabilities'] ?? null; - - if (null === $capabilities) { - throw new InvalidArgumentException('The model information could not be retrieved from the Ollama API. Your Ollama server might be too old. Try upgrade it.'); - } - return match (true) { - \in_array('completion', $capabilities, true) => $this->doCompletionRequest($payload, $options), - \in_array('embedding', $capabilities, true) => $this->doEmbeddingsRequest($model, $payload, $options), + \in_array(Capability::COMPLETION, $model->getCapabilities(), true) => $this->doCompletionRequest($payload, $options), + \in_array(Capability::EMBEDDINGS, $model->getCapabilities(), true) => $this->doEmbeddingsRequest($model, $payload, $options), default => throw new InvalidArgumentException(\sprintf('Unsupported model "%s": "%s".', $model::class, $model->getName())), }; } diff --git a/src/platform/src/Capability.php b/src/platform/src/Capability.php index 459c5d0ad..0cecd3167 100644 --- a/src/platform/src/Capability.php +++ b/src/platform/src/Capability.php @@ -35,11 +35,16 @@ enum Capability: string case OUTPUT_STREAMING = 'output-streaming'; case OUTPUT_STRUCTURED = 'output-structured'; case OUTPUT_TEXT = 'output-text'; + case COMPLETION = 'completion'; // FUNCTIONALITY case TOOL_CALLING = 'tool-calling'; + case TOOLS = 'tools'; // VOICE case TEXT_TO_SPEECH = 'text-to-speech'; case SPEECH_TO_TEXT = 'speech-to-text'; + + // EMBEDDINGS + case EMBEDDINGS = 'embeddings'; } diff --git a/src/platform/tests/Bridge/Ollama/OllamaApiCatalogTest.php b/src/platform/tests/Bridge/Ollama/OllamaApiCatalogTest.php new file mode 100644 index 000000000..50bcb833c --- /dev/null +++ b/src/platform/tests/Bridge/Ollama/OllamaApiCatalogTest.php @@ -0,0 +1,38 @@ + + * + * 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\Ollama; + +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\Ollama\ModelCatalog; +use Symfony\AI\Platform\Bridge\Ollama\Ollama; +use Symfony\AI\Platform\Bridge\Ollama\OllamaApiCatalog; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\JsonMockResponse; + +final class OllamaApiCatalogTest extends TestCase +{ + public function testModelCatalogCanReturnModelFromApi() + { + $httpClient = new MockHttpClient([ + new JsonMockResponse([ + 'capabilities' => ['completion'], + ]), + ]); + + $modelCatalog = new OllamaApiCatalog('http://127.0.0.1:11434', $httpClient, new ModelCatalog()); + + $model = $modelCatalog->getModel('foo'); + + $this->assertInstanceOf(Ollama::class, $model); + $this->assertSame(1, $httpClient->getRequestsCount()); + } +} diff --git a/src/platform/tests/Bridge/Ollama/OllamaClientTest.php b/src/platform/tests/Bridge/Ollama/OllamaClientTest.php index 83e252259..40d1df6b6 100644 --- a/src/platform/tests/Bridge/Ollama/OllamaClientTest.php +++ b/src/platform/tests/Bridge/Ollama/OllamaClientTest.php @@ -16,6 +16,7 @@ use Symfony\AI\Platform\Bridge\Ollama\OllamaClient; use Symfony\AI\Platform\Bridge\Ollama\OllamaResultConverter; use Symfony\AI\Platform\Bridge\Ollama\PlatformFactory; +use Symfony\AI\Platform\Capability; use Symfony\AI\Platform\Model; use Symfony\AI\Platform\Result\RawHttpResult; use Symfony\AI\Platform\Result\StreamResult; @@ -36,9 +37,6 @@ public function testSupportsModel() public function testOutputStructureIsSupported() { $httpClient = new MockHttpClient([ - new JsonMockResponse([ - 'capabilities' => ['completion', 'tools'], - ]), new JsonMockResponse([ 'model' => 'foo', 'response' => [ @@ -50,7 +48,10 @@ public function testOutputStructureIsSupported() ], 'http://127.0.0.1:1234'); $client = new OllamaClient($httpClient, 'http://127.0.0.1:1234'); - $response = $client->request(new Ollama('llama3.2'), [ + $response = $client->request(new Ollama('llama3.2', [ + Capability::COMPLETION, + Capability::TOOLS, + ]), [ 'messages' => [ [ 'role' => 'user', @@ -77,7 +78,7 @@ public function testOutputStructureIsSupported() ], ]); - $this->assertSame(2, $httpClient->getRequestsCount()); + $this->assertSame(1, $httpClient->getRequestsCount()); $this->assertSame([ 'model' => 'foo', 'response' => [ @@ -91,9 +92,6 @@ public function testOutputStructureIsSupported() public function testStreamingIsSupported() { $httpClient = new MockHttpClient([ - new JsonMockResponse([ - 'capabilities' => ['completion'], - ]), new MockResponse('data: '.json_encode([ 'model' => 'llama3.2', 'created_at' => '2025-08-23T10:00:00Z', From 3a9b5e12a8788a0bfcc35c5f5531e9a35a80f050 Mon Sep 17 00:00:00 2001 From: Guillaume Loulier Date: Fri, 14 Nov 2025 19:16:43 +0100 Subject: [PATCH 2/2] ref --- docs/components/platform.rst | 28 +++++++++ examples/ollama/chat-llama-api-catalog.php | 34 +++++++++++ src/ai-bundle/config/options.php | 4 +- src/ai-bundle/src/AiBundle.php | 5 +- .../DependencyInjection/AiBundleTest.php | 4 +- .../src/Bridge/Ollama/ModelCatalog.php | 5 +- .../src/Bridge/Ollama/OllamaApiCatalog.php | 59 +++++++++---------- .../src/Bridge/Ollama/OllamaClient.php | 2 +- src/platform/src/Capability.php | 5 +- 9 files changed, 104 insertions(+), 42 deletions(-) create mode 100644 examples/ollama/chat-llama-api-catalog.php diff --git a/docs/components/platform.rst b/docs/components/platform.rst index e2f2b974a..a9d7a44cb 100644 --- a/docs/components/platform.rst +++ b/docs/components/platform.rst @@ -77,6 +77,34 @@ You can also combine size variants with query parameters:: // Get model with size variant and query parameters $model = $catalog->getModel('qwen3:32b?temperature=0.5&top_p=0.9'); +Custom models +~~~~~~~~~~~~~ + +For providers like Ollama, you can use custom models (built on top of ``Modelfile``), as those models are not listed in +the default catalog, you can use the built-in ``OllamaApiCatalog`` to query the model informations from the API rather +than the default catalog:: + + use Symfony\AI\Platform\Bridge\Ollama\OllamaApiCatalog; + use Symfony\AI\Platform\Bridge\Ollama\PlatformFactory; + use Symfony\AI\Platform\Message\Message; + use Symfony\AI\Platform\Message\MessageBag; + + $platform = PlatformFactory::create('http://127.0.0.11434', HttpClient::create(), new OllamaApiCatalog( + 'http://127.0.0.11434', + HttpClient::create(), + )); + + $platform->invoke('your_custom_model_name', new MessageBag( + Message::ofUser(...) + )); + +When using the bundle, the usage of ``OllamaApiCatalog`` is available via the ``api_catalog`` option:: + + ai: + platform: + ollama: + api_catalog: true + Supported Models & Platforms ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/ollama/chat-llama-api-catalog.php b/examples/ollama/chat-llama-api-catalog.php new file mode 100644 index 000000000..dc9e00501 --- /dev/null +++ b/examples/ollama/chat-llama-api-catalog.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\AI\Platform\Bridge\Ollama\OllamaApiCatalog; +use Symfony\AI\Platform\Bridge\Ollama\PlatformFactory; +use Symfony\AI\Platform\Message\Message; +use Symfony\AI\Platform\Message\MessageBag; + +require_once dirname(__DIR__).'/bootstrap.php'; + +$platform = PlatformFactory::create(env('OLLAMA_HOST_URL'), http_client(), new OllamaApiCatalog( + env('OLLAMA_HOST_URL'), + http_client(), +)); + +$messages = new MessageBag( + Message::forSystem('You are a helpful assistant.'), + Message::ofUser('Tina has one brother and one sister. How many sisters do Tina\'s siblings have?'), +); + +try { + $result = $platform->invoke(env('OLLAMA_LLM'), $messages); + echo $result->asText().\PHP_EOL; +} catch (InvalidArgumentException $e) { + echo $e->getMessage()."\nMaybe use a different model?\n"; +} diff --git a/src/ai-bundle/config/options.php b/src/ai-bundle/config/options.php index 5e41906da..1cc61df14 100644 --- a/src/ai-bundle/config/options.php +++ b/src/ai-bundle/config/options.php @@ -160,7 +160,9 @@ ->defaultValue('http_client') ->info('Service ID of the HTTP client to use') ->end() - ->booleanNode('use_api_as_catalog')->end() + ->booleanNode('api_catalog') + ->info('If set, the Ollama API will be used to build the catalog and retrieve models informations, using this option lead to additional HTTP calls') + ->end() ->end() ->end() ->arrayNode('cerebras') diff --git a/src/ai-bundle/src/AiBundle.php b/src/ai-bundle/src/AiBundle.php index 50b07748c..0e4942184 100644 --- a/src/ai-bundle/src/AiBundle.php +++ b/src/ai-bundle/src/AiBundle.php @@ -565,13 +565,12 @@ private function processPlatformConfig(string $type, array $platform, ContainerB } if ('ollama' === $type) { - if (\array_key_exists('use_api_as_catalog', $platform)) { + if (\array_key_exists('api_catalog', $platform)) { $catalogDefinition = (new Definition(OllamaApiCatalog::class)) - ->setDecoratedService('ai.platform.model_catalog.ollama') + ->setLazy(true) ->setArguments([ $platform['host_url'], new Reference('http_client'), - new Reference('.inner'), ]); $container->setDefinition('ai.platform.model_catalog.ollama', $catalogDefinition); diff --git a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php index fa6a74a45..eba46d76b 100644 --- a/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php +++ b/src/ai-bundle/tests/DependencyInjection/AiBundleTest.php @@ -597,6 +597,7 @@ public function testOllamaCanBeCreatedWithCatalogFromApi() $ollamaDefinition = $container->getDefinition('ai.platform.ollama'); + $this->assertTrue($ollamaDefinition->isLazy()); $this->assertCount(4, $ollamaDefinition->getArguments()); $this->assertSame('http://127.0.0.1:11434', $ollamaDefinition->getArgument(0)); $this->assertInstanceOf(Reference::class, $ollamaDefinition->getArgument(1)); @@ -606,10 +607,9 @@ public function testOllamaCanBeCreatedWithCatalogFromApi() $this->assertInstanceOf(Reference::class, $ollamaDefinition->getArgument(3)); $this->assertSame('ai.platform.contract.ollama', (string) $ollamaDefinition->getArgument(3)); - $this->assertTrue($ollamaDefinition->isLazy()); - $ollamaCatalogDefinition = $container->getDefinition('ai.platform.model_catalog.ollama'); + $this->assertTrue($ollamaCatalogDefinition->isLazy()); $this->assertSame(OllamaApiCatalog::class, $ollamaCatalogDefinition->getClass()); $this->assertCount(3, $ollamaCatalogDefinition->getArguments()); $this->assertSame('http://127.0.0.1:11434', $ollamaCatalogDefinition->getArgument(0)); diff --git a/src/platform/src/Bridge/Ollama/ModelCatalog.php b/src/platform/src/Bridge/Ollama/ModelCatalog.php index 68f487e97..55bb7bd8c 100644 --- a/src/platform/src/Bridge/Ollama/ModelCatalog.php +++ b/src/platform/src/Bridge/Ollama/ModelCatalog.php @@ -218,6 +218,9 @@ public function __construct(array $additionalModels = []) ], ]; - $this->models = array_merge($defaultModels, $additionalModels); + $this->models = [ + ...$defaultModels, + ...$additionalModels, + ]; } } diff --git a/src/platform/src/Bridge/Ollama/OllamaApiCatalog.php b/src/platform/src/Bridge/Ollama/OllamaApiCatalog.php index 1559cfcd1..e782c3508 100644 --- a/src/platform/src/Bridge/Ollama/OllamaApiCatalog.php +++ b/src/platform/src/Bridge/Ollama/OllamaApiCatalog.php @@ -13,7 +13,6 @@ use Symfony\AI\Platform\Capability; use Symfony\AI\Platform\Exception\InvalidArgumentException; -use Symfony\AI\Platform\Model; use Symfony\AI\Platform\ModelCatalog\FallbackModelCatalog; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -25,12 +24,11 @@ final class OllamaApiCatalog extends FallbackModelCatalog public function __construct( private readonly string $host, private readonly HttpClientInterface $httpClient, - private readonly ModelCatalog $inner, ) { parent::__construct(); } - public function getModel(string $modelName): Model + public function getModel(string $modelName): Ollama { $model = parent::getModel($modelName); @@ -44,40 +42,37 @@ public function getModel(string $modelName): Model ); } - try { - $response = $this->httpClient->request('POST', \sprintf('%s/api/show', $this->host), [ - 'json' => [ - 'model' => $model->getName(), - ], - ]); + $response = $this->httpClient->request('POST', \sprintf('%s/api/show', $this->host), [ + 'json' => [ + 'model' => $model->getName(), + ], + ]); - $payload = $response->toArray(); + $payload = $response->toArray(); - if ([] === $payload['capabilities'] ?? []) { - throw new InvalidArgumentException('The model information could not be retrieved from the Ollama API. Your Ollama server might be too old. Try upgrade it.'); - } + if ([] === $payload['capabilities'] ?? []) { + throw new InvalidArgumentException('The model information could not be retrieved from the Ollama API. Your Ollama server might be too old. Try upgrade it.'); + } - $capabilities = array_map( - static fn (string $capability): Capability => match ($capability) { - 'embeddings' => Capability::EMBEDDINGS, - 'completion' => Capability::INPUT_TEXT, - 'tools' => Capability::TOOLS, - 'vision' => Capability::INPUT_IMAGE, - default => throw new InvalidArgumentException(\sprintf('The "%s" capability is not supported', $capability)), - }, - $payload['capabilities'], - ); + $capabilities = array_map( + static fn (string $capability): Capability => match ($capability) { + 'embedding' => Capability::EMBEDDINGS, + 'completion' => Capability::INPUT_TEXT, + 'tools' => Capability::TOOL_CALLING, + 'thinking' => Capability::THINKING, + 'vision' => Capability::INPUT_IMAGE, + default => throw new InvalidArgumentException(\sprintf('The "%s" capability is not supported', $capability)), + }, + $payload['capabilities'], + ); - $finalModel = new Ollama($model->getName(), $capabilities, $model->getOptions()); + $finalModel = new Ollama($model->getName(), $capabilities, $model->getOptions()); - $this->models[$finalModel->getName()] = [ - 'class' => Ollama::class, - 'capabilities' => $finalModel->getCapabilities(), - ]; + $this->models[$finalModel->getName()] = [ + 'class' => Ollama::class, + 'capabilities' => $finalModel->getCapabilities(), + ]; - return $finalModel; - } catch (\Throwable) { - return $this->inner->getModel($modelName); - } + return $finalModel; } } diff --git a/src/platform/src/Bridge/Ollama/OllamaClient.php b/src/platform/src/Bridge/Ollama/OllamaClient.php index bae2b3461..2867903ad 100644 --- a/src/platform/src/Bridge/Ollama/OllamaClient.php +++ b/src/platform/src/Bridge/Ollama/OllamaClient.php @@ -37,7 +37,7 @@ public function supports(Model $model): bool public function request(Model $model, array|string $payload, array $options = []): RawHttpResult { return match (true) { - \in_array(Capability::COMPLETION, $model->getCapabilities(), true) => $this->doCompletionRequest($payload, $options), + \in_array(Capability::INPUT_TEXT, $model->getCapabilities(), true) => $this->doCompletionRequest($payload, $options), \in_array(Capability::EMBEDDINGS, $model->getCapabilities(), true) => $this->doEmbeddingsRequest($model, $payload, $options), default => throw new InvalidArgumentException(\sprintf('Unsupported model "%s": "%s".', $model::class, $model->getName())), }; diff --git a/src/platform/src/Capability.php b/src/platform/src/Capability.php index 0cecd3167..47e6afc59 100644 --- a/src/platform/src/Capability.php +++ b/src/platform/src/Capability.php @@ -35,11 +35,9 @@ enum Capability: string case OUTPUT_STREAMING = 'output-streaming'; case OUTPUT_STRUCTURED = 'output-structured'; case OUTPUT_TEXT = 'output-text'; - case COMPLETION = 'completion'; // FUNCTIONALITY case TOOL_CALLING = 'tool-calling'; - case TOOLS = 'tools'; // VOICE case TEXT_TO_SPEECH = 'text-to-speech'; @@ -47,4 +45,7 @@ enum Capability: string // EMBEDDINGS case EMBEDDINGS = 'embeddings'; + + // Thinking + case THINKING = 'thinking'; }