Skip to content

Commit 4e7ab1a

Browse files
authored
Merge pull request moodle#1221 from mattporritt/MDL-82977
Update AI Provider plugin documentation for provider instances.
2 parents 376ecfb + eefc72a commit 4e7ab1a

1 file changed

Lines changed: 139 additions & 101 deletions

File tree

docs/apis/plugintypes/ai/provider.md

Lines changed: 139 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ Incoming data to the Provider plugin arrives via the Manager `core_ai\manager`.
1414
The Manager is the connective tissue between the Provider and the [Placement](/apis/plugintypes/ai/placement.md) plugins.
1515
Likewise, all responses from the Provider plugin are handed back to the Manager before being passed to the Placement plugin.
1616

17+
A Provider plugin allows many "provider instances" to be defined, each of which can support a different set of configurations.
18+
This facilitates having providers for specific tasks. So, you can use a more efficient model for lightweight tasks like summarisation, and a more fully featured model for text generation. Another example is defaulting to using a cheaper model with a lower token limit, and then falling back to a more expensive model if a request is too large for the default model.
19+
1720
:::warning The Golden Rule:
1821

1922
Placements **do not** know about Providers, and Providers **do not** know about Placements.
@@ -28,14 +31,17 @@ The naming convention for a Provider class is `aiprovider_<plugin name>`.
2831
For example: `aiprovider_openai`, or `aiprovider_azureai` (with a corresponding namespace).
2932

3033
Each Provider **must** inherit from the `\core_ai\provider` abstract class.
34+
35+
### Required Methods
36+
3137
They must also implement the following methods:
3238

3339
**`get_action_list(): array`**
3440

3541
This is the list of Actions that are supported by this Provider, for example the `aiprovider_openai` plugin defines this as:
3642

3743
```php
38-
public function get_action_list(): array {
44+
public static function get_action_list(): array {
3945
return [
4046
\core_ai\aiactions\generate_text::class,
4147
\core_ai\aiactions\generate_image::class,
@@ -57,7 +63,7 @@ the result.
5763

5864
```php
5965
public function is_provider_configured(): bool {
60-
return !empty($this->apikey) && !empty($this->apiendpoint);
66+
return !empty($this->config['apikey']) && !empty($this->config['endpoint']);
6167
}
6268
```
6369

@@ -108,18 +114,34 @@ files the developer is going to use.
108114

109115
```console
110116
.
117+
├── amd
118+
│   ├── build
119+
│   │   ├── modelchooser.min.js
120+
│   │   └── modelchooser.min.js.map
121+
│   └── src
122+
│   └── modelchooser.js
111123
├── classes
112124
│   ├── abstract_processor.php
125+
│   ├── aimodel
126+
│   │   ├── gpt4o.php
127+
│   │   └── o1.php
128+
│   ├── form
129+
│   │   ├── action_form.php
130+
│   │   ├── action_generate_image_form.php
131+
│   │   └── action_generate_text_form.php
132+
│   ├── helper.php
133+
│   ├── hook_listener.php
113134
│   ├── privacy
114135
│   │   └── provider.php
115136
│   ├── process_generate_image.php
116137
│   ├── process_generate_text.php
117138
│   ├── process_summarise_text.php
118139
│   └── provider.php
140+
├── db
141+
│   └── hooks.php
119142
├── lang
120143
│   └── en
121144
│   └── aiprovider_openai.php
122-
├── settings.php
123145
├── tests
124146
│   ├── fixtures
125147
│   │   ├── image_request_success.json
@@ -135,121 +157,137 @@ files the developer is going to use.
135157

136158
</details>
137159

138-
## Settings
160+
## Provider Settings
161+
162+
Settings for the Provider are defined as a [Hook](/apis/core/hooks/index.md).
163+
Each Provider plugin should create a new `classes/hook_listener.php` file. This file should contain a class with a static method that defines the hook callback.
164+
create a new admin settings page using `core_ai\admin\admin_settingspage_provider` class. This class should implement a `set_form_definition_for_aiprovider_<plugin name>` method.
139165

140-
Settings for the Provider should be defined in the `settings.php` file.
141-
Each Provider plugin should create a new admin settings page using `core_ai\admin\admin_settingspage_provider` class.
166+
This method should define the settings form for the provider plugin, using the provided `mform` object.
142167

143168
For example, the `aiprovider_openai` plugin defines this:
144169

