Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions src/ai-bundle/src/AiBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,31 @@
use Symfony\AI\Chat\ChatInterface;
use Symfony\AI\Chat\MessageStoreInterface;
use Symfony\AI\Platform\Bridge\Albert\PlatformFactory as AlbertPlatformFactory;
use Symfony\AI\Platform\Bridge\Anthropic\Claude;
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory as AnthropicPlatformFactory;
use Symfony\AI\Platform\Bridge\Azure\OpenAi\PlatformFactory as AzureOpenAiPlatformFactory;
use Symfony\AI\Platform\Bridge\Cartesia\PlatformFactory as CartesiaPlatformFactory;
use Symfony\AI\Platform\Bridge\Cerebras\PlatformFactory as CerebrasPlatformFactory;
use Symfony\AI\Platform\Bridge\DeepSeek\PlatformFactory as DeepSeekPlatformFactory;
use Symfony\AI\Platform\Bridge\DockerModelRunner\PlatformFactory as DockerModelRunnerPlatformFactory;
use Symfony\AI\Platform\Bridge\ElevenLabs\PlatformFactory as ElevenLabsPlatformFactory;
use Symfony\AI\Platform\Bridge\Gemini\Gemini;
use Symfony\AI\Platform\Bridge\Gemini\PlatformFactory as GeminiPlatformFactory;
use Symfony\AI\Platform\Bridge\HuggingFace\PlatformFactory as HuggingFacePlatformFactory;
use Symfony\AI\Platform\Bridge\LmStudio\PlatformFactory as LmStudioPlatformFactory;
use Symfony\AI\Platform\Bridge\Mistral\Mistral;
use Symfony\AI\Platform\Bridge\Mistral\PlatformFactory as MistralPlatformFactory;
use Symfony\AI\Platform\Bridge\Ollama\Ollama;
use Symfony\AI\Platform\Bridge\Ollama\OllamaApiCatalog;
use Symfony\AI\Platform\Bridge\Ollama\PlatformFactory as OllamaPlatformFactory;
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory as OpenAiPlatformFactory;
use Symfony\AI\Platform\Bridge\OpenRouter\PlatformFactory as OpenRouterPlatformFactory;
use Symfony\AI\Platform\Bridge\Perplexity\PlatformFactory as PerplexityPlatformFactory;
use Symfony\AI\Platform\Bridge\Scaleway\PlatformFactory as ScalewayPlatformFactory;
use Symfony\AI\Platform\Bridge\VertexAi\PlatformFactory as VertexAiPlatformFactory;
use Symfony\AI\Platform\Bridge\Voyage\PlatformFactory as VoyagePlatformFactory;
use Symfony\AI\Platform\Capability;
use Symfony\AI\Platform\Exception\RuntimeException;
use Symfony\AI\Platform\Message\Content\File;
use Symfony\AI\Platform\ModelClientInterface;
Expand Down Expand Up @@ -143,6 +149,12 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
foreach ($config['platform'] ?? [] as $type => $platform) {
$this->processPlatformConfig($type, $platform, $builder);
}

// Process model configuration and pass to ModelCatalog services
foreach ($config['model'] ?? [] as $platformName => $models) {
$this->processModelConfig($platformName, $models, $builder);
}

$platforms = array_keys($builder->findTaggedServiceIds('ai.platform'));
if (1 === \count($platforms)) {
$builder->setAlias(PlatformInterface::class, reset($platforms));
Expand Down Expand Up @@ -1799,4 +1811,79 @@ private static function normalizeAgentServiceId(string $agentName): string
{
return str_starts_with($agentName, 'ai.agent.') ? $agentName : 'ai.agent.'.$agentName;
}

/**
* Process model configuration and pass it to ModelCatalog services.
*
* @param array<string, array{capabilities: list<Capability|string>}> $models
*/
private function processModelConfig(string $platformName, array $models, ContainerBuilder $builder): void
{
$modelCatalogId = 'ai.platform.model_catalog.'.$platformName;

// Handle special cases for platform name mapping
if ('vertexai' === $platformName) {
$modelCatalogId = 'ai.platform.model_catalog.vertexai.gemini';
}

if (!$builder->hasDefinition($modelCatalogId)) {
return;
}

$modelClass = $this->getModelClassForPlatform($platformName);
if (null === $modelClass) {
return;
}

$additionalModels = [];
foreach ($models as $modelName => $modelConfig) {
$capabilities = [];
foreach ($modelConfig['capabilities'] ?? [] as $capability) {
// Capabilities may already be enum instances or strings
if ($capability instanceof Capability) {
$capabilities[] = $capability;
} elseif (\is_string($capability)) {
try {
$capabilities[] = Capability::from($capability);
} catch (\ValueError) {
// Skip invalid capability strings
continue;
}
}
}

if ([] === $capabilities) {
continue;
}

$additionalModels[$modelName] = [
'class' => $modelClass,
'capabilities' => $capabilities,
];
}

if ([] === $additionalModels) {
return;
}

$definition = $builder->getDefinition($modelCatalogId);
$definition->setArguments([$additionalModels]);
}

/**
* Get the model class for a given platform.
*
* @return class-string|null
*/
private function getModelClassForPlatform(string $platformName): ?string
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chr-hertel we can get rid of this when making the class required in the config which feels ok to me, WDYT?

I am talking about:

{
return match ($platformName) {
'anthropic' => Claude::class,
'openai' => Gpt::class,
'gemini' => Gemini::class,
'mistral' => Mistral::class,
'ollama' => Ollama::class,
default => null,
};
}
}
Loading