Merge branch 'MDL-85967-main' of https://github.com/davewoloszyn/moodle
This commit is contained in:
Vendored
+1
-1
@@ -5,6 +5,6 @@ define("core_ai/helper",["exports"],(function(_exports){function _defineProperty
|
||||
* @module core_ai/helper
|
||||
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;class AIHelper{static replaceLineBreaks(text){const textWithBreaks=text.replace(/\n{2,}|\r\n/g,"<br/><br/>").replace(/\n/g,"<br/>");return"<p>".concat(textWithBreaks,"</p>")}static replaceMarkdown(text){return text.replace(/\*\*(.*?)\*\*/g,"<strong>$1</strong>")}static formatResponse(text){let formattedText=this.replaceLineBreaks(text);return formattedText=this.replaceMarkdown(formattedText),formattedText}}return _exports.default=AIHelper,_defineProperty(AIHelper,"populateFields",((settings,containerId)=>{const container=document.getElementById(containerId);if(container)for(const[key,value]of Object.entries(settings)){container.querySelector('[name="'.concat(key,'"]')).value=value}})),_defineProperty(AIHelper,"clearFields",(containerId=>{const container=document.getElementById(containerId);if(container){container.querySelectorAll("input, select, textarea").forEach((element=>{"checkbox"===element.type||"radio"===element.type?element.checked=!1:"SELECT"===element.tagName?element.selectedIndex=0:element.value=""}))}})),_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;class AIHelper{static replaceLineBreaks(text){const textWithBreaks=text.replace(/\n{2,}|\r\n/g,"<br/><br/>").replace(/\n/g,"<br/>");return"<p>".concat(textWithBreaks,"</p>")}static replaceMarkdown(text){return text.replace(/\*\*(.*?)\*\*/g,"<strong>$1</strong>")}static formatResponse(text){let formattedText=this.replaceLineBreaks(text);return formattedText=this.replaceMarkdown(formattedText),formattedText}}return _exports.default=AIHelper,_defineProperty(AIHelper,"populateFields",((settings,containerId)=>{const container=document.getElementById(containerId);if(container)for(const[key,value]of Object.entries(settings)){const field=container.querySelector('[name="'.concat(key,'"]'));field&&(field.value=value)}})),_defineProperty(AIHelper,"clearFields",(containerId=>{const container=document.getElementById(containerId);if(container){container.querySelectorAll("input, select, textarea").forEach((element=>{"checkbox"===element.type||"radio"===element.type?element.checked=!1:"SELECT"===element.tagName?element.selectedIndex=0:element.value=""}))}})),_exports.default}));
|
||||
|
||||
//# sourceMappingURL=helper.min.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"helper.min.js","sources":["../src/helper.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * The helper module or AI Subsystem.\n *\n * @module core_ai/helper\n * @copyright 2024 Huong Nguyen <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class AIHelper {\n /**\n * Replace double line breaks with <br> and with </p><p> for paragraphs.\n * This is to handle the difference in response from the AI to what is expected by the editor.\n *\n * @param {String} text The text to replace.\n * @returns {String}\n */\n static replaceLineBreaks(text) {\n // Replace double line breaks with </p><p> for paragraphs\n const textWithParagraphs = text.replace(/\\n{2,}|\\r\\n/g, '<br/><br/>');\n\n // Replace remaining single line breaks with <br> tags\n const textWithBreaks = textWithParagraphs.replace(/\\n/g, '<br/>');\n\n // Add opening and closing <p> tags to wrap the entire content\n return `<p>${textWithBreaks}</p>`;\n }\n\n /**\n * Replace markdown formatting.\n * Even when asked not to, AI models will sometimes return markdown.\n *\n * @param {String} text The text to replace.\n * @returns {String}\n */\n static replaceMarkdown(text) {\n // Replace markdown bold formatting HTML equivalent.\n const textWithMarkdown = text.replace(/\\*\\*(.*?)\\*\\*/g, '<strong>$1</strong>');\n\n return textWithMarkdown;\n }\n\n /**\n * Format the response provided by the AI model.\n *\n * @param {String} text The text to format.\n * @returns {String}\n */\n static formatResponse(text) {\n let formattedText = this.replaceLineBreaks(text) ;\n formattedText = this.replaceMarkdown(formattedText);\n\n return formattedText;\n }\n\n /**\n * Populate fields using settings that match key to name of input.\n *\n * @param {Object} settings The settings to populate with.\n * @param {String} containerId The target container.\n */\n static populateFields = (settings, containerId) => {\n const container = document.getElementById(containerId);\n\n if (container) {\n for (const [key, value] of Object.entries(settings)) {\n const field = container.querySelector(`[name=\"${key}\"]`);\n field.value = value;\n }\n }\n };\n\n /**\n * Reset all fields in a container.\n *\n * @param {String} containerId The target container.\n */\n static clearFields = (containerId) => {\n const container = document.getElementById(containerId);\n\n if (container) {\n const allFormElements = container.querySelectorAll('input, select, textarea');\n allFormElements.forEach(element => {\n if (element.type === 'checkbox' || element.type === 'radio') {\n element.checked = false;\n } else if (element.tagName === 'SELECT') {\n element.selectedIndex = 0;\n } else {\n element.value = '';\n }\n });\n }\n };\n}\n"],"names":["AIHelper","text","textWithBreaks","replace","formattedText","this","replaceLineBreaks","replaceMarkdown","settings","containerId","container","document","getElementById","key","value","Object","entries","querySelector","querySelectorAll","forEach","element","type","checked","tagName","selectedIndex"],"mappings":";;;;;;;2FAsBqBA,kCAQQC,YAKfC,eAHqBD,KAAKE,QAAQ,eAAgB,cAGdA,QAAQ,MAAO,4BAG5CD,8CAUMD,aAEMA,KAAKE,QAAQ,iBAAkB,6CAWtCF,UACdG,cAAgBC,KAAKC,kBAAkBL,aAC3CG,cAAgBC,KAAKE,gBAAgBH,eAE9BA,gEA3CMJ,2BAoDO,CAACQ,SAAUC,qBACzBC,UAAYC,SAASC,eAAeH,gBAEtCC,cACK,MAAOG,IAAKC,SAAUC,OAAOC,QAAQR,UAAW,CACnCE,UAAUO,+BAAwBJ,WAC1CC,MAAQA,0BA1DTd,wBAoEKS,oBACZC,UAAYC,SAASC,eAAeH,gBAEtCC,UAAW,CACaA,UAAUQ,iBAAiB,2BACnCC,SAAQC,UACC,aAAjBA,QAAQC,MAAwC,UAAjBD,QAAQC,KACvCD,QAAQE,SAAU,EACS,WAApBF,QAAQG,QACfH,QAAQI,cAAgB,EAExBJ,QAAQN,MAAQ"}
|
||||
{"version":3,"file":"helper.min.js","sources":["../src/helper.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * The helper module or AI Subsystem.\n *\n * @module core_ai/helper\n * @copyright 2024 Huong Nguyen <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class AIHelper {\n /**\n * Replace double line breaks with <br> and with </p><p> for paragraphs.\n * This is to handle the difference in response from the AI to what is expected by the editor.\n *\n * @param {String} text The text to replace.\n * @returns {String}\n */\n static replaceLineBreaks(text) {\n // Replace double line breaks with </p><p> for paragraphs\n const textWithParagraphs = text.replace(/\\n{2,}|\\r\\n/g, '<br/><br/>');\n\n // Replace remaining single line breaks with <br> tags\n const textWithBreaks = textWithParagraphs.replace(/\\n/g, '<br/>');\n\n // Add opening and closing <p> tags to wrap the entire content\n return `<p>${textWithBreaks}</p>`;\n }\n\n /**\n * Replace markdown formatting.\n * Even when asked not to, AI models will sometimes return markdown.\n *\n * @param {String} text The text to replace.\n * @returns {String}\n */\n static replaceMarkdown(text) {\n // Replace markdown bold formatting HTML equivalent.\n const textWithMarkdown = text.replace(/\\*\\*(.*?)\\*\\*/g, '<strong>$1</strong>');\n\n return textWithMarkdown;\n }\n\n /**\n * Format the response provided by the AI model.\n *\n * @param {String} text The text to format.\n * @returns {String}\n */\n static formatResponse(text) {\n let formattedText = this.replaceLineBreaks(text) ;\n formattedText = this.replaceMarkdown(formattedText);\n\n return formattedText;\n }\n\n /**\n * Populate fields using settings that match key to name of input.\n *\n * @param {Object} settings The settings to populate with.\n * @param {String} containerId The target container.\n */\n static populateFields = (settings, containerId) => {\n const container = document.getElementById(containerId);\n\n if (container) {\n for (const [key, value] of Object.entries(settings)) {\n const field = container.querySelector(`[name=\"${key}\"]`);\n if (field) {\n field.value = value;\n }\n }\n }\n };\n\n /**\n * Reset all fields in a container.\n *\n * @param {String} containerId The target container.\n */\n static clearFields = (containerId) => {\n const container = document.getElementById(containerId);\n\n if (container) {\n const allFormElements = container.querySelectorAll('input, select, textarea');\n allFormElements.forEach(element => {\n if (element.type === 'checkbox' || element.type === 'radio') {\n element.checked = false;\n } else if (element.tagName === 'SELECT') {\n element.selectedIndex = 0;\n } else {\n element.value = '';\n }\n });\n }\n };\n}\n"],"names":["AIHelper","text","textWithBreaks","replace","formattedText","this","replaceLineBreaks","replaceMarkdown","settings","containerId","container","document","getElementById","key","value","Object","entries","field","querySelector","querySelectorAll","forEach","element","type","checked","tagName","selectedIndex"],"mappings":";;;;;;;2FAsBqBA,kCAQQC,YAKfC,eAHqBD,KAAKE,QAAQ,eAAgB,cAGdA,QAAQ,MAAO,4BAG5CD,8CAUMD,aAEMA,KAAKE,QAAQ,iBAAkB,6CAWtCF,UACdG,cAAgBC,KAAKC,kBAAkBL,aAC3CG,cAAgBC,KAAKE,gBAAgBH,eAE9BA,gEA3CMJ,2BAoDO,CAACQ,SAAUC,qBACzBC,UAAYC,SAASC,eAAeH,gBAEtCC,cACK,MAAOG,IAAKC,SAAUC,OAAOC,QAAQR,UAAW,OAC3CS,MAAQP,UAAUQ,+BAAwBL,WAC5CI,QACAA,MAAMH,MAAQA,2BA3Dbd,wBAsEKS,oBACZC,UAAYC,SAASC,eAAeH,gBAEtCC,UAAW,CACaA,UAAUS,iBAAiB,2BACnCC,SAAQC,UACC,aAAjBA,QAAQC,MAAwC,UAAjBD,QAAQC,KACvCD,QAAQE,SAAU,EACS,WAApBF,QAAQG,QACfH,QAAQI,cAAgB,EAExBJ,QAAQP,MAAQ"}
|
||||
@@ -78,7 +78,9 @@ export default class AIHelper {
|
||||
if (container) {
|
||||
for (const [key, value] of Object.entries(settings)) {
|
||||
const field = container.querySelector(`[name="${key}"]`);
|
||||
field.value = value;
|
||||
if (field) {
|
||||
field.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -53,15 +53,15 @@ class gpt4o extends base implements openai_base {
|
||||
'component' => 'aiprovider_openai',
|
||||
],
|
||||
],
|
||||
'max_tokens' => [
|
||||
'max_completion_tokens' => [
|
||||
'elementtype' => 'text',
|
||||
'label' => [
|
||||
'identifier' => 'settings_max_tokens',
|
||||
'identifier' => 'settings_max_completion_tokens',
|
||||
'component' => 'aiprovider_openai',
|
||||
],
|
||||
'type' => PARAM_INT,
|
||||
'help' => [
|
||||
'identifier' => 'settings_max_tokens',
|
||||
'identifier' => 'settings_max_completion_tokens',
|
||||
'component' => 'aiprovider_openai',
|
||||
],
|
||||
],
|
||||
|
||||
@@ -35,6 +35,36 @@ class o1 extends gpt4o {
|
||||
return 'O1';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get_model_settings(): array {
|
||||
return [
|
||||
'top_p' => [
|
||||
'elementtype' => 'text',
|
||||
'label' => [
|
||||
'identifier' => 'settings_top_p',
|
||||
'component' => 'aiprovider_openai',
|
||||
],
|
||||
'type' => PARAM_FLOAT,
|
||||
'help' => [
|
||||
'identifier' => 'settings_top_p',
|
||||
'component' => 'aiprovider_openai',
|
||||
],
|
||||
],
|
||||
'max_completion_tokens' => [
|
||||
'elementtype' => 'text',
|
||||
'label' => [
|
||||
'identifier' => 'settings_max_completion_tokens',
|
||||
'component' => 'aiprovider_openai',
|
||||
],
|
||||
'type' => PARAM_INT,
|
||||
'help' => [
|
||||
'identifier' => 'settings_max_completion_tokens',
|
||||
'component' => 'aiprovider_openai',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function model_type(): array {
|
||||
return [self::MODEL_TYPE_TEXT];
|
||||
|
||||
@@ -89,12 +89,16 @@ class action_form extends action_settings_form {
|
||||
$modelclass = helper::get_model_class($data->model);
|
||||
if ($modelclass) {
|
||||
if ($modelclass->has_model_settings()) {
|
||||
$modelsettings = array_keys($modelclass->get_model_settings());
|
||||
$modelsettings = $modelclass->get_model_settings();
|
||||
$modelsettingskeys = array_keys($modelclass->get_model_settings());
|
||||
// Process the model settings.
|
||||
$modeldata = [];
|
||||
foreach ($data as $key => $value) {
|
||||
if (in_array($key, $modelsettings)) {
|
||||
$modeldata[$key] = $value;
|
||||
if (in_array($key, $modelsettingskeys)) {
|
||||
$type = $modelsettings[$key]['type'];
|
||||
// Cast form values to their intended types.
|
||||
$modeldata[$key] = $this->cast_value_to_type($value, $type);
|
||||
$data->$key = $this->cast_value_to_type($value, $type);
|
||||
}
|
||||
}
|
||||
if (!empty($modeldata)) {
|
||||
@@ -223,4 +227,33 @@ class action_form extends action_settings_form {
|
||||
}
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast a value to its intended type.
|
||||
*
|
||||
* When form values are submitted they come through as strings.
|
||||
* We need to reassign the types for correct use with model requests.
|
||||
*
|
||||
* @param string $value Value to cast
|
||||
* @param string $type Intended type
|
||||
* @return mixed Cast value (string, int, float, null)
|
||||
*/
|
||||
protected function cast_value_to_type(string $value, string $type): mixed {
|
||||
switch ($type) {
|
||||
case PARAM_INT:
|
||||
$castvalue = $value !== '' ? intval($value) : null;
|
||||
break;
|
||||
|
||||
case PARAM_FLOAT:
|
||||
case PARAM_RAW:
|
||||
$castvalue = $value !== '' ? floatval($value) : null;
|
||||
break;
|
||||
|
||||
default:
|
||||
$castvalue = $value;
|
||||
break;
|
||||
}
|
||||
|
||||
return $castvalue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ $string['extraparams_help'] = 'Extra parameters can be configured here. We suppo
|
||||
<pre>
|
||||
{
|
||||
"temperature": 0.5,
|
||||
"max_tokens": 100
|
||||
"max_completion_tokens": 100
|
||||
}
|
||||
</pre>';
|
||||
$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,7 @@ $string['globalratelimit_desc'] = 'The number of site-wide requests allowed per
|
||||
$string['orgid_desc'] = 'Get an OpenAI organization ID from your <a href="https://platform.openai.com/account/org-settings">OpenAI Platform account</a>.';
|
||||
$string['userratelimit'] = 'Maximum number of requests per user';
|
||||
$string['userratelimit_desc'] = 'The number of requests allowed per hour, per user.';
|
||||
|
||||
// Deprecated since Moodle 5.1.
|
||||
$string['settings_max_tokens'] = 'max_tokens';
|
||||
$string['settings_max_tokens_help'] = 'The maximum number of tokens used in the generated text.';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -157,40 +157,34 @@ Feature: An administrator can manage AI subsystem settings
|
||||
# Configure model settings for 'GPT-4o'.
|
||||
And I click on the "Settings" link in the table row containing "Generate text"
|
||||
And I set the following fields to these values:
|
||||
| AI model | GPT-4o |
|
||||
| top_p | 11 |
|
||||
| max_tokens | 12 |
|
||||
| frequency_penalty | 13 |
|
||||
| presence_penalty | 14 |
|
||||
| AI model | GPT-4o |
|
||||
| top_p | 11 |
|
||||
| max_completion_tokens | 12 |
|
||||
| frequency_penalty | 13 |
|
||||
| presence_penalty | 14 |
|
||||
And I press "Save changes"
|
||||
And I click on the "Settings" link in the table row containing "Generate text"
|
||||
And the field "top_p" matches value "11"
|
||||
And the field "max_tokens" matches value "12"
|
||||
And the field "max_completion_tokens" matches value "12"
|
||||
And the field "frequency_penalty" matches value "13"
|
||||
And the field "presence_penalty" matches value "14"
|
||||
# Change the model and check fields are empty.
|
||||
And I set the following fields to these values:
|
||||
| AI model | O1 |
|
||||
And the field "top_p" matches value ""
|
||||
And the field "max_tokens" matches value ""
|
||||
And the field "frequency_penalty" matches value ""
|
||||
And the field "presence_penalty" matches value ""
|
||||
And the field "max_completion_tokens" matches value ""
|
||||
# Set model settings for 'O1'.
|
||||
And I set the following fields to these values:
|
||||
| top_p | 21 |
|
||||
| max_tokens | 22 |
|
||||
| frequency_penalty | 23 |
|
||||
| presence_penalty | 24 |
|
||||
| top_p | 21 |
|
||||
| max_completion_tokens | 22 |
|
||||
And I press "Save changes"
|
||||
# Go back and check both models have their settings stored.
|
||||
And I click on the "Settings" link in the table row containing "Generate text"
|
||||
And the field "top_p" matches value "21"
|
||||
And the field "max_tokens" matches value "22"
|
||||
And the field "frequency_penalty" matches value "23"
|
||||
And the field "presence_penalty" matches value "24"
|
||||
And the field "max_completion_tokens" matches value "22"
|
||||
And I set the following fields to these values:
|
||||
| AI model | GPT-4o |
|
||||
And the field "top_p" matches value "11"
|
||||
And the field "max_tokens" matches value "12"
|
||||
And the field "max_completion_tokens" matches value "12"
|
||||
And the field "frequency_penalty" matches value "13"
|
||||
And the field "presence_penalty" matches value "14"
|
||||
|
||||
@@ -1915,5 +1915,78 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2025070600.01);
|
||||
}
|
||||
|
||||
if ($oldversion < 2025072500.01) {
|
||||
// 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']);
|
||||
}
|
||||
// Rename max_tokens in model settings too (casting not necessary).
|
||||
if (isset($action['modelsettings'][$model]['max_tokens'])) {
|
||||
$actionconfig[$actionkey]['modelsettings'][$model]['max_completion_tokens'] =
|
||||
$action['modelsettings'][$model]['max_tokens'];
|
||||
unset($actionconfig[$actionkey]['modelsettings'][$model]['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']);
|
||||
}
|
||||
// Remove from model settings too.
|
||||
if (isset($action['modelsettings'][$model]['top_p'])) {
|
||||
unset($actionconfig[$actionkey]['modelsettings'][$model]['top_p']);
|
||||
}
|
||||
if (isset($action['modelsettings'][$model]['presence_penalty'])) {
|
||||
unset($actionconfig[$actionkey]['modelsettings'][$model]['presence_penalty']);
|
||||
}
|
||||
if (isset($action['modelsettings'][$model]['frequency_penalty'])) {
|
||||
unset($actionconfig[$actionkey]['modelsettings'][$model]['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, 2025072500.01);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user