145170
```php
146-
use core_ai\admin\admin_settingspage_provider;
147-
148-
if ($hassiteconfig) {
149-
// Provider specific settings heading.
150-
$settings = new admin_settingspage_provider(
151-
'aiprovider_openai',
152-
new lang_string('pluginname', 'aiprovider_openai'),
153-
'moodle/site:config',
154-
true,
155-
);
156-
...
157-
```
171+
namespace aiprovider_openai;
172+
use core_ai\hook\after_ai_provider_form_hook;
173+
174+
class hook_listener {
175+
176+
/**
177+
* Hook listener for the Open AI instance setup form.
178+
*
179+
* @param after_ai_provider_form_hook $hook The hook to add to the AI instance setup.
180+
*/
181+
public static function set_form_definition_for_aiprovider_openai(after_ai_provider_form_hook $hook): void {
182+
if ($hook->plugin !== 'aiprovider_openai') {
183+
return;
184+
}
158185

159-
## Rate limiting
186+
$mform = $hook->mform;
187+
188+
// Required setting to store OpenAI API key.
189+
$mform->addElement(
190+
'passwordunmask',
191+
'apikey',
192+
get_string('apikey', 'aiprovider_openai'),
193+
['size' => 75],
194+
);
195+
$mform->addHelpButton('apikey', 'apikey', 'aiprovider_openai');
196+
$mform->addRule('apikey', get_string('required'), 'required', null, 'client');
197+
198+
// Setting to store OpenAI organization ID.
199+
$mform->addElement(
200+
'text',
201+
'orgid',
202+
get_string('orgid', 'aiprovider_openai'),
203+
['size' => 25],
204+
);
205+
$mform->setType('orgid', PARAM_TEXT);
206+
$mform->addHelpButton('orgid', 'orgid', 'aiprovider_openai');
160207

161-
It is recommended that Providers implement rate limiting to prevent abuse of the external AI services.
208+
}
209+
210+
}
211+
```
162212

163-
To assist with this, the AI subsystem provides a `core_ai\rate_limiter` class that can be used to implement rate limiting.
164-
This class supports both user and system level rate limiting.
213+
Because a hook is used to define the settings, Provider plugins also need to have a `db/hooks.php` file to register its hook callback.
214+
The specified plugin callback method is called whenever the provider instance is chosen in the provider administration settings.
165215

166-
This should be implemented in a `is_request_allowed()` method in the Provider class. For example, from the
167-
`aiprovider_openai` plugin:
216+
For example, the `aiprovider_openai` plugin defines this:
168217

169218
```php
170-
/**
171-
* Check if the request is allowed by the rate limiter.
172-
*
173-
* @param aiactions\base $action The action to check.
174-
* @return array|bool True on success, array of error details on failure.
175-
*/
176-
public function is_request_allowed(aiactions\base $action): array|bool {
177-
$ratelimiter = \core\di::get(rate_limiter::class);
178-
$component = \core\component::get_component_from_classname(get_class($this));
179-
180-
// Check the user rate limit.
181-
if ($this->enableuserratelimit) {
182-
if (!$ratelimiter->check_user_rate_limit(
183-
component: $component,
184-
ratelimit: $this->userratelimit,
185-
userid: $action->get_configuration('userid')
186-
)) {
187-
return [
188-
'success' => false,
189-
'errorcode' => 429,
190-
'errormessage' => 'User rate limit exceeded',
191-
];
192-
}
193-
}
219+
defined('MOODLE_INTERNAL') || die();
220+
221+
$callbacks = [
222+
[
223+
'hook' => \core_ai\hook\after_ai_provider_form_hook::class,
224+
'callback' => \aiprovider_openai\hook_listener::class . '::set_form_definition_for_aiprovider_openai',
225+
],
226+
];
227+
```
194228

195-
// Check the global rate limit.
196-
if ($this->enableglobalratelimit) {
197-
if (!$ratelimiter->check_global_rate_limit(
198-
component: $component,
199-
ratelimit: $this->globalratelimit
200-
)) {
201-
return [
202-
'success' => false,
203-
'errorcode' => 429,
204-
'errormessage' => 'Global rate limit exceeded',
205-
];
206-
}
207-
}
229+
## Action Settings
230+
231+
Each of the actions that a provider plugin supports can have its own settings.
232+
If an action requires additional settings, the provider class for the plugin should override the `get_action_settings()` method.
233+
The method must return an instance of `core_ai\form\action_settings_form`.
234+
235+
For example, the `aiprovider_openai` plugin defines this:
208236

209-
return true;
237+
```php
238+
#[\Override]
239+
public static function get_action_settings(
240+
string $action,
241+
array $customdata = [],
242+
): action_settings_form|bool {
243+
$actionname = substr($action, (strrpos($action, '\\') + 1));
244+
$customdata['actionname'] = $actionname;
245+
$customdata['action'] = $action;
246+
if ($actionname === 'generate_text' || $actionname === 'summarise_text') {
247+
return new form\action_generate_text_form(customdata: $customdata);
248+
} else if ($actionname === 'generate_image') {
249+
return new form\action_generate_image_form(customdata: $customdata);
210250
}
251+
252+
return false;
253+
}
211254
```
212255

213-
If implementing rate limiting, settings for the rate limits should be provided in the plugin settings.
256+
The actual settings form for the actions should be defined in the `classes/form` directory.
257+
For example, the `aiprovider_openai` plugin defines `action_generate_text_form` and `action_generate_image_form` classes.
258+
These classes should extend the `core_ai\form\action_settings_form` class, and must implement the `definition()` method.
214259

215-
For example, the `aiprovider_openai` plugin provides settings for the user and global rate limits:
260+
For example, the `aiprovider_openai` plugin defines this:
216261

217262
```php
218-
// Setting to enable/disable global rate limiting.
219-
$settings->add(new admin_setting_configcheckbox(
220-
'aiprovider_openai/enableglobalratelimit',
221-
new lang_string('enableglobalratelimit', 'aiprovider_openai'),
222-
new lang_string('enableglobalratelimit_desc', 'aiprovider_openai'),
223-
0,
224-
));
225-
226-
// Setting to set how many requests per hour are allowed for the global rate limit.
227-
// Should only be enabled when global rate limiting is enabled.
228-
$settings->add(new admin_setting_configtext(
229-
'aiprovider_openai/globalratelimit',
230-
new lang_string('globalratelimit', 'aiprovider_openai'),
231-
new lang_string('globalratelimit_desc', 'aiprovider_openai'),
232-
100,
233-
PARAM_INT,
234-
));
235-
$settings->hide_if('aiprovider_openai/globalratelimit', 'aiprovider_openai/enableglobalratelimit', 'eq', 0);
236-
237-
// Setting to enable/disable user rate limiting.
238-
$settings->add(new admin_setting_configcheckbox(
239-
'aiprovider_openai/enableuserratelimit',
240-
new lang_string('enableuserratelimit', 'aiprovider_openai'),
241-
new lang_string('enableuserratelimit_desc', 'aiprovider_openai'),
242-
0,
243-
));
244-
245-
// Setting to set how many requests per hour are allowed for the user rate limit.
246-
// Should only be enabled when user rate limiting is enabled.
247-
$settings->add(new admin_setting_configtext(
248-
'aiprovider_openai/userratelimit',
249-
new lang_string('userratelimit', 'aiprovider_openai'),
250-
new lang_string('userratelimit_desc', 'aiprovider_openai'),
251-
10,
252-
PARAM_INT,
253-
));
254-
$settings->hide_if('aiprovider_openai/userratelimit', 'aiprovider_openai/enableuserratelimit', 'eq', 0);
263+
class action_generate_text_form extends action_settings_form {
264+
#[\Override]
265+
protected function definition() {
266+
$mform = $this->_form;
267+
$actionconfig = $this->_customdata['actionconfig']['settings'] ?? [];
268+
$returnurl = $this->_customdata['returnurl'] ?? null;
269+
$actionname = $this->_customdata['actionname'];
270+
$action = $this->_customdata['action'];
271+
$providerid = $this->_customdata['providerid'] ?? 0;
272+
273+
// Action model to use.
274+
$mform->addElement(
275+
'text',
276+
'model',
277+
get_string("action:{$actionname}:model", 'aiprovider_openai'),
278+
'maxlength="255" size="20"',
279+
);
280+
$mform->setType('model', PARAM_TEXT);
281+
$mform->addRule('model', null, 'required', null, 'client');
282+
$mform->setDefault('model', $actionconfig['model'] ?? 'gpt-4o');
283+
$mform->addHelpButton('model', "action:{$actionname}:model", 'aiprovider_openai');
284+
285+
...
255286
```
287+
288+
## Rate limiting
289+
290+
Provider plugins by default implement rate limiting to prevent abuse of the external AI services.
291+
This is inherited from the `core_ai\provider` class. Developers don't need to implement rate limiting themselves.
292+
293+
This default rate limiting behaviour can be changed by overriding the `is_request_allowed()` method `core_ai\provider` class.

0 commit comments

Comments
 (0)