diff --git a/Classes/Provider/SymfonyAi/SymfonyAiPlatformAdapter.php b/Classes/Provider/SymfonyAi/SymfonyAiPlatformAdapter.php index 6a83fbd..7b05d0c 100644 --- a/Classes/Provider/SymfonyAi/SymfonyAiPlatformAdapter.php +++ b/Classes/Provider/SymfonyAi/SymfonyAiPlatformAdapter.php @@ -64,6 +64,8 @@ class SymfonyAiPlatformAdapter implements /** @var array Platforms cached by configuration key */ private array $platforms = []; + private readonly string $maxTokensKey; + /** * @param string $factoryClass Fully-qualified class name of the bridge's PlatformFactory * @param string $factoryParam Name of the factory parameter to pass the config value to ('apiKey' or 'endpoint') @@ -71,7 +73,9 @@ class SymfonyAiPlatformAdapter implements public function __construct( private readonly string $factoryClass, private readonly string $factoryParam = 'apiKey', - ) {} + ) { + $this->maxTokensKey = self::resolveMaxTokensKey($factoryClass); + } public function processVisionRequest(VisionRequest $request): TextResponse { @@ -433,13 +437,26 @@ private function buildMessageBag(array $aiMessages, string $systemPrompt): Messa */ private function buildOptions(string $model, int $maxTokens, float $temperature, array $extra = []): array { - $options = ['max_output_tokens' => $maxTokens] + $extra; + $options = [$this->maxTokensKey => $maxTokens] + $extra; if (!$this->isReasoningModel($model)) { $options['temperature'] = $temperature; } return $options; } + /** + * Resolve the max-tokens option key expected by a Symfony AI bridge based on the used bridge + */ + public static function resolveMaxTokensKey(string $factoryClass): string + { + if (str_contains($factoryClass, '\\Bridge\\OpenAi\\') + || str_contains($factoryClass, '\\Bridge\\OpenResponses\\') + ) { + return 'max_output_tokens'; + } + return 'max_tokens'; + } + /** * Check if a model is a reasoning model that doesn't support temperature. * diff --git a/Tests/Unit/Provider/SymfonyAi/SymfonyAiPlatformAdapterTest.php b/Tests/Unit/Provider/SymfonyAi/SymfonyAiPlatformAdapterTest.php new file mode 100644 index 0000000..02e347b --- /dev/null +++ b/Tests/Unit/Provider/SymfonyAi/SymfonyAiPlatformAdapterTest.php @@ -0,0 +1,56 @@ + [ + 'Symfony\\AI\\Platform\\Bridge\\OpenAi\\PlatformFactory', + 'max_output_tokens', + ]; + yield 'OpenResponses bridge' => [ + 'Symfony\\AI\\Platform\\Bridge\\OpenResponses\\PlatformFactory', + 'max_output_tokens', + ]; + yield 'Anthropic Messages API' => [ + 'Symfony\\AI\\Platform\\Bridge\\Anthropic\\PlatformFactory', + 'max_tokens', + ]; + yield 'Mistral' => [ + 'Symfony\\AI\\Platform\\Bridge\\Mistral\\PlatformFactory', + 'max_tokens', + ]; + yield 'Ollama' => [ + 'Symfony\\AI\\Platform\\Bridge\\Ollama\\PlatformFactory', + 'max_tokens', + ]; + yield 'unknown bridge falls back to max_tokens' => [ + 'Acme\\AiBridge\\PlatformFactory', + 'max_tokens', + ]; + } + + #[Test] + #[DataProvider('maxTokensKeyProvider')] + public function resolveMaxTokensKeyMapsBridgeToCorrectOptionName(string $factoryClass, string $expectedKey): void + { + self::assertSame($expectedKey, SymfonyAiPlatformAdapter::resolveMaxTokensKey($factoryClass)); + } +}