diff --git a/versioned_docs/version-4.5/apis/plugintypes/ai/index.md b/versioned_docs/version-4.5/apis/plugintypes/ai/index.md index 1f1002c14..d888c09fe 100644 --- a/versioned_docs/version-4.5/apis/plugintypes/ai/index.md +++ b/versioned_docs/version-4.5/apis/plugintypes/ai/index.md @@ -7,33 +7,56 @@ tags: - Placement --- -The AI subsystem in the LMS is designed to be extensible, allowing for the integration of external AI services. -This is achieved through the use of AI plugins, which are divided into two types: Providers and Placements. +The [AI subsystem](/apis/subsystems/ai/index.md) provides a way for developers to integrate external AI services into Moodle LMS. -### Placements +The design of the AI subsystem features two distinct plugin types: -The aim of Placements is to provide a consistent UX and UI for users when they use AI backed functionality. +- [Provider plugins](/apis/plugintypes/ai/provider.md) +- [Placement plugins](/apis/plugintypes/ai/placement.md) -Placement plugins leverage the functionality of the other components of the AI subsystem. -This means plugin authors can focus on how users interact with the AI functionality, without needing to -implement the AI functionality itself. +This design allows for independent development of each plugin. The Provider plugin is not aware of the +Placement plugin, and the Placement plugin is not aware of the Provider plugin. All communication between the two plugins +travels through the Manager. -Because Placements are LMS plugins in their own right and are not "other" types of LMS plugins, -it gives great flexibility in how AI functionality is presented to users. +```mermaid +sequenceDiagram + User->>Placement: Input action + Placement->>Manager: Action data + Manager->>Provider: Formatted action data + Provider->>External AI: Send data + External AI-->>Provider: Receive response + Provider-->>Manager: Response data + Manager-->>Placement: Formatted response data + Placement-->>User: Action completed +``` -See the [Placements](/apis/plugintypes/ai/placement.md) documentation for more information -on developing Placement plugins. +### Provider plugins -### Providers +Providers are the interface between the [AI subsystem](/apis/subsystems/ai/index.md) and external AI. +Their focus is on formatting actions, passing them to the external AI system, and providing the response. -Provider plugins are the interface between the LMS AI subsystem and external AI systems. -Their focus is on converting the data requested by an Action into the format needed by the -external AI services API, and then correctly providing the response back from the AI -in an Action Response object. +Currently, Moodle supports the following AI Providers in core: -Because of this design the Providers that provide the AI Actions can be swapped out, mix and matched -or upgraded; both without the need to update the Placement code and without the need to change the -way users interact with the functionality. +- OpenAI `aiprovider_openai` +- Azure AI `aiprovider_azureai` See the [Providers](/apis/plugintypes/ai/provider.md) documentation for more information on developing Provider plugins. + +### Placement plugins + +Placements provide a consistent UX and UI for users when they use AI backed functionality (e.g. generating an image). + +Placement plugins leverage the functionality of the other components of the [AI subsystem](/apis/subsystems/ai/index.md). +This means plugin authors can focus on how users interact with the AI functionality, without needing to +implement the AI functionality itself. + +Because Placements are plugins in their own right, it allows for greater flexibility in how AI functionality is presented to users. + +Currently, Moodle supports the following AI Placements: + +- Course Assistance `aiplacement_courseassist` +- HTML Text Editor `aiplacement_editor` + +See the [Placements](/apis/plugintypes/ai/placement.md) documentation for more information +on developing Placement plugins. diff --git a/versioned_docs/version-4.5/apis/plugintypes/ai/placement.md b/versioned_docs/version-4.5/apis/plugintypes/ai/placement.md index 7dd02be7c..78d5d2592 100644 --- a/versioned_docs/version-4.5/apis/plugintypes/ai/placement.md +++ b/versioned_docs/version-4.5/apis/plugintypes/ai/placement.md @@ -6,30 +6,37 @@ tags: - Placement --- -The aim of Placements is to provide a consistent UX and UI for users when they use AI backed functionality. +Placements provide a consistent UX and UI for users when they use AI backed functionality (e.g. generating an image). -Placement plugins leverage the functionality of the other components of the AI subsystem. +Placement plugins leverage the functionality of the other components of the [AI subsystem](/apis/subsystems/ai/index.md). This means plugin authors can focus on how users interact with the AI functionality, without needing to implement the AI functionality itself. -Because Placements are LMS plugins in their own right and are not "other" types of LMS plugins, -it gives great flexibility in how AI functionality is presented to users. +Because Placements are plugins in their own right, it allows for greater flexibility in how AI functionality is presented to users. + +Outgoing data from the Placement plugin travels via the Manager `core_ai\manager`. +The Manager is the connective tissue between the [Provider](/apis/plugintypes/ai/provider.md) and the Placement plugins. +Likewise, all responses from the Provider plugin are handed back to the Manager before being passed to the Placement plugin. :::warning The Golden Rule: -Placements DO NOT know about Providers, and Providers DO NOT know about Placements. +Placements **do not** know about Providers, and Providers **do not** know about Placements. Everything should go via the Manager. ::: +## Class implementation + Placements are defined as classes in their own namespace according to their plugin name. -The naming convention for Action classes is `aiplacement_`, -for example: `aiplacement_editor`. With corresponding namespaces. +The naming convention for a Placement class is `aiplacement_`. +For example: `aiplacement_editor` (with a corresponding namespace). -Each Placement MUST inherit from the `\core_ai\placement` abstract class. +Each Placement **must** inherit from the `\core_ai\placement` abstract class. They must also implement the following methods: -- `get_action_list(): array` This is the list of Actions that are supported by this Placement, for example the `aiplacement_editor` plugin defines this as: +**`get_action_list(): array`** + +This is the list of Actions that are supported by this Placement, for example the `aiplacement_editor` plugin defines this as: ```php public function get_action_list(): array { @@ -40,17 +47,43 @@ public function get_action_list(): array { } ``` -## Capabilities and Permissions +## Capabilities and permissions + +Placements provide a way for a user to carry out an Action. +Placements are responsible for determining who can use a particular Action, and where it can be used. +It is not the job of Providers to determine access. -Placements are responsible for determining who and where a Placement (and by extension an Action can be used). -It is not the job of Actions or Providers to determine access. +```php +$capabilities = [ + 'aiplacement/editor:generate_image' => [ + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + 'teacher' => CAP_ALLOW, + 'student' => CAP_ALLOW, + ], + ], +... +``` -## Action Processing +## Action processing The following is the basic workflow in order for a placement to have an action processed for a user request: -- The Placement instantiates a new action object of type they wish to use. -- The action must be instantiated and passing it the required data. Each action will define what configuration it needs. As an example: +```mermaid +sequenceDiagram + Placement->>Manager: New action + Manager->>Provider: Process action + Provider->>Manager: Get response + Manager->>Placement: Pass response +``` + +### Step 1 + +- The Placement instantiates a new Action object of type they wish to use. +- The Action must be instantiated with the required data. Each action will define what configuration it needs. As an example: ```php // Prepare the action. @@ -65,8 +98,10 @@ $action = new \core_ai\aiactions\generate_image( ); ``` -- The Placement then instantiates the Manager class and calls `process_action()` -- passing in the configured action object: +### Step 2 + +- The Placement then instantiates the Manager class `core_ai\manager`. +- The Manager calls `process_action()`, passing in the configured Action object. As an example: ```php // Send the action to the AI manager. @@ -74,44 +109,51 @@ $manager = \core\di::get(\core_ai\manager::class); $response = $manager->process_action($action); ``` -- The process_action() method will then return a response object (instance of `responses\response_base`). -- It is up to the Placement to check for success (or not) of the response and pass the result back to the - user or for further processing. +### Step 3 + +- The `process_action()` method then returns a response object (instance of `responses\response_base`). +- It is up to the Placement to check for success/failure of the response and pass the result back to the + user. -## Plugin Structure +## Plugin structure -Placement plugins reside in the `ai/placement` directory. +The Placement plugins reside in the `ai/placement` directory. -Each Placement is in a separate subdirectory and consists of a number of mandatory files and any other -files the developer is going to use. +Each Placement is in a separate subdirectory and consists of a number of mandatory and supportive files that will +be necessary for development. -The following is the typical structure of a Placement plugin, using the Editor Placement as an example: +
+ The typical directory layout for the Placement plugin, using the Editor Placement as an example: -```bash +```console . ├── classes │   ├── external │   │   ├── generate_image.php │   │   └── generate_text.php │   ├── placement.php -│   └── privacy -│   └── provider.php +│   ├── privacy +│   │ └── provider.php +│ └── utils.php ├── db │   ├── access.php │   └── services.php ├── lang │   └── en │   └── aiplacement_editor.php +├── tests +│   └── utils_test.php └── version.php - ``` +
+ ## Settings Settings for the Placement should be defined in the `settings.php` file. Each Placement plugin should create a new admin settings page using `core_ai\admin\admin_settingspage_provider` class. -This is the same as for Provider plugins, for example: +This is the same for Provider plugins, for example: ```php use core_ai\admin\admin_settingspage_provider; @@ -119,11 +161,10 @@ use core_ai\admin\admin_settingspage_provider; if ($hassiteconfig) { // Placement specific settings heading. $settings = new admin_settingspage_provider( - 'aiprovider_openai', - new lang_string('pluginname', 'aiprovider_openai'), + 'aiplacement_courseassist', + new lang_string('pluginname', 'aiplacement_courseassist'), 'moodle/site:config', true, ); - ... ``` diff --git a/versioned_docs/version-4.5/apis/plugintypes/ai/provider.md b/versioned_docs/version-4.5/apis/plugintypes/ai/provider.md index 1af3f249c..b2ebd3b7d 100644 --- a/versioned_docs/version-4.5/apis/plugintypes/ai/provider.md +++ b/versioned_docs/version-4.5/apis/plugintypes/ai/provider.md @@ -5,26 +5,34 @@ tags: - LLM - Provider --- -Providers are the interface between the LMS AI subsystem and external AI systems. -Their focus should be on converting the data requested by an Action into the format needed -by the external AI services API, and then correctly providing the response back from the AI -in an Action Response object. + +Providers are the interface between the [AI subsystem](/apis/subsystems/ai/index.md) and external AI. +Their focus should be on converting the data requested into the format needed +by the external AI, and then correctly providing the response back. + +Incoming data to the Provider plugin arrives via the Manager `core_ai\manager`. +The Manager is the connective tissue between the Provider and the [Placement](/apis/plugintypes/ai/placement.md) plugins. +Likewise, all responses from the Provider plugin are handed back to the Manager before being passed to the Placement plugin. :::warning The Golden Rule: -Placements DO NOT know about Providers, and Providers DO NOT know about Placements. +Placements **do not** know about Providers, and Providers **do not** know about Placements. Everything should go via the Manager. ::: +## Class implementation + Providers are defined as classes in their own namespace according to their plugin name. -The naming convention for Action classes is `aiprovider_`, -for example: `aiprovider_openai`, `aiprovider_azureai`. With corresponding namespaces. +The naming convention for a Provider class is `aiprovider_`. +For example: `aiprovider_openai`, or `aiprovider_azureai` (with a corresponding namespace). -Each Provider MUST inherit from the `\core_ai\provider` abstract class. +Each Provider **must** inherit from the `\core_ai\provider` abstract class. They must also implement the following methods: -- `get_action_list(): array` This is the list of Actions that are supported by this Provider, for example the `aiprovider_openai` plugin defines this as: +**`get_action_list(): array`** + +This is the list of Actions that are supported by this Provider, for example the `aiprovider_openai` plugin defines this as: ```php public function get_action_list(): array { @@ -36,14 +44,31 @@ public function get_action_list(): array { } ``` +**`is_provider_configured(): bool`** + +Each provider will need to specify what it takes to be considered as configured. +It is likely that each provider will have a set of keys necessary to access the external AI API. + +The `is_provider_configured()` must return `true` for UI component visibility and functionality. If not overridden, it will +return `false` by default. + +For example, the `aiprovider_azureai` provider checks values are set for `$this->apikey` and `$this->apiendpoint` and returns +the result. + +```php +public function is_provider_configured(): bool { + return !empty($this->apikey) && !empty($this->apiendpoint); +} +``` + ## Process classes -For each action supported by the provider, the provider plugin **MUST** implement a `process_` class, +For each action supported by the provider, the provider plugin **must** implement a `process_` class, where `` is the name of the action. For example: `process_generate_image`. -Every process action class **MUST** inherit from the `\core_ai\process_base` abstract class. +Every process action class **must** inherit from the `\core_ai\process_base` abstract class. -The process action class **MUST** implement a `process()` method. This method is responsible for +The process action class **must** implement a `process()` method. This method is responsible for converting the data requested by an Action into the format needed by the external AI services API, and then correctly providing the response back from the AI in an Action Response object. @@ -68,19 +93,20 @@ graph TD; ``` Apart from this, Providers are free to define their own structure. It should be kept in mind that Providers -are designed to be a "thin wrapper" around the external AI systems API. They shouldn't store data, +are designed to be a 'thin wrapper' around the external AI systems API. They shouldn't store data, or have their own UI elements (beyond what is required for configuration). -## Plugin Structure +## Plugin structure Provider plugins reside in the `ai/provider` directory. Each Provider is in a separate subdirectory and consists of a number of mandatory files and any other files the developer is going to use. -The following is the typical structure of a Provider plugin, using the OpenAI Provider as an example: +
+ The typical directory layout for the Provider plugin, using OpenAI Provider as an example: -```bash +```console . ├── classes │   ├── abstract_processor.php @@ -107,6 +133,8 @@ The following is the typical structure of a Provider plugin, using the OpenAI Pr ``` +
+ ## Settings Settings for the Provider should be defined in the `settings.php` file. @@ -125,11 +153,10 @@ if ($hassiteconfig) { 'moodle/site:config', true, ); - ... ``` -## Rate Limiting +## Rate limiting It is recommended that Providers implement rate limiting to prevent abuse of the external AI services. @@ -141,45 +168,45 @@ This should be implemented in a `is_request_allowed()` method in the Provider cl ```php /** - * Check if the request is allowed by the rate limiter. - * - * @param aiactions\base $action The action to check. - * @return array|bool True on success, array of error details on failure. - */ - public function is_request_allowed(aiactions\base $action): array|bool { - $ratelimiter = \core\di::get(rate_limiter::class); - $component = \core\component::get_component_from_classname(get_class($this)); - - // Check the user rate limit. - if ($this->enableuserratelimit) { - if (!$ratelimiter->check_user_rate_limit( - component: $component, - ratelimit: $this->userratelimit, - userid: $action->get_configuration('userid') - )) { - return [ - 'success' => false, - 'errorcode' => 429, - 'errormessage' => 'User rate limit exceeded', - ]; - } + * Check if the request is allowed by the rate limiter. + * + * @param aiactions\base $action The action to check. + * @return array|bool True on success, array of error details on failure. + */ +public function is_request_allowed(aiactions\base $action): array|bool { + $ratelimiter = \core\di::get(rate_limiter::class); + $component = \core\component::get_component_from_classname(get_class($this)); + + // Check the user rate limit. + if ($this->enableuserratelimit) { + if (!$ratelimiter->check_user_rate_limit( + component: $component, + ratelimit: $this->userratelimit, + userid: $action->get_configuration('userid') + )) { + return [ + 'success' => false, + 'errorcode' => 429, + 'errormessage' => 'User rate limit exceeded', + ]; } + } - // Check the global rate limit. - if ($this->enableglobalratelimit) { - if (!$ratelimiter->check_global_rate_limit( - component: $component, - ratelimit: $this->globalratelimit - )) { - return [ - 'success' => false, - 'errorcode' => 429, - 'errormessage' => 'Global rate limit exceeded', - ]; - } + // Check the global rate limit. + if ($this->enableglobalratelimit) { + if (!$ratelimiter->check_global_rate_limit( + component: $component, + ratelimit: $this->globalratelimit + )) { + return [ + 'success' => false, + 'errorcode' => 429, + 'errormessage' => 'Global rate limit exceeded', + ]; } + } - return true; + return true; } ``` @@ -188,41 +215,41 @@ If implementing rate limiting, settings for the rate limits should be provided i For example, the `aiprovider_openai` plugin provides settings for the user and global rate limits: ```php - // Setting to enable/disable global rate limiting. - $settings->add(new admin_setting_configcheckbox( - 'aiprovider_openai/enableglobalratelimit', - new lang_string('enableglobalratelimit', 'aiprovider_openai'), - new lang_string('enableglobalratelimit_desc', 'aiprovider_openai'), - 0, - )); - - // Setting to set how many requests per hour are allowed for the global rate limit. - // Should only be enabled when global rate limiting is enabled. - $settings->add(new admin_setting_configtext( - 'aiprovider_openai/globalratelimit', - new lang_string('globalratelimit', 'aiprovider_openai'), - new lang_string('globalratelimit_desc', 'aiprovider_openai'), - 100, - PARAM_INT, - )); - $settings->hide_if('aiprovider_openai/globalratelimit', 'aiprovider_openai/enableglobalratelimit', 'eq', 0); - - // Setting to enable/disable user rate limiting. - $settings->add(new admin_setting_configcheckbox( - 'aiprovider_openai/enableuserratelimit', - new lang_string('enableuserratelimit', 'aiprovider_openai'), - new lang_string('enableuserratelimit_desc', 'aiprovider_openai'), - 0, - )); - - // Setting to set how many requests per hour are allowed for the user rate limit. - // Should only be enabled when user rate limiting is enabled. - $settings->add(new admin_setting_configtext( - 'aiprovider_openai/userratelimit', - new lang_string('userratelimit', 'aiprovider_openai'), - new lang_string('userratelimit_desc', 'aiprovider_openai'), - 10, - PARAM_INT, - )); - $settings->hide_if('aiprovider_openai/userratelimit', 'aiprovider_openai/enableuserratelimit', 'eq', 0); +// Setting to enable/disable global rate limiting. +$settings->add(new admin_setting_configcheckbox( + 'aiprovider_openai/enableglobalratelimit', + new lang_string('enableglobalratelimit', 'aiprovider_openai'), + new lang_string('enableglobalratelimit_desc', 'aiprovider_openai'), + 0, +)); + +// Setting to set how many requests per hour are allowed for the global rate limit. +// Should only be enabled when global rate limiting is enabled. +$settings->add(new admin_setting_configtext( + 'aiprovider_openai/globalratelimit', + new lang_string('globalratelimit', 'aiprovider_openai'), + new lang_string('globalratelimit_desc', 'aiprovider_openai'), + 100, + PARAM_INT, +)); +$settings->hide_if('aiprovider_openai/globalratelimit', 'aiprovider_openai/enableglobalratelimit', 'eq', 0); + +// Setting to enable/disable user rate limiting. +$settings->add(new admin_setting_configcheckbox( + 'aiprovider_openai/enableuserratelimit', + new lang_string('enableuserratelimit', 'aiprovider_openai'), + new lang_string('enableuserratelimit_desc', 'aiprovider_openai'), + 0, +)); + +// Setting to set how many requests per hour are allowed for the user rate limit. +// Should only be enabled when user rate limiting is enabled. +$settings->add(new admin_setting_configtext( + 'aiprovider_openai/userratelimit', + new lang_string('userratelimit', 'aiprovider_openai'), + new lang_string('userratelimit_desc', 'aiprovider_openai'), + 10, + PARAM_INT, +)); +$settings->hide_if('aiprovider_openai/userratelimit', 'aiprovider_openai/enableuserratelimit', 'eq', 0); ```