diff --git a/backup/moodle2/backup_qtype_aitext_plugin.class.php b/backup/moodle2/backup_qtype_aitext_plugin.class.php index 3d26ad8..da0c26d 100755 --- a/backup/moodle2/backup_qtype_aitext_plugin.class.php +++ b/backup/moodle2/backup_qtype_aitext_plugin.class.php @@ -46,10 +46,10 @@ protected function define_question_plugin_structure() { $plugin->add_child($pluginwrapper); // Now create the qtype own structures. - $aitext = new backup_nested_element('aitext', ['id'], array( + $aitext = new backup_nested_element('aitext', ['id'], [ 'aiprompt', 'responseformat', 'responsefieldlines', 'minwordlimit', 'maxwordlimit', 'graderinfo', 'graderinfoformat', 'responsetemplate', - 'responsetemplateformat', 'maxbytes')); + 'responsetemplateformat', 'maxbytes']); // Now the own qtype tree. $pluginwrapper->add_child($aitext); diff --git a/lang/en/qtype_aitext.php b/lang/en/qtype_aitext.php index 2e0190c..358b743 100755 --- a/lang/en/qtype_aitext.php +++ b/lang/en/qtype_aitext.php @@ -58,6 +58,10 @@ $string['minwordlimit_help'] = 'If the response requires that students enter text, this is the minimum number of words that each student will be allowed to submit.'; $string['minwordlimitboundary'] = 'This question requires a response of at least {$a->limit} words and you are attempting to submit {$a->count} words. Please expand your response and try again.'; $string['nlines'] = '{$a} lines'; +$string['prompt'] = 'Prompt'; +$string['prompt_setting'] = 'Wrapper text for the prompt set to the AI System'; +$string['jsonprompt'] = 'JSon prompt'; +$string['jsonprompt_setting'] = 'Instructions sent to convert the returned value into json'; $string['pluginname'] = 'AI Text'; $string['pluginname_help'] = 'In response to a question, the respondent enters text. A response template may be provided. Responses are given a preliminary grade by an AI system (e.g. ChatGPT) then can be graded manually.'; $string['pluginname_link'] = 'question/type/AI Text'; diff --git a/question.php b/question.php index bcb806d..811659a 100755 --- a/question.php +++ b/question.php @@ -136,17 +136,20 @@ public function apply_attempt_state(question_attempt_step $step) { public function grade_response(array $response) : array { $ai = new ai\ai(); if (is_array($response)) { - $prompt = 'in [[' . strip_tags($response['answer']) . ']]'; - $prompt .= ' analyse the part between [[ and ]] as follows: '; - $prompt .= $this->aiprompt; - - if ($this->markscheme > '') { + $responsetext = strip_tags($response['answer']); + $responsetext = '[['.$responsetext.']]'; + $prompt = get_config('qtype_aitext', 'prompt'); + $prompt = preg_replace("/\[responsetext\]/", $responsetext, $prompt); + $prompt .= ' '.trim($this->aiprompt); if ($this->markscheme > '') { $prompt .= ' '.$this->markscheme; } else { + // Todo should this be a plugin setting value?. $prompt .= ' Set marks to null in the json object.'.PHP_EOL; } - $prompt .= ' '.$this->get_json_prompt(); + + $prompt .= ' '.trim(get_config('qtype_aitext', 'jsonprompt')); $prompt .= ' respond in the language '.current_language(); + $llmresponse = $ai->prompt_completion($prompt); $feedback = $llmresponse['response']['choices'][0]['message']['content']; } @@ -158,7 +161,7 @@ public function grade_response(array $response) : array { $grade = [0 => 0, question_state::$needsgrading]; } else { $fraction = $contentobject->marks / $this->defaultmark; - $grade = array($fraction, question_state::graded_state_for_fraction($fraction)); + $grade = [$fraction, question_state::graded_state_for_fraction($fraction)]; } // The -aicontent data is used in question preview. Only needs to happen in preview. $this->insert_attempt_step_data('-aiprompt', $prompt); @@ -186,22 +189,12 @@ public function process_feedback(string $feedback) { } else { $contentobject = (object) [ "feedback" => $feedback, - "marks" => null + "marks" => null, ]; } return $contentobject; } - /** - * Get the LLM string that tells it to return the result as json - * - * @return string - */ - protected function get_json_prompt() :string { - return 'Return only a JSON object which enumerates a set of 2 elements. - The elements should have properties of "feedback" and "marks". - The resulting JSON object should be in this format: {"feedback":"string","marks":"number"} - where marks is a single value summing all marks.\n\n'; - } + /** * Translate into the current language and * store in a cache diff --git a/settings.php b/settings.php index 919628a..e5642c8 100644 --- a/settings.php +++ b/settings.php @@ -24,9 +24,30 @@ defined('MOODLE_INTERNAL') || die; if ($ADMIN->fulltree) { - $settings->add(new admin_setting_configtext('qtype_aitext/disclaimer', - new lang_string('disclaimer', 'qtype_aitext'), - new lang_string('disclaimer_setting', 'qtype_aitext'), - "(Response provided by ChatGPT)")); + $settings->add(new admin_setting_configtext( + 'qtype_aitext/disclaimer', + new lang_string('disclaimer', 'qtype_aitext'), + new lang_string('disclaimer_setting', 'qtype_aitext'), + '(Response provided by ChatGPT)' + )); + $settings->add(new admin_setting_configtextarea( + 'qtype_aitext/prompt', + new lang_string('prompt', 'qtype_aitext'), + new lang_string('prompt_setting', 'qtype_aitext'), + 'in [responsetext] analyse the part between [[ and ]] as follows.', + PARAM_RAW, + 20, + 3 + )); + $settings->add(new admin_setting_configtextarea( + 'qtype_aitext/jsonprompt', + new lang_string('jsonprompt', 'qtype_aitext'), + new lang_string('jsonprompt_setting', 'qtype_aitext'), + 'Return only a JSON object which enumerates a set of 2 elements.The JSON object should be in + this format: {feedback":"string","marks":"number"} where marks is a single value summing all marks.', + PARAM_RAW, + 20, + 6 + )); } diff --git a/version.php b/version.php index 445bf13..50623c5 100755 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'qtype_aitext'; -$plugin->version = 2024021800; +$plugin->version = 2024042201; $plugin->requires = 2020110900; $plugin->maturity = MATURITY_BETA; $plugin->dependencies = [