set_service_metadata( $metadata ); $this->set_api_client( new Google_AI_API_Client( self::DEFAULT_API_BASE_URL, self::DEFAULT_API_VERSION, 'Google Generative Language', $request_handler ?? new HTTP_With_Streams(), $authentication ) ); } /** * Lists the available generative model slugs and their metadata. * * @since 0.1.0 * @since 0.5.0 Return type changed to a map of model data shapes. * @since 0.7.0 Return type changed to a map of model metadata objects. * * @param array $request_options Optional. The request options. Default empty array. * @return array Metadata for each model, mapped by model slug. * * @throws Generative_AI_Exception Thrown if the request fails or the response is invalid. */ public function list_models( array $request_options = array() ): array { $api = $this->get_api_client(); $request = $api->create_get_request( 'models', array( // 1000 is the maximum page size - we just want to retrieve all models in one go. 'pageSize' => 1000, ), $request_options ); $response_data = $api->make_request( $request )->get_data(); if ( ! isset( $response_data['models'] ) || ! $response_data['models'] ) { throw $api->create_missing_response_key_exception( 'models' ); } $gemini_legacy_capabilities = array( AI_Capability::CHAT_HISTORY, AI_Capability::FUNCTION_CALLING, AI_Capability::TEXT_GENERATION, ); $gemini_capabilities = array( AI_Capability::CHAT_HISTORY, AI_Capability::FUNCTION_CALLING, AI_Capability::MULTIMODAL_INPUT, AI_Capability::TEXT_GENERATION, ); $imagen_capabilities = array( AI_Capability::IMAGE_GENERATION, ); return array_reduce( $response_data['models'], static function ( array $models_data, array $model_data ) use ( $gemini_legacy_capabilities, $gemini_capabilities, $imagen_capabilities ) { $model_slug = $model_data['baseModelId'] ?? $model_data['name']; if ( str_starts_with( $model_slug, 'models/' ) ) { $model_slug = substr( $model_slug, 7 ); } if ( isset( $model_data['supportedGenerationMethods'] ) && in_array( 'generateContent', $model_data['supportedGenerationMethods'], true ) ) { if ( str_starts_with( $model_slug, 'gemini-1.0' ) || str_starts_with( $model_slug, 'gemini-pro' ) // 'gemini-pro' without version refers to 1.0. ) { $model_caps = $gemini_legacy_capabilities; } else { $model_caps = $gemini_capabilities; if ( // Web search is supported by Gemini 2.0 and newer. str_starts_with( $model_slug, 'gemini-' ) && ! str_starts_with( $model_slug, 'gemini-1.5-' ) ) { $model_caps[] = AI_Capability::WEB_SEARCH; } if ( // Multimodal output models for image generation. str_ends_with( $model_slug, '-image' ) || str_ends_with( $model_slug, '-image-preview' ) || str_ends_with( $model_slug, '-image-generation' ) || str_starts_with( $model_slug, 'gemini-2.0-flash-exp' ) ) { $model_caps[] = AI_Capability::MULTIMODAL_OUTPUT; } elseif ( // New multimodal output model for audio generation. str_contains( $model_slug, '-tts' ) ) { $model_caps[] = AI_Capability::MULTIMODAL_OUTPUT; } } } elseif ( isset( $model_data['supportedGenerationMethods'] ) && in_array( 'predict', $model_data['supportedGenerationMethods'], true ) ) { $model_caps = $imagen_capabilities; } else { $model_caps = array(); } $models_data[ $model_slug ] = Model_Metadata::from_array( array( 'slug' => $model_slug, 'name' => $model_data['displayName'] ?? $model_slug, 'capabilities' => $model_caps, ) ); return $models_data; }, array() ); } /** * Creates a new model instance for the provided model metadata and parameters. * * @since 0.7.0 * * @param Model_Metadata $model_metadata The model metadata. * @param array $model_params Model parameters. See {@see Generative_AI_Service::get_model()} for * a list of available parameters. * @param array $request_options The request options. * @return Generative_AI_Model The new model instance. */ protected function create_model_instance( Model_Metadata $model_metadata, array $model_params, array $request_options ): Generative_AI_Model { $model_class = AI_Capabilities::get_model_class_for_capabilities( array( Google_AI_Text_Generation_Model::class, Google_AI_Image_Generation_Model::class, ), $model_metadata->get_capabilities() ); return new $model_class( $this->get_api_client(), $model_metadata, $model_params, $request_options ); } /** * Sorts model slugs by preference. * * @since 0.1.0 * * @param string[] $model_slugs The model slugs to sort. * @return string[] The model slugs, sorted by preference. */ protected function sort_models_by_preference( array $model_slugs ): array { usort( $model_slugs, array( $this, 'model_sort_callback' ) ); return $model_slugs; } /** * Callback function for sorting models by slug, 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 0.7.1 * * @param string $a_slug First model slug. * @param string $b_slug Second model slug. * @return int Comparison result. * * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function model_sort_callback( string $a_slug, string $b_slug ): int { // Prefer non-experimental models over experimental models. if ( str_contains( $a_slug, '-exp' ) && ! str_contains( $b_slug, '-exp' ) ) { return 1; } if ( str_contains( $b_slug, '-exp' ) && ! str_contains( $a_slug, '-exp' ) ) { return -1; } // Prefer non-preview models over preview models. if ( str_contains( $a_slug, '-preview' ) && ! str_contains( $b_slug, '-preview' ) ) { return 1; } if ( str_contains( $b_slug, '-preview' ) && ! str_contains( $a_slug, '-preview' ) ) { return -1; } // Prefer Gemini models over non-Gemini models. if ( str_starts_with( $a_slug, 'gemini-' ) && ! str_starts_with( $b_slug, 'gemini-' ) ) { return -1; } if ( str_starts_with( $b_slug, 'gemini-' ) && ! str_starts_with( $a_slug, 'gemini-' ) ) { return 1; } // Prefer Gemini models with version numbers (e.g. 'gemini-2.5', 'gemini-2.0') over those without. $a_match = preg_match( '/^gemini-([0-9.]+)(-[a-z0-9-]+)$/', $a_slug, $a_matches ); $b_match = preg_match( '/^gemini-([0-9.]+)(-[a-z0-9-]+)$/', $b_slug, $b_matches ); if ( $a_match && ! $b_match ) { return -1; } if ( $b_match && ! $a_match ) { return 1; } if ( $a_match && $b_match ) { // Prefer later model versions. $a_version = $a_matches[1]; $b_version = $b_matches[1]; if ( version_compare( $a_version, $b_version, '>' ) ) { return -1; } if ( version_compare( $b_version, $a_version, '>' ) ) { return 1; } // Prefer '-pro' models over other suffixes. if ( '-pro' === $a_matches[2] && '-pro' !== $b_matches[2] ) { return -1; } if ( '-pro' === $b_matches[2] && '-pro' !== $a_matches[2] ) { return 1; } // Prefer '-flash' models over other suffixes. if ( '-flash' === $a_matches[2] && '-flash' !== $b_matches[2] ) { return -1; } if ( '-flash' === $b_matches[2] && '-flash' !== $a_matches[2] ) { return 1; } } // Fallback: Sort alphabetically. return strcmp( $a_slug, $b_slug ); } }