├── .readme.yaml ├── LICENSE ├── README.md ├── composer.json ├── config ├── chat_request_handler.php ├── commands.php ├── completion_request_handler.php ├── experts.php ├── image_request_handler.php └── providers │ ├── anthropic │ ├── chat.php │ └── common.php │ ├── fireworksai │ ├── chat.php │ ├── common.php │ ├── completion.php │ ├── embeddings.php │ └── image.php │ ├── mistral │ ├── chat.php │ ├── common.php │ └── embeddings.php │ ├── ollama │ ├── chat.php │ ├── common.php │ ├── completion.php │ └── embeddings.php │ └── openai │ ├── chat.php │ ├── common.php │ ├── embeddings.php │ └── image.php └── src ├── Command └── ChatCommand.php ├── Config └── CriteriaContainer.php ├── Criteria ├── ModelCriteria.php └── ProviderCriteria.php ├── DecisionTree └── DecisionTreeDecorator.php └── ModelflowAiBundle.php /.readme.yaml: -------------------------------------------------------------------------------- 1 | title: Symfony Integration 2 | package: symfony-bundle 3 | description: This bundle integrates Modelflow-AI into a Symfony project. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Johannes Wachter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | Symfony Integration Logo 4 |
5 | 6 |

7 | Modelflow AI
8 | Symfony Integration
9 |
10 |

