Use symfony AI platform for AI provider

This commit is contained in:
Jan Böhmer 2026-04-23 23:26:23 +02:00
parent 90d327fdaa
commit 9cf16248e6
11 changed files with 1304 additions and 30 deletions

8
.env
View file

@ -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 ###

View file

@ -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.*",

610
composer.lock generated
View file

@ -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",

View file

@ -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],
];

27
config/packages/ai.yaml Normal file
View file

@ -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'

View file

@ -0,0 +1,5 @@
ai:
platform:
generic:
default:
base_url: '%env(GENERIC_BASE_URL)%'

View file

@ -0,0 +1,3 @@
ai:
platform:
lmstudio: null

View file

@ -0,0 +1,4 @@
ai:
platform:
openrouter:
api_key: '%env(OPENROUTER_API_KEY)%'

View file

@ -2674,6 +2674,465 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* ...<string, mixed>
* },
* }
* @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<string, array{ // Default: []
* api_key?: string|Param,
* base_url?: string|Param,
* deployment?: string|Param,
* api_version?: string|Param, // The used API version
* http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client"
* }>,
* bedrock?: array<string, array{ // Default: []
* bedrock_runtime_client?: string|Param, // Service ID of the Bedrock runtime client to use // Default: null
* model_catalog?: string|Param, // Default: null
* }>,
* cache?: array<string, array{ // Default: []
* platform?: string|Param,
* service?: string|Param, // The cache service id as defined under the "cache" configuration key // Default: "cache.app"
* cache_key?: string|Param, // Key used to store platform results, if not set, the current platform name will be used, the "prompt_cache_key" can be set during platform call to override this value
* ttl?: int|Param,
* }>,
* 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<string, array{ // Default: []
* platforms?: list<scalar|Param|null>,
* 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<string, array{ // Default: []
* base_url?: string|Param,
* api_key?: string|Param,
* http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client"
* model_catalog?: string|Param, // Service ID of the model catalog to use
* supports_completions?: bool|Param, // Default: true
* supports_embeddings?: bool|Param, // Default: true
* completions_path?: string|Param, // Default: "/v1/chat/completions"
* embeddings_path?: string|Param, // Default: "/v1/embeddings"
* }>,
* 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<mixed>,
* 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<string, array<string, array{ // Default: []
* class?: string|Param, // The fully qualified class name of the model (must extend Symfony\AI\Platform\Model) // Default: "Symfony\\AI\\Platform\\Model"
* capabilities?: list<value-of<\Symfony\AI\Platform\Capability>|\Symfony\AI\Platform\Capability|Param>,
* }>>,
* agent?: array<string, array{ // Default: []
* platform?: string|Param, // Service name of platform // Default: "Symfony\\AI\\Platform\\PlatformInterface"
* model?: mixed,
* memory?: mixed, // Memory configuration: string for static memory, or array with "service" key for service reference // Default: null
* prompt?: string|array{ // The system prompt configuration
* text?: string|Param, // The system prompt text
* file?: string|Param, // Path to file containing the system prompt
* include_tools?: bool|Param, // Include tool definitions at the end of the system prompt // Default: false
* enable_translation?: bool|Param, // Enable translation for the system prompt // Default: false
* translation_domain?: string|Param, // The translation domain for the system prompt // Default: null
* },
* tools?: bool|array{
* enabled?: bool|Param, // Default: true
* services?: list<string|array{ // Default: []
* service?: string|Param,
* agent?: string|Param,
* name?: string|Param,
* description?: string|Param,
* method?: string|Param,
* }>,
* },
* 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<string, array{ // Default: []
* orchestrator?: string|Param, // Service ID of the orchestrator agent
* handoffs?: array<string, list<scalar|Param|null>>,
* fallback?: string|Param, // Service ID of the fallback agent for unmatched requests
* }>,
* store?: array{
* azuresearch?: array<string, array{ // Default: []
* endpoint?: string|Param,
* api_key?: string|Param,
* api_version?: string|Param,
* index_name?: string|Param, // The name of the store will be used if the "index_name" option is not set
* http_client?: string|Param, // Default: "http_client"
* vector_field?: string|Param, // Default: "vector"
* }>,
* cache?: array<string, array{ // Default: []
* service?: string|Param, // Default: "cache.app"
* cache_key?: string|Param, // The name of the store will be used if the key is not set.
* strategy?: string|Param, // Default: "cosine"
* }>,
* chromadb?: array<string, array{ // Default: []
* client?: string|Param, // Default: "Codewithkyrian\\ChromaDB\\Client"
* collection?: string|Param,
* }>,
* clickhouse?: array<string, array{ // Default: []
* dsn?: string|Param,
* http_client?: string|Param,
* database?: string|Param,
* table?: string|Param,
* }>,
* cloudflare?: array<string, array{ // Default: []
* account_id?: string|Param,
* api_key?: string|Param,
* index_name?: string|Param,
* dimensions?: int|Param, // Default: 1536
* metric?: string|Param, // Default: "cosine"
* endpoint?: string|Param,
* }>,
* elasticsearch?: array<string, array{ // Default: []
* endpoint?: string|Param,
* index_name?: string|Param,
* vectors_field?: string|Param, // Default: "_vectors"
* dimensions?: int|Param, // Default: 1536
* similarity?: string|Param, // Default: "cosine"
* http_client?: string|Param, // Default: "http_client"
* }>,
* manticoresearch?: array<string, array{ // Default: []
* endpoint?: string|Param,
* table?: string|Param,
* field?: string|Param, // Default: "_vectors"
* type?: string|Param, // Default: "hnsw"
* similarity?: string|Param, // Default: "cosine"
* dimensions?: int|Param, // Default: 1536
* quantization?: string|Param,
* }>,
* mariadb?: array<string, array{ // Default: []
* connection?: string|Param,
* table_name?: string|Param,
* index_name?: string|Param,
* vector_field_name?: string|Param,
* setup_options?: array{
* dimensions?: int|Param,
* },
* distance?: "cosine"|"euclidean"|"distance"|Param, // Distance metric to use for vector similarity search // Default: "euclidean"
* }>,
* meilisearch?: array<string, array{ // Default: []
* endpoint?: string|Param,
* api_key?: string|Param,
* index_name?: string|Param,
* embedder?: string|Param, // Default: "default"
* vector_field?: string|Param, // Default: "_vectors"
* dimensions?: int|Param, // Default: 1536
* semantic_ratio?: float|Param, // The ratio between semantic (vector) and full-text search (0.0 to 1.0). Default: 1.0 (100% semantic) // Default: 1.0
* }>,
* memory?: array<string, array{ // Default: []
* strategy?: string|Param,
* }>,
* milvus?: array<string, array{ // Default: []
* endpoint?: string|Param,
* api_key?: string|Param,
* database?: string|Param,
* collection?: string|Param,
* vector_field?: string|Param, // Default: "_vectors"
* dimensions?: int|Param, // Default: 1536
* metric_type?: string|Param, // Default: "COSINE"
* }>,
* mongodb?: array<string, array{ // Default: []
* client?: string|Param, // Default: "MongoDB\\Client"
* database?: string|Param,
* collection?: string|Param,
* index_name?: string|Param,
* vector_field?: string|Param, // Default: "vector"
* bulk_write?: bool|Param, // Default: false
* setup_options?: array{
* fields?: mixed, // Default: []
* },
* }>,
* neo4j?: array<string, array{ // Default: []
* endpoint?: string|Param,
* username?: string|Param,
* password?: string|Param,
* database?: string|Param,
* vector_index_name?: string|Param,
* node_name?: string|Param,
* vector_field?: string|Param, // Default: "embeddings"
* dimensions?: int|Param, // Default: 1536
* distance?: string|Param, // Default: "cosine"
* quantization?: bool|Param,
* }>,
* opensearch?: array<string, array{ // Default: []
* endpoint?: string|Param,
* index_name?: string|Param,
* vectors_field?: string|Param, // Default: "_vectors"
* dimensions?: int|Param, // Default: 1536
* space_type?: string|Param, // Default: "l2"
* http_client?: string|Param, // Default: "http_client"
* }>,
* pinecone?: array<string, array{ // Default: []
* client?: string|Param, // Default: "Probots\\Pinecone\\Client"
* index_name?: string|Param,
* namespace?: string|Param,
* filter?: list<scalar|Param|null>,
* top_k?: int|Param,
* }>,
* postgres?: array<string, array{ // Default: []
* dsn?: string|Param,
* username?: string|Param,
* password?: string|Param,
* table_name?: string|Param,
* vector_field?: string|Param, // Default: "embedding"
* distance?: "cosine"|"inner_product"|"l1"|"l2"|Param, // Distance metric to use for vector similarity search // Default: "l2"
* dbal_connection?: string|Param,
* setup_options?: array{
* vector_type?: string|Param, // Default: "vector"
* vector_size?: int|Param, // Default: 1536
* index_method?: string|Param, // Default: "ivfflat"
* index_opclass?: string|Param, // Default: "vector_cosine_ops"
* },
* }>,
* qdrant?: array<string, array{ // Default: []
* endpoint?: string|Param,
* api_key?: string|Param,
* collection_name?: string|Param, // The name of the store will be used if the "collection_name" is not set
* http_client?: string|Param, // Default: "http_client"
* dimensions?: int|Param, // Default: 1536
* distance?: string|Param, // Default: "Cosine"
* async?: bool|Param, // Default: false
* }>,
* redis?: array<string, array{ // Default: []
* connection_parameters?: mixed, // see https://github.com/phpredis/phpredis?tab=readme-ov-file#example-1
* client?: string|Param, // a service id of a Redis client
* index_name?: string|Param,
* key_prefix?: string|Param, // Default: "vector:"
* distance?: "COSINE"|"L2"|"IP"|Param, // Distance metric to use for vector similarity search // Default: "COSINE"
* }>,
* s3vectors?: array<string, array{ // Default: []
* client?: string|Param, // Service reference to an existing S3VectorsClient
* configuration?: array<mixed>,
* vector_bucket_name?: string|Param,
* index_name?: string|Param,
* filter?: array<mixed>,
* top_k?: int|Param, // Default number of results to return // Default: 3
* }>,
* sqlite?: array<string, array{ // Default: []
* dsn?: string|Param,
* connection?: string|Param,
* table_name?: string|Param,
* strategy?: string|Param,
* vec?: bool|Param, // Default: false
* distance?: "cosine"|"L2"|Param, // Default: "cosine"
* vector_dimension?: int|Param, // Default: 1536
* }>,
* supabase?: array<string, array{ // Default: []
* http_client?: string|Param, // Service ID of the HTTP client to use // Default: "http_client"
* url?: string|Param,
* api_key?: string|Param,
* table?: string|Param,
* vector_field?: string|Param, // Default: "embedding"
* vector_dimension?: int|Param, // Default: 1536
* function_name?: string|Param, // Default: "match_documents"
* }>,
* surrealdb?: array<string, array{ // Default: []
* endpoint?: string|Param,
* username?: string|Param,
* password?: string|Param,
* namespace?: string|Param,
* database?: string|Param,
* table?: string|Param,
* vector_field?: string|Param, // Default: "_vectors"
* strategy?: string|Param, // Default: "cosine"
* dimensions?: int|Param, // Default: 1536
* namespaced_user?: bool|Param,
* }>,
* typesense?: array<string, array{ // Default: []
* endpoint?: string|Param,
* api_key?: string|Param,
* collection?: string|Param,
* vector_field?: string|Param, // Default: "_vectors"
* dimensions?: int|Param, // Default: 1536
* }>,
* weaviate?: array<string, array{ // Default: []
* endpoint?: string|Param,
* api_key?: string|Param,
* http_client?: string|Param, // Default: "http_client"
* collection?: string|Param, // The name of the store will be used if the "collection" is not set
* }>,
* vektor?: array<string, array{ // Default: []
* storage_path?: string|Param, // Default: "%kernel.project_dir%/var/share"
* dimensions?: int|Param, // Default: 1536
* }>,
* },
* message_store?: array{
* cache?: array<string, array{ // Default: []
* service?: string|Param, // Default: "cache.app"
* key?: string|Param, // The name of the message store will be used if the key is not set
* ttl?: int|Param,
* }>,
* cloudflare?: array<string, array{ // Default: []
* account_id?: string|Param,
* api_key?: string|Param,
* namespace?: string|Param,
* endpoint_url?: string|Param, // If the version of the Cloudflare API is updated, use this key to support it.
* }>,
* doctrine?: array{
* dbal?: array<string, array{ // Default: []
* connection?: string|Param,
* table_name?: string|Param, // The name of the message store will be used if the table_name is not set
* }>,
* },
* meilisearch?: array<string, array{ // Default: []
* endpoint?: string|Param,
* api_key?: string|Param,
* index_name?: string|Param,
* }>,
* memory?: array<string, array{ // Default: []
* identifier?: string|Param,
* }>,
* mongodb?: array<string, array{ // Default: []
* client?: string|Param, // Default: "MongoDB\\Client"
* database?: string|Param,
* collection?: string|Param,
* }>,
* pogocache?: array<string, array{ // Default: []
* endpoint?: string|Param,
* password?: string|Param,
* key?: string|Param,
* }>,
* redis?: array<string, array{ // Default: []
* connection_parameters?: mixed, // see https://github.com/phpredis/phpredis?tab=readme-ov-file#example-1
* client?: string|Param, // a service id of a Redis client
* endpoint?: string|Param,
* index_name?: string|Param,
* }>,
* session?: array<string, array{ // Default: []
* identifier?: string|Param,
* }>,
* surrealdb?: array<string, array{ // Default: []
* endpoint?: string|Param,
* username?: string|Param,
* password?: string|Param,
* namespace?: string|Param,
* database?: string|Param,
* table?: string|Param,
* namespaced_user?: bool|Param, // Using a namespaced user is a good practice to prevent any undesired access to a specific table, see https://surrealdb.com/docs/surrealdb/reference-guide/security-best-practices
* }>,
* },
* chat?: array<string, array{ // Default: []
* agent?: string|Param,
* message_store?: string|Param,
* }>,
* vectorizer?: array<string, array{ // Default: []
* platform?: string|Param, // Service name of platform // Default: "Symfony\\AI\\Platform\\PlatformInterface"
* model?: mixed,
* }>,
* indexer?: array<string, array{ // Default: []
* loader?: string|Param, // Service name of loader // Default: null
* source?: mixed, // Source identifier (file path, URL, etc.) or array of sources // Default: null
* transformers?: list<scalar|Param|null>,
* filters?: list<scalar|Param|null>,
* 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<string, array{ // Default: []
* 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"
* }>,
* }
* @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,
* },
* ...<string, ExtensionType|array{ // extra keys must follow the when@%env% pattern or match an extension alias
* imports?: ImportsConfig,

View file

@ -32,6 +32,11 @@ use App\Services\InfoProviderSystem\DTOs\PriceDTO;
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
use App\Settings\InfoProviderSystem\AIExtractorSettings;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\AI\Platform\Platform;
use Symfony\AI\Platform\PlatformInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class AIInfoExtractor implements InfoProviderInterface
@ -40,7 +45,10 @@ class AIInfoExtractor implements InfoProviderInterface
private readonly HttpClientInterface $httpClient;
public function __construct(HttpClientInterface $httpClient, private readonly AIExtractorSettings $settings)
public function __construct(HttpClientInterface $httpClient, private readonly AIExtractorSettings $settings,
#[Autowire(service: "ai.traceable_platform.openrouter")]
private readonly PlatformInterface $aiPlatform
)
{
$this->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

View file

@ -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": {