ADR: AI Platform Integration Architecture
Authors: Dezső Biczó (@mxr576), Wolfgang Ziegler (@fago)
Date: 2026-07-09
Status
Implemented
Context
The AI module needs to integrate an external orchestration component, Symfony AI's \Symfony\AI\Platform\Platform class, to manage communication with various AI models. This Platform is responsible for holding a catalog of available models (from different providers like OpenAI, Anthropic, etc.) and routing requests to the appropriate model.
We require a solution that allows site administrators to configure these platforms through the Drupal UI using configuration entities (AiPlatformConfig). The central architectural question is how to connect the Drupal configuration entity (AiPlatformConfig) to the underlying Symfony AI Platform object in a way that is flexible, extensible, and aligns with Drupal best practices.
We considered three main options for this integration.
Option A: Direct Config-to-Implementation Mapping
In this model, the AiPlatformConfig entity's schema would directly mirror the properties required by the \Symfony\AI\Platform\Platform class (e.g., providers, routing). A dedicated service would be responsible for reading the properties from the config entity and using them to instantiate the Platform object.
- Pros: Simple and direct for the initial use case.
- Cons:
- Tightly Coupled: The config entity is permanently tied to one specific PHP implementation.
- Violates Open/Closed Principle: To introduce a different type of platform (e.g., a caching proxy or a platform that connects to an external gateway), we would need to alter the core service or introduce complex conditional logic.
- Configuration Bloat: The top-level config entity would have to hold properties for every possible platform type, even those it doesn\'t use.
Option B: Dedicated Platform Plugin Wrapper
This approach involves creating a single, dedicated plugin type (e.g., @AiManagedPlatform) whose sole purpose is to wrap the AiPlatformConfig entity. The plugin would be responsible for instantiating the Symfony AI Platform.
- Pros: Better encapsulation than Option A.
- Cons: Still fundamentally couples the concept of a "platform" in Drupal to the Symfony AI Platform implementation. It offers no clear path for third-party developers to introduce alternative platform orchestrators.
Option C: Generic "Plugin-in-Config" System
This approach, common in the Drupal ecosystem (e.g., Search API, Commerce, Key), treats the config entity as a generic container. It introduces a new, generic plugin type: @AiPlatform.
- The
AiPlatformConfigentity stores aplugin_idfor the desired@AiPlatformplugin and aplugin_configurationarray for that specific plugin. - We provide a default
@AiPlatformplugin that wraps the\Symfony\AI\Platform\Platformclass. Its specific configuration schema includesprovidersandrouting. -
When the platform object is needed,
AiPlatformConfiguses the AI Platform plugin manager to instantiate the selected plugin, passing it the stored configuration. The plugin\'s factory method returns a fully configured\Symfony\AI\Platform\Platformobject. -
Pros:
- Maximum Extensibility: Developers can introduce entirely new platform backends by creating a new
@AiPlatformplugin, without any changes to the core AI module. - Separation of Concerns: The config entity stores data, and the plugin provides behavior. Each component has a single responsibility.
- Follows Drupal Best Practices: The pattern is familiar to Drupal developers, reducing the learning curve.
- Maximum Extensibility: Developers can introduce entirely new platform backends by creating a new
- Cons:
- Introduces a minor layer of abstraction (a new plugin type), which adds slight initial complexity.
Decision
We have chosen Option C: Generic "Plugin-in-Config" System, implemented via a plugin deriver.
This architecture provides the best long-term flexibility and aligns with established Drupal design patterns. The AiPlatformConfig entity acts as a passive data container, and a deriver is responsible for providing a new @AiPlatform plugin for each config entity.
This approach results in a linear, decoupled data flow that avoids service location within entities and promotes a clear separation of concerns.
A consumer requests a platform plugin from the manager. The manager determines the instantiation flow:
Consumer (e.g., an Automator plugin)
→ Requests a platform plugin from `ai.platform.manager`
(e.g., `$manager->createInstance('plugin_id')`)
IF plugin_id corresponds to an AiPlatformConfig entity:
→ The `ManagedPlatformDeriver` provides the definition (`managed:<entity_id>`).
→ The plugin manager calls `AiManagedPlatform::create()` for the plugin:
1. Loads the `AiPlatformConfig` entity as passive data.
2. Instantiates the inner "backend" `@AiPlatform` plugin (e.g.,
`platform_with_model_routing`) to create the base `\Symfony\AI\Platform\Platform` object.
3. Instantiates each `@AiPlatformMiddleware` plugin and decorates the base platform.
4. The `AiManagedPlatform` instance delegates calls to the final decorated platform.
ELSE (for a non-entity-based platform, e.g. a `provider:<id>` derivative):
→ A generic plugin factory creates the instance directly.
→ The consumer receives a fully configured `\Symfony\AI\Platform\Platform` plugin.
This allows the default plugin wrapper around Symfony AI's \Symfony\AI\Platform\Platform class (implemented in Drupal\ai\Plugin\ai\Platform\PlatformWithModelRouting) to be the standard implementation while giving developers a clear, defined extension point to create their own platform orchestrators (e.g., for caching, failover, or proxying) by simply creating a new plugin. This adherence to the Open/Closed Principle is critical for the long-term health and adoption of the module.
Consequences
- An
AiPlatformplugin manager is created to manage three categories of plugins: managed:<id>derivatives — one perai_platformconfig entity, produced byManagedPlatformDeriverviaAiManagedPlatform. These are the consumer-facing plugins for admin-composed platforms.provider:<id>derivatives — one perai_inference_providerconfig entity, produced byProviderPlatformDeriverviaAiProviderPlatform. These auto-expose every inference provider as a zero-config single-provider platform.- Backend
@AiPlatformplugins (isBackendOnly: TRUE, e.g.platform_with_model_routing) — the internal engine options selected when configuring anai_platformentity. Never consumer-facing. - The
AiPlatformConfigentity is a passive container for a backend plugin ID, its configuration, and an ordered middleware stack. - The configuration form for the entity uses AJAX to dynamically load the sub-form for the selected backend
@AiPlatformplugin. - The initial implementation ships one default backend plugin (
platform_with_model_routing) that wraps the\Symfony\AI\Platform\Platformclass. - Documentation should guide developers on creating their own backend
@AiPlatformplugins and@AiPlatformMiddlewareplugins.