* } */ class GroqModelMetadataDirectory extends AbstractOpenAiCompatibleModelMetadataDirectory { /** * {@inheritDoc} * * @since 1.0.0 */ protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request { return new Request( $method, GroqProvider::url($path), $headers, $data ); } /** * {@inheritDoc} * * @since 1.0.0 */ protected function parseResponseToModelMetadataList(Response $response): array { /** @var ModelsResponseData $responseData */ $responseData = $response->getData(); if (!isset($responseData['data']) || !$responseData['data']) { throw ResponseException::fromMissingData('Groq', 'data'); } $groqCapabilities = [ CapabilityEnum::textGeneration(), CapabilityEnum::chatHistory(), ]; $groqBaseOptions = [ new SupportedOption(OptionEnum::systemInstruction()), new SupportedOption(OptionEnum::candidateCount()), new SupportedOption(OptionEnum::maxTokens()), new SupportedOption(OptionEnum::temperature()), new SupportedOption(OptionEnum::topP()), new SupportedOption(OptionEnum::stopSequences()), new SupportedOption(OptionEnum::presencePenalty()), new SupportedOption(OptionEnum::frequencyPenalty()), new SupportedOption(OptionEnum::logprobs()), new SupportedOption(OptionEnum::topLogprobs()), new SupportedOption(OptionEnum::outputMimeType(), ['text/plain', 'application/json']), new SupportedOption(OptionEnum::outputSchema()), new SupportedOption(OptionEnum::functionDeclarations()), new SupportedOption(OptionEnum::customOptions()), ]; $groqTextOptions = array_merge($groqBaseOptions, [ new SupportedOption(OptionEnum::inputModalities(), [[ModalityEnum::text()]]), new SupportedOption(OptionEnum::outputModalities(), [[ModalityEnum::text()]]), ]); $groqMultimodalInputOptions = array_merge($groqBaseOptions, [ new SupportedOption( OptionEnum::inputModalities(), [ [ModalityEnum::text()], [ModalityEnum::text(), ModalityEnum::image()], ] ), new SupportedOption(OptionEnum::outputModalities(), [[ModalityEnum::text()]]), ]); $modelsData = (array) $responseData['data']; $models = array_values( array_map( static function (array $modelData) use ( $groqCapabilities, $groqTextOptions, $groqMultimodalInputOptions ): ModelMetadata { $modelId = $modelData['id']; /* * Groq serves audio models (Whisper speech-to-text and PlayAI * text-to-speech) through the same listing. They are not chat * models, so they are exposed without text generation capability. */ if (self::isAudioModel($modelId)) { $modelCaps = []; $modelOptions = []; } else { $modelCaps = $groqCapabilities; $modelOptions = self::supportsMultimodalTextInput($modelId) ? $groqMultimodalInputOptions : $groqTextOptions; } return new ModelMetadata( $modelId, $modelId, // The Groq API does not return a display name. $modelCaps, $modelOptions ); }, $modelsData ) ); usort($models, [$this, 'modelSortCallback']); return $models; } /** * Checks whether a Groq model is an audio model (speech-to-text or text-to-speech). * * @since 1.0.0 * * @param string $modelId The model ID. * @return bool True if the model is an audio model, false otherwise. */ private static function isAudioModel(string $modelId): bool { return str_contains($modelId, 'whisper') || str_contains($modelId, 'tts'); } /** * Checks whether a Groq text generation model supports multimodal (image) input. * * @since 1.0.0 * * @param string $modelId The model ID. * @return bool True if the model supports multimodal text input, false otherwise. */ private static function supportsMultimodalTextInput(string $modelId): bool { return str_contains($modelId, 'vision') || str_contains($modelId, 'llava') || str_contains($modelId, 'llama-4') || str_contains($modelId, 'maverick') || str_contains($modelId, 'scout'); } /** * Checks whether a Groq model is a utility model (audio or safety/guard models). * * Utility models are functional but not general-purpose text generators, so they * are sorted to the bottom of the list. * * @since 1.0.0 * * @param string $modelId The model ID. * @return bool True if the model is a utility model, false otherwise. */ private static function isUtilityModel(string $modelId): bool { return self::isAudioModel($modelId) || str_contains($modelId, 'guard'); } /** * Callback function for sorting models by ID, to be used with `usort()`. * * This method expresses preferences for certain models or model families within the provider by putting them * earlier in the sorted list. The objective is not to be opinionated about which models are better, but to ensure * that more commonly used, more recent, or flagship models are presented first to users. * * @since 1.0.0 * * @param ModelMetadata $a First model. * @param ModelMetadata $b Second model. * @return int Comparison result. */ protected function modelSortCallback(ModelMetadata $a, ModelMetadata $b): int { $aId = $a->getId(); $bId = $b->getId(); // Push utility models (audio, safety/guard) to the bottom. $aUtil = self::isUtilityModel($aId); $bUtil = self::isUtilityModel($bId); if ($aUtil !== $bUtil) { return $aUtil ? 1 : -1; } // Deprioritize preview models in favor of stable ones. $aPreview = str_contains($aId, 'preview'); $bPreview = str_contains($bId, 'preview'); if ($aPreview !== $bPreview) { return $aPreview ? 1 : -1; } // Prefer the Llama family, Groq's most widely used models. $aLlama = str_contains($aId, 'llama'); $bLlama = str_contains($bId, 'llama'); if ($aLlama !== $bLlama) { return $aLlama ? -1 : 1; } // Fallback: Sort alphabetically. return strcmp($aId, $bId); } }