11 | 12 |
13 | 14 |
15 | This bundle integrates Modelflow-AI into a Symfony project. 16 |
17 | 18 |
19 | 20 | > **Note**: 21 | > This is part of the `modelflow-ai` project create issues in the [main repository](https://github.com/modelflow-ai/.github). 22 | 23 | > **Note**: 24 | > This project is heavily under development and any feedback is greatly appreciated. 25 | 26 |
27 | 28 | ## Installation 29 | 30 | To install the Symfony Integration package, you need to have PHP 8.2 or higher and Composer installed on your machine. 31 | Then, you can add the package to your project by running the following command: 32 | 33 | ```bash 34 | composer require modelflow-ai/symfony-bundle 35 | ``` 36 | 37 | ## Usage 38 | 39 | Detailed usage instructions will be provided in the future. For now, you can refer to the source code and unit tests for 40 | usage examples. 41 | 42 | ## Contributing 43 | 44 | Contributions are welcome. Please open an issue or submit a pull request in the main repository 45 | at [https://github.com/modelflow-ai/.github](https://github.com/modelflow-ai/.github). 46 | 47 | ## License 48 | 49 | This project is licensed under the MIT License. For the full copyright and license information, please view the LICENSE 50 | file that was distributed with this source code. 51 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modelflow-ai/symfony-bundle", 3 | "description": "An integration of modelflow-ai via a bundle into the Symfony Framework.", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "ai", 8 | "openai", 9 | "mistral", 10 | "ollama", 11 | "anthropic", 12 | "fireworksai", 13 | "gpt4", 14 | "gpt3.5", 15 | "llama2", 16 | "llama3.1", 17 | "mistral-large" 18 | ], 19 | "autoload": { 20 | "psr-4": { 21 | "ModelflowAi\\Integration\\Symfony\\": "src/" 22 | } 23 | }, 24 | "autoload-dev": { 25 | "psr-4": { 26 | "ModelflowAi\\Integration\\Symfony\\Tests\\": "tests/" 27 | } 28 | }, 29 | "authors": [ 30 | { 31 | "name": "Johannes Wachter", 32 | "email": "johannes@sulu.io" 33 | } 34 | ], 35 | "require": { 36 | "php": "^8.2", 37 | "symfony/console": "^6.2 | ^7.0", 38 | "symfony/dependency-injection": "^6.2 | ^7.0", 39 | "symfony/http-kernel": "^6.2 | ^7.0", 40 | "symfony/config": "^6.2 | ^7.0", 41 | "symfony/framework-bundle": "^6.2 | ^7.0" 42 | }, 43 | "require-dev": { 44 | "modelflow-ai/anthropic-adapter": "^0.2", 45 | "modelflow-ai/chat": "^0.2", 46 | "modelflow-ai/completion": "^0.2", 47 | "modelflow-ai/embeddings": "^0.2", 48 | "modelflow-ai/experts": "^0.2", 49 | "modelflow-ai/fireworksai-adapter": "^0.2", 50 | "modelflow-ai/image": "^0.2", 51 | "modelflow-ai/mistral-adapter": "^0.2", 52 | "modelflow-ai/ollama-adapter": "^0.2", 53 | "modelflow-ai/openai-adapter": "^0.2", 54 | "modelflow-ai/prompt-template": "^0.2", 55 | "php-cs-fixer/shim": "^3.15", 56 | "phpstan/extension-installer": "^1.2", 57 | "phpstan/phpstan": "^1.10, <1.10.55", 58 | "phpstan/phpstan-phpunit": "^1.3", 59 | "phpunit/phpunit": "^10.3", 60 | "rector/rector": "^0.18.1", 61 | "symfony/yaml": "^6.2 | ^7.0", 62 | "symfony/filesystem": "^6.2 | ^7.0", 63 | "asapo/remove-vendor-plugin": "^0.1" 64 | }, 65 | "suggest": { 66 | "modelflow-ai/chat": "Library to handle chat requests.", 67 | "modelflow-ai/completion": "Library to handle completion requests.", 68 | "modelflow-ai/embeddings": "Library to manage embeddings.", 69 | "modelflow-ai/experts": "Library that provide experts.", 70 | "modelflow-ai/fireworksai-adapter": "Adapter to interact with fireworksai models.", 71 | "modelflow-ai/image": "Library to generate and manipulate images.", 72 | "modelflow-ai/anthropic-adapter": "Adapter to interact with anthropic models.", 73 | "modelflow-ai/mistral-adapter": "Adapter to interact with mistral models.", 74 | "modelflow-ai/ollama-adapter": "Adapter to interact with ollama models.", 75 | "modelflow-ai/openai-adapter": "Adapter to interact with openai models." 76 | }, 77 | "scripts": { 78 | "test-with-coverage": "@test --coverage-php var/reports/coverage.cov --coverage-cobertura=var/cobertura-coverage.xml --coverage-html var/reports/html --log-junit var/reports/junit.xml", 79 | "test": [ 80 | "Composer\\Config::disableProcessTimeout", 81 | "vendor/bin/phpunit" 82 | ], 83 | "phpstan": "@php vendor/bin/phpstan analyze", 84 | "lint-rector": "@php vendor/bin/rector process --dry-run", 85 | "lint-php-cs": "@php vendor/bin/php-cs-fixer fix --verbose --diff --dry-run", 86 | "lint": [ 87 | "@phpstan", 88 | "@lint-php-cs", 89 | "@lint-rector", 90 | "@lint-composer" 91 | ], 92 | "lint-composer": "@composer validate --strict", 93 | "rector": "@php vendor/bin/rector process", 94 | "php-cs-fix": "@php vendor/bin/php-cs-fixer fix", 95 | "fix": [ 96 | "@rector", 97 | "@php-cs-fix" 98 | ] 99 | }, 100 | "repositories": [ 101 | { 102 | "type": "path", 103 | "url": "./../../packages/*", 104 | "options": { 105 | "symlink": false 106 | } 107 | } 108 | ], 109 | "minimum-stability": "dev", 110 | "config": { 111 | "allow-plugins": { 112 | "phpstan/extension-installer": true, 113 | "php-http/discovery": true, 114 | "asapo/remove-vendor-plugin": true 115 | } 116 | }, 117 | "extra": { 118 | "remove-folders": [ 119 | "modelflow-ai/*/vendor" 120 | ] 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /config/chat_request_handler.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\Chat\AIChatRequestHandler; 17 | use ModelflowAi\Chat\AIChatRequestHandlerInterface; 18 | use ModelflowAi\Chat\ToolInfo\ToolExecutor; 19 | use ModelflowAi\Chat\ToolInfo\ToolExecutorInterface; 20 | use ModelflowAi\Integration\Symfony\DecisionTree\DecisionTreeDecorator; 21 | use ModelflowAi\Integration\Symfony\ModelflowAiBundle; 22 | 23 | /* 24 | * @internal 25 | */ 26 | return static function (ContainerConfigurator $container) { 27 | $container->services() 28 | ->set('modelflow_ai.chat_request_handler.decision_tree', DecisionTreeDecorator::class) 29 | ->args([ 30 | tagged_iterator(ModelflowAiBundle::TAG_CHAT_DECISION_TREE_RULE), 31 | ]); 32 | 33 | $container->services() 34 | ->set('modelflow_ai.chat_request_handler', AIChatRequestHandler::class) 35 | ->args([ 36 | service('modelflow_ai.chat_request_handler.decision_tree'), 37 | ]) 38 | ->alias(AIChatRequestHandlerInterface::class, 'modelflow_ai.chat_request_handler'); 39 | 40 | $container->services() 41 | ->set('modelflow_ai.tool_executor', ToolExecutor::class) 42 | ->alias(ToolExecutorInterface::class, 'modelflow_ai.tool_executor'); 43 | }; 44 | -------------------------------------------------------------------------------- /config/commands.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\Integration\Symfony\Command\ChatCommand; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.command.chat', ChatCommand::class) 24 | ->args([ 25 | service('modelflow_ai.chat_request_handler'), 26 | ]) 27 | ->tag('console.command', ['command' => 'modelflow-ai:chat']); 28 | }; 29 | -------------------------------------------------------------------------------- /config/completion_request_handler.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\Completion\AICompletionRequestHandler; 17 | use ModelflowAi\Completion\AICompletionRequestHandlerInterface; 18 | use ModelflowAi\Integration\Symfony\DecisionTree\DecisionTreeDecorator; 19 | use ModelflowAi\Integration\Symfony\ModelflowAiBundle; 20 | 21 | /* 22 | * @internal 23 | */ 24 | return static function (ContainerConfigurator $container) { 25 | $container->services() 26 | ->set('modelflow_ai.completion_request_handler.decision_tree', DecisionTreeDecorator::class) 27 | ->args([ 28 | tagged_iterator(ModelflowAiBundle::TAG_COMPLETION_DECISION_TREE_RULE), 29 | ]); 30 | 31 | $container->services() 32 | ->set('modelflow_ai.completion_request_handler', AICompletionRequestHandler::class) 33 | ->args([ 34 | service('modelflow_ai.completion_request_handler.decision_tree'), 35 | ]) 36 | ->alias(AICompletionRequestHandlerInterface::class, 'modelflow_ai.completion_request_handler'); 37 | }; 38 | -------------------------------------------------------------------------------- /config/experts.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\Experts\ThreadFactory; 17 | use ModelflowAi\Experts\ThreadFactoryInterface; 18 | 19 | /* 20 | * @internal 21 | */ 22 | return static function (ContainerConfigurator $container) { 23 | $container->services() 24 | ->set('modelflow_ai.experts.thread_factory', ThreadFactory::class) 25 | ->args([ 26 | service('modelflow_ai.chat_request_handler'), 27 | ]) 28 | ->alias(ThreadFactoryInterface::class, 'modelflow_ai.experts.thread_factory'); 29 | }; 30 | -------------------------------------------------------------------------------- /config/image_request_handler.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\DecisionTree\DecisionTreeInterface; 17 | use ModelflowAi\Image\AIImageRequestHandler; 18 | use ModelflowAi\Image\AIImageRequestHandlerInterface; 19 | use ModelflowAi\Image\Middleware\HandleMiddleware; 20 | use ModelflowAi\Integration\Symfony\DecisionTree\DecisionTreeDecorator; 21 | use ModelflowAi\Integration\Symfony\ModelflowAiBundle; 22 | 23 | /* 24 | * @internal 25 | */ 26 | return static function (ContainerConfigurator $container) { 27 | $container->services() 28 | ->set('modelflow_ai.image_request_handler.decision_tree', DecisionTreeDecorator::class) 29 | ->args([ 30 | tagged_iterator(ModelflowAiBundle::TAG_IMAGE_DECISION_TREE_RULE), 31 | ]) 32 | ->alias(DecisionTreeInterface::class, 'modelflow_ai.chat_request_handler.decision_tree'); 33 | 34 | $container->services() 35 | ->set('modelflow_ai.image_request_handler.middleware.handle', HandleMiddleware::class) 36 | ->args([ 37 | service('modelflow_ai.image_request_handler.decision_tree'), 38 | ]); 39 | 40 | $container->services() 41 | ->set('modelflow_ai.image_request_handler', AIImageRequestHandler::class) 42 | ->args([ 43 | service('modelflow_ai.image_request_handler.middleware.handle'), 44 | ]) 45 | ->alias(AIImageRequestHandlerInterface::class, 'modelflow_ai.image_request_handler'); 46 | }; 47 | -------------------------------------------------------------------------------- /config/providers/anthropic/chat.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\AnthropicAdapter\Chat\AnthropicChatAdapterFactory; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.providers.anthropic.chat_adapter_factory', AnthropicChatAdapterFactory::class) 24 | ->args([ 25 | service('modelflow_ai.providers.anthropic.client'), 26 | '%modelflow_ai.providers.anthropic.max_tokens%', 27 | ]); 28 | }; 29 | -------------------------------------------------------------------------------- /config/providers/anthropic/common.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\Anthropic\Anthropic; 17 | use ModelflowAi\Anthropic\ClientInterface; 18 | use ModelflowAi\Anthropic\Factory; 19 | 20 | /* 21 | * @internal 22 | */ 23 | return static function (ContainerConfigurator $container) { 24 | $container->services() 25 | ->set('modelflow_ai.providers.anthropic.client_factory', Factory::class) 26 | ->factory([Anthropic::class, 'factory']) 27 | ->call('withApiKey', ['%modelflow_ai.providers.anthropic.credentials.api_key%']); 28 | 29 | $container->services() 30 | ->set('modelflow_ai.providers.anthropic.client', ClientInterface::class) 31 | ->factory([service('modelflow_ai.providers.anthropic.client_factory'), 'make']); 32 | }; 33 | -------------------------------------------------------------------------------- /config/providers/fireworksai/chat.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\FireworksAiAdapter\Chat\FireworksAiChatAdapterFactory; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.providers.fireworksai.chat_adapter_factory', FireworksAiChatAdapterFactory::class) 24 | ->args([ 25 | service('modelflow_ai.providers.fireworksai.client'), 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /config/providers/fireworksai/common.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\FireworksAiAdapter\ClientFactory; 17 | use OpenAI\Client; 18 | 19 | /* 20 | * @internal 21 | */ 22 | return static function (ContainerConfigurator $container) { 23 | $container->services() 24 | ->set('modelflow_ai.providers.fireworksai.client_factory', ClientFactory::class) 25 | ->factory([ClientFactory::class, 'create']) 26 | ->call('withApiKey', ['%modelflow_ai.providers.fireworksai.credentials.api_key%']); 27 | 28 | $container->services() 29 | ->set('modelflow_ai.providers.fireworksai.client', Client::class) 30 | ->factory([service('modelflow_ai.providers.fireworksai.client_factory'), 'make']); 31 | }; 32 | -------------------------------------------------------------------------------- /config/providers/fireworksai/completion.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | /* 17 | * @internal 18 | */ 19 | 20 | use ModelflowAi\FireworksAiAdapter\Completion\FireworksAiCompletionAdapterFactory; 21 | 22 | return static function (ContainerConfigurator $container) { 23 | $container->services() 24 | ->set('modelflow_ai.providers.fireworksai.completion_adapter_factory', FireworksAiCompletionAdapterFactory::class) 25 | ->args([ 26 | service('modelflow_ai.providers.fireworksai.client'), 27 | '%modelflow_ai.providers.fireworksai.max_tokens%', 28 | ]); 29 | }; 30 | -------------------------------------------------------------------------------- /config/providers/fireworksai/embeddings.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\FireworksAiAdapter\Embeddings\FireworksAiEmbeddingsAdapterFactory; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.providers.fireworksai.embedding_adapter_factory', FireworksAiEmbeddingsAdapterFactory::class) 24 | ->args([ 25 | service('modelflow_ai.providers.fireworksai.client'), 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /config/providers/fireworksai/image.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\FireworksAiAdapter\Image\FireworksAiImageAdapterFactory; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.providers.fireworksai.image_adapter_factory', FireworksAiImageAdapterFactory::class) 24 | ->args([ 25 | service('http_client'), 26 | param('modelflow_ai.providers.fireworksai.credentials.api_key'), 27 | ]); 28 | }; 29 | -------------------------------------------------------------------------------- /config/providers/mistral/chat.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\MistralAdapter\Chat\MistralChatAdapterFactory; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.providers.mistral.chat_adapter_factory', MistralChatAdapterFactory::class) 24 | ->args([ 25 | service('modelflow_ai.providers.mistral.client'), 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /config/providers/mistral/common.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\Mistral\ClientInterface; 17 | use ModelflowAi\Mistral\Factory; 18 | use ModelflowAi\Mistral\Mistral; 19 | 20 | /* 21 | * @internal 22 | */ 23 | return static function (ContainerConfigurator $container) { 24 | $container->services() 25 | ->set('modelflow_ai.providers.mistral.client_factory', Factory::class) 26 | ->factory([Mistral::class, 'factory']) 27 | ->call('withApiKey', ['%modelflow_ai.providers.mistral.credentials.api_key%']); 28 | 29 | $container->services() 30 | ->set('modelflow_ai.providers.mistral.client', ClientInterface::class) 31 | ->factory([service('modelflow_ai.providers.mistral.client_factory'), 'make']); 32 | }; 33 | -------------------------------------------------------------------------------- /config/providers/mistral/embeddings.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\MistralAdapter\Embeddings\MistralEmbeddingsAdapterFactory; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.providers.mistral.embedding_adapter_factory', MistralEmbeddingsAdapterFactory::class) 24 | ->args([ 25 | service('modelflow_ai.providers.mistral.client'), 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /config/providers/ollama/chat.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\OllamaAdapter\Chat\OllamaChatAdapterFactory; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.providers.ollama.chat_adapter_factory', OllamaChatAdapterFactory::class) 24 | ->args([ 25 | service('modelflow_ai.providers.ollama.client'), 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /config/providers/ollama/common.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\Ollama\ClientInterface; 17 | use ModelflowAi\Ollama\Factory; 18 | use ModelflowAi\Ollama\Ollama; 19 | 20 | /* 21 | * @internal 22 | */ 23 | return static function (ContainerConfigurator $container) { 24 | $container->services() 25 | ->set('modelflow_ai.providers.ollama.client_factory', Factory::class) 26 | ->factory([Ollama::class, 'factory']) 27 | ->call('withBaseUrl', ['%modelflow_ai.providers.ollama.url%']); 28 | 29 | $container->services() 30 | ->set('modelflow_ai.providers.ollama.client', ClientInterface::class) 31 | ->factory([service('modelflow_ai.providers.ollama.client_factory'), 'make']); 32 | }; 33 | -------------------------------------------------------------------------------- /config/providers/ollama/completion.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\OllamaAdapter\Completion\OllamaCompletionAdapterFactory; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.providers.ollama.completion_adapter_factory', OllamaCompletionAdapterFactory::class) 24 | ->args([ 25 | service('modelflow_ai.providers.ollama.client'), 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /config/providers/ollama/embeddings.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\OllamaAdapter\Embeddings\OllamaEmbeddingsAdapterFactory; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.providers.ollama.embedding_adapter_factory', OllamaEmbeddingsAdapterFactory::class) 24 | ->args([ 25 | service('modelflow_ai.providers.ollama.client'), 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /config/providers/openai/chat.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\OpenaiAdapter\Chat\OpenaiChatAdapterFactory; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.providers.openai.chat_adapter_factory', OpenaiChatAdapterFactory::class) 24 | ->args([ 25 | service('modelflow_ai.providers.openai.client'), 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /config/providers/openai/common.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use OpenAI\Client; 17 | use OpenAI\Factory; 18 | 19 | /* 20 | * @internal 21 | */ 22 | return static function (ContainerConfigurator $container) { 23 | $container->services() 24 | ->set('modelflow_ai.providers.openai.client_factory', Factory::class) 25 | ->factory([\OpenAI::class, 'factory']) 26 | ->call('withApiKey', ['%modelflow_ai.providers.openai.credentials.api_key%']); 27 | 28 | $container->services() 29 | ->set('modelflow_ai.providers.openai.client', Client::class) 30 | ->factory([service('modelflow_ai.providers.openai.client_factory'), 'make']); 31 | }; 32 | -------------------------------------------------------------------------------- /config/providers/openai/embeddings.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\OpenaiAdapter\Embeddings\OpenaiEmbeddingsAdapterFactory; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.providers.openai.embedding_adapter_factory', OpenaiEmbeddingsAdapterFactory::class) 24 | ->args([ 25 | service('modelflow_ai.providers.openai.client'), 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /config/providers/openai/image.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use ModelflowAi\OpenaiAdapter\Image\OpenaiImageAdapterFactory; 17 | 18 | /* 19 | * @internal 20 | */ 21 | return static function (ContainerConfigurator $container) { 22 | $container->services() 23 | ->set('modelflow_ai.providers.openai.image_adapter_factory', OpenaiImageAdapterFactory::class) 24 | ->args([ 25 | service('http_client'), 26 | service('modelflow_ai.providers.openai.client'), 27 | ]); 28 | }; 29 | -------------------------------------------------------------------------------- /src/Command/ChatCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace ModelflowAi\Integration\Symfony\Command; 15 | 16 | use ModelflowAi\Chat\AIChatRequestHandlerInterface; 17 | use ModelflowAi\Chat\Request\Message\AIChatMessage; 18 | use ModelflowAi\Chat\Request\Message\AIChatMessageRoleEnum; 19 | use ModelflowAi\Chat\Response\AIChatResponse; 20 | use ModelflowAi\DecisionTree\Criteria\PrivacyCriteria; 21 | use ModelflowAi\PromptTemplate\ChatPromptTemplate; 22 | use Symfony\Component\Console\Command\Command; 23 | use Symfony\Component\Console\Input\InputInterface; 24 | use Symfony\Component\Console\Output\OutputInterface; 25 | 26 | final class ChatCommand extends Command 27 | { 28 | public function __construct( 29 | private readonly AIChatRequestHandlerInterface $requestHandler, 30 | ) { 31 | parent::__construct(); 32 | } 33 | 34 | protected function execute(InputInterface $input, OutputInterface $output): int 35 | { 36 | /** @var AIChatResponse $response */ 37 | $response = $this->requestHandler->createRequest(...ChatPromptTemplate::create( 38 | new AIChatMessage(AIChatMessageRoleEnum::SYSTEM, 'You are an {feeling} bot'), 39 | new AIChatMessage(AIChatMessageRoleEnum::USER, 'Hello {where}!'), 40 | )->format(['where' => 'world', 'feeling' => 'angry'])) 41 | ->addCriteria(PrivacyCriteria::HIGH) 42 | ->build() 43 | ->execute(); 44 | 45 | $output->writeln($response->getMessage()->content); 46 | 47 | return 0; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Config/CriteriaContainer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace ModelflowAi\Integration\Symfony\Config; 15 | 16 | use ModelflowAi\DecisionTree\Criteria\CriteriaInterface; 17 | use ModelflowAi\DecisionTree\DecisionEnum; 18 | 19 | final readonly class CriteriaContainer implements CriteriaInterface, \Stringable 20 | { 21 | public function __construct( 22 | private CriteriaInterface $inner, 23 | ) { 24 | } 25 | 26 | public function matches(CriteriaInterface $toMatch): DecisionEnum 27 | { 28 | return $this->inner->matches($toMatch); 29 | } 30 | 31 | public function getValue(): int|string 32 | { 33 | return $this->inner->getValue(); 34 | } 35 | 36 | public function getName(): string 37 | { 38 | return $this->inner->getName(); 39 | } 40 | 41 | public function __toString(): string 42 | { 43 | return \sprintf( 44 | '!php/const %s::%s', 45 | $this->inner::class, 46 | $this->inner->getName(), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Criteria/ModelCriteria.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace ModelflowAi\Integration\Symfony\Criteria; 15 | 16 | use ModelflowAi\DecisionTree\Criteria\CriteriaInterface; 17 | use ModelflowAi\DecisionTree\Criteria\FlagCriteriaTrait; 18 | 19 | enum ModelCriteria: string implements CriteriaInterface 20 | { 21 | use FlagCriteriaTrait; 22 | 23 | // Ollama 24 | case LLAMA2 = 'llama2'; 25 | case LLAMA3 = 'llama3'; 26 | case LLAMA3_2 = 'llama3.2'; 27 | case NEXUSRAVEN = 'nexusraven'; 28 | case LLAVA = 'llava'; 29 | 30 | // OpenAI 31 | case GPT4O = 'gpt4o'; 32 | case GPT4O_MINI = 'gpt4o-mini'; 33 | case GPT4 = 'gpt4'; 34 | case GPT3_5 = 'gpt3.5-turbo'; 35 | case DALL_E_3 = 'dall-e-3'; 36 | case DALL_E_2 = 'dall-e-2'; 37 | 38 | // Mistral 39 | case MISTRAL_TINY = 'mistral-tiny'; 40 | case MISTRAL_SMALL = 'mistral-small-latest'; 41 | case MISTRAL_MEDIUM = 'mistral-medium-latest'; 42 | case MISTRAL_LARGE = 'mistral-large-latest'; 43 | case MISTRAL_NEMO = 'open-mistral-nemo'; 44 | 45 | // Anthropic 46 | case CLAUDE_3_OPUS = 'claude-3-opus-20240229'; 47 | case CLAUDE_3_5_SONNET = 'claude-3-5-sonnet-20241022'; 48 | case CLAUDE_3_SONNET = 'claude-3-sonnet-20240229'; 49 | case CLAUDE_3_5_HAIKU = 'claude-3-5-haiku-20241022'; 50 | case CLAUDE_3_HAIKU = 'claude-3-haiku-20240307'; 51 | 52 | // FireworksAI 53 | case LLAMA3_1_405B_FIREWORKS = 'accounts/fireworks/models/llama-v3p1-405b-instruct'; 54 | case LLAMA3_1_70B_FIREWORKS = 'accounts/fireworks/models/llama-v3p1-70b-instruct'; 55 | case LLAMA3_1_8B_FIREWORKS = 'accounts/fireworks/models/llama-v3p1-8b-instruct'; 56 | case LLAMA3_70B_FIREWORKS = 'accounts/fireworks/models/llama-v3-70b-instruct'; 57 | case FIREFUNCTION_V2_FIREWORKS = 'accounts/fireworks/models/firefunction-v2'; 58 | case MIXTRAL_FIREWORKS = 'accounts/fireworks/models/mixtral-8x22b-instruct'; 59 | case LLAVA_13B_FIREWORKS = 'accounts/fireworks/models/firellava-13b'; 60 | case STABLE_DIFFUSSION_XL_1024_FIREWORKS = 'stable-diffusion-xl-1024-v1-0'; 61 | } 62 | -------------------------------------------------------------------------------- /src/Criteria/ProviderCriteria.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace ModelflowAi\Integration\Symfony\Criteria; 15 | 16 | use ModelflowAi\DecisionTree\Criteria\CriteriaInterface; 17 | use ModelflowAi\DecisionTree\Criteria\FlagCriteriaTrait; 18 | 19 | enum ProviderCriteria: string implements CriteriaInterface 20 | { 21 | use FlagCriteriaTrait; 22 | 23 | case OLLAMA = 'ollama'; 24 | case OPENAI = 'openai'; 25 | case MISTRAL = 'mistral'; 26 | case ANTHROPIC = 'anthropic'; 27 | case FIREWORKSAI = 'fireworksai'; 28 | } 29 | -------------------------------------------------------------------------------- /src/DecisionTree/DecisionTreeDecorator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace ModelflowAi\Integration\Symfony\DecisionTree; 15 | 16 | use ModelflowAi\DecisionTree\Behaviour\CriteriaBehaviour; 17 | use ModelflowAi\DecisionTree\Behaviour\SupportsBehaviour; 18 | use ModelflowAi\DecisionTree\DecisionRuleInterface; 19 | use ModelflowAi\DecisionTree\DecisionTree; 20 | use ModelflowAi\DecisionTree\DecisionTreeInterface; 21 | 22 | /** 23 | * @template T of CriteriaBehaviour 24 | * @template U of SupportsBehaviour 25 | * 26 | * @implements DecisionTreeInterface 27 | */ 28 | final readonly class DecisionTreeDecorator implements DecisionTreeInterface 29 | { 30 | /** 31 | * @var DecisionTreeInterface 32 | */ 33 | private DecisionTreeInterface $inner; 34 | 35 | /** 36 | * @param \Traversable> $rules 37 | */ 38 | public function __construct( 39 | \Traversable $rules, 40 | ) { 41 | $this->inner = new DecisionTree(\iterator_to_array($rules)); 42 | } 43 | 44 | public function determineAdapter(object $request): object 45 | { 46 | return $this->inner->determineAdapter($request); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ModelflowAiBundle.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace ModelflowAi\Integration\Symfony; 15 | 16 | use ModelflowAi\AnthropicAdapter\AnthropicAdapterPackage; 17 | use ModelflowAi\Chat\Adapter\AIChatAdapterInterface; 18 | use ModelflowAi\Chat\ChatPackage; 19 | use ModelflowAi\Completion\Adapter\AICompletionAdapterInterface; 20 | use ModelflowAi\Completion\CompletionPackage; 21 | use ModelflowAi\DecisionTree\Criteria\CapabilityCriteria; 22 | use ModelflowAi\DecisionTree\Criteria\CriteriaInterface; 23 | use ModelflowAi\DecisionTree\Criteria\FeatureCriteria; 24 | use ModelflowAi\DecisionTree\Criteria\PrivacyCriteria; 25 | use ModelflowAi\DecisionTree\DecisionRule; 26 | use ModelflowAi\Embeddings\Adapter\Cache\CacheEmbeddingAdapter; 27 | use ModelflowAi\Embeddings\Adapter\EmbeddingAdapterInterface; 28 | use ModelflowAi\Embeddings\EmbeddingsPackage; 29 | use ModelflowAi\Embeddings\Formatter\EmbeddingFormatter; 30 | use ModelflowAi\Embeddings\Generator\EmbeddingGenerator; 31 | use ModelflowAi\Embeddings\Splitter\EmbeddingSplitter; 32 | use ModelflowAi\Experts\Expert; 33 | use ModelflowAi\Experts\ResponseFormat\JsonSchemaResponseFormat; 34 | use ModelflowAi\Image\Adapter\AIImageAdapterInterface; 35 | use ModelflowAi\Image\ImagePackage; 36 | use ModelflowAi\Integration\Symfony\Config\CriteriaContainer; 37 | use ModelflowAi\Integration\Symfony\Criteria\ModelCriteria; 38 | use ModelflowAi\Integration\Symfony\Criteria\ProviderCriteria; 39 | use ModelflowAi\MistralAdapter\MistralAdapterPackage; 40 | use ModelflowAi\OllamaAdapter\OllamaAdapterPackage; 41 | use ModelflowAi\OpenaiAdapter\OpenAiAdapterPackage; 42 | use Symfony\Bundle\FrameworkBundle\Console\Application; 43 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; 44 | use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; 45 | use Symfony\Component\DependencyInjection\ContainerBuilder; 46 | use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; 47 | 48 | use function Symfony\Component\DependencyInjection\Loader\Configurator\service; 49 | 50 | use Symfony\Component\HttpKernel\Bundle\AbstractBundle; 51 | use Symfony\Component\HttpKernel\KernelInterface; 52 | 53 | class ModelflowAiBundle extends AbstractBundle 54 | { 55 | final public const TAG_IMAGE_DECISION_TREE_RULE = 'modelflow_ai.image_request_handler.decision_tree.rule'; 56 | final public const TAG_CHAT_DECISION_TREE_RULE = 'modelflow_ai.chat_request_handler.decision_tree.rule'; 57 | final public const TAG_COMPLETION_DECISION_TREE_RULE = 'modelflow_ai.completion_request_handler.decision_tree.rule'; 58 | 59 | protected string $extensionAlias = 'modelflow_ai'; 60 | 61 | final public const DEFAULT_ADAPTER_KEY_ORDER = [ 62 | 'enabled', 63 | 'model', 64 | 'provider', 65 | 'chat', 66 | 'completion', 67 | 'tools', 68 | 'image_to_text', 69 | 'text_to_image', 70 | 'criteria', 71 | 'priority', 72 | ]; 73 | 74 | final public const DEFAULT_VALUES = [ 75 | 'gpt4o_mini' => [ 76 | 'provider' => ProviderCriteria::OPENAI->value, 77 | 'model' => ModelCriteria::GPT4O_MINI->value, 78 | 'chat' => true, 79 | 'completion' => false, 80 | 'stream' => true, 81 | 'tools' => true, 82 | 'image_to_text' => false, 83 | 'text_to_image' => false, 84 | 'criteria' => [ 85 | ModelCriteria::GPT4O_MINI, 86 | ProviderCriteria::OPENAI, 87 | CapabilityCriteria::INTERMEDIATE, 88 | ], 89 | ], 90 | 'gpt4o' => [ 91 | 'provider' => ProviderCriteria::OPENAI->value, 92 | 'model' => ModelCriteria::GPT4O->value, 93 | 'chat' => true, 94 | 'completion' => false, 95 | 'stream' => true, 96 | 'tools' => true, 97 | 'image_to_text' => false, 98 | 'text_to_image' => false, 99 | 'criteria' => [ 100 | ModelCriteria::GPT4O, 101 | ProviderCriteria::OPENAI, 102 | CapabilityCriteria::SMART, 103 | ], 104 | ], 105 | 'gpt4' => [ 106 | 'provider' => ProviderCriteria::OPENAI->value, 107 | 'model' => ModelCriteria::GPT4->value, 108 | 'chat' => true, 109 | 'completion' => false, 110 | 'stream' => true, 111 | 'tools' => true, 112 | 'image_to_text' => false, 113 | 'text_to_image' => false, 114 | 'criteria' => [ 115 | ModelCriteria::GPT4, 116 | ProviderCriteria::OPENAI, 117 | CapabilityCriteria::SMART, 118 | ], 119 | ], 120 | 'gpt3.5' => [ 121 | 'provider' => ProviderCriteria::OPENAI->value, 122 | 'model' => ModelCriteria::GPT3_5->value, 123 | 'chat' => true, 124 | 'completion' => false, 125 | 'stream' => true, 126 | 'tools' => true, 127 | 'image_to_text' => false, 128 | 'text_to_image' => false, 129 | 'criteria' => [ 130 | ModelCriteria::GPT3_5, 131 | ProviderCriteria::OPENAI, 132 | CapabilityCriteria::INTERMEDIATE, 133 | ], 134 | ], 135 | 'dall_e_3' => [ 136 | 'provider' => ProviderCriteria::OPENAI->value, 137 | 'model' => ModelCriteria::DALL_E_3->value, 138 | 'chat' => false, 139 | 'completion' => false, 140 | 'stream' => false, 141 | 'tools' => false, 142 | 'image_to_text' => false, 143 | 'text_to_image' => true, 144 | 'criteria' => [ 145 | ModelCriteria::DALL_E_3, 146 | ProviderCriteria::OPENAI, 147 | CapabilityCriteria::BASIC, 148 | ], 149 | ], 150 | 'dall_e_2' => [ 151 | 'provider' => ProviderCriteria::OPENAI->value, 152 | 'model' => ModelCriteria::DALL_E_2->value, 153 | 'chat' => false, 154 | 'completion' => false, 155 | 'stream' => false, 156 | 'tools' => false, 157 | 'image_to_text' => false, 158 | 'text_to_image' => true, 159 | 'criteria' => [ 160 | ModelCriteria::DALL_E_2, 161 | ProviderCriteria::OPENAI, 162 | CapabilityCriteria::BASIC, 163 | ], 164 | ], 165 | 'mistral_tiny' => [ 166 | 'provider' => ProviderCriteria::MISTRAL->value, 167 | 'model' => ModelCriteria::MISTRAL_TINY->value, 168 | 'chat' => true, 169 | 'completion' => false, 170 | 'stream' => true, 171 | 'tools' => false, 172 | 'image_to_text' => false, 173 | 'text_to_image' => false, 174 | 'criteria' => [ 175 | ModelCriteria::MISTRAL_TINY, 176 | ProviderCriteria::MISTRAL, 177 | CapabilityCriteria::BASIC, 178 | ], 179 | ], 180 | 'mistral_small' => [ 181 | 'provider' => ProviderCriteria::MISTRAL->value, 182 | 'model' => ModelCriteria::MISTRAL_SMALL->value, 183 | 'chat' => true, 184 | 'completion' => false, 185 | 'stream' => true, 186 | 'tools' => false, 187 | 'image_to_text' => false, 188 | 'text_to_image' => false, 189 | 'criteria' => [ 190 | ModelCriteria::MISTRAL_SMALL, 191 | ProviderCriteria::MISTRAL, 192 | CapabilityCriteria::INTERMEDIATE, 193 | ], 194 | ], 195 | 'mistral_medium' => [ 196 | 'provider' => ProviderCriteria::MISTRAL->value, 197 | 'model' => ModelCriteria::MISTRAL_MEDIUM->value, 198 | 'chat' => true, 199 | 'completion' => false, 200 | 'stream' => true, 201 | 'tools' => false, 202 | 'image_to_text' => false, 203 | 'text_to_image' => false, 204 | 'criteria' => [ 205 | ModelCriteria::MISTRAL_MEDIUM, 206 | ProviderCriteria::MISTRAL, 207 | CapabilityCriteria::ADVANCED, 208 | ], 209 | ], 210 | 'mistral_nemo' => [ 211 | 'provider' => ProviderCriteria::MISTRAL->value, 212 | 'model' => ModelCriteria::MISTRAL_NEMO->value, 213 | 'chat' => true, 214 | 'completion' => false, 215 | 'stream' => true, 216 | 'tools' => true, 217 | 'image_to_text' => false, 218 | 'text_to_image' => false, 219 | 'criteria' => [ 220 | ModelCriteria::MISTRAL_NEMO, 221 | ProviderCriteria::MISTRAL, 222 | CapabilityCriteria::INTERMEDIATE, 223 | ], 224 | ], 225 | 'mistral_large' => [ 226 | 'provider' => ProviderCriteria::MISTRAL->value, 227 | 'model' => ModelCriteria::MISTRAL_LARGE->value, 228 | 'chat' => true, 229 | 'completion' => false, 230 | 'stream' => true, 231 | 'tools' => true, 232 | 'image_to_text' => false, 233 | 'text_to_image' => false, 234 | 'criteria' => [ 235 | ModelCriteria::MISTRAL_LARGE, 236 | ProviderCriteria::MISTRAL, 237 | CapabilityCriteria::SMART, 238 | ], 239 | ], 240 | 'llama2' => [ 241 | 'provider' => ProviderCriteria::OLLAMA->value, 242 | 'model' => ModelCriteria::LLAMA2->value, 243 | 'chat' => true, 244 | 'completion' => true, 245 | 'stream' => true, 246 | 'tools' => false, 247 | 'image_to_text' => false, 248 | 'text_to_image' => false, 249 | 'criteria' => [ 250 | ModelCriteria::LLAMA2, 251 | ProviderCriteria::OLLAMA, 252 | CapabilityCriteria::BASIC, 253 | ], 254 | ], 255 | 'llama3' => [ 256 | 'provider' => ProviderCriteria::OLLAMA->value, 257 | 'model' => ModelCriteria::LLAMA3->value, 258 | 'chat' => true, 259 | 'completion' => true, 260 | 'stream' => true, 261 | 'tools' => false, 262 | 'image_to_text' => false, 263 | 'text_to_image' => false, 264 | 'criteria' => [ 265 | ModelCriteria::LLAMA3, 266 | ProviderCriteria::OLLAMA, 267 | CapabilityCriteria::BASIC, 268 | ], 269 | ], 270 | 'llama3_2' => [ 271 | 'provider' => ProviderCriteria::OLLAMA->value, 272 | 'model' => ModelCriteria::LLAMA3_2->value, 273 | 'chat' => true, 274 | 'completion' => true, 275 | 'stream' => true, 276 | 'tools' => false, 277 | 'image_to_text' => false, 278 | 'text_to_image' => false, 279 | 'criteria' => [ 280 | ModelCriteria::LLAMA3_2, 281 | ProviderCriteria::OLLAMA, 282 | CapabilityCriteria::BASIC, 283 | ], 284 | ], 285 | 'nexusraven' => [ 286 | 'provider' => ProviderCriteria::OLLAMA->value, 287 | 'model' => ModelCriteria::NEXUSRAVEN->value, 288 | 'chat' => true, 289 | 'completion' => true, 290 | 'stream' => true, 291 | 'tools' => false, 292 | 'image_to_text' => false, 293 | 'text_to_image' => false, 294 | 'criteria' => [ 295 | ModelCriteria::NEXUSRAVEN, 296 | ProviderCriteria::OLLAMA, 297 | CapabilityCriteria::BASIC, 298 | ], 299 | ], 300 | 'llava' => [ 301 | 'provider' => ProviderCriteria::OLLAMA->value, 302 | 'model' => ModelCriteria::LLAVA->value, 303 | 'chat' => true, 304 | 'completion' => true, 305 | 'stream' => true, 306 | 'tools' => false, 307 | 'image_to_text' => true, 308 | 'text_to_image' => false, 309 | 'criteria' => [ 310 | ModelCriteria::LLAVA, 311 | ProviderCriteria::OLLAMA, 312 | CapabilityCriteria::BASIC, 313 | ], 314 | ], 315 | 'claude_3_5_sonnet' => [ 316 | 'provider' => ProviderCriteria::ANTHROPIC->value, 317 | 'model' => ModelCriteria::CLAUDE_3_5_SONNET->value, 318 | 'chat' => true, 319 | 'completion' => false, 320 | 'stream' => true, 321 | 'tools' => false, 322 | 'image_to_text' => true, 323 | 'text_to_image' => false, 324 | 'criteria' => [ 325 | ModelCriteria::CLAUDE_3_5_SONNET, 326 | ProviderCriteria::ANTHROPIC, 327 | CapabilityCriteria::SMART, 328 | ], 329 | ], 330 | 'claude_3_opus' => [ 331 | 'provider' => ProviderCriteria::ANTHROPIC->value, 332 | 'model' => ModelCriteria::CLAUDE_3_OPUS->value, 333 | 'chat' => true, 334 | 'completion' => false, 335 | 'stream' => true, 336 | 'tools' => false, 337 | 'image_to_text' => true, 338 | 'text_to_image' => false, 339 | 'criteria' => [ 340 | ModelCriteria::CLAUDE_3_OPUS, 341 | ProviderCriteria::ANTHROPIC, 342 | CapabilityCriteria::SMART, 343 | ], 344 | ], 345 | 'claude_3_sonnet' => [ 346 | 'provider' => ProviderCriteria::ANTHROPIC->value, 347 | 'model' => ModelCriteria::CLAUDE_3_SONNET->value, 348 | 'chat' => true, 349 | 'completion' => false, 350 | 'stream' => true, 351 | 'tools' => false, 352 | 'image_to_text' => true, 353 | 'text_to_image' => false, 354 | 'criteria' => [ 355 | ModelCriteria::CLAUDE_3_SONNET, 356 | ProviderCriteria::ANTHROPIC, 357 | CapabilityCriteria::ADVANCED, 358 | ], 359 | ], 360 | 'claude_3_5_haiku' => [ 361 | 'provider' => ProviderCriteria::ANTHROPIC->value, 362 | 'model' => ModelCriteria::CLAUDE_3_5_HAIKU->value, 363 | 'chat' => true, 364 | 'completion' => false, 365 | 'stream' => true, 366 | 'tools' => false, 367 | 'image_to_text' => true, 368 | 'text_to_image' => false, 369 | 'criteria' => [ 370 | ModelCriteria::CLAUDE_3_5_HAIKU, 371 | ProviderCriteria::ANTHROPIC, 372 | CapabilityCriteria::BASIC, 373 | ], 374 | ], 375 | 'claude_3_haiku' => [ 376 | 'provider' => ProviderCriteria::ANTHROPIC->value, 377 | 'model' => ModelCriteria::CLAUDE_3_HAIKU->value, 378 | 'chat' => true, 379 | 'completion' => false, 380 | 'stream' => true, 381 | 'tools' => false, 382 | 'image_to_text' => true, 383 | 'text_to_image' => false, 384 | 'criteria' => [ 385 | ModelCriteria::CLAUDE_3_HAIKU, 386 | ProviderCriteria::ANTHROPIC, 387 | CapabilityCriteria::BASIC, 388 | ], 389 | ], 390 | 'fireworksai_llama3_1_405b' => [ 391 | 'provider' => ProviderCriteria::FIREWORKSAI->value, 392 | 'model' => ModelCriteria::LLAMA3_1_405B_FIREWORKS->value, 393 | 'chat' => true, 394 | 'completion' => true, 395 | 'stream' => true, 396 | 'tools' => false, 397 | 'image_to_text' => false, 398 | 'text_to_image' => false, 399 | 'criteria' => [ 400 | ModelCriteria::LLAMA3_1_405B_FIREWORKS, 401 | ProviderCriteria::FIREWORKSAI, 402 | CapabilityCriteria::SMART, 403 | ], 404 | ], 405 | 'fireworksai_llama3_1_70b' => [ 406 | 'provider' => ProviderCriteria::FIREWORKSAI->value, 407 | 'model' => ModelCriteria::LLAMA3_1_70B_FIREWORKS->value, 408 | 'chat' => true, 409 | 'completion' => true, 410 | 'stream' => true, 411 | 'tools' => false, 412 | 'image_to_text' => false, 413 | 'text_to_image' => false, 414 | 'criteria' => [ 415 | ModelCriteria::LLAMA3_1_70B_FIREWORKS, 416 | ProviderCriteria::FIREWORKSAI, 417 | CapabilityCriteria::INTERMEDIATE, 418 | ], 419 | ], 420 | 'fireworksai_llama3_1_8b' => [ 421 | 'provider' => ProviderCriteria::FIREWORKSAI->value, 422 | 'model' => ModelCriteria::LLAMA3_1_8B_FIREWORKS->value, 423 | 'chat' => true, 424 | 'completion' => true, 425 | 'stream' => true, 426 | 'tools' => false, 427 | 'image_to_text' => false, 428 | 'text_to_image' => false, 429 | 'criteria' => [ 430 | ModelCriteria::LLAMA3_1_8B_FIREWORKS, 431 | ProviderCriteria::FIREWORKSAI, 432 | CapabilityCriteria::INTERMEDIATE, 433 | ], 434 | ], 435 | 'fireworksai_llama3_70b' => [ 436 | 'provider' => ProviderCriteria::FIREWORKSAI->value, 437 | 'model' => ModelCriteria::LLAMA3_70B_FIREWORKS->value, 438 | 'chat' => true, 439 | 'completion' => true, 440 | 'stream' => true, 441 | 'tools' => false, 442 | 'image_to_text' => false, 443 | 'text_to_image' => false, 444 | 'criteria' => [ 445 | ModelCriteria::LLAMA3_70B_FIREWORKS, 446 | ProviderCriteria::FIREWORKSAI, 447 | CapabilityCriteria::INTERMEDIATE, 448 | ], 449 | ], 450 | 'fireworksai_mixtral' => [ 451 | 'provider' => ProviderCriteria::FIREWORKSAI->value, 452 | 'model' => ModelCriteria::MIXTRAL_FIREWORKS->value, 453 | 'chat' => true, 454 | 'completion' => true, 455 | 'stream' => true, 456 | 'tools' => false, 457 | 'image_to_text' => false, 458 | 'text_to_image' => false, 459 | 'criteria' => [ 460 | ModelCriteria::MIXTRAL_FIREWORKS, 461 | ProviderCriteria::FIREWORKSAI, 462 | CapabilityCriteria::ADVANCED, 463 | ], 464 | ], 465 | 'fireworksai_firefunction_v2' => [ 466 | 'provider' => ProviderCriteria::FIREWORKSAI->value, 467 | 'model' => ModelCriteria::FIREFUNCTION_V2_FIREWORKS->value, 468 | 'chat' => true, 469 | 'completion' => true, 470 | 'stream' => true, 471 | 'tools' => true, 472 | 'image_to_text' => false, 473 | 'text_to_image' => false, 474 | 'criteria' => [ 475 | ModelCriteria::FIREFUNCTION_V2_FIREWORKS, 476 | ProviderCriteria::FIREWORKSAI, 477 | CapabilityCriteria::INTERMEDIATE, 478 | ], 479 | ], 480 | 'fireworksai_llava_13b' => [ 481 | 'provider' => ProviderCriteria::FIREWORKSAI->value, 482 | 'model' => ModelCriteria::LLAVA_13B_FIREWORKS->value, 483 | 'chat' => true, 484 | 'completion' => true, 485 | 'stream' => true, 486 | 'tools' => false, 487 | 'image_to_text' => true, 488 | 'text_to_image' => false, 489 | 'criteria' => [ 490 | ModelCriteria::LLAVA_13B_FIREWORKS, 491 | ProviderCriteria::FIREWORKSAI, 492 | CapabilityCriteria::BASIC, 493 | ], 494 | ], 495 | 'stable_diffusion_xl_fireworks' => [ 496 | 'provider' => ProviderCriteria::FIREWORKSAI->value, 497 | 'model' => ModelCriteria::STABLE_DIFFUSSION_XL_1024_FIREWORKS->value, 498 | 'chat' => false, 499 | 'completion' => false, 500 | 'stream' => false, 501 | 'tools' => false, 502 | 'image_to_text' => false, 503 | 'text_to_image' => true, 504 | 'criteria' => [ 505 | ModelCriteria::STABLE_DIFFUSSION_XL_1024_FIREWORKS, 506 | ProviderCriteria::FIREWORKSAI, 507 | CapabilityCriteria::BASIC, 508 | ], 509 | ], 510 | ]; 511 | 512 | private function getCriteria(CriteriaInterface $criteria, bool $isReferenceDumping): CriteriaInterface 513 | { 514 | if ($isReferenceDumping) { 515 | return new CriteriaContainer($criteria); 516 | } 517 | 518 | return $criteria; 519 | } 520 | 521 | /** 522 | * @param CriteriaInterface[] $default 523 | */ 524 | public function createCriteriaNode(array $default, bool $isReferenceDumping): ArrayNodeDefinition 525 | { 526 | $nodeDefinition = new ArrayNodeDefinition('criteria'); 527 | $nodeDefinition 528 | ->defaultValue(\array_map( 529 | fn (CriteriaInterface $criteria) => $this->getCriteria(PrivacyCriteria::LOW, $isReferenceDumping), 530 | $default, 531 | )); 532 | $nodeDefinition 533 | ->beforeNormalization() 534 | ->ifArray() 535 | ->then(function ($value) use ($isReferenceDumping): array { 536 | $result = []; 537 | foreach ($value as $item) { 538 | if ($item instanceof CriteriaInterface) { 539 | $result[] = $this->getCriteria($item, $isReferenceDumping); 540 | } else { 541 | $result[] = $item; 542 | } 543 | } 544 | 545 | return $result; 546 | }) 547 | ->end(); 548 | $nodeDefinition 549 | ->variablePrototype() 550 | ->validate() 551 | ->ifTrue(static fn ($value): bool => !$value instanceof CriteriaInterface) 552 | ->thenInvalid('The value has to be an instance of CriteriaInterface') 553 | ->end() 554 | ->end(); 555 | 556 | return $nodeDefinition; 557 | } 558 | 559 | public function configure(DefinitionConfigurator $definition): void 560 | { 561 | // @phpstan-ignore-next-line 562 | $arguments = $argv ?? $_SERVER['argv'] ?? null; 563 | 564 | $isReferenceDumping = false; 565 | $container = $this->container ?? null; 566 | if ($container && $arguments) { 567 | /** @var KernelInterface $kernel */ 568 | $kernel = $container->get('kernel'); 569 | $application = new Application($kernel); 570 | $command = $application->find($arguments[1] ?? null); 571 | $isReferenceDumping = 'config:dump-reference' === $command->getName(); 572 | } 573 | 574 | $adapters = []; 575 | 576 | // @phpstan-ignore-next-line 577 | $definition->rootNode() 578 | ->children() 579 | ->arrayNode('providers') 580 | ->children() 581 | ->arrayNode('openai') 582 | ->children() 583 | ->booleanNode('enabled')->defaultFalse()->end() 584 | ->arrayNode('credentials') 585 | ->isRequired() 586 | ->children() 587 | ->scalarNode('api_key')->isRequired()->end() 588 | ->end() 589 | ->end() 590 | ->append($this->createCriteriaNode([PrivacyCriteria::LOW], $isReferenceDumping)) 591 | ->end() 592 | ->end() 593 | ->arrayNode('mistral') 594 | ->children() 595 | ->booleanNode('enabled')->defaultFalse()->end() 596 | ->arrayNode('credentials') 597 | ->isRequired() 598 | ->children() 599 | ->scalarNode('api_key')->isRequired()->end() 600 | ->end() 601 | ->end() 602 | ->append($this->createCriteriaNode([PrivacyCriteria::MEDIUM], $isReferenceDumping)) 603 | ->end() 604 | ->end() 605 | ->arrayNode('anthropic') 606 | ->children() 607 | ->booleanNode('enabled')->defaultFalse()->end() 608 | ->arrayNode('credentials') 609 | ->isRequired() 610 | ->children() 611 | ->scalarNode('api_key')->isRequired()->end() 612 | ->end() 613 | ->end() 614 | ->integerNode('max_tokens')->defaultValue(1024)->end() 615 | ->append($this->createCriteriaNode([PrivacyCriteria::LOW], $isReferenceDumping)) 616 | ->end() 617 | ->end() 618 | ->arrayNode('fireworksai') 619 | ->children() 620 | ->booleanNode('enabled')->defaultFalse()->end() 621 | ->arrayNode('credentials') 622 | ->isRequired() 623 | ->children() 624 | ->scalarNode('api_key')->isRequired()->end() 625 | ->end() 626 | ->end() 627 | ->integerNode('max_tokens')->defaultValue(1024)->end() 628 | ->append($this->createCriteriaNode([PrivacyCriteria::LOW], $isReferenceDumping)) 629 | ->end() 630 | ->end() 631 | ->arrayNode('ollama') 632 | ->children() 633 | ->booleanNode('enabled')->defaultFalse()->end() 634 | ->scalarNode('url') 635 | ->defaultValue('http://localhost:11434/api/') 636 | ->validate() 637 | ->ifTrue(static fn ($value): bool => !\filter_var($value, \FILTER_VALIDATE_URL)) 638 | ->thenInvalid('The value has to be a valid URL') 639 | ->end() 640 | ->beforeNormalization() 641 | ->ifString() 642 | ->then(static fn ($value): string => \rtrim((string) $value, '/') . '/') 643 | ->end() 644 | ->end() 645 | ->append($this->createCriteriaNode([PrivacyCriteria::HIGH], $isReferenceDumping)) 646 | ->end() 647 | ->end() 648 | ->arrayNode('custom') 649 | ->arrayPrototype() 650 | ->children() 651 | ->scalarNode('chat_factory')->end() 652 | ->scalarNode('completion_factory')->end() 653 | ->scalarNode('image_factory')->end() 654 | ->append($this->createCriteriaNode([], $isReferenceDumping)) 655 | ->end() 656 | ->end() 657 | ->end() 658 | ->end() 659 | ->end() 660 | ->arrayNode('adapters') 661 | ->defaultValue([]) 662 | ->info('You can configure your own adapter here or use a preconfigured one (see examples) and enable it.') 663 | ->example(self::DEFAULT_VALUES) 664 | ->beforeNormalization() 665 | ->ifArray() 666 | ->then(static function ($value) use (&$adapters): array { 667 | foreach ($value as $key => $item) { 668 | $value[$key]['key'] = $key; 669 | $adapters[$key] = $item; 670 | } 671 | 672 | return $value; 673 | }) 674 | ->end() 675 | ->arrayPrototype() 676 | ->beforeNormalization() 677 | ->ifArray() 678 | ->then(static function ($value): array { 679 | $key = $value['key']; 680 | unset($value['key']); 681 | 682 | $explicitlyDisabled = ($value['enabled'] ?? null) === false; 683 | $enabled = $value['enabled'] ?? false; 684 | unset($value['enabled']); 685 | 686 | if (!$explicitlyDisabled && 0 !== \count($value)) { 687 | $enabled = true; 688 | } 689 | 690 | $value = \array_merge(self::DEFAULT_VALUES[$key] ?? [], $value); 691 | $value['enabled'] = $enabled; 692 | 693 | \uksort($value, fn ($key1, $key2) => (\array_search($key1, self::DEFAULT_ADAPTER_KEY_ORDER, true) > \array_search($key2, self::DEFAULT_ADAPTER_KEY_ORDER, true)) ? 1 : -1); 694 | 695 | return $value; 696 | }) 697 | ->end() 698 | ->children() 699 | ->booleanNode('enabled')->defaultFalse()->end() 700 | ->scalarNode('provider')->isRequired()->end() 701 | ->scalarNode('model')->isRequired()->end() 702 | ->integerNode('priority')->defaultValue(0)->end() 703 | ->booleanNode('chat')->defaultFalse()->end() 704 | ->booleanNode('completion')->defaultFalse()->end() 705 | ->booleanNode('stream')->defaultFalse()->end() 706 | ->booleanNode('tools')->defaultFalse()->end() 707 | ->booleanNode('image_to_text')->defaultFalse()->end() 708 | ->booleanNode('text_to_image')->defaultFalse()->end() 709 | ->append($this->createCriteriaNode([], $isReferenceDumping)) 710 | ->end() 711 | ->end() 712 | ->end() 713 | ->arrayNode('embeddings') 714 | ->children() 715 | ->arrayNode('generators') 716 | ->defaultValue([]) 717 | ->arrayPrototype() 718 | ->children() 719 | ->booleanNode('enabled')->defaultFalse()->end() 720 | ->scalarNode('provider')->end() 721 | ->scalarNode('model')->end() 722 | ->arrayNode('splitter') 723 | ->addDefaultsIfNotSet() 724 | ->children() 725 | ->integerNode('max_length')->defaultValue(1000)->end() 726 | ->scalarNode('separator')->defaultValue(' ')->end() 727 | ->end() 728 | ->end() 729 | ->arrayNode('cache') 730 | ->children() 731 | ->booleanNode('enabled')->defaultFalse()->end() 732 | ->scalarNode('cache_pool')->end() 733 | ->end() 734 | ->end() 735 | ->end() 736 | ->end() 737 | ->end() 738 | ->end() 739 | ->end() 740 | ->arrayNode('experts') 741 | ->defaultValue([]) 742 | ->info('You can configure your experts here.') 743 | ->arrayPrototype() 744 | ->children() 745 | ->scalarNode('name')->isRequired()->end() 746 | ->scalarNode('description')->isRequired()->end() 747 | ->scalarNode('instructions')->isRequired()->end() 748 | ->arrayNode('response_format') 749 | ->children() 750 | ->enumNode('type')->values(['json_schema'])->isRequired()->end() 751 | ->variableNode('schema')->end() 752 | ->end() 753 | ->end() 754 | ->append($this->createCriteriaNode([], $isReferenceDumping)) 755 | ->end() 756 | ->end() 757 | ->end() 758 | ->end(); 759 | } 760 | 761 | /** 762 | * @param array $array 763 | * 764 | * @return array 765 | */ 766 | private function flattenArray(array $array, string $prefix = ''): array 767 | { 768 | $result = []; 769 | foreach ($array as $key => $value) { 770 | if (\is_array($value)) { 771 | $result = \array_merge($result, $this->flattenArray($value, $prefix . $key . '.')); 772 | } else { 773 | $result[$prefix . $key] = $value; 774 | } 775 | } 776 | 777 | return $result; 778 | } 779 | 780 | /** 781 | * @param array{ 782 | * providers?: array{ 783 | * openai: array{ 784 | * enabled: bool, 785 | * credentials: array{ 786 | * api_key: string 787 | * }, 788 | * criteria: CriteriaInterface[] 789 | * }, 790 | * mistral: array{ 791 | * enabled: bool, 792 | * credentials: array{ 793 | * api_key: string 794 | * }, 795 | * criteria: CriteriaInterface[] 796 | * }, 797 | * anthropic: array{ 798 | * enabled: bool, 799 | * credentials: array{ 800 | * api_key: string 801 | * }, 802 | * max_tokens: int, 803 | * criteria: CriteriaInterface[] 804 | * }, 805 | * fireworksai: array{ 806 | * enabled: bool, 807 | * credentials: array{ 808 | * api_key: string 809 | * }, 810 | * max_tokens: int, 811 | * criteria: CriteriaInterface[] 812 | * }, 813 | * ollama: array{ 814 | * enabled: bool, 815 | * url: string, 816 | * criteria: CriteriaInterface[] 817 | * }, 818 | * custom?: array 824 | * }, 825 | * adapters?: array, 838 | * embeddings?: array{ 839 | * generators: array 852 | * }, 853 | * experts?: array, 863 | * } $config 864 | */ 865 | public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void 866 | { 867 | $providerConfig = $this->flattenArray($config['providers'] ?? []); 868 | foreach ($providerConfig as $key => $value) { 869 | $container->parameters() 870 | ->set('modelflow_ai.providers.' . $key, $value); 871 | } 872 | 873 | $container->import(\dirname(__DIR__) . '/config/commands.php'); 874 | 875 | $providers = $config['providers'] ?? []; 876 | $customProviders = $providers['custom'] ?? []; 877 | unset($providers['custom']); 878 | 879 | $adapters = \array_filter($config['adapters'] ?? [], fn (array $adapter) => $adapter['enabled']); 880 | $providers = \array_filter($providers, fn (array $provider) => $provider['enabled']); 881 | 882 | $generators = $config['embeddings']['generators'] ?? []; 883 | 884 | $chatAdapters = []; 885 | $completionAdapters = []; 886 | $imageAdapters = []; 887 | $configFiles = []; 888 | foreach ($adapters as $key => $adapter) { 889 | $tmpConfigFiles[] = $adapter['provider'] . '/common.php'; 890 | $isCustomProvider = \array_key_exists($adapter['provider'], $customProviders); 891 | 892 | if ($adapter['chat']) { 893 | $chatAdapters[] = $key; 894 | $tmpConfigFiles[] = $adapter['provider'] . '/chat.php'; 895 | } 896 | 897 | if ($adapter['completion']) { 898 | $completionAdapters[] = $key; 899 | $tmpConfigFiles[] = $adapter['provider'] . '/completion.php'; 900 | } 901 | 902 | if ($adapter['text_to_image']) { 903 | $imageAdapters[] = $key; 904 | $tmpConfigFiles[] = $adapter['provider'] . '/image.php'; 905 | } 906 | 907 | if (!$isCustomProvider) { 908 | $configFiles = \array_merge($configFiles, $tmpConfigFiles); 909 | } 910 | } 911 | 912 | foreach ($generators as $generator) { 913 | $configFiles[] = $generator['provider'] . '/embeddings.php'; 914 | } 915 | 916 | if ([] !== $chatAdapters && !\class_exists(ChatPackage::class)) { 917 | $chatAdapters = []; 918 | } 919 | 920 | if ([] !== $completionAdapters && !\class_exists(CompletionPackage::class)) { 921 | $completionAdapters = []; 922 | } 923 | 924 | if ([] !== $imageAdapters && !\class_exists(ImagePackage::class)) { 925 | $imageAdapters = []; 926 | } 927 | 928 | if (\count($generators) > 0 && !\class_exists(EmbeddingsPackage::class)) { 929 | throw new \Exception('Embeddings package is enabled but the package is not installed. Please install it with composer require modelflow-ai/embeddings'); 930 | } 931 | 932 | $container->parameters() 933 | ->set('modelflow_ai.adapters', $adapters) 934 | ->set('modelflow_ai.providers', $providers); 935 | 936 | if (($providers['openai']['enabled'] ?? false) && !\class_exists(OpenAiAdapterPackage::class)) { 937 | throw new \Exception('OpenAi adapter is enabled but the OpenAi adapter library is not installed. Please install it with composer require modelflow-ai/openai-adapter'); 938 | } 939 | 940 | if (($providers['mistral']['enabled'] ?? false) && !\class_exists(MistralAdapterPackage::class)) { 941 | throw new \Exception('Mistral adapter is enabled but the Mistral adapter library is not installed. Please install it with composer require modelflow-ai/mistral-adapter'); 942 | } 943 | 944 | if (($providers['anthropic']['enabled'] ?? false) && !\class_exists(AnthropicAdapterPackage::class)) { 945 | throw new \Exception('Anthropic adapter is enabled but the Anthropic adapter library is not installed. Please install it with composer require modelflow-ai/anthropic-adapter'); 946 | } 947 | 948 | if (($providers['ollama']['enabled'] ?? false) && !\class_exists(OllamaAdapterPackage::class)) { 949 | throw new \Exception('Ollama adapter is enabled but the Ollama adapter library is not installed. Please install it with composer require modelflow-ai/ollama-adapter'); 950 | } 951 | 952 | foreach (\array_unique($configFiles) as $configFile) { 953 | $container->import(\dirname(__DIR__) . '/config/providers/' . $configFile); 954 | } 955 | 956 | $container->import(\dirname(__DIR__) . '/config/chat_request_handler.php'); 957 | 958 | foreach ($chatAdapters as $key) { 959 | $adapter = $adapters[$key] ?? null; 960 | if (!$adapter) { 961 | throw new \Exception('Chat adapter ' . $key . ' is enabled but not configured.'); 962 | } 963 | 964 | $provider = $providers[$adapter['provider']] ?? $customProviders[$adapter['provider']] ?? null; 965 | if (!$provider) { 966 | throw new \Exception('Chat adapter ' . $key . ' is enabled but the provider ' . $adapter['provider'] . ' is not enabled.'); 967 | } 968 | 969 | $factory = $customProviders[$adapter['provider']]['chat_factory'] 970 | ?? 'modelflow_ai.providers.' . $adapter['provider'] . '.chat_adapter_factory'; 971 | 972 | $container->services() 973 | ->set('modelflow_ai.chat_adapter.' . $key . '.adapter', AIChatAdapterInterface::class) 974 | ->factory([service($factory), 'createChatAdapter']) 975 | ->args([ 976 | $adapter, 977 | ]); 978 | 979 | $featureCriteria = []; 980 | if ($adapter['image_to_text']) { 981 | $featureCriteria[] = FeatureCriteria::IMAGE_TO_TEXT; 982 | } 983 | if ($adapter['tools']) { 984 | $featureCriteria[] = FeatureCriteria::TOOLS; 985 | } 986 | if ($adapter['stream']) { 987 | $featureCriteria[] = FeatureCriteria::STREAM; 988 | } 989 | 990 | $container->services() 991 | ->set('modelflow_ai.chat_adapter.' . $key . '.rule', DecisionRule::class) 992 | ->args([ 993 | service('modelflow_ai.chat_adapter.' . $key . '.adapter'), 994 | \array_merge($provider['criteria'], $adapter['criteria'], $featureCriteria), 995 | ]) 996 | ->tag(self::TAG_CHAT_DECISION_TREE_RULE); 997 | } 998 | 999 | $container->import(\dirname(__DIR__) . '/config/completion_request_handler.php'); 1000 | 1001 | foreach ($completionAdapters as $key) { 1002 | $adapter = $adapters[$key] ?? null; 1003 | if (!$adapter) { 1004 | throw new \Exception('Completion adapter ' . $key . ' is enabled but not configured.'); 1005 | } 1006 | 1007 | $provider = $providers[$adapter['provider']] ?? null; 1008 | if (!$provider) { 1009 | throw new \Exception('Completion adapter ' . $key . ' is enabled but the provider ' . $adapter['provider'] . ' is not enabled.'); 1010 | } 1011 | 1012 | $factory = $customProviders[$adapter['provider']]['completion_factory'] 1013 | ?? 'modelflow_ai.providers.' . $adapter['provider'] . '.completion_adapter_factory'; 1014 | 1015 | $container->services() 1016 | ->set('modelflow_ai.completion_adapter.' . $key . '.adapter', AICompletionAdapterInterface::class) 1017 | ->factory([service($factory), 'createCompletionAdapter']) 1018 | ->args([ 1019 | $adapter, 1020 | ]); 1021 | 1022 | $container->services() 1023 | ->set('modelflow_ai.completion_adapter.' . $key . '.rule', DecisionRule::class) 1024 | ->args([ 1025 | service('modelflow_ai.completion_adapter.' . $key . '.adapter'), 1026 | \array_merge($provider['criteria'], $adapter['criteria']), 1027 | ]) 1028 | ->tag(self::TAG_COMPLETION_DECISION_TREE_RULE); 1029 | } 1030 | 1031 | if ([] !== $imageAdapters && !\class_exists(ImagePackage::class)) { 1032 | throw new \Exception( 1033 | 'Image adapters are enabled but the image package is not installed. Please install it with composer require modelflow-ai/image', 1034 | ); 1035 | } 1036 | 1037 | $container->import(\dirname(__DIR__) . '/config/image_request_handler.php'); 1038 | 1039 | foreach ($imageAdapters as $key) { 1040 | $adapter = $adapters[$key] ?? null; 1041 | if (!$adapter) { 1042 | throw new \Exception('Image adapter ' . $key . ' is enabled but not configured.'); 1043 | } 1044 | 1045 | $provider = $providers[$adapter['provider']] ?? null; 1046 | if (!$provider) { 1047 | throw new \Exception('Image adapter ' . $key . ' is enabled but the provider ' . $adapter['provider'] . ' is not enabled.'); 1048 | } 1049 | 1050 | $factory = $customProviders[$adapter['provider']]['image_factory'] 1051 | ?? 'modelflow_ai.providers.' . $adapter['provider'] . '.image_adapter_factory'; 1052 | 1053 | $container->services() 1054 | ->set('modelflow_ai.image_adapter.' . $key . '.adapter', AIImageAdapterInterface::class) 1055 | ->factory([service($factory), 'createImageAdapter']) 1056 | ->args([ 1057 | $adapter, 1058 | ]); 1059 | 1060 | $container->services() 1061 | ->set('modelflow_ai.image_adapter.' . $key . '.rule', DecisionRule::class) 1062 | ->args([ 1063 | service('modelflow_ai.image_adapter.' . $key . '.adapter'), 1064 | \array_merge($provider['criteria'], $adapter['criteria']), 1065 | ]) 1066 | ->tag(self::TAG_IMAGE_DECISION_TREE_RULE); 1067 | } 1068 | 1069 | foreach ($generators as $key => $embedding) { 1070 | $adapterId = $key . '.adapter'; 1071 | $container->services() 1072 | ->set($adapterId, EmbeddingAdapterInterface::class) 1073 | ->factory([service('modelflow_ai.providers.' . $embedding['provider'] . '.embedding_adapter_factory'), 'createEmbeddingGenerator']) 1074 | ->args([ 1075 | $embedding, 1076 | ]); 1077 | 1078 | if ($embedding['cache']['enabled']) { 1079 | $container->services() 1080 | ->set($adapterId . '.cache', CacheEmbeddingAdapter::class) 1081 | ->args([ 1082 | service($adapterId), 1083 | service($embedding['cache']['cache_pool']), 1084 | ]); 1085 | 1086 | $adapterId .= '.cache'; 1087 | } 1088 | 1089 | $container->services() 1090 | ->set($key . '.splitter', EmbeddingSplitter::class) 1091 | ->args([ 1092 | $embedding['splitter']['max_length'], 1093 | $embedding['splitter']['separator'], 1094 | ]); 1095 | 1096 | $container->services() 1097 | ->set($key . '.formatter', EmbeddingFormatter::class); 1098 | 1099 | $container->services() 1100 | ->set($key . '.generator', EmbeddingGenerator::class) 1101 | ->args([ 1102 | service($key . '.splitter'), 1103 | service($key . '.formatter'), 1104 | service($adapterId), 1105 | ]); 1106 | } 1107 | 1108 | $experts = $config['experts'] ?? []; 1109 | if (\count($experts) > 0) { 1110 | if (!\class_exists(Expert::class)) { 1111 | throw new \Exception('Experts package is enabled but the package is not installed. Please install it with composer require modelflow-ai/experts'); 1112 | } 1113 | 1114 | $container->import(\dirname(__DIR__) . '/config/experts.php'); 1115 | } 1116 | 1117 | foreach ($experts as $key => $expert) { 1118 | $responseFormatService = null; 1119 | $responseFormat = $expert['response_format'] ?? null; 1120 | if (null !== $responseFormat && 'json_schema' === $responseFormat['type']) { 1121 | $responseFormatId = 'modelflow_ai.experts.' . $key . '.response_format'; 1122 | $responseFormatService = service($responseFormatId); 1123 | $container->services() 1124 | ->set($responseFormatId, JsonSchemaResponseFormat::class) 1125 | ->args([ 1126 | $responseFormat['schema'], 1127 | ]); 1128 | } 1129 | 1130 | $container->services() 1131 | ->set('modelflow_ai.experts.' . $key, Expert::class) 1132 | ->args([ 1133 | $expert['name'], 1134 | $expert['description'], 1135 | $expert['instructions'], 1136 | $expert['criteria'], 1137 | $responseFormatService, 1138 | ]); 1139 | } 1140 | } 1141 | } 1142 | --------------------------------------------------------------------------------