Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lang/de/qtype_aitext.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@
$string['err_maxwordlimitnegative'] = 'Maximales Wortlimit kann keine negative Zahl sein';
$string['err_minwordlimit'] = 'Minimum word limit is enabled but is not set';
$string['err_minwordlimitnegative'] = 'Minimales Wortlimit kann keine negative Zahl sein';
$string['err_nofeedback'] = 'Von der KI wurde kein Feedback zurückgegeben. Die Antwort konnte nicht automatisch bewertet werden.';
$string['err_parammissing'] = 'Ungültige Parameter. Stellen Sie sicher, dass Sie eine Beispiel-Antwort und einen Prompt eingegeben haben.';
$string['err_retrievingfeedback'] = 'Fehler beim Abrufen des Feedbacks vom KI-Tool: {$a}';
$string['err_nofeedback'] = 'Von der KI wurde kein Feedback zurückgegeben. Die Antwort konnte nicht automatisch bewertet werden.';
$string['err_retrievingtranslation'] = 'Fehler beim Abrufen der Übersetzung: {$a}';
$string['expertmodeconfirm'] = 'Dies ersetzt den aktuellen Prompt durch die Expertenmodus-Vorlage.<br><br><strong>Was ist der Expertenmodus?</strong><br>Im Expertenmodus haben Sie die volle Kontrolle über den gesamten KI-Prompt. Die globale Vorlage wird ignoriert und Ihr Prompt wird direkt an die KI gesendet.<br><br><strong>Verfügbare Platzhalter:</strong><ul><li><code>{{response}}</code> - Die Schülerantwort (erforderlich zur Aktivierung des Expertenmodus)</li><li><code>{{questiontext}}</code> - Der Fragetext</li><li><code>{{markscheme}}</code> - Das Bewertungsschema</li><li><code>{{language}}</code> - Die Sprache für die Antwort</li><li><code>{{role}}</code> - Die Rollenbeschreibung aus der globalen Vorlage (aktuell: <em>"{$a}"</em>)</li></ul><strong>Hinweis:</strong> Der Platzhalter <code>{{role}}</code> fügt den in der globalen Vorlage definierten Rollen-Prompt ein. Sie können entweder diesen Platzhalter verwenden oder Ihre eigene Rollenbeschreibung direkt in Ihren Prompt schreiben.<br><br>Fortfahren?';
$string['formateditor'] = 'HTML-Editor';
Expand Down
3 changes: 3 additions & 0 deletions question.php
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,9 @@ public function process_feedback(string $feedback) {
$contentobject->marks = null;
}
$disclaimer = get_config('qtype_aitext', 'disclaimer');
// The format_text will interprete a backslash as escaping character. To preserve one we need to double them first.
// This is especially important so that the mathjax filter still has a chance to have its delimiters \( ... \).
$contentobject->feedback = str_replace('\\', '\\\\', $contentobject->feedback);
$contentobject->feedback = format_text($contentobject->feedback, FORMAT_MARKDOWN, ['para' => false]);
$contentobject->feedback .= ' ' . $this->llm_translate($disclaimer);

Expand Down
52 changes: 43 additions & 9 deletions tests/question_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@
namespace qtype_aitext;

use coding_exception;
use core_reportbuilder\external\filters\set;
use Exception;
use PHPUnit\Framework\ExpectationFailedException;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Container\ContainerExceptionInterface;
use question_attempt_step;
use SebastianBergmann\RecursionContext\InvalidArgumentException;

Expand All @@ -33,8 +30,6 @@
require_once($CFG->dirroot . '/question/type/aitext/questiontype.php');

use qtype_aitext_test_helper;
use qtype_aitext;
use Random\RandomException;

