* @license GPL-2.0+ https://www.gnu.org/licenses/gpl-2.0.html * @link https://aisk.chat */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * AISK_Model_Manager Class * * @category WordPress * @package AISK * @author Aisk Team * @license GPL-2.0+ https://www.gnu.org/licenses/gpl-2.0.html * @link https://aisk.chat */ class AISK_Model_Manager { // Best quality/cost balance for chat and JSON intent classification const DEFAULT_MODEL = 'gpt-4.1-mini'; const CACHE_KEY = 'aisk_openai_models_'; const CACHE_TTL = 12 * HOUR_IN_SECONDS; // Shown when the OpenAI model list cannot be loaded const FALLBACK_MODELS = [ 'gpt-4.1-mini', 'gpt-4.1', 'gpt-4.1-nano', 'gpt-4o-mini', 'gpt-4o', ]; /** * Get the chat model selected in settings * * @since 2.7.3 * * @return string */ public static function get_chat_model() { $settings = get_option( 'aisk_settings', [] ); $model = isset( $settings['general']['chat_model'] ) ? (string) $settings['general']['chat_model'] : ''; return self::is_supported( $model ) ? $model : self::DEFAULT_MODEL; } /** * Whether a model works with the request parameters the plugin sends * (temperature, max_tokens, penalties, json_object response format). * Reasoning models (o-series, gpt-5) reject those parameters. * * @param string $model Model ID * * @since 2.7.3 * * @return bool */ public static function is_supported( $model ) { return (bool) preg_match( '/^(gpt-4\.1(-mini|-nano)?|gpt-4o(-mini)?|gpt-4-turbo)(-\d{4}-\d{2}-\d{2})?$/', (string) $model ); } /** * Load selectable models from the OpenAI API, cached per API key * * @param bool $refresh Bypass the cache * * @since 2.7.3 * * @return array { models: string[], default: string, source: 'openai'|'fallback' } */ public static function get_available_models( $refresh = false ) { $settings = get_option( 'aisk_settings', [] ); $api_key = isset( $settings['general']['openai_key'] ) ? $settings['general']['openai_key'] : ''; $result = [ 'models' => self::FALLBACK_MODELS, 'default' => self::DEFAULT_MODEL, 'source' => 'fallback', ]; if ( empty( $api_key ) ) { return $result; } $cache_key = self::CACHE_KEY . md5( $api_key ); if ( ! $refresh ) { $cached = get_transient( $cache_key ); if ( is_array( $cached ) && ! empty( $cached ) ) { $result['models'] = $cached; $result['source'] = 'openai'; return $result; } } $response = wp_remote_get( AISK_OPENAI_API_URL . '/models', [ 'headers' => [ 'Authorization' => 'Bearer ' . $api_key ], 'timeout' => 15, ] ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return $result; } $body = json_decode( wp_remote_retrieve_body( $response ), true ); $models = []; foreach ( isset( $body['data'] ) && is_array( $body['data'] ) ? $body['data'] : [] as $item ) { if ( isset( $item['id'] ) && self::is_supported( $item['id'] ) ) { $models[] = $item['id']; } } if ( empty( $models ) ) { return $result; } // Recommended aliases first, then dated snapshots alphabetically usort( $models, function ( $a, $b ) { $rank_a = array_search( $a, self::FALLBACK_MODELS, true ); $rank_b = array_search( $b, self::FALLBACK_MODELS, true ); $rank_a = false === $rank_a ? PHP_INT_MAX : $rank_a; $rank_b = false === $rank_b ? PHP_INT_MAX : $rank_b; return $rank_a === $rank_b ? strcmp( $a, $b ) : $rank_a <=> $rank_b; } ); set_transient( $cache_key, $models, self::CACHE_TTL ); $result['models'] = $models; $result['source'] = 'openai'; return $result; } }