diff --git a/.env b/.env index 79bdcefe..2ea04325 100644 --- a/.env +++ b/.env @@ -155,3 +155,11 @@ APP_ENV=prod APP_SECRET=a03498528f5a5fc089273ec9ae5b2849 APP_SHARE_DIR=var/share ###< symfony/framework-bundle ### + +###> symfony/ai-generic-platform ### +# GENERIC_BASE_URL=https://api.example.com/v1 +###< symfony/ai-generic-platform ### + +###> symfony/ai-open-router-platform ### +OPENROUTER_API_KEY= +###< symfony/ai-open-router-platform ### diff --git a/composer.json b/composer.json index 9d51033e..c7c52d5c 100644 --- a/composer.json +++ b/composer.json @@ -56,6 +56,9 @@ "scheb/2fa-trusted-device": "^v7.11.0", "shivas/versioning-bundle": "^4.0", "spatie/db-dumper": "^3.3.1", + "symfony/ai-bundle": "^0.8.0", + "symfony/ai-lm-studio-platform": "^0.8.0", + "symfony/ai-open-router-platform": "^0.8.0", "symfony/apache-pack": "^1.0", "symfony/asset": "7.4.*", "symfony/console": "7.4.*", diff --git a/composer.lock b/composer.lock index 3ad11a45..00b5e831 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8fd737684b48f8d24fcad35fce37a297", + "content-hash": "699f421ad81f8a1acacf8e2c4af66491", "packages": [ { "name": "amphp/amp", @@ -7821,6 +7821,58 @@ ], "time": "2025-12-09T10:50:49+00:00" }, + { + "name": "oskarstark/enum-helper", + "version": "1.8.4", + "source": { + "type": "git", + "url": "https://github.com/OskarStark/enum-helper.git", + "reference": "14e185f1cc259d7cd3f61eea17f9b174a886a6da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/OskarStark/enum-helper/zipball/14e185f1cc259d7cd3f61eea17f9b174a886a6da", + "reference": "14e185f1cc259d7cd3f61eea17f9b174a886a6da", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "conflict": { + "phpunit/phpunit": "<10" + }, + "require-dev": { + "ergebnis/php-cs-fixer-config": "^6.58", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^10.5", + "rector/rector": "^2.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "OskarStark\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oskar Stark", + "email": "oskarstark@googlemail.com" + } + ], + "description": "This library provides helpers for several enum operations", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/OskarStark/enum-helper/issues", + "source": "https://github.com/OskarStark/enum-helper/tree/1.8.4" + }, + "time": "2026-02-05T08:59:09+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v3.1.3", @@ -10341,6 +10393,552 @@ ], "time": "2026-03-23T22:56:56+00:00" }, + { + "name": "symfony/ai-bundle", + "version": "v0.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/ai-bundle.git", + "reference": "847365e0f885f8814421e9c94f03ce19e0b54bbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ai-bundle/zipball/847365e0f885f8814421e9c94f03ce19e0b54bbc", + "reference": "847365e0f885f8814421e9c94f03ce19e0b54bbc", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/ai-platform": "^0.8", + "symfony/clock": "^7.3|^8.0", + "symfony/config": "^7.3|^8.0", + "symfony/console": "^7.3|^8.0", + "symfony/dependency-injection": "^7.3|^8.0", + "symfony/framework-bundle": "^7.3|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.3|^8.0" + }, + "require-dev": { + "google/auth": "^1.47", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^11.5.53", + "symfony/ai-agent": "^0.8", + "symfony/ai-ai-ml-api-platform": "^0.8", + "symfony/ai-albert-platform": "^0.8", + "symfony/ai-amazee-ai-platform": "^0.8", + "symfony/ai-anthropic-platform": "^0.8", + "symfony/ai-azure-platform": "^0.8", + "symfony/ai-azure-search-store": "^0.8", + "symfony/ai-bedrock-platform": "^0.8", + "symfony/ai-cache-message-store": "^0.8", + "symfony/ai-cache-platform": "^0.8", + "symfony/ai-cache-store": "^0.8", + "symfony/ai-cartesia-platform": "^0.8", + "symfony/ai-cerebras-platform": "^0.8", + "symfony/ai-chat": "^0.8", + "symfony/ai-chroma-db-store": "^0.8", + "symfony/ai-click-house-store": "^0.8", + "symfony/ai-cloudflare-message-store": "^0.8", + "symfony/ai-cloudflare-store": "^0.8", + "symfony/ai-decart-platform": "^0.8", + "symfony/ai-deep-seek-platform": "^0.8", + "symfony/ai-docker-model-runner-platform": "^0.8", + "symfony/ai-doctrine-message-store": "^0.8", + "symfony/ai-elasticsearch-store": "^0.8", + "symfony/ai-eleven-labs-platform": "^0.8", + "symfony/ai-failover-platform": "^0.8", + "symfony/ai-gemini-platform": "^0.8", + "symfony/ai-generic-platform": "^0.8", + "symfony/ai-hugging-face-platform": "^0.8", + "symfony/ai-lm-studio-platform": "^0.8", + "symfony/ai-manticore-search-store": "^0.8", + "symfony/ai-maria-db-store": "^0.8", + "symfony/ai-meilisearch-message-store": "^0.8", + "symfony/ai-meilisearch-store": "^0.8", + "symfony/ai-meta-platform": "^0.8", + "symfony/ai-milvus-store": "^0.8", + "symfony/ai-mistral-platform": "^0.8", + "symfony/ai-mongo-db-message-store": "^0.8", + "symfony/ai-mongo-db-store": "^0.8", + "symfony/ai-neo4j-store": "^0.8", + "symfony/ai-ollama-platform": "^0.8", + "symfony/ai-open-ai-platform": "^0.8", + "symfony/ai-open-responses-platform": "^0.8", + "symfony/ai-open-router-platform": "^0.8", + "symfony/ai-open-search-store": "^0.8", + "symfony/ai-perplexity-platform": "^0.8", + "symfony/ai-pinecone-store": "^0.8", + "symfony/ai-pogocache-message-store": "^0.8", + "symfony/ai-postgres-store": "^0.8", + "symfony/ai-qdrant-store": "^0.8", + "symfony/ai-redis-message-store": "^0.8", + "symfony/ai-redis-store": "^0.8", + "symfony/ai-replicate-platform": "^0.8", + "symfony/ai-s3vectors-store": "^0.8", + "symfony/ai-scaleway-platform": "^0.8", + "symfony/ai-session-message-store": "^0.8", + "symfony/ai-sqlite-store": "^0.8", + "symfony/ai-store": "^0.8", + "symfony/ai-supabase-store": "^0.8", + "symfony/ai-surreal-db-message-store": "^0.8", + "symfony/ai-surreal-db-store": "^0.8", + "symfony/ai-transformers-php-platform": "^0.8", + "symfony/ai-typesense-store": "^0.8", + "symfony/ai-vektor-store": "^0.8", + "symfony/ai-vertex-ai-platform": "^0.8", + "symfony/ai-voyage-platform": "^0.8", + "symfony/ai-weaviate-store": "^0.8", + "symfony/expression-language": "^7.3|^8.0", + "symfony/security-core": "^7.3|^8.0", + "symfony/translation": "^7.3|^8.0", + "symfony/validator": "^7.3|^8.0" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ai", + "name": "symfony/ai" + } + }, + "autoload": { + "psr-4": { + "Symfony\\AI\\AiBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christopher Hertel", + "email": "mail@christopher-hertel.de" + }, + { + "name": "Oskar Stark", + "email": "oskarstark@googlemail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Integration bundle for Symfony AI components", + "support": { + "source": "https://github.com/symfony/ai-bundle/tree/v0.8.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-20T21:23:24+00:00" + }, + { + "name": "symfony/ai-generic-platform", + "version": "v0.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/ai-generic-platform.git", + "reference": "2e358c0e88c676fad0b61b3df715f9822d29a7e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ai-generic-platform/zipball/2e358c0e88c676fad0b61b3df715f9822d29a7e3", + "reference": "2e358c0e88c676fad0b61b3df715f9822d29a7e3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/ai-platform": "^0.8", + "symfony/http-client": "^7.3|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^11.5.53" + }, + "type": "symfony-ai-platform", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ai", + "name": "symfony/ai" + } + }, + "autoload": { + "psr-4": { + "Symfony\\AI\\Platform\\Bridge\\Generic\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christopher Hertel", + "email": "mail@christopher-hertel.de" + }, + { + "name": "Oskar Stark", + "email": "oskarstark@googlemail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic platform bridge for Symfony AI", + "keywords": [ + "Bridge", + "ai", + "generic", + "platform" + ], + "support": { + "source": "https://github.com/symfony/ai-generic-platform/tree/v0.8.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-20T21:23:24+00:00" + }, + { + "name": "symfony/ai-lm-studio-platform", + "version": "v0.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/ai-lm-studio-platform.git", + "reference": "ad1c046dd9e7d6e474bc86554443e2d9400a7826" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ai-lm-studio-platform/zipball/ad1c046dd9e7d6e474bc86554443e2d9400a7826", + "reference": "ad1c046dd9e7d6e474bc86554443e2d9400a7826", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/ai-generic-platform": "^0.8", + "symfony/ai-platform": "^0.8", + "symfony/http-client": "^7.3|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^11.5.53" + }, + "type": "symfony-ai-platform", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ai", + "name": "symfony/ai" + } + }, + "autoload": { + "psr-4": { + "Symfony\\AI\\Platform\\Bridge\\LmStudio\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christopher Hertel", + "email": "mail@christopher-hertel.de" + }, + { + "name": "Oskar Stark", + "email": "oskarstark@googlemail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "LM Studio platform bridge for Symfony AI", + "keywords": [ + "Bridge", + "ai", + "lmstudio", + "local", + "platform" + ], + "support": { + "source": "https://github.com/symfony/ai-lm-studio-platform/tree/v0.8.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-20T21:23:24+00:00" + }, + { + "name": "symfony/ai-open-router-platform", + "version": "v0.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/ai-open-router-platform.git", + "reference": "eb5ed3176b78bc489bf325c5d6bc4efc255804be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ai-open-router-platform/zipball/eb5ed3176b78bc489bf325c5d6bc4efc255804be", + "reference": "eb5ed3176b78bc489bf325c5d6bc4efc255804be", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/ai-generic-platform": "^0.8", + "symfony/ai-platform": "^0.8", + "symfony/http-client": "^7.3|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^11.5.53" + }, + "type": "symfony-ai-platform", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ai", + "name": "symfony/ai" + } + }, + "autoload": { + "psr-4": { + "Symfony\\AI\\Platform\\Bridge\\OpenRouter\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christopher Hertel", + "email": "mail@christopher-hertel.de" + }, + { + "name": "Oskar Stark", + "email": "oskarstark@googlemail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "OpenRouter platform bridge for Symfony AI", + "keywords": [ + "Bridge", + "OpenRouter", + "ai", + "platform" + ], + "support": { + "source": "https://github.com/symfony/ai-open-router-platform/tree/v0.8.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-20T21:23:24+00:00" + }, + { + "name": "symfony/ai-platform", + "version": "v0.8.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/ai-platform.git", + "reference": "86ed9396f53cad02b5d1ca8092956ea74f65823f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ai-platform/zipball/86ed9396f53cad02b5d1ca8092956ea74f65823f", + "reference": "86ed9396f53cad02b5d1ca8092956ea74f65823f", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "oskarstark/enum-helper": "^1.5", + "php": ">=8.2", + "phpdocumentor/reflection-docblock": "^5.4|^6.0", + "phpstan/phpdoc-parser": "^2.1", + "psr/log": "^3.0", + "symfony/clock": "^7.3|^8.0", + "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/property-access": "^7.3|^8.0", + "symfony/property-info": "^7.3|^8.0", + "symfony/serializer": "^7.3|^8.0", + "symfony/type-info": "^7.3|^8.0", + "symfony/uid": "^7.3|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^11.5.53", + "symfony/cache": "^7.3|^8.0", + "symfony/console": "^7.3|^8.0", + "symfony/dotenv": "^7.3|^8.0", + "symfony/expression-language": "^7.3|^8.0", + "symfony/finder": "^7.3|^8.0", + "symfony/http-client": "^7.3|^8.0", + "symfony/http-client-contracts": "^3.5", + "symfony/intl": "^7.3|^8.0", + "symfony/process": "^7.3|^8.0", + "symfony/validator": "^7.3|^8.0", + "symfony/var-dumper": "^7.3|^8.0", + "symfony/yaml": "^7.3|^8.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/ai", + "name": "symfony/ai" + } + }, + "autoload": { + "psr-4": { + "Symfony\\AI\\Platform\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christopher Hertel", + "email": "mail@christopher-hertel.de" + }, + { + "name": "Oskar Stark", + "email": "oskarstark@googlemail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "PHP library for interacting with AI platform provider.", + "keywords": [ + "Gemini", + "OpenRouter", + "ai", + "aimlapi", + "albert", + "amazeeai", + "anthropic", + "azure", + "bedrock", + "cerebras", + "dockermodelrunner", + "elevenlabs", + "huggingface", + "inference", + "litellm", + "llama", + "lmstudio", + "meta", + "mistral", + "nova", + "ollama", + "openai", + "ovh", + "perplexity", + "replicate", + "speech", + "transformers", + "vertexai", + "voyage" + ], + "support": { + "source": "https://github.com/symfony/ai-platform/tree/v0.8.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-20T21:28:38+00:00" + }, { "name": "symfony/apache-pack", "version": "v1.0.1", @@ -19256,12 +19854,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "f41f65e527608a9a76cfbafd873756ed76c5452d" + "reference": "10b8a93511210c9bae3be31f4fe13c3ff974cad4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/f41f65e527608a9a76cfbafd873756ed76c5452d", - "reference": "f41f65e527608a9a76cfbafd873756ed76c5452d", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/10b8a93511210c9bae3be31f4fe13c3ff974cad4", + "reference": "10b8a93511210c9bae3be31f4fe13c3ff974cad4", "shasum": "" }, "conflict": { @@ -19375,7 +19973,7 @@ "cesnet/simplesamlphp-module-proxystatistics": "<3.1", "chriskacerguis/codeigniter-restserver": "<=2.7.1", "chrome-php/chrome": "<1.14", - "ci4-cms-erp/ci4ms": "<=0.31.3", + "ci4-cms-erp/ci4ms": "<0.31.5", "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", "ckeditor/ckeditor": "<4.25", "clickstorm/cs-seo": ">=6,<6.8|>=7,<7.5|>=8,<8.4|>=9,<9.3", @@ -20304,7 +20902,7 @@ "type": "tidelift" } ], - "time": "2026-04-21T17:25:01+00:00" + "time": "2026-04-22T18:27:19+00:00" }, { "name": "sebastian/cli-parser", diff --git a/config/bundles.php b/config/bundles.php index ae7dc9cc..000c58a1 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -33,4 +33,5 @@ return [ Jbtronics\SettingsBundle\JbtronicsSettingsBundle::class => ['all' => true], Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true], ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], + Symfony\AI\AiBundle\AiBundle::class => ['all' => true], ]; diff --git a/config/packages/ai.yaml b/config/packages/ai.yaml new file mode 100644 index 00000000..89f8e7ae --- /dev/null +++ b/config/packages/ai.yaml @@ -0,0 +1,27 @@ +ai: + platform: + # Inference Platform configuration + # see https://github.com/symfony/ai/tree/main/src/platform#platform-bridges + + # openai: + # api_key: '%env(OPENAI_API_KEY)%' + + agent: + # Agent configuration + # see https://symfony.com/doc/current/ai/bundles/ai-bundle.html + + # default: + # platform: 'ai.platform.openai' + # model: 'gpt-5-mini' + # prompt: | + # You are a pirate and you write funny. + # tools: + # - 'Symfony\AI\Agent\Bridge\Clock\Clock' + + store: + # Store configuration + + # chromadb: + # default: + # client: 'client.service.id' + # collection: 'my_collection' diff --git a/config/packages/ai_generic_platform.yaml b/config/packages/ai_generic_platform.yaml new file mode 100644 index 00000000..c2c2e133 --- /dev/null +++ b/config/packages/ai_generic_platform.yaml @@ -0,0 +1,5 @@ +ai: + platform: + generic: + default: + base_url: '%env(GENERIC_BASE_URL)%' diff --git a/config/packages/ai_lm_studio_platform.yaml b/config/packages/ai_lm_studio_platform.yaml new file mode 100644 index 00000000..0e6e4ac0 --- /dev/null +++ b/config/packages/ai_lm_studio_platform.yaml @@ -0,0 +1,3 @@ +ai: + platform: + lmstudio: null diff --git a/config/packages/ai_open_router_platform.yaml b/config/packages/ai_open_router_platform.yaml new file mode 100644 index 00000000..bef68793 --- /dev/null +++ b/config/packages/ai_open_router_platform.yaml @@ -0,0 +1,4 @@ +ai: + platform: + openrouter: + api_key: '%env(OPENROUTER_API_KEY)%' diff --git a/config/reference.php b/config/reference.php index 77a5d432..71850e24 100644 --- a/config/reference.php +++ b/config/reference.php @@ -2674,6 +2674,465 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * ... * }, * } + * @psalm-type AiConfig = array{ + * platform?: array{ + * albert?: array{ + * api_key?: string|Param, + * base_url?: string|Param, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * amazeeai?: array{ + * base_url?: string|Param, + * api_key?: string|Param, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * anthropic?: array{ + * api_key?: string|Param, + * version?: string|Param, // Default: null + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * cache_retention?: "none"|"short"|"long"|Param, // Prompt cache retention policy for Anthropic models // Default: "short" + * }, + * azure?: array, + * bedrock?: array, + * cache?: array, + * cartesia?: array{ + * api_key?: string|Param, + * version?: string|Param, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * cerebras?: array{ + * api_key?: string|Param, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * cohere?: array{ + * api_key?: string|Param, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * decart?: array{ + * api_key?: string|Param, + * host?: string|Param, // Default: "https://api.decart.ai/v1" + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * deepseek?: array{ + * api_key?: string|Param, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * dockermodelrunner?: array{ + * host_url?: string|Param, // Default: "http://127.0.0.1:12434" + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * elevenlabs?: array{ + * api_key?: string|Param, + * endpoint?: string|Param, // Default: "https://api.elevenlabs.io/v1/" + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * failover?: array, + * rate_limiter?: string|Param, + * }>, + * gemini?: array{ + * api_key?: string|Param, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * generic?: array, + * huggingface?: array{ + * api_key?: string|Param, + * provider?: string|Param, // Default: "hf-inference" + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * lmstudio?: array{ + * host_url?: string|Param, // Default: "http://127.0.0.1:1234" + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * mistral?: array{ + * api_key?: string|Param, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * ollama?: array{ + * endpoint?: string|Param, // Endpoint for Ollama (e.g. "http://127.0.0.1:11434" for local, or a cloud endpoint). If null, the http_client is used as-is and must already be configured with a base URI. + * api_key?: string|Param, // API key for Ollama Cloud authentication (optional for local usage) + * http_client?: string|Param, // Service ID of the HTTP client to use. When "endpoint" is null, this client must be pre-configured (e.g. with a base_uri). // Default: "http_client" + * }, + * openai?: array{ + * api_key?: string|Param, + * region?: scalar|Param|null, // The region for OpenAI API (EU, US, or null for default) // Default: null + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * openrouter?: array{ + * api_key?: string|Param, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * ovh?: array{ + * api_key?: scalar|Param|null, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * perplexity?: array{ + * api_key?: string|Param, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * scaleway?: array{ + * api_key?: scalar|Param|null, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * transformersphp?: array, + * vertexai?: array{ + * location?: string|Param, // Required for the project-scoped endpoint. Must be set together with "project_id". // Default: null + * project_id?: string|Param, // Required for the project-scoped endpoint. Must be set together with "location". // Default: null + * api_key?: string|Param, // When set without "location" and "project_id", uses the global endpoint. Note: API keys only identify the project for billing and do not provide identity-based access control. For production use with IAM, audit logging, or data residency, prefer the project-scoped endpoint with service account authentication. // Default: null + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * voyage?: array{ + * api_key?: string|Param, + * http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client" + * }, + * }, + * model?: array|\Symfony\AI\Platform\Capability|Param>, + * }>>, + * agent?: array, + * }, + * keep_tool_messages?: bool|Param, // Keep tool messages in the conversation history // Default: false + * include_sources?: bool|Param, // Include sources exposed by tools as part of the tool result metadata // Default: false + * fault_tolerant_toolbox?: bool|Param, // Continue the agent run even if a tool call fails // Default: true + * speech?: bool|array{ // Speech (TTS/STT) decorator configuration + * enabled?: bool|Param, // Default: true + * text_to_speech_platform?: string|Param, // Service name of the TTS platform (e.g. ai.platform.elevenlabs). // Default: null + * speech_to_text_platform?: string|Param, // Service name of the STT platform (e.g. ai.platform.openai). // Default: null + * tts_model?: string|Param, // Text-to-speech model name // Default: null + * tts_options?: mixed, // Provider-specific TTS options // Default: [] + * stt_model?: string|Param, // Speech-to-text model name // Default: null + * stt_options?: mixed, // Provider-specific STT options // Default: [] + * }, + * }>, + * multi_agent?: array>, + * fallback?: string|Param, // Service ID of the fallback agent for unmatched requests + * }>, + * store?: array{ + * azuresearch?: array, + * cache?: array, + * chromadb?: array, + * clickhouse?: array, + * cloudflare?: array, + * elasticsearch?: array, + * manticoresearch?: array, + * mariadb?: array, + * meilisearch?: array, + * memory?: array, + * milvus?: array, + * mongodb?: array, + * neo4j?: array, + * opensearch?: array, + * pinecone?: array, + * top_k?: int|Param, + * }>, + * postgres?: array, + * qdrant?: array, + * redis?: array, + * s3vectors?: array, + * vector_bucket_name?: string|Param, + * index_name?: string|Param, + * filter?: array, + * top_k?: int|Param, // Default number of results to return // Default: 3 + * }>, + * sqlite?: array, + * supabase?: array, + * surrealdb?: array, + * typesense?: array, + * weaviate?: array, + * vektor?: array, + * }, + * message_store?: array{ + * cache?: array, + * cloudflare?: array, + * doctrine?: array{ + * dbal?: array, + * }, + * meilisearch?: array, + * memory?: array, + * mongodb?: array, + * pogocache?: array, + * redis?: array, + * session?: array, + * surrealdb?: array, + * }, + * chat?: array, + * vectorizer?: array, + * indexer?: array, + * filters?: list, + * vectorizer?: scalar|Param|null, // Service name of vectorizer // Default: "Symfony\\AI\\Store\\Document\\VectorizerInterface" + * store?: string|Param, // Service name of store // Default: "Symfony\\AI\\Store\\StoreInterface" + * }>, + * retriever?: array, + * } * @psalm-type ConfigType = array{ * imports?: ImportsConfig, * parameters?: ParametersConfig, @@ -2703,6 +3162,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * nelmio_cors?: NelmioCorsConfig, * jbtronics_settings?: JbtronicsSettingsConfig, * api_platform?: ApiPlatformConfig, + * ai?: AiConfig, * "when@dev"?: array{ * imports?: ImportsConfig, * parameters?: ParametersConfig, @@ -2736,6 +3196,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * jbtronics_settings?: JbtronicsSettingsConfig, * jbtronics_translation_editor?: JbtronicsTranslationEditorConfig, * api_platform?: ApiPlatformConfig, + * ai?: AiConfig, * }, * "when@docker"?: array{ * imports?: ImportsConfig, @@ -2766,6 +3227,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * nelmio_cors?: NelmioCorsConfig, * jbtronics_settings?: JbtronicsSettingsConfig, * api_platform?: ApiPlatformConfig, + * ai?: AiConfig, * }, * "when@prod"?: array{ * imports?: ImportsConfig, @@ -2796,6 +3258,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * nelmio_cors?: NelmioCorsConfig, * jbtronics_settings?: JbtronicsSettingsConfig, * api_platform?: ApiPlatformConfig, + * ai?: AiConfig, * }, * "when@test"?: array{ * imports?: ImportsConfig, @@ -2829,6 +3292,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * nelmio_cors?: NelmioCorsConfig, * jbtronics_settings?: JbtronicsSettingsConfig, * api_platform?: ApiPlatformConfig, + * ai?: AiConfig, * }, * ...httpClient = $httpClient->withOptions([ 'timeout' => 30, @@ -68,7 +76,8 @@ class AIInfoExtractor implements InfoProviderInterface public function isActive(): bool { - return !empty($this->settings->apiKey) && $this->settings->enabled; + return true; + //return !empty($this->settings->apiKey) && $this->settings->enabled; } public function searchByKeyword(string $keyword): array @@ -76,28 +85,28 @@ class AIInfoExtractor implements InfoProviderInterface // Treat the keyword as a URL and return a single search result $url = $this->normalizeURL($keyword); - try { - $part = $this->getDetails($url); - return [ - new SearchResultDTO( - provider_key: $this->getProviderKey(), - provider_id: $url, - name: $part->name, - description: $part->description, - category: $part->category, - manufacturer: $part->manufacturer, - mpn: $part->mpn, - preview_image_url: $part->preview_image_url, - manufacturing_status: $part->manufacturing_status, - provider_url: $part->provider_url, - footprint: $part->footprint, - gtin: $part->gtin, - ), - ]; - } catch (\Throwable $e) { - // Return empty array on error - return []; - } + //try { + $part = $this->getDetails($url); + return [ + new SearchResultDTO( + provider_key: $this->getProviderKey(), + provider_id: $url, + name: $part->name, + description: $part->description, + category: $part->category, + manufacturer: $part->manufacturer, + mpn: $part->mpn, + preview_image_url: $part->preview_image_url, + manufacturing_status: $part->manufacturing_status, + provider_url: $part->provider_url, + footprint: $part->footprint, + gtin: $part->gtin, + ), + ]; + //} catch (\Throwable $e) { + // // Return empty array on error + // return []; + //} } public function getDetails(string $id): PartDetailDTO @@ -194,6 +203,108 @@ class AIInfoExtractor implements InfoProviderInterface private function callOpenRouterAPI(string $htmlContent, string $url): string { + $input = new MessageBag( + Message::forSystem($this->buildSystemPrompt()), + Message::ofUser("Extract part information from this webpage content:\n\nURL: $url\n\n$htmlContent") + ); + + $models = $this->aiPlatform->getModelCatalog()->getModels(); + + try { + //'openai/gpt-5-mini' + $result = $this->aiPlatform->invoke('openrouter/auto', $input, [ + 'response_format' => 'json_schema', + 'json_schema' => [ + 'name' => 'clock', + 'strict' => true, + 'schema' => [ + 'type' => 'object', + 'properties' => [ + 'name' => ['type' => 'string', 'description' => 'Product name'], + 'description' => ['type' => 'string', 'description' => 'Product description'], + 'manufacturer' => ['type' => ['string', 'null'], 'description' => 'Manufacturer name'], + 'mpn' => ['type' => ['string', 'null'], 'description' => 'Manufacturer Part Number'], + 'category' => ['type' => ['string', 'null'], 'description' => 'Product category'], + 'manufacturing_status' => ['type' => ['string', 'null'], 'enum' => ['active', 'obsolete', 'nrfnd', 'discontinued', null], 'description' => 'Manufacturing status'], + 'footprint' => ['type' => ['string', 'null'], 'description' => 'Package/footprint type'], + 'mass' => ['type' => ['number', 'null'], 'description' => 'Mass in grams'], + 'parameters' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'name' => ['type' => 'string'], + 'value' => ['type' => 'string'], + 'unit' => ['type' => ['string', 'null']], + ], + 'required' => ['name', 'value'], + ], + ], + 'datasheets' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'url' => ['type' => 'string'], + 'description' => ['type' => 'string'], + ], + 'required' => ['url'], + ], + ], + 'images' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'url' => ['type' => 'string'], + 'description' => ['type' => 'string'], + ], + 'required' => ['url'], + ], + ], + 'vendor_infos' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'distributor_name' => ['type' => 'string'], + 'order_number' => ['type' => ['string', 'null']], + 'product_url' => ['type' => 'string'], + 'prices' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'minimum_quantity' => ['type' => 'integer'], + 'price' => ['type' => 'number'], + 'currency' => ['type' => 'string'], + ], + 'required' => ['minimum_quantity', 'price', 'currency'], + ], + ], + ], + 'required' => ['distributor_name', 'product_url'], + ], + ], + 'manufacturer_product_url' => ['type' => ['string', 'null'], 'description' => 'Manufacturer product page URL'], + ], + 'required' => ['name', 'description'], + ], + ], + ]); + } catch (\Throwable $e) { + dump($e); + throw new \RuntimeException('LLM invocation failed: ' . $e->getMessage(), previous: $e); + } + + + + dump($result->getResult()->getContent()); + + return json_encode($result->getResult()->getContent()); + + /* + $systemPrompt = $this->buildSystemPrompt(); // Define the tool/function for structured output @@ -331,6 +442,8 @@ class AIInfoExtractor implements InfoProviderInterface $content = trim($content); return $content; + */ + } private function buildSystemPrompt(): string diff --git a/symfony.lock b/symfony.lock index 68de2da7..dde7df36 100644 --- a/symfony.lock +++ b/symfony.lock @@ -375,6 +375,54 @@ "shivas/versioning-bundle": { "version": "4.0.3" }, + "symfony/ai-bundle": { + "version": "0.8", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "0.1", + "ref": "2be6ccd77335c2631fdf12d1680649b072efb8ad" + }, + "files": [ + "config/packages/ai.yaml" + ] + }, + "symfony/ai-generic-platform": { + "version": "0.8", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "0.1", + "ref": "f38913b87380322d4c40c302b41626e811516bc4" + }, + "files": [ + "config/packages/ai_generic_platform.yaml" + ] + }, + "symfony/ai-lm-studio-platform": { + "version": "0.8", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "0.1", + "ref": "e35cced28f6559fc5effccb8f22597f309fedfdf" + }, + "files": [ + "config/packages/ai_lm_studio_platform.yaml" + ] + }, + "symfony/ai-open-router-platform": { + "version": "0.8", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "0.1", + "ref": "c39a146c6ec3df8b874accf6ce1cccbda431a688" + }, + "files": [ + "config/packages/ai_open_router_platform.yaml" + ] + }, "symfony/apache-pack": { "version": "1.0", "recipe": {