/**
* Unit tests for the aitext question definition class.
Expand Down Expand Up @@ -272,12 +267,14 @@ public function test_build_full_ai_prompt_empty_markscheme(): void {
* @param string $json The JSON string generated by the LLM.
* @param bool $exceptionexpected If there is an exception expected during parsing.
* @param string $expectedfeedback The expected feedback extracted from the JSON.
* @param bool $expectedmathjaxapplied if it is expected that mathjax filter is being applied
* @param float|null $expectedmarks The expected marks extracted from the JSON.
*/
public function test_process_feedback(
string $json,
bool $exceptionexpected,
string $expectedfeedback,
bool $expectedmathjaxapplied,
?float $expectedmarks
): void {
$this->resetAfterTest();
Expand Down Expand Up @@ -306,10 +303,14 @@ public function test_process_feedback(
);
} else {
// Normal case: feedback is formatted and disclaimer is appended.
$expectedfeedback = format_text($expectedfeedback, FORMAT_MARKDOWN);
$this->assertEquals(
format_text($expectedfeedback, FORMAT_MARKDOWN) . ' (example disclaimer)',
$expectedfeedback . ' (example disclaimer)',
$processedfeedback->feedback
);
if ($expectedmathjaxapplied) {
$this->assertStringContainsString('<span class="filter_mathjaxloader_equation">', $processedfeedback->feedback);
}
}
$this->assertEquals($expectedmarks, $processedfeedback->marks);
}
Expand All @@ -329,31 +330,36 @@ public static function process_feedback_provider(): array {
'json' => '{"feedback": "Good job", "marks": 0}',
'exceptionexpected' => false,
'expectedfeedback' => 'Good job',
'expectedmathjaxapplied' => false,
'expectedmarks' => 0,
],
'broken_json' => [
'json' => '{"feedback": "Good job", "marks": 0',
'exceptionexpected' => false,
'expectedfeedback' => '{"feedback": "Good job", "marks": 0',
'expectedmathjaxapplied' => false,
'expectedmarks' => 0,
],
'valid_json_markdown_formatted' => [
// @codingStandardsIgnoreLine moodle.Strings.ForbiddenStrings.Found
'json' => '```json{"feedback": "Good job", "marks": 1}```',
'exceptionexpected' => false,
'expectedfeedback' => 'Good job',
'expectedmathjaxapplied' => false,
'expectedmarks' => 1,
],
'valid_json_with_text_around' => [
'json' => 'Here is the feedback: {"feedback": "Well done", "marks": 0.5} Thank you!',
'exceptionexpected' => false,
'expectedfeedback' => 'Well done',
'expectedmathjaxapplied' => false,
'expectedmarks' => 0.5,
],
'valid_json_with_wrapped_html_tags' => [
'json' => '<p>{"feedback": "Well done", "marks": 0.5} Thank you!</p>',
'exceptionexpected' => false,
'expectedfeedback' => 'Well done',
'expectedmathjaxapplied' => false,
'expectedmarks' => 0.5,
],
'valid_json_with_code' => [
Expand All @@ -362,23 +368,30 @@ public static function process_feedback_provider(): array {
'exceptionexpected' => false,
'expectedfeedback' => 'The code has a syntax error: the opening brace '
. '\'{\' after the function signature is missing.',
'expectedmathjaxapplied' => false,
'expectedmarks' => 0.5,
],
'not_a_json_at_all' => [
'json' => 'Not a json string',
'exceptionexpected' => false,
'expectedfeedback' => 'Not a json string',
'expectedmathjaxapplied' => false,
'expectedmarks' => 0,
],
'empty_json' => [
'json' => '',
'exceptionexpected' => false,
'expectedfeedback' => '',
'expectedmathjaxapplied' => false,
'expectedmarks' => null,
],
// The following test cases includes crazy backslash scenarios. The process_feedback function will first "strip" one
// backslash when parsing the JSON. Then it will substitute each backslash with a double backslash, before sending it
// through format_text which again will strip one backslash.
// Overall, the following test cases should represent properly what happens.
// @codingStandardsIgnoreStart moodle.Strings.ForbiddenStrings.Found
'json_with_backslashes' => [
// This JSON is real answer from an LLM with markdown code block delimiters.
// This JSON is real answer from an LLM with Markdown code block delimiters.
'json' => '```json { "feedback": "Die Antwort des Schülers zeigt ein grundlegendes Verständnis für das ' .
'Röntgenspektrum und die Entstehung des Bremsspektrums sowie des charakteristischen Spektrums. Es wird ' .
'korrekt beschrieben, dass das Bremsspektrum durch das Bremsen der Elektronen entsteht und von der ' .
Expand All @@ -397,13 +410,34 @@ public static function process_feedback_provider(): array {
'Beschleunigungsspannung abhängt. Auch die materialabhängige Entstehung des charakteristischen Spektrums ' .
'wird erwähnt und teilweise richtig interpretiert. Allerdings fehlen wesentliche physikalische Inhalte: 1. ' .
'Es wird nicht erklärt, dass die Bremsstrahlung ein kontinuierliches Spektrum darstellt. ' .
'2. Der Unterschied zwischen \(K_\alpha\)- und \(K_\beta\)-Linien des Röntgenspektrums wird nicht ' .
'angesprochen. 3. Der Zusammenhang zwischen der Kernladungszahl und den Frequenzen \(K_\alpha\)-Linien ' .
'2. Der Unterschied zwischen \\\\(K_\\\\alpha\\\\)- und \\\\(K_\\\\beta\\\\)-Linien des Röntgenspektrums wird nicht ' .
'angesprochen. 3. Der Zusammenhang zwischen der Kernladungszahl und den Frequenzen \\\\(K_\\\\alpha\\\\)-Linien ' .
'(Moseleys Gesetz) ist ungenau erläutert und könnte präziser beschrieben werden. Insgesamt ist die ' .
'Antwort gut, aber es sind noch einige physikalische Details notwendig, um die volle Punktzahl zu ' .
'erreichen. Gegeben: 7/10 Punkte.',
'expectedmathjaxapplied' => true,
'expectedmarks' => 7,
],
[
'json' => '{"feedback":"Leider nicht richtig. Erwartet war eine Subtraktionsaufgabe der Form ___ - ___. ' .
'Deine Eingabe \\(3+2\\) ist eine Addition und stimmt nicht mit \\(5-3\\) oder \\(5-2\\) ' .
'überein. Vorschlag: Schreibe z. B. \\(5-3\\) oder \\(5-2\\). Punkte: 0/1.","marks":0}',
'exceptionexpected' => false,
'expectedfeedback' => 'Leider nicht richtig. Erwartet war eine Subtraktionsaufgabe der Form ___ - ___. ' .
'Deine Eingabe \\\\(3+2\\\\) ist eine Addition und stimmt nicht mit \\\\(5-3\\\\) oder \\\\(5-2\\\\) ' .
'überein. Vorschlag: Schreibe z. B. \\\\(5-3\\\\) oder \\\\(5-2\\\\). Punkte: 0/1.',
'expectedmathjaxapplied' => true,
'expectedmarks' => 0,
],
[
'json' => '{"feedback":"Richtig! Deine Eingabe \(3 \cdot 7\) ist gültig (auch \(7 \cdot 3\) ' .
'wäre korrekt). Punkte: 1/1.","marks":1}',
'exceptionexpected' => false,
'expectedfeedback' => 'Richtig! Deine Eingabe \\\\(3 \cdot 7\\\\) ist gültig (auch \\\\(7 \cdot 3\\\\) ' .
'wäre korrekt). Punkte: 1/1.',
'expectedmathjaxapplied' => true,
'expectedmarks' => 1,
]
// @codingStandardsIgnoreEnd
];
}
Expand Down
Loading