mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 08:56:36 +02:00
Merge branch 'MDL-82980-main' of https://github.com/HuongNV13/moodle
This commit is contained in:
commit
809849ef34
31 changed files with 1126 additions and 148 deletions
15
.upgradenotes/MDL-82980-2025021903294284.yml
Normal file
15
.upgradenotes/MDL-82980-2025021903294284.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
issueNumber: MDL-82980
|
||||
notes:
|
||||
core_ai:
|
||||
- message: |2
|
||||
- The `\core_ai\form\action_settings_form` class has been updated to automatically include action buttons such as Save and Cancel.
|
||||
- AI provider plugins should update their form classes by removing the `$this->add_action_buttons();` call, as it is no longer required.
|
||||
type: changed
|
||||
- message: >
|
||||
- A new hook, `\core_ai\hook\after_ai_action_settings_form_hook`, has
|
||||
been introduced. It will allows AI provider plugins to add additional
|
||||
form elements for action settings configuration.
|
||||
type: improved
|
||||
- message: |2
|
||||
- AI provider plugins that want to implement `pre-defined models` and display additional settings for models must now extend the `\core_ai\aimodel\base` class.
|
||||
type: improved
|
61
ai/classes/aimodel/base.php
Normal file
61
ai/classes/aimodel/base.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aimodel;
|
||||
|
||||
use MoodleQuickForm;
|
||||
|
||||
/**
|
||||
* Base Model class.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2025 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class base {
|
||||
/**
|
||||
* Get the display name of the model.
|
||||
* This name is used to display the model in the UI.
|
||||
*
|
||||
* @return string The display name of the model.
|
||||
*/
|
||||
abstract public function get_model_display_name(): string;
|
||||
|
||||
/**
|
||||
* Get the name of the model.
|
||||
* This name is used to identify the model. The system will use this model name to make the request to the AI services.
|
||||
*
|
||||
* @return string The name of the model.
|
||||
*/
|
||||
abstract public function get_model_name(): string;
|
||||
|
||||
/**
|
||||
* Add the model settings to the form.
|
||||
*
|
||||
* @param MoodleQuickForm $mform The form to add the model settings to.
|
||||
*/
|
||||
public function add_model_settings(MoodleQuickForm $mform): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model has settings.
|
||||
*
|
||||
* @return bool Whether the model has settings.
|
||||
*/
|
||||
public function has_model_settings(): bool {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -34,6 +34,24 @@ class action_settings_form extends moodleform {
|
|||
protected function definition() {
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
protected function after_definition() {
|
||||
parent::after_definition();
|
||||
$this->_form->_registerCancelButton('cancel');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function definition_after_data() {
|
||||
// Dispatch a hook for plugins to add their fields.
|
||||
$hook = new \core_ai\hook\after_ai_action_settings_form_hook(
|
||||
mform: $this->_form,
|
||||
plugin: $this->_customdata['providername'],
|
||||
);
|
||||
\core\di::get(\core\hook\manager::class)->dispatch($hook);
|
||||
// Add action buttons.
|
||||
$this->add_action_buttons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default values for the form.
|
||||
*
|
||||
|
|
48
ai/classes/hook/after_ai_action_settings_form_hook.php
Normal file
48
ai/classes/hook/after_ai_action_settings_form_hook.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\hook;
|
||||
|
||||
use MoodleQuickForm;
|
||||
|
||||
/**
|
||||
* Hook after AI action settings form is initiated.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2025 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @property-read MoodleQuickForm $mform The form element
|
||||
* @property-read ?string $plugin Name of the model
|
||||
*/
|
||||
#[\core\attribute\label('Allows plugins to add form elements for action settings setup.')]
|
||||
#[\core\attribute\tags('ai')]
|
||||
class after_ai_action_settings_form_hook {
|
||||
|
||||
/**
|
||||
* Constructor for the hook.
|
||||
*
|
||||
* @param MoodleQuickForm $mform The moodle form instance.
|
||||
* @param ?string $plugin The name of the plugin
|
||||
*/
|
||||
public function __construct(
|
||||
/** @var MoodleQuickForm The moodle form instance. */
|
||||
public readonly MoodleQuickForm $mform,
|
||||
|
||||
/** @var ?string The name of the plugin */
|
||||
public readonly ?string $plugin,
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -366,6 +366,7 @@ class manager {
|
|||
* @param string $name The name of the provider config.
|
||||
* @param bool $enabled The enabled state of the provider.
|
||||
* @param array|null $config The config json.
|
||||
* @param array|null $actionconfig The action config json.
|
||||
* @return provider
|
||||
*/
|
||||
public function create_provider_instance(
|
||||
|
@ -373,6 +374,7 @@ class manager {
|
|||
string $name,
|
||||
bool $enabled = false,
|
||||
?array $config = null,
|
||||
?array $actionconfig = null,
|
||||
): provider {
|
||||
if (!class_exists($classname) || !is_a($classname, provider::class, true)) {
|
||||
throw new \coding_exception("Provider class not valid: {$classname}");
|
||||
|
@ -381,6 +383,7 @@ class manager {
|
|||
enabled: $enabled,
|
||||
name: $name,
|
||||
config: json_encode($config ?? []),
|
||||
actionconfig: $actionconfig ? json_encode($actionconfig) : '',
|
||||
);
|
||||
|
||||
$id = $this->db->insert_record('ai_providers', $provider->to_record());
|
||||
|
|
|
@ -32,7 +32,7 @@ $provider = required_param('provider', PARAM_PLUGIN);
|
|||
$action = required_param('action', PARAM_TEXT);
|
||||
$id = required_param('providerid', PARAM_INT);
|
||||
$returnurl = optional_param('returnurl', null, PARAM_LOCALURL);
|
||||
$data = ['providerid' => $id];
|
||||
$customdata = ['providerid' => $id];
|
||||
|
||||
// Handle return URL.
|
||||
if (empty($returnurl)) {
|
||||
|
@ -43,7 +43,7 @@ if (empty($returnurl)) {
|
|||
} else {
|
||||
$returnurl = new moodle_url($returnurl);
|
||||
}
|
||||
$data['returnurl'] = $returnurl;
|
||||
$customdata['returnurl'] = $returnurl;
|
||||
|
||||
$manager = \core\di::get(\core_ai\manager::class);
|
||||
$providerrecord = $manager->get_provider_records(['id' => $id]);
|
||||
|
@ -52,7 +52,8 @@ $providerrecord = reset($providerrecord);
|
|||
$actionconfig = json_decode($providerrecord->actionconfig, true, 512, JSON_THROW_ON_ERROR);
|
||||
$actionconfig = $actionconfig[$action];
|
||||
|
||||
$data['actionconfig'] = $actionconfig;
|
||||
$customdata['actionconfig'] = $actionconfig;
|
||||
$customdata['providername'] = $provider;
|
||||
|
||||
$urlparams = [
|
||||
'provider' => $provider,
|
||||
|
@ -69,7 +70,7 @@ $PAGE->set_title($title);
|
|||
$PAGE->set_heading($title);
|
||||
|
||||
$providerclass = "\\$provider\\provider";
|
||||
$mform = $providerclass::get_action_settings($action, $data);
|
||||
$mform = $providerclass::get_action_settings($action, $customdata);
|
||||
|
||||
if ($mform->is_cancelled()) {
|
||||
$data = $mform->get_data();
|
||||
|
|
|
@ -75,8 +75,6 @@ class action_generate_image_form extends action_settings_form {
|
|||
$mform->addElement('hidden', 'providerid', $providerid);
|
||||
$mform->setType('providerid', PARAM_INT);
|
||||
|
||||
$this->add_action_buttons();
|
||||
|
||||
$this->set_data($actionconfig);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,8 +86,6 @@ class action_generate_text_form extends action_settings_form {
|
|||
$mform->addElement('hidden', 'providerid', $providerid);
|
||||
$mform->setType('providerid', PARAM_INT);
|
||||
|
||||
$this->add_action_buttons();
|
||||
|
||||
$this->set_data($actionconfig);
|
||||
}
|
||||
}
|
||||
|
|
11
ai/provider/openai/amd/build/modelchooser.min.js
vendored
Normal file
11
ai/provider/openai/amd/build/modelchooser.min.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
define("aiprovider_openai/modelchooser",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;
|
||||
/**
|
||||
* AI provider model selection handler.
|
||||
*
|
||||
* @module aiprovider_openai/modelchooser
|
||||
* @copyright 2025 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
const Selectors_fields={selector:'[data-modelchooser-field="selector"]',updateButton:'[data-modelchooser-field="updateButton"]'};_exports.init=()=>{const modelSelector=document.querySelector(Selectors_fields.selector);modelSelector&&modelSelector.addEventListener("change",(e=>{modelSelector.options[e.target.selectedIndex].selected=!0;e.target.closest("form").querySelector(Selectors_fields.updateButton).click()}))}}));
|
||||
|
||||
//# sourceMappingURL=modelchooser.min.js.map
|
1
ai/provider/openai/amd/build/modelchooser.min.js.map
Normal file
1
ai/provider/openai/amd/build/modelchooser.min.js.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"modelchooser.min.js","sources":["../src/modelchooser.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/ //\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 * AI provider model selection handler.\n *\n * @module aiprovider_openai/modelchooser\n * @copyright 2025 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst Selectors = {\n fields: {\n selector: '[data-modelchooser-field=\"selector\"]',\n updateButton: '[data-modelchooser-field=\"updateButton\"]',\n },\n};\n\n/**\n * Initialise the AI provider chooser.\n */\nexport const init = () => {\n const modelSelector = document.querySelector(Selectors.fields.selector);\n if (modelSelector) {\n modelSelector.addEventListener('change', e => {\n modelSelector.options[e.target.selectedIndex].selected = true;\n const form = e.target.closest('form');\n const updateButton = form.querySelector(Selectors.fields.updateButton);\n updateButton.click();\n });\n }\n};\n"],"names":["Selectors","selector","updateButton","modelSelector","document","querySelector","addEventListener","e","options","target","selectedIndex","selected","closest","click"],"mappings":";;;;;;;;MAsBMA,iBACM,CACJC,SAAU,uCACVC,aAAc,0DAOF,WACVC,cAAgBC,SAASC,cAAcL,iBAAiBC,UAC1DE,eACAA,cAAcG,iBAAiB,UAAUC,IACrCJ,cAAcK,QAAQD,EAAEE,OAAOC,eAAeC,UAAW,EAC5CJ,EAAEE,OAAOG,QAAQ,QACJP,cAAcL,iBAAiBE,cAC5CW"}
|
43
ai/provider/openai/amd/src/modelchooser.js
Normal file
43
ai/provider/openai/amd/src/modelchooser.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
// This file is part of Moodle - http://moodle.org/ //
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* AI provider model selection handler.
|
||||
*
|
||||
* @module aiprovider_openai/modelchooser
|
||||
* @copyright 2025 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
const Selectors = {
|
||||
fields: {
|
||||
selector: '[data-modelchooser-field="selector"]',
|
||||
updateButton: '[data-modelchooser-field="updateButton"]',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise the AI provider chooser.
|
||||
*/
|
||||
export const init = () => {
|
||||
const modelSelector = document.querySelector(Selectors.fields.selector);
|
||||
if (modelSelector) {
|
||||
modelSelector.addEventListener('change', e => {
|
||||
modelSelector.options[e.target.selectedIndex].selected = true;
|
||||
const form = e.target.closest('form');
|
||||
const updateButton = form.querySelector(Selectors.fields.updateButton);
|
||||
updateButton.click();
|
||||
});
|
||||
}
|
||||
};
|
|
@ -51,6 +51,32 @@ abstract class abstract_processor extends process_base {
|
|||
return $this->provider->actionconfig[$this->action::class]['settings']['model'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_model_settings(): array {
|
||||
$settings = $this->provider->actionconfig[$this->action::class]['settings'];
|
||||
if (!empty($settings['modelextraparams'])) {
|
||||
// Custom model settings.
|
||||
$params = json_decode($settings['modelextraparams'], true);
|
||||
foreach ($params as $key => $param) {
|
||||
$settings[$key] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
// Unset unnecessary settings.
|
||||
unset(
|
||||
$settings['model'],
|
||||
$settings['endpoint'],
|
||||
$settings['systeminstruction'],
|
||||
$settings['providerid'],
|
||||
$settings['modelextraparams'],
|
||||
);
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the system instructions.
|
||||
*
|
||||
|
|
49
ai/provider/openai/classes/aimodel/dalle3.php
Normal file
49
ai/provider/openai/classes/aimodel/dalle3.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace aiprovider_openai\aimodel;
|
||||
|
||||
use core_ai\aimodel\base;
|
||||
|
||||
/**
|
||||
* DALL-e-3 AI model.
|
||||
*
|
||||
* @package aiprovider_openai
|
||||
* @copyright 2025 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class dalle3 extends base implements openai_base {
|
||||
|
||||
#[\Override]
|
||||
public function get_model_name(): string {
|
||||
return 'dall-e-3';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get_model_display_name(): string {
|
||||
return 'DALL-e-3';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function has_model_settings(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function model_type(): int {
|
||||
return self::MODEL_TYPE_IMAGE;
|
||||
}
|
||||
}
|
87
ai/provider/openai/classes/aimodel/gpt4o.php
Normal file
87
ai/provider/openai/classes/aimodel/gpt4o.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace aiprovider_openai\aimodel;
|
||||
|
||||
use core_ai\aimodel\base;
|
||||
use MoodleQuickForm;
|
||||
|
||||
/**
|
||||
* GPT-4o AI model.
|
||||
*
|
||||
* @package aiprovider_openai
|
||||
* @copyright 2025 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class gpt4o extends base implements openai_base {
|
||||
|
||||
#[\Override]
|
||||
public function get_model_name(): string {
|
||||
return 'gpt-4o';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get_model_display_name(): string {
|
||||
return 'GPT-4o';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function has_model_settings(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function add_model_settings(MoodleQuickForm $mform): void {
|
||||
$mform->addElement(
|
||||
'text',
|
||||
'top_p',
|
||||
get_string('settings_top_p', 'aiprovider_openai'),
|
||||
);
|
||||
$mform->setType('top_p', PARAM_FLOAT);
|
||||
$mform->addHelpButton('top_p', 'settings_top_p', 'aiprovider_openai');
|
||||
|
||||
$mform->addElement(
|
||||
'text',
|
||||
'max_tokens',
|
||||
get_string('settings_max_tokens', 'aiprovider_openai'),
|
||||
);
|
||||
$mform->setType('max_tokens', PARAM_INT);
|
||||
$mform->addHelpButton('max_tokens', 'settings_max_tokens', 'aiprovider_openai');
|
||||
|
||||
$mform->addElement(
|
||||
'text',
|
||||
'frequency_penalty',
|
||||
get_string('settings_frequency_penalty', 'aiprovider_openai'),
|
||||
);
|
||||
// This is a raw value because it can be a float from -2.0 to 2.0.
|
||||
$mform->setType('frequency_penalty', PARAM_RAW);
|
||||
$mform->addHelpButton('frequency_penalty', 'settings_frequency_penalty', 'aiprovider_openai');
|
||||
|
||||
$mform->addElement(
|
||||
'text',
|
||||
'presence_penalty',
|
||||
get_string('settings_presence_penalty', 'aiprovider_openai'),
|
||||
);
|
||||
// This is a raw value because it can be a float from -2.0 to 2.0.
|
||||
$mform->setType('presence_penalty', PARAM_RAW);
|
||||
$mform->addHelpButton('presence_penalty', 'settings_presence_penalty', 'aiprovider_openai');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function model_type(): int {
|
||||
return self::MODEL_TYPE_TEXT;
|
||||
}
|
||||
}
|
47
ai/provider/openai/classes/aimodel/o1.php
Normal file
47
ai/provider/openai/classes/aimodel/o1.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace aiprovider_openai\aimodel;
|
||||
|
||||
/**
|
||||
* O1 AI model.
|
||||
*
|
||||
* @package aiprovider_openai
|
||||
* @copyright 2025 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class o1 extends gpt4o {
|
||||
|
||||
#[\Override]
|
||||
public function get_model_name(): string {
|
||||
return 'o1';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get_model_display_name(): string {
|
||||
return 'O1';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function has_model_settings(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function model_type(): int {
|
||||
return self::MODEL_TYPE_TEXT;
|
||||
}
|
||||
}
|
39
ai/provider/openai/classes/aimodel/openai_base.php
Normal file
39
ai/provider/openai/classes/aimodel/openai_base.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace aiprovider_openai\aimodel;
|
||||
|
||||
/**
|
||||
* OpenAI base AI model interface.
|
||||
*
|
||||
* @package aiprovider_openai
|
||||
* @copyright 2025 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
interface openai_base {
|
||||
|
||||
/** @var int MODEL_TYPE_TEXT Text model type. */
|
||||
public const MODEL_TYPE_TEXT = 1;
|
||||
/** @var int MODEL_TYPE_IMAGE Image model type. */
|
||||
public const MODEL_TYPE_IMAGE = 2;
|
||||
|
||||
/**
|
||||
* Get model type.
|
||||
*
|
||||
* @return int Model type.
|
||||
*/
|
||||
public function model_type(): int;
|
||||
}
|
198
ai/provider/openai/classes/form/action_form.php
Normal file
198
ai/provider/openai/classes/form/action_form.php
Normal file
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace aiprovider_openai\form;
|
||||
|
||||
use aiprovider_openai\helper;
|
||||
use core_ai\form\action_settings_form;
|
||||
|
||||
/**
|
||||
* Base action settings form for OpenAI provider.
|
||||
*
|
||||
* @package aiprovider_openai
|
||||
* @copyright 2025 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class action_form extends action_settings_form {
|
||||
/**
|
||||
* @var array Action configuration.
|
||||
*/
|
||||
protected array $actionconfig;
|
||||
/**
|
||||
* @var string|null Return URL.
|
||||
*/
|
||||
protected ?string $returnurl;
|
||||
/**
|
||||
* @var string Action name.
|
||||
*/
|
||||
protected string $actionname;
|
||||
/**
|
||||
* @var string Action class.
|
||||
*/
|
||||
protected string $action;
|
||||
/**
|
||||
* @var int Provider ID.
|
||||
*/
|
||||
protected int $providerid;
|
||||
/**
|
||||
* @var string Provider name.
|
||||
*/
|
||||
protected string $providername;
|
||||
|
||||
#[\Override]
|
||||
protected function definition(): void {
|
||||
$mform = $this->_form;
|
||||
$this->actionconfig = $this->_customdata['actionconfig']['settings'] ?? [];
|
||||
$this->returnurl = $this->_customdata['returnurl'] ?? null;
|
||||
$this->actionname = $this->_customdata['actionname'];
|
||||
$this->action = $this->_customdata['action'];
|
||||
$this->providerid = $this->_customdata['providerid'] ?? 0;
|
||||
$this->providername = $this->_customdata['providername'] ?? 'aiprovider_openai';
|
||||
|
||||
$mform->addElement('header', 'generalsettingsheader', get_string('general', 'core'));
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function set_data($data): void {
|
||||
if (!empty($data['modelextraparams'])) {
|
||||
$data['modelextraparams'] = json_encode(json_decode($data['modelextraparams']), JSON_PRETTY_PRINT);
|
||||
}
|
||||
parent::set_data($data);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get_data(): ?\stdClass {
|
||||
$data = parent::get_data();
|
||||
|
||||
if ($data) {
|
||||
if (isset($data->modeltemplate)) {
|
||||
if ($data->modeltemplate === 'custom') {
|
||||
$data->model = $data->custommodel;
|
||||
} else {
|
||||
// Set the model to the selected model template.
|
||||
$data->model = $data->modeltemplate;
|
||||
}
|
||||
|
||||
}
|
||||
// Unset the model template.
|
||||
unset($data->custommodel);
|
||||
unset($data->modeltemplate);
|
||||
|
||||
// Unset any false-y values.
|
||||
$data = (object) array_filter((array) $data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function validation($data, $files): array {
|
||||
$errors = parent::validation($data, $files);
|
||||
|
||||
// Validate the extra parameters.
|
||||
if (!empty($data['modelextraparams'])) {
|
||||
json_decode($data['modelextraparams']);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$errors['modelextraparams'] = get_string('invalidjson', 'aiprovider_openai');
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the model.
|
||||
if ($data['modeltemplate'] === 'custom' && empty($data['custommodel'])) {
|
||||
$errors['custommodel'] = get_string('required');
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function get_defaults(): array {
|
||||
$data = parent::get_defaults();
|
||||
|
||||
unset(
|
||||
$data['modeltemplate'],
|
||||
$data['custommodel'],
|
||||
$data['modelextraparams'],
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add model fields to the form.
|
||||
*
|
||||
* @param int $modeltype Model type.
|
||||
*/
|
||||
protected function add_model_fields(int $modeltype): void {
|
||||
global $PAGE;
|
||||
$PAGE->requires->js_call_amd('aiprovider_openai/modelchooser', 'init');
|
||||
$mform = $this->_form;
|
||||
|
||||
// Action model to use.
|
||||
$mform->addElement(
|
||||
'select',
|
||||
'modeltemplate',
|
||||
get_string("action:{$this->actionname}:model", 'aiprovider_openai'),
|
||||
$this->get_model_list($modeltype),
|
||||
['data-modelchooser-field' => 'selector'],
|
||||
);
|
||||
$mform->setType('modeltemplate', PARAM_TEXT);
|
||||
$mform->addRule('modeltemplate', null, 'required', null, 'client');
|
||||
if (!empty($this->actionconfig['model']) &&
|
||||
(!array_key_exists($this->actionconfig['model'], $this->get_model_list($modeltype)) ||
|
||||
!empty($this->actionconfig['modelextraparams']))) {
|
||||
$defaultmodel = 'custom';
|
||||
} else {
|
||||
$defaultmodel = $this->actionconfig['model'] ?? 'gpt-4o';
|
||||
}
|
||||
$mform->setDefault('modeltemplate', $defaultmodel);
|
||||
$mform->addHelpButton('modeltemplate', "action:{$this->actionname}:model", 'aiprovider_openai');
|
||||
|
||||
$mform->addElement('hidden', 'model', $this->actionconfig['model'] ?? 'gpt-4o');
|
||||
$mform->setType('model', PARAM_TEXT);
|
||||
|
||||
$mform->addElement('text', 'custommodel', get_string('custom_model_name', 'aiprovider_openai'));
|
||||
$mform->setType('custommodel', PARAM_TEXT);
|
||||
$mform->setDefault('custommodel', $this->actionconfig['model'] ?? '');
|
||||
$mform->hideIf('custommodel', 'modeltemplate', 'neq', 'custom');
|
||||
|
||||
$mform->registerNoSubmitButton('updateactionsettings');
|
||||
$mform->addElement(
|
||||
'submit',
|
||||
'updateactionsettings',
|
||||
'updateactionsettings',
|
||||
['data-modelchooser-field' => 'updateButton', 'class' => 'd-none']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of models.
|
||||
*
|
||||
* @param int $modeltype Model type.
|
||||
* @return array List of models.
|
||||
*/
|
||||
protected function get_model_list(int $modeltype): array {
|
||||
$models = [];
|
||||
$models['custom'] = get_string('custom', 'core_form');
|
||||
foreach (helper::get_model_classes() as $class) {
|
||||
$model = new $class();
|
||||
if ($model->model_type() == $modeltype) {
|
||||
$models[$model->get_model_name()] = $model->get_model_display_name();
|
||||
}
|
||||
}
|
||||
return $models;
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
namespace aiprovider_openai\form;
|
||||
|
||||
use core_ai\form\action_settings_form;
|
||||
use aiprovider_openai\aimodel\openai_base;
|
||||
|
||||
/**
|
||||
* Generate image action provider settings form.
|
||||
|
@ -25,58 +25,42 @@ use core_ai\form\action_settings_form;
|
|||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class action_generate_image_form extends action_settings_form {
|
||||
class action_generate_image_form extends action_form {
|
||||
#[\Override]
|
||||
protected function definition() {
|
||||
protected function definition(): void {
|
||||
parent::definition();
|
||||
$mform = $this->_form;
|
||||
$actionconfig = $this->_customdata['actionconfig']['settings'] ?? [];
|
||||
$returnurl = $this->_customdata['returnurl'] ?? null;
|
||||
$actionname = $this->_customdata['actionname'];
|
||||
$action = $this->_customdata['action'];
|
||||
$providerid = $this->_customdata['providerid'] ?? 0;
|
||||
|
||||
// Action model to use.
|
||||
$mform->addElement(
|
||||
'text',
|
||||
'model',
|
||||
get_string("action:{$actionname}:model", 'aiprovider_openai'),
|
||||
'maxlength="255" size="20"',
|
||||
);
|
||||
$mform->setType('model', PARAM_TEXT);
|
||||
$mform->addRule('model', null, 'required', null, 'client');
|
||||
$mform->setDefault('model', $actionconfig['model'] ?? 'dall-e-3');
|
||||
$mform->addHelpButton('model', "action:{$actionname}:model", 'aiprovider_openai');
|
||||
$this->add_model_fields(openai_base::MODEL_TYPE_IMAGE);
|
||||
|
||||
// API endpoint.
|
||||
$mform->addElement(
|
||||
'text',
|
||||
'endpoint',
|
||||
get_string("action:{$actionname}:endpoint", 'aiprovider_openai'),
|
||||
get_string("action:{$this->actionname}:endpoint", 'aiprovider_openai'),
|
||||
'maxlength="255" size="30"',
|
||||
);
|
||||
$mform->setType('endpoint', PARAM_URL);
|
||||
$mform->addRule('endpoint', null, 'required', null, 'client');
|
||||
$mform->setDefault('endpoint', $actionconfig['endpoint'] ?? 'https://api.openai.com/v1/images/generations');
|
||||
$mform->setDefault('endpoint', $this->actionconfig['endpoint'] ?? 'https://api.openai.com/v1/images/generations');
|
||||
|
||||
if ($returnurl) {
|
||||
$mform->addElement('hidden', 'returnurl', $returnurl);
|
||||
if ($this->returnurl) {
|
||||
$mform->addElement('hidden', 'returnurl', $this->returnurl);
|
||||
$mform->setType('returnurl', PARAM_LOCALURL);
|
||||
}
|
||||
|
||||
// Add the action class as a hidden field.
|
||||
$mform->addElement('hidden', 'action', $action);
|
||||
$mform->addElement('hidden', 'action', $this->action);
|
||||
$mform->setType('action', PARAM_TEXT);
|
||||
|
||||
// Add the provider class as a hidden field.
|
||||
$mform->addElement('hidden', 'provider', 'aiprovider_openai');
|
||||
$mform->addElement('hidden', 'provider', $this->providername);
|
||||
$mform->setType('provider', PARAM_TEXT);
|
||||
|
||||
// Add the provider id as a hidden field.
|
||||
$mform->addElement('hidden', 'providerid', $providerid);
|
||||
$mform->addElement('hidden', 'providerid', $this->providerid);
|
||||
$mform->setType('providerid', PARAM_INT);
|
||||
|
||||
$this->add_action_buttons();
|
||||
|
||||
$this->set_data($actionconfig);
|
||||
$this->set_data($this->actionconfig);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
namespace aiprovider_openai\form;
|
||||
|
||||
use core_ai\form\action_settings_form;
|
||||
use aiprovider_openai\aimodel\openai_base;
|
||||
|
||||
/**
|
||||
* Generate text action provider settings form.
|
||||
|
@ -25,33 +25,19 @@ use core_ai\form\action_settings_form;
|
|||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class action_generate_text_form extends action_settings_form {
|
||||
class action_generate_text_form extends action_form {
|
||||
#[\Override]
|
||||
protected function definition() {
|
||||
protected function definition(): void {
|
||||
parent::definition();
|
||||
$mform = $this->_form;
|
||||
$actionconfig = $this->_customdata['actionconfig']['settings'] ?? [];
|
||||
$returnurl = $this->_customdata['returnurl'] ?? null;
|
||||
$actionname = $this->_customdata['actionname'];
|
||||
$action = $this->_customdata['action'];
|
||||
$providerid = $this->_customdata['providerid'] ?? 0;
|
||||
|
||||
// Action model to use.
|
||||
$mform->addElement(
|
||||
'text',
|
||||
'model',
|
||||
get_string("action:{$actionname}:model", 'aiprovider_openai'),
|
||||
'maxlength="255" size="20"',
|
||||
);
|
||||
$mform->setType('model', PARAM_TEXT);
|
||||
$mform->addRule('model', null, 'required', null, 'client');
|
||||
$mform->setDefault('model', $actionconfig['model'] ?? 'gpt-4o');
|
||||
$mform->addHelpButton('model', "action:{$actionname}:model", 'aiprovider_openai');
|
||||
$this->add_model_fields(openai_base::MODEL_TYPE_TEXT);
|
||||
|
||||
// API endpoint.
|
||||
$mform->addElement(
|
||||
'text',
|
||||
'endpoint',
|
||||
get_string("action:{$actionname}:endpoint", 'aiprovider_openai'),
|
||||
get_string("action:{$this->actionname}:endpoint", 'aiprovider_openai'),
|
||||
'maxlength="255" size="30"',
|
||||
);
|
||||
$mform->setType('endpoint', PARAM_URL);
|
||||
|
@ -62,32 +48,31 @@ class action_generate_text_form extends action_settings_form {
|
|||
$mform->addElement(
|
||||
'textarea',
|
||||
'systeminstruction',
|
||||
get_string("action:{$actionname}:systeminstruction", 'aiprovider_openai'),
|
||||
get_string("action:{$this->actionname}:systeminstruction", 'aiprovider_openai'),
|
||||
'wrap="virtual" rows="5" cols="20"',
|
||||
);
|
||||
$mform->setType('systeminstruction', PARAM_TEXT);
|
||||
$mform->setDefault('systeminstruction', $actionconfig['systeminstruction'] ?? $action::get_system_instruction());
|
||||
$mform->addHelpButton('systeminstruction', "action:{$actionname}:systeminstruction", 'aiprovider_openai');
|
||||
$mform->setDefault('systeminstruction', $actionconfig['systeminstruction'] ?? $this->action::get_system_instruction());
|
||||
$mform->addHelpButton('systeminstruction', "action:{$this->actionname}:systeminstruction", 'aiprovider_openai');
|
||||
|
||||
if ($returnurl) {
|
||||
$mform->addElement('hidden', 'returnurl', $returnurl);
|
||||
if ($this->returnurl) {
|
||||
$mform->addElement('hidden', 'returnurl', $this->returnurl);
|
||||
$mform->setType('returnurl', PARAM_LOCALURL);
|
||||
}
|
||||
|
||||
// Add the action class as a hidden field.
|
||||
$mform->addElement('hidden', 'action', $action);
|
||||
$mform->addElement('hidden', 'action', $this->action);
|
||||
$mform->setType('action', PARAM_TEXT);
|
||||
|
||||
// Add the provider class as a hidden field.
|
||||
$mform->addElement('hidden', 'provider', 'aiprovider_openai');
|
||||
$mform->addElement('hidden', 'provider', $this->providername);
|
||||
$mform->setType('provider', PARAM_TEXT);
|
||||
|
||||
// Add the provider id as a hidden field.
|
||||
$mform->addElement('hidden', 'providerid', $providerid);
|
||||
$mform->addElement('hidden', 'providerid', $this->providerid);
|
||||
$mform->setType('providerid', PARAM_INT);
|
||||
|
||||
$this->add_action_buttons();
|
||||
|
||||
$this->set_data($actionconfig);
|
||||
$this->set_data($this->actionconfig);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
62
ai/provider/openai/classes/helper.php
Normal file
62
ai/provider/openai/classes/helper.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace aiprovider_openai;
|
||||
|
||||
use core_ai\aimodel\base;
|
||||
|
||||
/**
|
||||
* Helper class for the OpenAI provider.
|
||||
*
|
||||
* @package aiprovider_openai
|
||||
* @copyright 2025 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper {
|
||||
|
||||
/**
|
||||
* Get all model classes.
|
||||
*
|
||||
* @return array Array of model classes.
|
||||
*/
|
||||
public static function get_model_classes(): array {
|
||||
$models = [];
|
||||
$modelclasses = \core_component::get_component_classes_in_namespace('aiprovider_openai', 'aimodel');
|
||||
foreach ($modelclasses as $class => $path) {
|
||||
if (!class_exists($class) || !is_a($class, base::class, true)) {
|
||||
throw new \coding_exception("Model class not valid: {$class}");
|
||||
}
|
||||
$models[] = $class;
|
||||
}
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get model class by name.
|
||||
*
|
||||
* @param string $modelname Model name.
|
||||
* @return base|null
|
||||
*/
|
||||
public static function get_model_class(string $modelname): ?base {
|
||||
foreach (static::get_model_classes() as $classname) {
|
||||
$model = new $classname();
|
||||
if ($model->get_model_name() === $modelname) {
|
||||
return $model;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
namespace aiprovider_openai;
|
||||
|
||||
use aiprovider_openai\model\base;
|
||||
use core_ai\hook\after_ai_action_settings_form_hook;
|
||||
use core_ai\hook\after_ai_provider_form_hook;
|
||||
|
||||
/**
|
||||
|
@ -61,4 +63,44 @@ class hook_listener {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook listener for the Open AI action settings form.
|
||||
*
|
||||
* @param after_ai_action_settings_form_hook $hook The hook to add to config action settings.
|
||||
*/
|
||||
public static function set_model_form_definition_for_aiprovider_openai(after_ai_action_settings_form_hook $hook): void {
|
||||
if ($hook->plugin !== 'aiprovider_openai') {
|
||||
return;
|
||||
}
|
||||
|
||||
$mform = $hook->mform;
|
||||
if (isset($mform->_elementIndex['modeltemplate'])) {
|
||||
$model = $mform->getElementValue('modeltemplate');
|
||||
if (is_array($model)) {
|
||||
$model = $model[0];
|
||||
}
|
||||
|
||||
if ($model == 'custom') {
|
||||
$mform->addElement('header', 'modelsettingsheader', get_string('settings', 'aiprovider_openai'));
|
||||
$mform->addElement('html', get_string('settings_help', 'aiprovider_openai'));
|
||||
$mform->addElement(
|
||||
'textarea',
|
||||
'modelextraparams',
|
||||
get_string('extraparams', 'aiprovider_openai'),
|
||||
['rows' => 5, 'cols' => 20],
|
||||
);
|
||||
$mform->setType('modelextraparams', PARAM_TEXT);
|
||||
$mform->addElement('static', 'modelextraparams_help', null, get_string('extraparams_help', 'aiprovider_openai'));
|
||||
} else {
|
||||
$targetmodel = helper::get_model_class($model);
|
||||
if ($targetmodel) {
|
||||
if ($targetmodel->has_model_settings()) {
|
||||
$mform->addElement('header', 'modelsettingsheader', get_string('settings', 'aiprovider_openai'));
|
||||
$mform->addElement('html', get_string('settings_help', 'aiprovider_openai'));
|
||||
$targetmodel->add_model_settings($mform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,22 +75,28 @@ class process_generate_image extends abstract_processor {
|
|||
|
||||
#[\Override]
|
||||
protected function create_request_object(string $userid): RequestInterface {
|
||||
// Create the request object.
|
||||
$requestobj = new \stdClass();
|
||||
$requestobj->model = $this->get_model();
|
||||
$requestobj->user = $userid;
|
||||
$requestobj->prompt = $this->action->get_configuration('prompttext');
|
||||
$requestobj->n = $this->numberimages;
|
||||
$requestobj->quality = $this->action->get_configuration('quality');
|
||||
$requestobj->response_format = $this->responseformat;
|
||||
$requestobj->size = $this->calculate_size($this->action->get_configuration('aspectratio'));
|
||||
$requestobj->style = $this->action->get_configuration('style');
|
||||
// Append the extra model settings.
|
||||
$modelsettings = $this->get_model_settings();
|
||||
foreach ($modelsettings as $setting => $value) {
|
||||
$requestobj->$setting = $value;
|
||||
}
|
||||
return new Request(
|
||||
method: 'POST',
|
||||
uri: '',
|
||||
headers: [
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
body: json_encode((object) [
|
||||
'prompt' => $this->action->get_configuration('prompttext'),
|
||||
'model' => $this->get_model(),
|
||||
'n' => $this->numberimages,
|
||||
'quality' => $this->action->get_configuration('quality'),
|
||||
'response_format' => $this->responseformat,
|
||||
'size' => $this->calculate_size($this->action->get_configuration('aspectratio')),
|
||||
'style' => $this->action->get_configuration('style'),
|
||||
'user' => $userid,
|
||||
]),
|
||||
uri: '',
|
||||
headers: [
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
body: json_encode($requestobj),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,12 @@ class process_generate_text extends abstract_processor {
|
|||
$requestobj->messages = [$userobj];
|
||||
}
|
||||
|
||||
// Append the extra model settings.
|
||||
$modelsettings = $this->get_model_settings();
|
||||
foreach ($modelsettings as $setting => $value) {
|
||||
$requestobj->$setting = $value;
|
||||
}
|
||||
|
||||
return new Request(
|
||||
method: 'POST',
|
||||
uri: '',
|
||||
|
|
|
@ -94,6 +94,7 @@ class provider extends \core_ai\provider {
|
|||
$customdata = [
|
||||
'actionname' => $actionname,
|
||||
'action' => $action,
|
||||
'providername' => 'aiprovider_openai',
|
||||
];
|
||||
if ($actionname === 'generate_text' || $actionname === 'summarise_text') {
|
||||
$mform = new form\action_generate_text_form(customdata: $customdata);
|
||||
|
|
66
ai/provider/openai/classes/test/testcase_helper_trait.php
Normal file
66
ai/provider/openai/classes/test/testcase_helper_trait.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace aiprovider_openai\test;
|
||||
|
||||
/**
|
||||
* Trait for test cases.
|
||||
*
|
||||
* @package aiprovider_openai
|
||||
* @copyright 2025 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
trait testcase_helper_trait {
|
||||
|
||||
/**
|
||||
* Create the provider object.
|
||||
*
|
||||
* @param string $actionclass The action class to use.
|
||||
* @param array $actionconfig The action configuration to use.
|
||||
*/
|
||||
public function create_provider(
|
||||
string $actionclass,
|
||||
array $actionconfig = [],
|
||||
): \core_ai\provider {
|
||||
$manager = \core\di::get(\core_ai\manager::class);
|
||||
$config = [
|
||||
'apikey' => '123',
|
||||
'enableuserratelimit' => true,
|
||||
'userratelimit' => 1,
|
||||
'enableglobalratelimit' => true,
|
||||
'globalratelimit' => 1,
|
||||
];
|
||||
$defaultactionconfig = [
|
||||
$actionclass => [
|
||||
'settings' => [
|
||||
'model' => 'gpt-4o',
|
||||
'endpoint' => "https://api.openai.com/v1/chat/completions",
|
||||
],
|
||||
],
|
||||
];
|
||||
foreach ($actionconfig as $key => $value) {
|
||||
$defaultactionconfig[$actionclass]['settings'][$key] = $value;
|
||||
}
|
||||
$provider = $manager->create_provider_instance(
|
||||
classname: '\aiprovider_openai\provider',
|
||||
name: 'dummy',
|
||||
config: $config,
|
||||
actionconfig: $defaultactionconfig,
|
||||
);
|
||||
|
||||
return $provider;
|
||||
}
|
||||
}
|
|
@ -29,4 +29,9 @@ $callbacks = [
|
|||
'hook' => \core_ai\hook\after_ai_provider_form_hook::class,
|
||||
'callback' => \aiprovider_openai\hook_listener::class . '::set_form_definition_for_aiprovider_openai',
|
||||
],
|
||||
|
||||
[
|
||||
'hook' => \core_ai\hook\after_ai_action_settings_form_hook::class,
|
||||
'callback' => \aiprovider_openai\hook_listener::class . '::set_model_form_definition_for_aiprovider_openai',
|
||||
],
|
||||
];
|
||||
|
|
|
@ -37,6 +37,16 @@ $string['action:summarise_text:systeminstruction'] = 'System instruction';
|
|||
$string['action:summarise_text:systeminstruction_help'] = 'This instruction is sent to the AI model along with the user\'s prompt. Editing this instruction is not recommended unless absolutely required.';
|
||||
$string['apikey'] = 'OpenAI API key';
|
||||
$string['apikey_help'] = 'Get a key from your <a href="https://platform.openai.com/account/api-keys" target="_blank">OpenAI API keys</a>.';
|
||||
$string['custom_model_name'] = 'Custom model name';
|
||||
$string['extraparams'] = 'Extra parameters';
|
||||
$string['extraparams_help'] = 'Extra parameters can be configured here. We support JSON format. For example:
|
||||
<pre>
|
||||
{
|
||||
"temperature": 0.5,
|
||||
"max_tokens": 100
|
||||
}
|
||||
</pre>';
|
||||
$string['invalidjson'] = 'Invalid JSON string';
|
||||
$string['orgid'] = 'OpenAI organization ID';
|
||||
$string['orgid_help'] = 'Get your OpenAI organization ID from your <a href="https://platform.openai.com/account/org-settings" target="_blank">OpenAI account</a>.';
|
||||
$string['pluginname'] = 'OpenAI API provider';
|
||||
|
@ -46,6 +56,16 @@ $string['privacy:metadata:aiprovider_openai:model'] = 'The model used to generat
|
|||
$string['privacy:metadata:aiprovider_openai:numberimages'] = 'When generating images the number of images used in the response.';
|
||||
$string['privacy:metadata:aiprovider_openai:prompttext'] = 'The user entered text prompt used to generate the response.';
|
||||
$string['privacy:metadata:aiprovider_openai:responseformat'] = 'The format of the response. When generating images.';
|
||||
$string['settings'] = 'Settings';
|
||||
$string['settings_frequency_penalty'] = 'frequency_penalty';
|
||||
$string['settings_frequency_penalty_help'] = 'Penalizes new tokens based on their frequency in the text so far';
|
||||
$string['settings_help'] = 'You can adjust the settings below to customize how requests are sent to OpenAI. Update the values as needed, ensuring they align with your requirements.<br><br>';
|
||||
$string['settings_max_tokens'] = 'max_tokens';
|
||||
$string['settings_max_tokens_help'] = 'The maximum number of tokens to generate in the response';
|
||||
$string['settings_presence_penalty'] = 'presence_penalty';
|
||||
$string['settings_presence_penalty_help'] = 'Penalizes new tokens based on whether they appear in the text so far';
|
||||
$string['settings_top_p'] = 'top_p';
|
||||
$string['settings_top_p_help'] = 'Controls nucleus sampling';
|
||||
|
||||
// Deprecated since Moodle 5.0.
|
||||
$string['action:generate_image:model_desc'] = 'The model used to generate images from user prompts.';
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
namespace aiprovider_openai;
|
||||
|
||||
use aiprovider_openai\test\testcase_helper_trait;
|
||||
use core_ai\aiactions\base;
|
||||
use core_ai\provider;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
|
@ -31,6 +32,9 @@ use GuzzleHttp\Psr7\Response;
|
|||
* @covers \aiprovider_openai\abstract_processor
|
||||
*/
|
||||
final class process_generate_image_test extends \advanced_testcase {
|
||||
|
||||
use testcase_helper_trait;
|
||||
|
||||
/** @var string A successful response in JSON format. */
|
||||
protected string $responsebodyjson;
|
||||
|
||||
|
@ -51,27 +55,14 @@ final class process_generate_image_test extends \advanced_testcase {
|
|||
$this->resetAfterTest();
|
||||
// Load a response body from a file.
|
||||
$this->responsebodyjson = file_get_contents(self::get_fixture_path('aiprovider_openai', 'image_request_success.json'));
|
||||
$this->create_provider();
|
||||
$this->create_action();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the provider object.
|
||||
*/
|
||||
private function create_provider(): void {
|
||||
$this->manager = \core\di::get(\core_ai\manager::class);
|
||||
$config = [
|
||||
'apikey' => '123',
|
||||
'enableuserratelimit' => true,
|
||||
'userratelimit' => 1,
|
||||
'enableglobalratelimit' => true,
|
||||
'globalratelimit' => 1,
|
||||
];
|
||||
$this->provider = $this->manager->create_provider_instance(
|
||||
classname: '\aiprovider_openai\provider',
|
||||
name: 'dummy',
|
||||
config: $config,
|
||||
$this->provider = $this->create_provider(
|
||||
actionclass: \core_ai\aiactions\generate_image::class,
|
||||
actionconfig: [
|
||||
'model' => 'dall-e-3',
|
||||
],
|
||||
);
|
||||
$this->create_action();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,6 +123,50 @@ final class process_generate_image_test extends \advanced_testcase {
|
|||
$this->assertEquals('1024x1024', $requestdata->size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create_request_object with extra model settings.
|
||||
*/
|
||||
public function test_create_request_object_with_model_settings(): void {
|
||||
$this->provider = $this->create_provider(
|
||||
actionclass: \core_ai\aiactions\generate_image::class,
|
||||
actionconfig: [
|
||||
'model' => 'dall-e-3',
|
||||
'temperature' => '0.5',
|
||||
'max_tokens' => '100',
|
||||
],
|
||||
);
|
||||
$processor = new process_generate_image($this->provider, $this->action);
|
||||
|
||||
// We're working with a private method here, so we need to use reflection.
|
||||
$method = new \ReflectionMethod($processor, 'create_request_object');
|
||||
$request = $method->invoke($processor, 1);
|
||||
|
||||
$body = (object) json_decode($request->getBody()->getContents());
|
||||
|
||||
$this->assertEquals('dall-e-3', $body->model);
|
||||
$this->assertEquals('0.5', $body->temperature);
|
||||
$this->assertEquals('100', $body->max_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}',
|
||||
],
|
||||
);
|
||||
$processor = new process_generate_image($this->provider, $this->action);
|
||||
|
||||
// We're working with a private method here, so we need to use reflection.
|
||||
$method = new \ReflectionMethod($processor, 'create_request_object');
|
||||
$request = $method->invoke($processor, 1);
|
||||
|
||||
$body = (object) json_decode($request->getBody()->getContents());
|
||||
|
||||
$this->assertEquals('my-custom-gpt', $body->model);
|
||||
$this->assertEquals('0.5', $body->temperature);
|
||||
$this->assertEquals('100', $body->max_tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the API error response handler method.
|
||||
*/
|
||||
|
@ -397,6 +432,14 @@ final class process_generate_image_test extends \advanced_testcase {
|
|||
classname: '\aiprovider_openai\provider',
|
||||
name: 'dummy',
|
||||
config: $config,
|
||||
actionconfig: [
|
||||
\core_ai\aiactions\generate_image::class => [
|
||||
'settings' => [
|
||||
'model' => 'dall-e-3',
|
||||
'endpoint' => "https://api.openai.com/v1/chat/completions",
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
// Mock the http client to return a successful response.
|
||||
|
@ -538,6 +581,14 @@ final class process_generate_image_test extends \advanced_testcase {
|
|||
classname: '\aiprovider_openai\provider',
|
||||
name: 'dummy',
|
||||
config: $config,
|
||||
actionconfig: [
|
||||
\core_ai\aiactions\generate_image::class => [
|
||||
'settings' => [
|
||||
'model' => 'dall-e-3',
|
||||
'endpoint' => "https://api.openai.com/v1/chat/completions",
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
// Mock the http client to return a successful response.
|
||||
|
@ -592,7 +643,7 @@ final class process_generate_image_test extends \advanced_testcase {
|
|||
['Content-Type' => 'image/jpeg'],
|
||||
\GuzzleHttp\Psr7\Utils::streamFor(fopen(self::get_fixture_path('aiprovider_openai', 'test.jpg'), 'r')),
|
||||
));
|
||||
$this->create_provider();
|
||||
$this->provider = $this->create_provider(\core_ai\aiactions\generate_image::class);
|
||||
$this->create_action($user1->id);
|
||||
$processor = new process_generate_image($provider, $this->action);
|
||||
$result = $processor->process();
|
||||
|
@ -603,7 +654,7 @@ final class process_generate_image_test extends \advanced_testcase {
|
|||
// Case 3: Global rate limit has been reached for a different user too.
|
||||
// Log in user2.
|
||||
$this->setUser($user2);
|
||||
$this->create_provider();
|
||||
$this->provider = $this->create_provider(\core_ai\aiactions\generate_image::class);
|
||||
$this->create_action($user2->id);
|
||||
// The response from OpenAI.
|
||||
$mock->append(new Response(
|
||||
|
@ -653,7 +704,7 @@ final class process_generate_image_test extends \advanced_testcase {
|
|||
['Content-Type' => 'image/jpeg'],
|
||||
\GuzzleHttp\Psr7\Utils::streamFor(fopen(self::get_fixture_path('aiprovider_openai', 'test.jpg'), 'r')),
|
||||
));
|
||||
$this->create_provider();
|
||||
$this->provider = $this->create_provider(\core_ai\aiactions\generate_image::class);
|
||||
$this->create_action($user1->id);
|
||||
$processor = new process_generate_image($provider, $this->action);
|
||||
$result = $processor->process();
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
namespace aiprovider_openai;
|
||||
|
||||
use aiprovider_openai\process_generate_text;
|
||||
use aiprovider_openai\test\testcase_helper_trait;
|
||||
use core_ai\aiactions\base;
|
||||
use core_ai\provider;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
|
@ -32,6 +32,9 @@ use GuzzleHttp\Psr7\Response;
|
|||
* @covers \aiprovider_openai\abstract_processor
|
||||
*/
|
||||
final class process_generate_text_test extends \advanced_testcase {
|
||||
|
||||
use testcase_helper_trait;
|
||||
|
||||
/** @var string A successful response in JSON format. */
|
||||
protected string $responsebodyjson;
|
||||
|
||||
|
@ -52,27 +55,14 @@ final class process_generate_text_test extends \advanced_testcase {
|
|||
$this->resetAfterTest();
|
||||
// Load a response body from a file.
|
||||
$this->responsebodyjson = file_get_contents(self::get_fixture_path('aiprovider_openai', 'text_request_success.json'));
|
||||
$this->create_provider();
|
||||
$this->create_action();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the provider object.
|
||||
*/
|
||||
private function create_provider(): void {
|
||||
$this->manager = \core\di::get(\core_ai\manager::class);
|
||||
$config = [
|
||||
'apikey' => '123',
|
||||
'enableuserratelimit' => true,
|
||||
'userratelimit' => 1,
|
||||
'enableglobalratelimit' => true,
|
||||
'globalratelimit' => 1,
|
||||
];
|
||||
$this->provider = $this->manager->create_provider_instance(
|
||||
classname: '\aiprovider_openai\provider',
|
||||
name: 'dummy',
|
||||
config: $config,
|
||||
$this->provider = $this->create_provider(
|
||||
actionclass: \core_ai\aiactions\generate_text::class,
|
||||
actionconfig: [
|
||||
'systeminstruction' => get_string('action_generate_text_instruction', 'core_ai'),
|
||||
],
|
||||
);
|
||||
$this->create_action();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,6 +93,51 @@ final class process_generate_text_test extends \advanced_testcase {
|
|||
$this->assertEquals('user', $body->messages[1]->role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create_request_object with extra model settings.
|
||||
*/
|
||||
public function test_create_request_object_with_model_settings(): void {
|
||||
$this->provider = $this->create_provider(
|
||||
actionclass: \core_ai\aiactions\generate_text::class,
|
||||
actionconfig: [
|
||||
'systeminstruction' => get_string('action_generate_text_instruction', 'core_ai'),
|
||||
'temperature' => '0.5',
|
||||
'max_tokens' => '100',
|
||||
],
|
||||
);
|
||||
$processor = new process_generate_text($this->provider, $this->action);
|
||||
|
||||
// We're working with a private method here, so we need to use reflection.
|
||||
$method = new \ReflectionMethod($processor, 'create_request_object');
|
||||
$request = $method->invoke($processor, 1);
|
||||
|
||||
$body = (object) json_decode($request->getBody()->getContents());
|
||||
|
||||
$this->assertEquals('gpt-4o', $body->model);
|
||||
$this->assertEquals('0.5', $body->temperature);
|
||||
$this->assertEquals('100', $body->max_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}',
|
||||
],
|
||||
);
|
||||
$processor = new process_generate_text($this->provider, $this->action);
|
||||
|
||||
// We're working with a private method here, so we need to use reflection.
|
||||
$method = new \ReflectionMethod($processor, 'create_request_object');
|
||||
$request = $method->invoke($processor, 1);
|
||||
|
||||
$body = (object) json_decode($request->getBody()->getContents());
|
||||
|
||||
$this->assertEquals('my-custom-gpt', $body->model);
|
||||
$this->assertEquals('0.5', $body->temperature);
|
||||
$this->assertEquals('100', $body->max_tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the API error response handler method.
|
||||
*/
|
||||
|
@ -325,6 +360,15 @@ final class process_generate_text_test extends \advanced_testcase {
|
|||
classname: '\aiprovider_openai\provider',
|
||||
name: 'dummy',
|
||||
config: $config,
|
||||
actionconfig: [
|
||||
\core_ai\aiactions\generate_text::class => [
|
||||
'settings' => [
|
||||
'model' => 'gpt-4o',
|
||||
'endpoint' => "https://api.openai.com/v1/chat/completions",
|
||||
'systeminstruction' => get_string('action_generate_text_instruction', 'core_ai'),
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
// Mock the http client to return a successful response.
|
||||
|
@ -381,7 +425,7 @@ final class process_generate_text_test extends \advanced_testcase {
|
|||
['Content-Type' => 'application/json'],
|
||||
$this->responsebodyjson,
|
||||
));
|
||||
$this->create_provider();
|
||||
$this->provider = $this->create_provider(\core_ai\aiactions\generate_text::class);
|
||||
$this->create_action($user1->id);
|
||||
$processor = new process_generate_text($provider, $this->action);
|
||||
$result = $processor->process();
|
||||
|
@ -410,6 +454,15 @@ final class process_generate_text_test extends \advanced_testcase {
|
|||
classname: '\aiprovider_openai\provider',
|
||||
name: 'dummy',
|
||||
config: $config,
|
||||
actionconfig: [
|
||||
\core_ai\aiactions\generate_text::class => [
|
||||
'settings' => [
|
||||
'model' => 'gpt-4o',
|
||||
'endpoint' => "https://api.openai.com/v1/chat/completions",
|
||||
'systeminstruction' => get_string('action_generate_text_instruction', 'core_ai'),
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
// Mock the http client to return a successful response.
|
||||
|
@ -466,7 +519,7 @@ final class process_generate_text_test extends \advanced_testcase {
|
|||
['Content-Type' => 'application/json'],
|
||||
$this->responsebodyjson,
|
||||
));
|
||||
$this->create_provider();
|
||||
$this->provider = $this->create_provider(\core_ai\aiactions\generate_text::class);
|
||||
$this->create_action($user1->id);
|
||||
$processor = new process_generate_text($provider, $this->action);
|
||||
$result = $processor->process();
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
namespace aiprovider_openai;
|
||||
|
||||
use aiprovider_openai\process_summarise_text;
|
||||
use aiprovider_openai\test\testcase_helper_trait;
|
||||
use core_ai\aiactions\base;
|
||||
use core_ai\provider;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
|
@ -32,6 +32,9 @@ use GuzzleHttp\Psr7\Response;
|
|||
* @covers \aiprovider_openai\abstract_processor
|
||||
*/
|
||||
final class process_summarise_text_test extends \advanced_testcase {
|
||||
|
||||
use testcase_helper_trait;
|
||||
|
||||
/** @var string A successful response in JSON format. */
|
||||
protected string $responsebodyjson;
|
||||
|
||||
|
@ -52,27 +55,14 @@ final class process_summarise_text_test extends \advanced_testcase {
|
|||
$this->resetAfterTest();
|
||||
// Load a response body from a file.
|
||||
$this->responsebodyjson = file_get_contents(self::get_fixture_path('aiprovider_openai', 'text_request_success.json'));
|
||||
$this->create_provider();
|
||||
$this->create_action();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the provider object.
|
||||
*/
|
||||
private function create_provider(): void {
|
||||
$this->manager = \core\di::get(\core_ai\manager::class);
|
||||
$config = [
|
||||
'apikey' => '123',
|
||||
'enableuserratelimit' => true,
|
||||
'userratelimit' => 1,
|
||||
'enableglobalratelimit' => true,
|
||||
'globalratelimit' => 1,
|
||||
];
|
||||
$this->provider = $this->manager->create_provider_instance(
|
||||
classname: '\aiprovider_openai\provider',
|
||||
name: 'dummy',
|
||||
config: $config,
|
||||
$this->provider = $this->create_provider(
|
||||
actionclass: \core_ai\aiactions\summarise_text::class,
|
||||
actionconfig: [
|
||||
'systeminstruction' => get_string('action_summarise_text_instruction', 'core_ai'),
|
||||
],
|
||||
);
|
||||
$this->create_action();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,6 +95,51 @@ final class process_summarise_text_test extends \advanced_testcase {
|
|||
$this->assertEquals('user', $body->messages[1]->role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create_request_object with extra model settings.
|
||||
*/
|
||||
public function test_create_request_object_with_model_settings(): void {
|
||||
$this->provider = $this->create_provider(
|
||||
actionclass: \core_ai\aiactions\summarise_text::class,
|
||||
actionconfig: [
|
||||
'systeminstruction' => get_string('action_summarise_text_instruction', 'core_ai'),
|
||||
'temperature' => '0.5',
|
||||
'max_tokens' => '100',
|
||||
],
|
||||
);
|
||||
$processor = new process_summarise_text($this->provider, $this->action);
|
||||
|
||||
// We're working with a private method here, so we need to use reflection.
|
||||
$method = new \ReflectionMethod($processor, 'create_request_object');
|
||||
$request = $method->invoke($processor, 1);
|
||||
|
||||
$body = (object) json_decode($request->getBody()->getContents());
|
||||
|
||||
$this->assertEquals('gpt-4o', $body->model);
|
||||
$this->assertEquals('0.5', $body->temperature);
|
||||
$this->assertEquals('100', $body->max_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}',
|
||||
],
|
||||
);
|
||||
$processor = new process_summarise_text($this->provider, $this->action);
|
||||
|
||||
// We're working with a private method here, so we need to use reflection.
|
||||
$method = new \ReflectionMethod($processor, 'create_request_object');
|
||||
$request = $method->invoke($processor, 1);
|
||||
|
||||
$body = (object) json_decode($request->getBody()->getContents());
|
||||
|
||||
$this->assertEquals('my-custom-gpt', $body->model);
|
||||
$this->assertEquals('0.5', $body->temperature);
|
||||
$this->assertEquals('100', $body->max_tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the API error response handler method.
|
||||
*
|
||||
|
@ -319,6 +354,15 @@ final class process_summarise_text_test extends \advanced_testcase {
|
|||
classname: '\aiprovider_openai\provider',
|
||||
name: 'dummy',
|
||||
config: $config,
|
||||
actionconfig: [
|
||||
\core_ai\aiactions\summarise_text::class => [
|
||||
'settings' => [
|
||||
'model' => 'gpt-4o',
|
||||
'endpoint' => "https://api.openai.com/v1/chat/completions",
|
||||
'systeminstruction' => get_string('action_summarise_text_instruction', 'core_ai'),
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
// Mock the http client to return a successful response.
|
||||
|
@ -403,6 +447,15 @@ final class process_summarise_text_test extends \advanced_testcase {
|
|||
classname: '\aiprovider_openai\provider',
|
||||
name: 'dummy',
|
||||
config: $config,
|
||||
actionconfig: [
|
||||
\core_ai\aiactions\summarise_text::class => [
|
||||
'settings' => [
|
||||
'model' => 'gpt-4o',
|
||||
'endpoint' => "https://api.openai.com/v1/chat/completions",
|
||||
'systeminstruction' => get_string('action_summarise_text_instruction', 'core_ai'),
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
// Mock the http client to return a successful response.
|
||||
|
|
|
@ -64,9 +64,10 @@ Feature: An administrator can manage AI subsystem settings
|
|||
And I should see "Configure provider instance"
|
||||
And I click on the "Settings" link in the table row containing "Generate text"
|
||||
And I should see "Generate text action settings"
|
||||
And I set the field "AI model" to "Custom"
|
||||
And I set the following fields to these values:
|
||||
| AI model | gpt-3 |
|
||||
| API endpoint | https://api.openai.com/v1/engines/gpt-3/completions |
|
||||
| Custom model name | gpt-3 |
|
||||
| API endpoint | https://api.openai.com/v1/engines/gpt-3/completions |
|
||||
And I click on "Save changes" "button"
|
||||
Then I should see "Generate text action settings updated"
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue