From 2baf81a2436df25c98d4c006daf95e411db58b2c Mon Sep 17 00:00:00 2001 From: David Woloszyn Date: Wed, 25 Sep 2024 14:43:13 +1000 Subject: [PATCH 1/5] MDL-83154 AI: Add model field to ai_action_register table --- lib/db/install.xml | 3 ++- lib/db/upgrade.php | 15 +++++++++++++++ version.php | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/db/install.xml b/lib/db/install.xml index b7bd829f0ac..b146f627643 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -4899,6 +4899,7 @@ + diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index f040a03a1d7..6c2912a02b6 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -1447,5 +1447,20 @@ function xmldb_main_upgrade($oldversion) { // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. + if ($oldversion < 2024102500.02) { + + // Define field model to be added to ai_action_register. + $table = new xmldb_table('ai_action_register'); + $field = new xmldb_field('model', XMLDB_TYPE_CHAR, '50', null, null, null, null, null); + + // Conditionally launch add field model. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2024102500.02); + } + return true; } diff --git a/version.php b/version.php index c1b67266d40..c3ee0d1915f 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2024102500.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2024102500.02; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '5.0dev (Build: 20241025)'; // Human-friendly version name From b7d9100445a8f931b34ed946d8e6895a794fa319 Mon Sep 17 00:00:00 2001 From: David Woloszyn Date: Wed, 25 Sep 2024 16:48:40 +1000 Subject: [PATCH 2/5] MDL-83154 AI: Added model to the response Not all responses will return the model used. When possible, we can fallback to Moodle's stored config value. --- ai/classes/aiactions/responses/response_base.php | 12 ++++++++++++ .../aiactions/responses/response_generate_image.php | 2 ++ .../aiactions/responses/response_generate_text.php | 2 ++ .../aiactions/responses/response_summarise_text.php | 2 ++ ai/classes/manager.php | 1 + .../azureai/classes/process_generate_text.php | 1 + .../openai/classes/process_generate_image.php | 1 + ai/provider/openai/classes/process_generate_text.php | 1 + 8 files changed, 22 insertions(+) diff --git a/ai/classes/aiactions/responses/response_base.php b/ai/classes/aiactions/responses/response_base.php index 298b8f4564d..d7bc1f1d2ec 100644 --- a/ai/classes/aiactions/responses/response_base.php +++ b/ai/classes/aiactions/responses/response_base.php @@ -37,6 +37,7 @@ abstract class response_base { * @param string $actionname The name of the action that was processed. * @param int $errorcode Error code. Must exist if success is false. * @param string $errormessage Error message. Must exist if success is false + * @param string $model The model used to generate the response. */ public function __construct( /** @var bool The success status of the action. */ @@ -47,6 +48,8 @@ abstract class response_base { private int $errorcode = 0, /** @var string Error message. Must exist if status is error */ private string $errormessage = '', + /** @var string The model used to generate the response (if available). */ + protected ?string $model = null, ) { $this->timecreated = \core\di::get(\core\clock::class)->time(); @@ -113,4 +116,13 @@ abstract class response_base { public function get_errormessage(): string { return $this->errormessage; } + + /** + * Get the model used to generate the response (if available). + * + * @return ?string + */ + public function get_model_used(): ?string { + return $this->model; + } } diff --git a/ai/classes/aiactions/responses/response_generate_image.php b/ai/classes/aiactions/responses/response_generate_image.php index 501f3ea6361..80d9f8bd63f 100644 --- a/ai/classes/aiactions/responses/response_generate_image.php +++ b/ai/classes/aiactions/responses/response_generate_image.php @@ -60,6 +60,7 @@ class response_generate_image extends response_base { $this->draftfile = $response['draftfile'] ?? null; $this->revisedprompt = $response['revisedprompt'] ?? null; $this->sourceurl = $response['sourceurl'] ?? null; + $this->model = $response['model'] ?? null; } #[\Override] @@ -68,6 +69,7 @@ class response_generate_image extends response_base { 'draftfile' => $this->draftfile, 'revisedprompt' => $this->revisedprompt, 'sourceurl' => $this->sourceurl, + 'model' => $this->model, ]; } } diff --git a/ai/classes/aiactions/responses/response_generate_text.php b/ai/classes/aiactions/responses/response_generate_text.php index f82c694c614..ebf66f070fd 100644 --- a/ai/classes/aiactions/responses/response_generate_text.php +++ b/ai/classes/aiactions/responses/response_generate_text.php @@ -72,6 +72,7 @@ class response_generate_text extends response_base { $this->finishreason = $response['finishreason'] ?? null; $this->prompttokens = $response['prompttokens'] ?? null; $this->completiontokens = $response['completiontokens'] ?? null; + $this->model = $response['model'] ?? null; } #[\Override] @@ -83,6 +84,7 @@ class response_generate_text extends response_base { 'finishreason' => $this->finishreason, 'prompttokens' => $this->prompttokens, 'completiontokens' => $this->completiontokens, + 'model' => $this->model, ]; } } diff --git a/ai/classes/aiactions/responses/response_summarise_text.php b/ai/classes/aiactions/responses/response_summarise_text.php index a4b850245ed..60a6b0e9df2 100644 --- a/ai/classes/aiactions/responses/response_summarise_text.php +++ b/ai/classes/aiactions/responses/response_summarise_text.php @@ -72,6 +72,7 @@ class response_summarise_text extends response_base { $this->finishreason = $response['finishreason'] ?? null; $this->prompttokens = $response['prompttokens'] ?? null; $this->completiontokens = $response['completiontokens'] ?? null; + $this->model = $response['model'] ?? null; } #[\Override] @@ -83,6 +84,7 @@ class response_summarise_text extends response_base { 'finishreason' => $this->finishreason, 'prompttokens' => $this->prompttokens, 'completiontokens' => $this->completiontokens, + 'model' => $this->model, ]; } } diff --git a/ai/classes/manager.php b/ai/classes/manager.php index acf2fcabf98..411bde7e8f3 100644 --- a/ai/classes/manager.php +++ b/ai/classes/manager.php @@ -174,6 +174,7 @@ class manager { 'errormessage' => $response->get_errormessage(), 'timecreated' => $action->get_configuration('timecreated'), 'timecompleted' => $response->get_timecreated(), + 'model' => $response->get_model_used(), ]; try { diff --git a/ai/provider/azureai/classes/process_generate_text.php b/ai/provider/azureai/classes/process_generate_text.php index 4776753f955..981343e7bfc 100644 --- a/ai/provider/azureai/classes/process_generate_text.php +++ b/ai/provider/azureai/classes/process_generate_text.php @@ -106,6 +106,7 @@ class process_generate_text extends abstract_processor { 'finishreason' => $bodyobj->choices[0]->finish_reason, 'prompttokens' => $bodyobj->usage->prompt_tokens, 'completiontokens' => $bodyobj->usage->completion_tokens, + 'model' => $bodyobj->model, ]; } } diff --git a/ai/provider/openai/classes/process_generate_image.php b/ai/provider/openai/classes/process_generate_image.php index d43da6048f7..6eacd1f938f 100644 --- a/ai/provider/openai/classes/process_generate_image.php +++ b/ai/provider/openai/classes/process_generate_image.php @@ -115,6 +115,7 @@ class process_generate_image extends abstract_processor { 'success' => true, 'sourceurl' => $bodyobj->data[0]->url, 'revisedprompt' => $bodyobj->data[0]->revised_prompt, + 'model' => $this->get_model(), // There is no model in the response, use config. ]; } diff --git a/ai/provider/openai/classes/process_generate_text.php b/ai/provider/openai/classes/process_generate_text.php index eed7be703e2..9f66da29b84 100644 --- a/ai/provider/openai/classes/process_generate_text.php +++ b/ai/provider/openai/classes/process_generate_text.php @@ -96,6 +96,7 @@ class process_generate_text extends abstract_processor { 'finishreason' => $bodyobj->choices[0]->finish_reason, 'prompttokens' => $bodyobj->usage->prompt_tokens, 'completiontokens' => $bodyobj->usage->completion_tokens, + 'model' => $bodyobj->model ?? $this->get_model(), // Fallback to config model. ]; } } From f26c38bd208eb399da7068145f5243b1edbe3c74 Mon Sep 17 00:00:00 2001 From: David Woloszyn Date: Wed, 25 Sep 2024 16:42:44 +1000 Subject: [PATCH 3/5] MDL-83154 AI: Updated tests to include model response --- ai/provider/azureai/tests/process_generate_text_test.php | 5 ++++- ai/provider/azureai/tests/process_summarise_text_test.php | 5 ++++- ai/provider/openai/tests/process_generate_image_test.php | 4 ++++ ai/provider/openai/tests/process_generate_text_test.php | 5 ++++- ai/provider/openai/tests/process_summarise_text_test.php | 5 ++++- ai/tests/aiactions/generate_image_test.php | 1 + ai/tests/aiactions/generate_text_test.php | 1 + ai/tests/aiactions/summarise_text_test.php | 1 + ai/tests/manager_test.php | 2 ++ 9 files changed, 25 insertions(+), 4 deletions(-) diff --git a/ai/provider/azureai/tests/process_generate_text_test.php b/ai/provider/azureai/tests/process_generate_text_test.php index 0189facbe3e..bc0f03b772e 100644 --- a/ai/provider/azureai/tests/process_generate_text_test.php +++ b/ai/provider/azureai/tests/process_generate_text_test.php @@ -152,7 +152,7 @@ final class process_generate_text_test extends \advanced_testcase { $this->assertEquals('stop', $result['finishreason']); $this->assertEquals('12', $result['prompttokens']); $this->assertEquals('14', $result['completiontokens']); - + $this->assertEquals('gpt-4o-2024-05-13', $result['model']); } /** @@ -180,6 +180,7 @@ final class process_generate_text_test extends \advanced_testcase { $this->assertEquals('stop', $result['finishreason']); $this->assertEquals('12', $result['prompttokens']); $this->assertEquals('14', $result['completiontokens']); + $this->assertEquals('gpt-4o-2024-05-13', $result['model']); } /** @@ -199,6 +200,7 @@ final class process_generate_text_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => '11', 'completiontokens' => '14', + 'model' => 'gpt-4o', ]; $result = $method->invoke($processor, $response); @@ -208,6 +210,7 @@ final class process_generate_text_test extends \advanced_testcase { $this->assertEquals('generate_text', $result->get_actionname()); $this->assertEquals($response['success'], $result->get_success()); $this->assertEquals($response['generatedcontent'], $result->get_response_data()['generatedcontent']); + $this->assertEquals($response['model'], $result->get_response_data()['model']); } /** diff --git a/ai/provider/azureai/tests/process_summarise_text_test.php b/ai/provider/azureai/tests/process_summarise_text_test.php index 6cda50fe30f..86a2eb0b106 100644 --- a/ai/provider/azureai/tests/process_summarise_text_test.php +++ b/ai/provider/azureai/tests/process_summarise_text_test.php @@ -144,7 +144,7 @@ final class process_summarise_text_test extends \advanced_testcase { $this->assertEquals('stop', $result['finishreason']); $this->assertEquals('12', $result['prompttokens']); $this->assertEquals('14', $result['completiontokens']); - + $this->assertEquals('gpt-4o-2024-05-13', $result['model']); } /** @@ -172,6 +172,7 @@ final class process_summarise_text_test extends \advanced_testcase { $this->assertEquals('stop', $result['finishreason']); $this->assertEquals('12', $result['prompttokens']); $this->assertEquals('14', $result['completiontokens']); + $this->assertEquals('gpt-4o-2024-05-13', $result['model']); } /** @@ -191,6 +192,7 @@ final class process_summarise_text_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => '11', 'completiontokens' => '568', + 'model' => 'gpt-4o', ]; $result = $method->invoke($processor, $response); @@ -200,6 +202,7 @@ final class process_summarise_text_test extends \advanced_testcase { $this->assertEquals('summarise_text', $result->get_actionname()); $this->assertEquals($response['success'], $result->get_success()); $this->assertEquals($response['generatedcontent'], $result->get_response_data()['generatedcontent']); + $this->assertEquals($response['model'], $result->get_response_data()['model']); } /** diff --git a/ai/provider/openai/tests/process_generate_image_test.php b/ai/provider/openai/tests/process_generate_image_test.php index 358152e9fe3..2cb0e5affe6 100644 --- a/ai/provider/openai/tests/process_generate_image_test.php +++ b/ai/provider/openai/tests/process_generate_image_test.php @@ -165,6 +165,7 @@ final class process_generate_image_test extends \advanced_testcase { $this->stringContains('An image that represents the concept of a \'test\'.', $result['revisedprompt']); $this->stringContains('oaidalleapiprodscus.blob.core.windows.net', $result['sourceurl']); + $this->assertEquals('dall-e-3', $result['model']); } /** @@ -210,6 +211,7 @@ final class process_generate_image_test extends \advanced_testcase { $this->stringContains('An image that represents the concept of a \'test\'.', $result['revisedprompt']); $this->stringContains('oaidalleapiprodscus.blob.core.windows.net', $result['sourceurl']); + $this->assertEquals('dall-e-3', $result['model']); } /** @@ -225,6 +227,7 @@ final class process_generate_image_test extends \advanced_testcase { 'success' => true, 'revisedprompt' => 'An image that represents the concept of a \'test\'.', 'imageurl' => 'oaidalleapiprodscus.blob.core.windows.net', + 'model' => 'dall-e-3', ]; $result = $method->invoke($processor, $response); @@ -234,6 +237,7 @@ final class process_generate_image_test extends \advanced_testcase { $this->assertEquals('generate_image', $result->get_actionname()); $this->assertEquals($response['success'], $result->get_success()); $this->assertEquals($response['revisedprompt'], $result->get_response_data()['revisedprompt']); + $this->assertEquals($response['model'], $result->get_response_data()['model']); } /** diff --git a/ai/provider/openai/tests/process_generate_text_test.php b/ai/provider/openai/tests/process_generate_text_test.php index 910683fd77c..e4fa1775e88 100644 --- a/ai/provider/openai/tests/process_generate_text_test.php +++ b/ai/provider/openai/tests/process_generate_text_test.php @@ -150,7 +150,7 @@ final class process_generate_text_test extends \advanced_testcase { $this->assertEquals('stop', $result['finishreason']); $this->assertEquals('11', $result['prompttokens']); $this->assertEquals('568', $result['completiontokens']); - + $this->assertEquals('gpt-4o-2024-05-13', $result['model']); } /** @@ -178,6 +178,7 @@ final class process_generate_text_test extends \advanced_testcase { $this->assertEquals('stop', $result['finishreason']); $this->assertEquals('11', $result['prompttokens']); $this->assertEquals('568', $result['completiontokens']); + $this->assertEquals('gpt-4o-2024-05-13', $result['model']); } /** @@ -197,6 +198,7 @@ final class process_generate_text_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => '11', 'completiontokens' => '568', + 'model' => 'gpt-4o', ]; $result = $method->invoke($processor, $response); @@ -206,6 +208,7 @@ final class process_generate_text_test extends \advanced_testcase { $this->assertEquals('generate_text', $result->get_actionname()); $this->assertEquals($response['success'], $result->get_success()); $this->assertEquals($response['generatedcontent'], $result->get_response_data()['generatedcontent']); + $this->assertEquals($response['model'], $result->get_response_data()['model']); } /** diff --git a/ai/provider/openai/tests/process_summarise_text_test.php b/ai/provider/openai/tests/process_summarise_text_test.php index 8c0e45e8af0..dc1fbbe7430 100644 --- a/ai/provider/openai/tests/process_summarise_text_test.php +++ b/ai/provider/openai/tests/process_summarise_text_test.php @@ -144,7 +144,7 @@ final class process_summarise_text_test extends \advanced_testcase { $this->assertEquals('stop', $result['finishreason']); $this->assertEquals('11', $result['prompttokens']); $this->assertEquals('568', $result['completiontokens']); - + $this->assertEquals('gpt-4o-2024-05-13', $result['model']); } /** @@ -172,6 +172,7 @@ final class process_summarise_text_test extends \advanced_testcase { $this->assertEquals('stop', $result['finishreason']); $this->assertEquals('11', $result['prompttokens']); $this->assertEquals('568', $result['completiontokens']); + $this->assertEquals('gpt-4o-2024-05-13', $result['model']); } /** @@ -191,6 +192,7 @@ final class process_summarise_text_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => '11', 'completiontokens' => '568', + 'model' => 'gpt-4o', ]; $result = $method->invoke($processor, $response); @@ -200,6 +202,7 @@ final class process_summarise_text_test extends \advanced_testcase { $this->assertEquals('summarise_text', $result->get_actionname()); $this->assertEquals($response['success'], $result->get_success()); $this->assertEquals($response['generatedcontent'], $result->get_response_data()['generatedcontent']); + $this->assertEquals($response['model'], $result->get_response_data()['model']); } /** diff --git a/ai/tests/aiactions/generate_image_test.php b/ai/tests/aiactions/generate_image_test.php index b76bb40c470..559b9d0d91d 100644 --- a/ai/tests/aiactions/generate_image_test.php +++ b/ai/tests/aiactions/generate_image_test.php @@ -85,6 +85,7 @@ final class generate_image_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'sourceurl' => 'https://example.com/image.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, diff --git a/ai/tests/aiactions/generate_text_test.php b/ai/tests/aiactions/generate_text_test.php index f4ad5db2516..55b128940b4 100644 --- a/ai/tests/aiactions/generate_text_test.php +++ b/ai/tests/aiactions/generate_text_test.php @@ -70,6 +70,7 @@ final class generate_text_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, diff --git a/ai/tests/aiactions/summarise_text_test.php b/ai/tests/aiactions/summarise_text_test.php index 4981b9adc75..339b08fc187 100644 --- a/ai/tests/aiactions/summarise_text_test.php +++ b/ai/tests/aiactions/summarise_text_test.php @@ -70,6 +70,7 @@ final class summarise_text_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_summarise_text( success: true, diff --git a/ai/tests/manager_test.php b/ai/tests/manager_test.php index 330834c34e1..8771d1b26bc 100644 --- a/ai/tests/manager_test.php +++ b/ai/tests/manager_test.php @@ -304,6 +304,7 @@ final class manager_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'imageurl' => 'https://example.com/image.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, @@ -327,6 +328,7 @@ final class manager_test extends \advanced_testcase { $this->assertEquals($actionresponse->get_errormessage(), $record->errormessage); $this->assertEquals($action->get_configuration('timecreated'), $record->timecreated); $this->assertEquals($actionresponse->get_timecreated(), $record->timecompleted); + $this->assertEquals($actionresponse->get_model_used(), $record->model); } /** From dd05af925992442a5256c0855031e59d98d1d268 Mon Sep 17 00:00:00 2001 From: David Woloszyn Date: Wed, 25 Sep 2024 18:18:31 +1000 Subject: [PATCH 4/5] MDL-83154 AI: Update privacy provider to include model --- ai/classes/privacy/provider.php | 10 +++++--- ai/tests/provider/provider_test.php | 39 +++++++++++++++++++++++++++++ lang/en/ai.php | 1 + 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/ai/classes/privacy/provider.php b/ai/classes/privacy/provider.php index 471479f1ea3..dc60ee17f4e 100644 --- a/ai/classes/privacy/provider.php +++ b/ai/classes/privacy/provider.php @@ -56,6 +56,7 @@ class provider implements 'provider' => 'privacy:metadata:ai_action_register:provider', 'timecreated' => 'privacy:metadata:ai_action_register:timecreated', 'timecompleted' => 'privacy:metadata:ai_action_register:timecompleted', + 'model' => 'privacy:metadata:ai_action_register:model', ], 'privacy:metadata:ai_action_register'); $collection->add_database_table('ai_action_generate_image', [ 'prompt' => 'privacy:metadata:ai_action_generate_image:prompt', @@ -178,7 +179,7 @@ class provider implements // AI action generate text. $sql = "SELECT aar.actionname, aar.success, aar.provider, aar.timecreated, aar.timecompleted, aar.contextid, aagt.prompt, aagt.responseid, aagt.fingerprint, aagt.generatedcontent, - aagt.prompttokens, aagt.completiontoken + aagt.prompttokens, aagt.completiontoken, aar.model FROM {ai_action_register} aar JOIN {ai_action_generate_text} aagt ON aar.actionid = aagt.id @@ -207,6 +208,7 @@ class provider implements 'generatedcontent' => $textgeneratedetail->generatedcontent, 'prompttokens' => $textgeneratedetail->prompttokens, 'completiontoken' => $textgeneratedetail->completiontoken, + 'model' => $textgeneratedetail->model, 'success' => transform::yesno($textgeneratedetail->success), 'provider' => $textgeneratedetail->provider, 'timecreated' => transform::datetime($textgeneratedetail->timecreated), @@ -221,7 +223,7 @@ class provider implements // AI action generate image. $sql = "SELECT aar.actionname, aar.success, aar.provider, aar.timecreated, aar.timecompleted, aar.contextid, aagi.prompt, aagi.numberimages, aagi.quality, aagi.aspectratio, aagi.style, aagi.sourceurl, - aagi.revisedprompt + aagi.revisedprompt, aar.model FROM {ai_action_register} aar JOIN {ai_action_generate_image} aagi ON aar.actionid = aagi.id @@ -251,6 +253,7 @@ class provider implements 'style' => $imagegeneratedetail->style, 'sourceurl' => $imagegeneratedetail->sourceurl, 'revisedprompt' => $imagegeneratedetail->revisedprompt, + 'model' => $imagegeneratedetail->model, 'success' => transform::yesno($imagegeneratedetail->success), 'provider' => $imagegeneratedetail->provider, 'timecreated' => transform::datetime($imagegeneratedetail->timecreated), @@ -265,7 +268,7 @@ class provider implements // AI action summarise text. $sql = "SELECT aar.actionname, aar.success, aar.provider, aar.timecreated, aar.timecompleted, aar.contextid, aast.prompt, aast.responseid, aast.fingerprint, aast.generatedcontent, - aast.prompttokens, aast.completiontoken + aast.prompttokens, aast.completiontoken, aar.model FROM {ai_action_register} aar JOIN {ai_action_summarise_text} aast ON aar.actionid = aast.id @@ -294,6 +297,7 @@ class provider implements 'generatedcontent' => $textsummarisedetail->generatedcontent, 'prompttokens' => $textsummarisedetail->prompttokens, 'completiontoken' => $textsummarisedetail->completiontoken, + 'model' => $textsummarisedetail->model, 'success' => transform::yesno($textsummarisedetail->success), 'provider' => $textsummarisedetail->provider, 'timecreated' => transform::datetime($textsummarisedetail->timecreated), diff --git a/ai/tests/provider/provider_test.php b/ai/tests/provider/provider_test.php index a7addb09e30..d6d03927085 100644 --- a/ai/tests/provider/provider_test.php +++ b/ai/tests/provider/provider_test.php @@ -86,6 +86,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, @@ -118,6 +119,7 @@ final class provider_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'sourceurl' => 'https://example.com/image.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, @@ -157,6 +159,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, @@ -230,6 +233,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, @@ -256,6 +260,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 10, 'completiontokens' => 15, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, @@ -288,6 +293,7 @@ final class provider_test extends \advanced_testcase { $this->assertEquals('9', $data->prompttokens); $this->assertEquals('12', $data->completiontoken); $this->assertEquals(get_string('yes'), $data->success); + $this->assertEquals('gpt-4o', $data->model); } if ($context->instanceid == $course2context->instanceid) { @@ -305,6 +311,7 @@ final class provider_test extends \advanced_testcase { $this->assertEquals('10', $data->prompttokens); $this->assertEquals('15', $data->completiontoken); $this->assertEquals(get_string('yes'), $data->success); + $this->assertEquals('gpt-4o', $data->model); } } } @@ -334,6 +341,7 @@ final class provider_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'sourceurl' => 'https://example.com/image1.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, @@ -360,6 +368,7 @@ final class provider_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'sourceurl' => 'https://example.com/image2.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, @@ -394,6 +403,7 @@ final class provider_test extends \advanced_testcase { $this->assertEquals('vivid', $data->style); $this->assertEquals('https://example.com/image1.png', $data->sourceurl); $this->assertEquals(get_string('yes'), $data->success); + $this->assertEquals('dall-e-3', $data->model); } if ($context->instanceid == $course2context->instanceid) { @@ -413,6 +423,7 @@ final class provider_test extends \advanced_testcase { $this->assertEquals('vivid', $data->style); $this->assertEquals('https://example.com/image2.png', $data->sourceurl); $this->assertEquals(get_string('yes'), $data->success); + $this->assertEquals('dall-e-3', $data->model); } } } @@ -442,6 +453,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_summarise_text( success: true, @@ -468,6 +480,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 10, 'completiontokens' => 15, + 'model' => 'gpt-4o', ]; $actionresponse = new response_summarise_text( success: true, @@ -500,6 +513,7 @@ final class provider_test extends \advanced_testcase { $this->assertEquals('9', $data->prompttokens); $this->assertEquals('12', $data->completiontoken); $this->assertEquals(get_string('yes'), $data->success); + $this->assertEquals('gpt-4o', $data->model); } if ($context->instanceid == $course2context->instanceid) { @@ -517,6 +531,7 @@ final class provider_test extends \advanced_testcase { $this->assertEquals('10', $data->prompttokens); $this->assertEquals('15', $data->completiontoken); $this->assertEquals(get_string('yes'), $data->success); + $this->assertEquals('gpt-4o', $data->model); } } } @@ -575,6 +590,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, @@ -600,6 +616,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 10, 'completiontokens' => 15, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, @@ -651,6 +668,7 @@ final class provider_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'sourceurl' => 'https://example.com/image1.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, @@ -676,6 +694,7 @@ final class provider_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'sourceurl' => 'https://example.com/image2.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, @@ -725,6 +744,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_summarise_text( success: true, @@ -750,6 +770,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 10, 'completiontokens' => 15, + 'model' => 'gpt-4o', ]; $actionresponse = new response_summarise_text( success: true, @@ -800,6 +821,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, @@ -825,6 +847,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 10, 'completiontokens' => 15, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, @@ -877,6 +900,7 @@ final class provider_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'sourceurl' => 'https://example.com/image1.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, @@ -902,6 +926,7 @@ final class provider_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'sourceurl' => 'https://example.com/image2.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, @@ -952,6 +977,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_summarise_text( success: true, @@ -977,6 +1003,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 10, 'completiontokens' => 15, + 'model' => 'gpt-4o', ]; $actionresponse = new response_summarise_text( success: true, @@ -1059,6 +1086,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, @@ -1084,6 +1112,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 10, 'completiontokens' => 15, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, @@ -1132,6 +1161,7 @@ final class provider_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'sourceurl' => 'https://example.com/image1.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, @@ -1157,6 +1187,7 @@ final class provider_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'sourceurl' => 'https://example.com/image2.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, @@ -1206,6 +1237,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_summarise_text( success: true, @@ -1231,6 +1263,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 10, 'completiontokens' => 15, + 'model' => 'gpt-4o', ]; $actionresponse = new response_summarise_text( success: true, @@ -1311,6 +1344,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, @@ -1336,6 +1370,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 10, 'completiontokens' => 15, + 'model' => 'gpt-4o', ]; $actionresponse = new response_generate_text( success: true, @@ -1391,6 +1426,7 @@ final class provider_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'sourceurl' => 'https://example.com/image1.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, @@ -1416,6 +1452,7 @@ final class provider_test extends \advanced_testcase { $body = [ 'revisedprompt' => 'This is a revised prompt', 'sourceurl' => 'https://example.com/image2.png', + 'model' => 'dall-e-3', ]; $actionresponse = new response_generate_image( success: true, @@ -1468,6 +1505,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 9, 'completiontokens' => 12, + 'model' => 'gpt-4o', ]; $actionresponse = new response_summarise_text( success: true, @@ -1493,6 +1531,7 @@ final class provider_test extends \advanced_testcase { 'finishreason' => 'stop', 'prompttokens' => 10, 'completiontokens' => 15, + 'model' => 'gpt-4o', ]; $actionresponse = new response_summarise_text( success: true, diff --git a/lang/en/ai.php b/lang/en/ai.php index ca4e186276d..89578422b40 100644 --- a/lang/en/ai.php +++ b/lang/en/ai.php @@ -85,6 +85,7 @@ $string['privacy:metadata:ai_action_generate_text:responseid'] = 'The ID of the $string['privacy:metadata:ai_action_register'] = 'A table storing the action requests made by users.'; $string['privacy:metadata:ai_action_register:actionid'] = 'The ID of the action request.'; $string['privacy:metadata:ai_action_register:actionname'] = 'The action name of the request.'; +$string['privacy:metadata:ai_action_register:model'] = 'The model used to generate the response.'; $string['privacy:metadata:ai_action_register:provider'] = 'The name of the provider that handled the request.'; $string['privacy:metadata:ai_action_register:success'] = 'The state of the action request.'; $string['privacy:metadata:ai_action_register:timecompleted'] = 'The completed time of the request.'; From e4b39e764d0dcafdf7271c63802ee7dc4ab9942a Mon Sep 17 00:00:00 2001 From: David Woloszyn Date: Thu, 26 Sep 2024 15:13:05 +1000 Subject: [PATCH 5/5] MDL-83154 hub: Add model to AI usage data --- lib/classes/hub/registration.php | 16 +++++++++++++++- lib/tests/hub/registration_test.php | 14 ++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/classes/hub/registration.php b/lib/classes/hub/registration.php index 88dd42eb20b..42f891b0288 100644 --- a/lib/classes/hub/registration.php +++ b/lib/classes/hub/registration.php @@ -756,6 +756,7 @@ class registration { 'fail_count' => 0, 'times' => [], 'errors' => [], + 'modelstemp' => [], ]; } @@ -769,9 +770,13 @@ class registration { // Collect errors for determing the predominant one. $data[$provider][$actionname]['errors'][] = $action->errorcode; } + + // Collect models used and identify unknown ones. + $model = $action->model ?? 'unknown'; + $data[$provider][$actionname]['modelstemp'][] = $model; } - // Parse the errors and everage the times, then add them to the data. + // Parse the errors, average the times, count the models and then add them to the data. foreach ($data as $p => $provider) { foreach ($provider as $a => $actionname) { if (isset($data[$p][$a]['errors'])) { @@ -796,6 +801,15 @@ class registration { } } unset($data[$p][$a]['times']); + + if (isset($data[$p][$a]['modelstemp'])) { + // Create an array with the models counted. + $countedmodels = array_count_values($data[$p][$a]['modelstemp']); + foreach ($countedmodels as $model => $count) { + $data[$p][$a]['models'][$model]['count'] = $count; + } + } + unset($data[$p][$a]['modelstemp']); } } diff --git a/lib/tests/hub/registration_test.php b/lib/tests/hub/registration_test.php index e0dbb519532..69c5f440c9b 100644 --- a/lib/tests/hub/registration_test.php +++ b/lib/tests/hub/registration_test.php @@ -94,6 +94,9 @@ class registration_test extends \advanced_testcase { $this->resetAfterTest(); $clock = $this->mock_clock_with_frozen(); + $gpt4omodel = 'gpt-4o'; + $dalle3model = 'dall-e-3'; + // Record some generated text. $record = new \stdClass(); $record->provider = 'openai'; @@ -104,15 +107,17 @@ class registration_test extends \advanced_testcase { $record->success = true; $record->timecreated = $clock->time() - 5; $record->timecompleted = $clock->time(); + $record->model = $gpt4omodel; $DB->insert_record('ai_action_register', $record); // Record a generated image. $record->actionname = 'generate_image'; - $record->actionid = 2; + $record->actionid = 111; $record->timecreated = $clock->time() - 20; + $record->model = $dalle3model; $DB->insert_record('ai_action_register', $record); // Record another image. - $record->actionid = 3; + $record->actionid = 222; $record->timecreated = $clock->time() - 10; $DB->insert_record('ai_action_register', $record); @@ -121,6 +126,7 @@ class registration_test extends \advanced_testcase { $record->actionid = 4; $record->success = false; $record->errorcode = 403; + $record->model = null; $DB->insert_record('ai_action_register', $record); $record->actionid = 5; $record->errorcode = 403; @@ -143,5 +149,9 @@ class registration_test extends \advanced_testcase { // Check time range is set correctly. $this->assertEquals($clock->time() - WEEKSECS, $aisuage->time_range->timefrom); $this->assertEquals($clock->time(), $aisuage->time_range->timeto); + // Check model counts. + $this->assertEquals(1, $aisuage->openai->generate_text->models->{$gpt4omodel}->count); + $this->assertEquals(2, $aisuage->openai->generate_image->models->{$dalle3model}->count); + $this->assertEquals(3, $aisuage->openai->generate_image->models->unknown->count); } }