MDL-82609 AI: Provider - Action settings

Allow each each Provider plugin to be able
to specify settings for the actions that they
support. Adds admin setting support for these
settings.
This commit is contained in:
Matt Porritt 2024-08-05 16:34:17 +10:00
parent 09e56f2d1a
commit 2ae9b25ad4
13 changed files with 176 additions and 17 deletions

View File

@ -89,8 +89,8 @@ abstract class base {
*
* @return string The system instruction for the action.
*/
public function get_system_instruction(): string {
$stringid = 'action_' . $this->get_basename() . '_instruction';
public static function get_system_instruction(): string {
$stringid = 'action_' . self::get_basename() . '_instruction';
// If the string doesn't exist, return an empty string.
if (!get_string_manager()->string_exists($stringid, 'core_ai')) {

View File

@ -54,4 +54,21 @@ abstract class provider {
return get_string('pluginname', $component);
}
/**
* Get any action settings for this provider.
*
* @param string $action The action class name.
* @param \admin_root $ADMIN The admin root object.
* @param string $section The section name.
* @param bool $hassiteconfig Whether the current user has moodle/site:config capability.
* @return array An array of settings.
*/
public function get_action_settings(
string $action,
\admin_root $ADMIN,
string $section,
bool $hassiteconfig
): array {
return [];
}
}

View File

@ -175,7 +175,14 @@ class aiprovider_action_management_table extends flexible_table implements dynam
* @return string
*/
protected function col_settings(stdClass $row): string {
// TODO: MDL-82609 - Add settings link.
global $CFG;
require_once($CFG->libdir . '/adminlib.php'); // Needed for the AJAX calls.
$tree = \admin_get_root();
$sectionname = $this->pluginname . '_' . $row->action::get_basename();
$section = $tree->locate($sectionname);
if ($section) {
return \html_writer::link($section->get_settings_page_url(), get_string('settings'));
}
return '';
}

View File

@ -47,6 +47,15 @@ abstract class abstract_processor extends process_base {
*/
abstract protected function get_model(): string;
/**
* Get the system instructions.
*
* @return string
*/
protected function get_system_instruction(): string {
return $this->action::get_system_instruction();
}
/**
* Create the request object to send to the OpenAI API.
*

View File

@ -22,6 +22,7 @@ use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
/**
* Class process image generation.
@ -38,13 +39,13 @@ class process_generate_image extends abstract_processor {
private string $responseformat = 'url';
#[\Override]
protected function get_endpoint(): \Psr\Http\Message\UriInterface {
return new Uri('https://api.openai.com/v1/images/generations');
protected function get_endpoint(): UriInterface {
return new Uri(get_config('aiprovider_openai', 'action_generate_image_endpoint'));
}
#[\Override]
protected function get_model(): string {
return 'dall-e-3';
return get_config('aiprovider_openai', 'action_generate_image_model');
}
#[\Override]

View File

@ -32,12 +32,17 @@ use Psr\Http\Message\UriInterface;
class process_generate_text extends abstract_processor {
#[\Override]
protected function get_endpoint(): UriInterface {
return new Uri('https://api.openai.com/v1/chat/completions');
return new Uri(get_config('aiprovider_openai', 'action_generate_text_endpoint'));
}
#[\Override]
protected function get_model(): string {
return 'gpt-4o';
return get_config('aiprovider_openai', 'action_generate_text_model');
}
#[\Override]
protected function get_system_instruction(): string {
return get_config('aiprovider_openai', 'action_generate_text_systeminstruction');
}
#[\Override]
@ -53,7 +58,7 @@ class process_generate_text extends abstract_processor {
$requestobj->user = $userid;
// If there is a system string available, use it.
$systeminstruction = $this->action->get_system_instruction();
$systeminstruction = $this->get_system_instruction();
if (!empty($systeminstruction)) {
$systemobj = new \stdClass();
$systemobj->role = 'system';

View File

@ -16,6 +16,9 @@
namespace aiprovider_openai;
use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\UriInterface;
/**
* Class process text summarisation.
*
@ -24,4 +27,18 @@ namespace aiprovider_openai;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class process_summarise_text extends process_generate_text {
#[\Override]
protected function get_endpoint(): UriInterface {
return new Uri(get_config('aiprovider_openai', 'action_summarise_text_endpoint'));
}
#[\Override]
protected function get_model(): string {
return get_config('aiprovider_openai', 'action_summarise_text_model');
}
#[\Override]
protected function get_system_instruction(): string {
return get_config('aiprovider_openai', 'action_summarise_text_systeminstruction');
}
}

View File

@ -143,4 +143,68 @@ class provider extends \core_ai\provider {
return true;
}
/**
* Get any action settings for this provider.
*
* @param string $action The action class name.
* @param \admin_root $ADMIN The admin root object.
* @param string $section The section name.
* @param bool $hassiteconfig Whether the current user has moodle/site:config capability.
* @return array An array of settings.
*/
public function get_action_settings(
string $action,
\admin_root $ADMIN,
string $section,
bool $hassiteconfig
): array {
$actionname = substr($action, (strrpos($action, '\\') + 1));
$settings = [];
if ($actionname === 'generate_text' || $actionname === 'summarise_text') {
// Add the model setting.
$settings[] = new \admin_setting_configtext(
"aiprovider_openai/action_{$actionname}_model",
new \lang_string("action:{$actionname}:model", 'aiprovider_openai'),
new \lang_string("action:{$actionname}:model_desc", 'aiprovider_openai'),
'gpt-4o',
PARAM_TEXT,
);
// Add API endpoint.
$settings[] = new \admin_setting_configtext(
"aiprovider_openai/action_{$actionname}_endpoint",
new \lang_string("action:{$actionname}:endpoint", 'aiprovider_openai'),
new \lang_string("action:{$actionname}:endpoint_desc", 'aiprovider_openai'),
'https://api.openai.com/v1/chat/completions',
PARAM_URL,
);
// Add system instruction settings.
$settings[] = new \admin_setting_configtextarea(
"aiprovider_openai/action_{$actionname}_systeminstruction",
new \lang_string("action:{$actionname}:systeminstruction", 'aiprovider_openai'),
new \lang_string("action:{$actionname}:systeminstruction_desc", 'aiprovider_openai'),
$action::get_system_instruction(),
PARAM_TEXT
);
} else if ($actionname === 'generate_image') {
// Add the model setting.
$settings[] = new \admin_setting_configtext(
"aiprovider_openai/action_{$actionname}_model",
new \lang_string("action:{$actionname}:model", 'aiprovider_openai'),
new \lang_string("action:{$actionname}:model_desc", 'aiprovider_openai'),
'dall-e-3',
PARAM_TEXT,
);
// Add API endpoint.
$settings[] = new \admin_setting_configtext(
"aiprovider_openai/action_{$actionname}_endpoint",
new \lang_string("action:{$actionname}:endpoint", 'aiprovider_openai'),
new \lang_string("action:{$actionname}:endpoint_desc", 'aiprovider_openai'),
'https://api.openai.com/v1/images/generations',
PARAM_URL,
);
}
return $settings;
}
}

View File

@ -22,6 +22,22 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['action:generate_image:endpoint'] = 'API endpoint';
$string['action:generate_image:endpoint_desc'] = 'The API endpoint the provider uses for this action.';
$string['action:generate_image:model'] = 'Image generation model';
$string['action:generate_image:model_desc'] = 'The model used to generate images from user prompts.';
$string['action:generate_text:endpoint'] = 'API endpoint';
$string['action:generate_text:endpoint_desc'] = 'The API endpoint for the provider uses for this action.';
$string['action:generate_text:model'] = 'Text generation model';
$string['action:generate_text:model_desc'] = 'The model used to generate the text response.';
$string['action:generate_text:systeminstruction'] = 'System instruction';
$string['action:generate_text:systeminstruction_desc'] = 'This instruction is provided together with the user prompt for this action. It provides information to the AI model on how to generate the response.';
$string['action:summarise_text:endpoint'] = 'API endpoint';
$string['action:summarise_text:endpoint_desc'] = 'The API endpoint the provider uses for this action.';
$string['action:summarise_text:model'] = 'Text summarisation model';
$string['action:summarise_text:model_desc'] = 'The model used to summarise the provided text.';
$string['action:summarise_text:systeminstruction'] = 'System instruction';
$string['action:summarise_text:systeminstruction_desc'] = 'This instruction is provided together with the user prompt for this action. It provides information to the AI model on how to generate the response.';
$string['apikey'] = 'OpenAI API key';
$string['apikey_desc'] = 'Enter your OpenAI API key. You can get one from <a href="https://platform.openai.com/account/api-keys" target="_blank">here</a>';
$string['enableglobalratelimit'] = 'Enable global rate limiting';

View File

@ -83,8 +83,8 @@ final class process_generate_text_test extends \advanced_testcase {
$body = (object) json_decode($request->getBody()->getContents());
$this->assertEquals('This is a test prompt', $body->messages[0]->content);
$this->assertEquals('user', $body->messages[0]->role);
$this->assertEquals('This is a test prompt', $body->messages[1]->content);
$this->assertEquals('user', $body->messages[1]->role);
}
/**

View File

@ -25,6 +25,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'aiprovider_openai';
$plugin->version = 2024060900;
$plugin->version = 2024091300;
$plugin->requires = 2024041600;
$plugin->maturity = MATURITY_STABLE;

View File

@ -27,6 +27,9 @@ $string['action_generate_image'] = 'Generate image';
$string['action_generate_image_desc'] = 'Generates an image based on a text prompt.';
$string['action_generate_text'] = 'Generate text';
$string['action_generate_text_desc'] = 'Generates text based on a text prompt.';
$string['action_generate_text_instruction'] = 'You will receive a text input from the user. Your task is to generate text based on their request. Follow these important instructions:
1. Return the summary in plain text only.
2. Do not include any markdown formatting, greetings, or platitudes.';
$string['action_summarise_text'] = 'Summarise text';
$string['action_summarise_text_desc'] = 'Summarises text based on provided input text.';
$string['action_summarise_text_instruction'] = 'You will receive a text input from the user. Your task is to summarize the provided text. Follow these guidelines:
@ -41,6 +44,9 @@ Important Instructions:
Ensure the summary is easy to read and effectively conveys the main points of the original text.';
$string['action_translate_text'] = 'Translate text';
$string['action_translate_text_desc'] = 'Translate provided text from one language to another.';
$string['actionsettingprovider'] = '{$a} action settings';
$string['actionsettingprovider_desc'] = 'These settings are specifc to this action for this provider.<br/>
They are used to control how the action is processed by the provider.';
$string['ai'] = 'AI';
$string['aiplacementsettings'] = 'Manage settings for AI placements';
$string['aiprovidersettings'] = 'Manage settings for AI providers';

View File

@ -18,7 +18,6 @@ namespace core\plugininfo;
use core_plugin_manager;
use moodle_url;
use core\lang_string;
/**
* AI placement plugin info class.
@ -103,18 +102,36 @@ class aiprovider extends base {
} else {
// Provider action settings heading.
$settings->add(new \admin_setting_heading("{$section}/generals",
new lang_string('provideractionsettings', 'core_ai'),
new lang_string('provideractionsettings_desc', 'core_ai')));
new \lang_string('provideractionsettings', 'core_ai'),
new \lang_string('provideractionsettings_desc', 'core_ai')));
// Load the setting table of actions that this provider supports.
$settings->add(new \core_ai\admin\admin_setting_action_manager(
$section,
\core_ai\table\aiprovider_action_management_table::class,
'manageaiproviders',
new lang_string('manageaiproviders', 'core_ai'),
new \lang_string('manageaiproviders', 'core_ai'),
));
}
$ADMIN->add($parentnodename, $settings);
// Load any action settings for this provider.
$providerclass = "\\{$section}\\provider";
$provider = new $providerclass();
$actionlist = $provider->get_action_list();
foreach ($actionlist as $action) {
$actionsettings = $provider->get_action_settings($action, $ADMIN, $section, $hassiteconfig);
if (!empty($actionsettings)) {
$actionname = substr($action, (strrpos($action, '\\') + 1));
$settings = new \admin_settingpage($section . '_' . $actionname, $action::get_name(), 'moodle/site:config', true);
$setting = new \admin_setting_heading("{$section}_actions/heading",
new \lang_string('actionsettingprovider', 'core_ai', $provider->get_name()),
new \lang_string('actionsettingprovider_desc', 'core_ai'));
$settings->add($setting);
foreach ($actionsettings as $setting) {
$settings->add($setting);
}
$ADMIN->add('root', $settings);
}
}
}
/**