diff --git a/ai/provider/openai/classes/aimodel/gpt4o.php b/ai/provider/openai/classes/aimodel/gpt4o.php index 07e89eef1ad..67df0b81ed6 100644 --- a/ai/provider/openai/classes/aimodel/gpt4o.php +++ b/ai/provider/openai/classes/aimodel/gpt4o.php @@ -55,11 +55,11 @@ class gpt4o extends base implements openai_base { $mform->addElement( 'text', - 'max_tokens', - get_string('settings_max_tokens', 'aiprovider_openai'), + 'max_completion_tokens', + get_string('settings_max_completion_tokens', 'aiprovider_openai'), ); - $mform->setType('max_tokens', PARAM_INT); - $mform->addHelpButton('max_tokens', 'settings_max_tokens', 'aiprovider_openai'); + $mform->setType('max_completion_tokens', PARAM_INT); + $mform->addHelpButton('max_completion_tokens', 'settings_max_completion_tokens', 'aiprovider_openai'); $mform->addElement( 'text', diff --git a/ai/provider/openai/classes/aimodel/o1.php b/ai/provider/openai/classes/aimodel/o1.php index 3a5c2bdc6d6..43e632752dd 100644 --- a/ai/provider/openai/classes/aimodel/o1.php +++ b/ai/provider/openai/classes/aimodel/o1.php @@ -16,6 +16,8 @@ namespace aiprovider_openai\aimodel; +use MoodleQuickForm; + /** * O1 AI model. * @@ -40,6 +42,17 @@ class o1 extends gpt4o { return true; } + #[\Override] + public function add_model_settings(MoodleQuickForm $mform): void { + $mform->addElement( + 'text', + 'max_completion_tokens', + get_string('settings_max_completion_tokens', 'aiprovider_openai'), + ); + $mform->setType('max_completion_tokens', PARAM_INT); + $mform->addHelpButton('max_completion_tokens', 'settings_max_completion_tokens', 'aiprovider_openai'); + } + #[\Override] public function model_type(): array { return [self::MODEL_TYPE_TEXT]; diff --git a/ai/provider/openai/classes/form/action_form.php b/ai/provider/openai/classes/form/action_form.php index bbd1260c023..c2dd846d8bb 100644 --- a/ai/provider/openai/classes/form/action_form.php +++ b/ai/provider/openai/classes/form/action_form.php @@ -84,8 +84,23 @@ class action_form extends action_settings_form { } else { // Set the model to the selected model template. $data->model = $data->modeltemplate; - } + // Cast settings to their intended types. + if ($data->model === 'gpt-4o' || $data->model === 'o1') { + if (isset($data->top_p)) { + $data->top_p = floatval($data->top_p); + } + if (isset($data->max_completion_tokens)) { + $data->max_completion_tokens = intval($data->max_completion_tokens); + } + if (isset($data->presence_penalty)) { + $data->presence_penalty = floatval($data->presence_penalty); + } + if (isset($data->frequency_penalty)) { + $data->frequency_penalty = floatval($data->frequency_penalty); + } + } + } } // Unset the model template. unset($data->custommodel); diff --git a/ai/provider/openai/lang/en/aiprovider_openai.php b/ai/provider/openai/lang/en/aiprovider_openai.php index 421884dd703..2552fb87608 100644 --- a/ai/provider/openai/lang/en/aiprovider_openai.php +++ b/ai/provider/openai/lang/en/aiprovider_openai.php @@ -48,7 +48,7 @@ $string['extraparams_help'] = 'Extra parameters can be configured here. We suppo
 {
     "temperature": 0.5,
-    "max_tokens": 100
+    "max_completion_tokens": 100
 }
 
'; $string['invalidjson'] = 'Invalid JSON string'; @@ -65,8 +65,8 @@ $string['settings'] = 'Settings'; $string['settings_frequency_penalty'] = 'frequency_penalty'; $string['settings_frequency_penalty_help'] = 'The frequency penalty adjusts how often words are repeated. The higher the penalty, the less repetitions in the generated text.'; $string['settings_help'] = 'Adjust the settings below to customise how requests are sent to OpenAI.'; -$string['settings_max_tokens'] = 'max_tokens'; -$string['settings_max_tokens_help'] = 'The maximum number of tokens used in the generated text.'; +$string['settings_max_completion_tokens'] = 'max_completion_tokens'; +$string['settings_max_completion_tokens_help'] = 'The maximum number of tokens used in the generated text.'; $string['settings_presence_penalty'] = 'presence_penalty'; $string['settings_presence_penalty_help'] = 'The presence penalty encourages the model to use new words by increasing the likelihood of choosing words it hasn\'t used before. A higher value makes the generated text more diverse, while a lower value allows more repetition.'; $string['settings_top_p'] = 'top_p'; @@ -88,3 +88,5 @@ $string['globalratelimit_desc'] = 'The number of site-wide requests allowed per $string['orgid_desc'] = 'Get an OpenAI organization ID from your OpenAI Platform account.'; $string['userratelimit'] = 'Maximum number of requests per user'; $string['userratelimit_desc'] = 'The number of requests allowed per hour, per user.'; +$string['settings_max_tokens'] = 'max_tokens'; +$string['settings_max_tokens_help'] = 'The maximum number of tokens used in the generated text.'; diff --git a/ai/provider/openai/lang/en/deprecated.txt b/ai/provider/openai/lang/en/deprecated.txt index 612439be544..1856a3afd1c 100644 --- a/ai/provider/openai/lang/en/deprecated.txt +++ b/ai/provider/openai/lang/en/deprecated.txt @@ -13,3 +13,5 @@ globalratelimit_desc,aiprovider_openai orgid_desc,aiprovider_openai userratelimit,aiprovider_openai userratelimit_desc,aiprovider_openai +settings_max_tokens,aiprovider_openai +settings_max_tokens_help,aiprovider_openai diff --git a/ai/provider/openai/tests/process_generate_image_test.php b/ai/provider/openai/tests/process_generate_image_test.php index 78620fffb3b..fb829763019 100644 --- a/ai/provider/openai/tests/process_generate_image_test.php +++ b/ai/provider/openai/tests/process_generate_image_test.php @@ -132,7 +132,7 @@ final class process_generate_image_test extends \advanced_testcase { actionconfig: [ 'model' => 'dall-e-3', 'temperature' => '0.5', - 'max_tokens' => '100', + 'max_completion_tokens' => '100', ], ); $processor = new process_generate_image($this->provider, $this->action); @@ -145,13 +145,13 @@ final class process_generate_image_test extends \advanced_testcase { $this->assertEquals('dall-e-3', $body->model); $this->assertEquals('0.5', $body->temperature); - $this->assertEquals('100', $body->max_tokens); + $this->assertEquals('100', $body->max_completion_tokens); $this->provider = $this->create_provider( actionclass: \core_ai\aiactions\generate_image::class, actionconfig: [ 'model' => 'my-custom-gpt', - 'modelextraparams' => '{"temperature": 0.5,"max_tokens": 100}', + 'modelextraparams' => '{"temperature": 0.5,"max_completion_tokens": 100}', ], ); $processor = new process_generate_image($this->provider, $this->action); @@ -164,7 +164,7 @@ final class process_generate_image_test extends \advanced_testcase { $this->assertEquals('my-custom-gpt', $body->model); $this->assertEquals('0.5', $body->temperature); - $this->assertEquals('100', $body->max_tokens); + $this->assertEquals('100', $body->max_completion_tokens); } /** diff --git a/ai/provider/openai/tests/process_generate_text_test.php b/ai/provider/openai/tests/process_generate_text_test.php index c98284b12fc..d2cbdd11cd3 100644 --- a/ai/provider/openai/tests/process_generate_text_test.php +++ b/ai/provider/openai/tests/process_generate_text_test.php @@ -102,7 +102,7 @@ final class process_generate_text_test extends \advanced_testcase { actionconfig: [ 'systeminstruction' => get_string('action_generate_text_instruction', 'core_ai'), 'temperature' => '0.5', - 'max_tokens' => '100', + 'max_completion_tokens' => '100', ], ); $processor = new process_generate_text($this->provider, $this->action); @@ -115,14 +115,14 @@ final class process_generate_text_test extends \advanced_testcase { $this->assertEquals('gpt-4o', $body->model); $this->assertEquals('0.5', $body->temperature); - $this->assertEquals('100', $body->max_tokens); + $this->assertEquals('100', $body->max_completion_tokens); $this->provider = $this->create_provider( actionclass: \core_ai\aiactions\generate_text::class, actionconfig: [ 'model' => 'my-custom-gpt', 'systeminstruction' => get_string('action_generate_text_instruction', 'core_ai'), - 'modelextraparams' => '{"temperature": 0.5,"max_tokens": 100}', + 'modelextraparams' => '{"temperature": 0.5,"max_completion_tokens": 100}', ], ); $processor = new process_generate_text($this->provider, $this->action); @@ -135,7 +135,7 @@ final class process_generate_text_test extends \advanced_testcase { $this->assertEquals('my-custom-gpt', $body->model); $this->assertEquals('0.5', $body->temperature); - $this->assertEquals('100', $body->max_tokens); + $this->assertEquals('100', $body->max_completion_tokens); } /** diff --git a/ai/provider/openai/tests/process_summarise_text_test.php b/ai/provider/openai/tests/process_summarise_text_test.php index 06d3004a478..84d8ea65297 100644 --- a/ai/provider/openai/tests/process_summarise_text_test.php +++ b/ai/provider/openai/tests/process_summarise_text_test.php @@ -104,7 +104,7 @@ final class process_summarise_text_test extends \advanced_testcase { actionconfig: [ 'systeminstruction' => get_string('action_summarise_text_instruction', 'core_ai'), 'temperature' => '0.5', - 'max_tokens' => '100', + 'max_completion_tokens' => '100', ], ); $processor = new process_summarise_text($this->provider, $this->action); @@ -117,14 +117,14 @@ final class process_summarise_text_test extends \advanced_testcase { $this->assertEquals('gpt-4o', $body->model); $this->assertEquals('0.5', $body->temperature); - $this->assertEquals('100', $body->max_tokens); + $this->assertEquals('100', $body->max_completion_tokens); $this->provider = $this->create_provider( actionclass: \core_ai\aiactions\summarise_text::class, actionconfig: [ 'model' => 'my-custom-gpt', 'systeminstruction' => get_string('action_summarise_text_instruction', 'core_ai'), - 'modelextraparams' => '{"temperature": 0.5,"max_tokens": 100}', + 'modelextraparams' => '{"temperature": 0.5,"max_completion_tokens": 100}', ], ); $processor = new process_summarise_text($this->provider, $this->action); @@ -137,7 +137,7 @@ final class process_summarise_text_test extends \advanced_testcase { $this->assertEquals('my-custom-gpt', $body->model); $this->assertEquals('0.5', $body->temperature); - $this->assertEquals('100', $body->max_tokens); + $this->assertEquals('100', $body->max_completion_tokens); } /** diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 310a238436a..81daa665b77 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -1797,5 +1797,62 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2025041400.09); } + if ($oldversion < 2025041401.08) { + // Get all OpenAI providers. + $records = $DB->get_records('ai_providers', ['provider' => 'aiprovider_openai\provider']); + + foreach ($records as $record) { + $actionconfig = json_decode($record->actionconfig, true, 512); + $originalactionconfig = $actionconfig; + + foreach ($actionconfig as $actionkey => $action) { + $model = $action['settings']['model']; + if ($model === 'gpt-4o' || $model === 'o1') { + // Rename setting max_tokens to max_completion_tokens. + if (isset($action['settings']['max_tokens'])) { + $actionconfig[$actionkey]['settings']['max_completion_tokens'] = intval($action['settings']['max_tokens']); + unset($actionconfig[$actionkey]['settings']['max_tokens']); + } + } + // Cast settings for 'gpt-4o' model. + if ($model === 'gpt-4o') { + if (isset($action['settings']['top_p'])) { + $actionconfig[$actionkey]['settings']['top_p'] = floatval($action['settings']['top_p']); + } + if (isset($action['settings']['presence_penalty'])) { + $actionconfig[$actionkey]['settings']['presence_penalty'] = + floatval($action['settings']['presence_penalty']); + } + if (isset($action['settings']['frequency_penalty'])) { + $actionconfig[$actionkey]['settings']['frequency_penalty'] = + floatval($action['settings']['frequency_penalty']); + } + } + // Remove settings from 'o1' model. + if ($model === 'o1') { + if (isset($action['settings']['top_p'])) { + unset($actionconfig[$actionkey]['settings']['top_p']); + } + if (isset($action['settings']['presence_penalty'])) { + unset($actionconfig[$actionkey]['settings']['presence_penalty']); + } + if (isset($action['settings']['frequency_penalty'])) { + unset($actionconfig[$actionkey]['settings']['frequency_penalty']); + } + } + } + + if ($originalactionconfig !== $actionconfig) { + $updatedrecord = new stdClass(); + $updatedrecord->id = $record->id; + $updatedrecord->actionconfig = json_encode($actionconfig); + $DB->update_record('ai_providers', $updatedrecord); + } + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2025041401.08); + } + return true; } diff --git a/version.php b/version.php index 7f17bcc6955..4291de57151 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2025041401.07; // 20250414 = branching date YYYYMMDD - do not modify! +$version = 2025041401.08; // 20250414 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '5.0.1+ (Build: 20250725)'; // Human-friendly version name