feat: #3586427 Adopt symfony/ai v0.8's Platform concept

Dependencies

!! This MR builds upon the MR #1613 - please review this one first!

Description

This MR implements the Symfony AI integration architecture as documented in ADR 001 (AI Platform Integration Architecture), using the Plugin-in-Config pattern for both platform and inference provider configuration entities.

New Plugin Types & Config Entities

  • @AiInferenceProvider plugin type + ai_inference_provider config entity

    • One Symfony inference backend with API key / base URL / model list
    • Ships Generic (OpenAI-compatible) and Gemini providers
    • Plugin namespace: Plugin/ai/InferenceProvider/
    • Config entity is a passive data container - uses Plugin-in-Config pattern (no getProviderInstance())
  • @AiModelRouter plugin type

    • Routes model requests to appropriate providers
    • Ships CatalogBasedModelRouter (default)
    • Plugin namespace: Plugin/ai/ModelRouter/
  • @AiPlatform plugin type with three categories:

    • Backend plugins (isBackendOnly: TRUE) - internal engine options (e.g., platform_with_model_routing)
    • managed:<id> derivatives - one per ai_platform config entity via ManagedPlatformDeriver
    • provider:<id> derivatives - one per ai_inference_provider config entity via ProviderPlatformDeriver, auto-exposing every inference provider as a zero-config single-provider platform
  • @AiPlatformMiddleware plugin type

    • Cross-cutting wrappers (caching / retry / fallback / tracing)
    • Ships Tracing middleware (Symfony\AI\Platform\TraceablePlatform wrapper)
    • UI sub-form is @todo

Config Entity Architecture (Plugin-in-Config Pattern)

Both config entities follow the Plugin-in-Config pattern per ADR 001:

ai_platform config entity:

  • Stores: platform_plugin_id, platform_plugin_configuration, middlewares[]
  • Pure data container - NO getPlatform() method
  • Consumers MUST use plugin manager: $platform_manager->createInstance('managed:' . $entity_id)

ai_inference_provider config entity:

  • Stores: plugin, configuration
  • Pure data container - NO getProviderInstance() method
  • Consumers MUST use plugin manager: $provider_manager->createInstance($entity->getPluginId(), $entity->getPluginConfiguration())

APIs

New canonical API:

// Get platforms for an operation type
$platforms = $platform_manager->getPlatformsForOperationType($operation_type);

// Access a specific platform
$platform = $platform_manager->createInstance('managed:my_platform');
$platform = $platform_manager->createInstance('provider:my_provider');

Legacy API (deprecated but functional):

// Returns PlatformProxy BC adapters for 1.x consumers
$providers = $this->providerManager->getProvidersForOperationType($operation_type);

The legacy AiProviderInterface / ChatInterface consumers continue working unchanged via PlatformProxy adapters.

Admin UI

  • /admin/config/ai/inference-providers - CRUD for inference provider configs
  • /admin/config/ai/platforms - CRUD for platform configs
    • Backend plugin selector (e.g., "Platform with Model Routing")
    • Provider multi-select + router picker
    • Middleware stack (UI sub-form is @todo)

Addressing Schemes

Via plugin.manager.ai.platform:

  • managed:<entity_id> - Composed platform from config entity
  • provider:<inference_provider_id> - Auto-derived single-provider platform

Via legacy ai.provider API:

  • ai_platform:managed:<id> - Wrapped in PlatformProxy for BC
  • ai_platform:provider:<id> - Wrapped in PlatformProxy for BC

Echo Test Fixture

Migrated to new architecture:

  • Plugin/ai/InferenceProvider/EchoProvider
  • Config: ai.inference_provider.echo_test + ai.platform.echo_test

Testing instructions

  1. Add an inference provider at /admin/config/ai/inference-providers:

    • Plugin: Generic
    • Base URL + Key entity + models (one per line)
  2. Add a platform entity at /admin/config/ai/platforms:

    • Label: "My Composed Platform"
    • Backend plugin: Platform with Model Routing
    • Providers: select the provider from step 1
    • Routing plugin: Catalog-based
    • (Middleware sub-form is @todo — field exists but UI is placeholder)
  3. Configure default provider at /admin/config/ai/settings:

    • For the chat operation, try both addressing schemes:
      • ai_platform:provider:<inference_provider_id> (auto-derived single-provider platform — no config entity required)
      • ai_platform:managed:<your_platform_id> (the explicit composed platform from step 2)
  4. Test in API Explorer at /admin/config/ai/ai-api-explorer/chat-generation:

    • Send a prompt with each configuration
    • Both should reply successfully

Architecture Documentation

  • ADR 001: docs/adr/symfony-ai-integration/001-ai-platform-integration.md - Core architecture decision
  • ADR 002: docs/adr/symfony-ai-integration/002-inference-provider-config-migration.md - Migration to Plugin-in-Config pattern

Checklist

  • I have linked the related issue in the MR title or description
  • I have performed a self-review of my own code
  • I have added or updated tests, or explained in the description why this change is not covered by tests
  • I have updated documentation for any new or changed functionality
  • I have written testing instructions and verified them locally
  • I have noted any required post-merge steps (config imports, cache rebuilds, manual changes)
  • This MR contains no breaking API or hook changes, or they are explicitly documented in the description

AI Compliance

Note

Check the one that best describes your usage, or leave all unchecked if AI was not significantly used.

  • AI Assisted Code
    Mainly written by a human; AI used for autocomplete or partial generation under full human supervision.

  • AI Generated Code
    Mainly generated by AI, reviewed and approved by a human before this MR was created.

  • Vibe Coded
    Generated by AI and only functionally reviewed before this MR was created.

Closes #3586427 (closed)

Edited by Dezső Biczó

Merge request reports

Loading