Korrekturdienste müssen aktuell von einem Administrator zentral installiert werden.
Derzeit läuft eine Testphase, in der wir mit Endkunden den Entwicklungsprozess erproben.
Sie haben Interesse an der Entwicklung eigener Korrekturdienste? Dann kontaktieren Sie uns bitte über unser Helpdesk: dynexite@medien.rwth-aachen.de.
Korrekturdienste sind kleinere Hilfsdienste, welche Antworten, Konfigurationen und Zusatzdaten erhalten und Dynexite Kommentare, Debug-Output und ein Ergebnis liefern. Sie ergänzen so die reguläre Korrektur.
Korrekturdienste können als einfacher Webdienst bereitgestellt oder z.B. mithilfe des http-docker-runner
entwickelt werden (s.u.).
Um einen Dienst zu betreiben, muss dieser:
Die Entwicklung der Korrekturendpunkte und der Registration findet typischerweise zeitgleich statt.
Diese Daten liefern Dynexite alle notwendigen Informationen, um die Korrekturdienste korrekt im Aufgabeneditor anzuzeigen und während der Korrektur zu benutzen.
Wir empfehlen, dass der Korrekturdienst einen Endpunkt
/info
besitzt und dort die Registration bereitstellt.
Im Folgenden ein kommentiertes Beispiel von der Konfiguration des Python 3 Korrekturdienstes:
{
// Die ID des Korrekturdienstes. Kann grundsätzlich frei gewählt werden.
// Für Dynexite interne Dienste verwenden eine URI Hierarchie "de.dynexite.<name>.<version>".
"id": "de.dynexite.python3-v1",
// Die `baseURI` ist die Basisadresse unter welcher der Dienst zu finden ist.
// Wenn angegeben, wird über `<baseURI>/info` die Registration aktualisiert.
// Die `gradeURI` ist die Adresse, welche zur eigentlichen Korrektur genutzt wird.
//
// Info: Der Basispfad beider Adressen wird vom Systemadministrator festgelegt, da
// dieser von der Art des Deployments abhängt.
"baseURI": "http://python-cas-grader/",
"gradeURI": "http://python-cas-grader/grade",
// Der sichtbare Name des Korrekturdienstes mit der entsprechenden Übersetzung.
"name": {
"de": "Python3",
"en": "Python3",
},
// Eine kurze Beschreibung des Korrekturdienstes mit der Übersetzung.
"description": {
"de": "Ermöglicht die Korrektur der Antwort durch ein Python-Skript.",
"en": "Allows the use of a python script to grade the given answer.",
},
// Die Variable `useVariables` definiert, ob die Variablen zum Korrekturdienst
// geschickt werden sollen.
"useVariables": True,
// Die Variable `useBindings` definiert, ob die Antworten von anderen (verbundenen)
// Elementen an diesen Korrekturdienst geschickt werden sollen.
"useBindings": True,
// Die Variable `showBindingInfos` gibt an, ob dem Benutzer Informationen zu den
// verbundenen Elementen angezeigt werden sollen.
"showBindingInfos": True,
// Über `supports` wird festgelegt, welche Elementtypen unterstützt werden. Der
// Wert `None` bedeutet, dass der Korrekturdienst für alle Elementtypen angeboten wird.
"supports": ["block:code-input"],
// Über `configSchema` wird die Korrekturdienst-Konfiguration mittels einer
// JSONSchema-Definition festgelegt.
// Die Daten werden an den Korrekturdienst gesendet.
"configSchema": {
// [...] Erklärung weiter unten.
},
}
configSchema
)Um einen Korrekturdienst konfigurierbar zu machen, muss der Autor Einstellungen vornehmen können.
Da Dynexite nicht weiß, welche Informationen der Korrekturdienst benötigt, übergibt der Entwickler die Struktur der Konfigurationsdaten mithilfe einer JSONSchema
-Definition.
Während das Konzept nicht hundertprozentig übereinstimmt (z.B. fehlen einige Optionen und zusätzlich Widgets können ausgewählt werden), ist es sehr ratsam die offizielle Dokumentation einzusehen.
type
)Es gibt verschiedene Typen, die über das Attribut type
ausgewählt werden können. Elemente können beliebig verschachtelt werden, sodass Arrays von Objekten genauso möglich sind wie Arrays innerhalb von Objekten.
"successText": {
"type": "string",
// Wenn ein Quellcode-Editor angezeigt werden soll, muss hier das `widget`
// mit "code" gewählt werden. Sonst kann das Feld weggelassen werden.
// Bei "multitine" kann der Autor mehrere Zeilen Text eintragen.
"widget": "" | "code" | "multiline",
// Wenn `widget` den Wert "code" hat, kann hier die Programmiersprache eingestellt werden.
"lang": ""
}
Ausgabe für den Korrekturdienst:
"successText": "Lorem ipsum dolor sit amet."
"weight": {
"type": "number",
"default": 1,
"minimum": 0.1,
"maximum": 100
},
Ausgabe für den Korrekturdienst:
weight: 5.65
"essential": {
"type": "boolean",
"title": "Muss erfüllt sein um weitere Tests auszuführen"
},
Ausgabe für den Korrekturdienst:
essential: true | false,
{
"type": "object",
"properties": {
"name": {
...
},
"age": {
...
}
}
}
Ausgabe für den Korrekturdienst:
{
"name": "Karl",
"age": 25
}
{
"type": "array",
"items": {
"test": {
"type": "object",
"properties": {
"city": ...
"street": ...
"num": ...
}
},
}
}
Ausgabe für den Korrekturdienst:
[{
"city": "Aachen",
"street": "Some Street",
"num": "37"
}, {
"city": "Hamburg",
"street": "Some Other Street",
"num": "12a"
}]
title
, description
)Jedes Element sollte einen Titel title
und und eine Beschreibung description
erhalten. Diese werden jeweils in den vorhandenen Sprachen bereitgestellt.
"title": {
"de": "Quellcode",
"en": "Sourcecode"
},
"description": {
"de": "Trage hier deinen Python 3-Code ein. Er muss ...",
"en": "Enter your Python 3 code here. It must return ..."
}
Elemente vom ConfigSchema fangen typischerweise mit einem object
an.
Schema:
"configSchema": {
"type": "object",
"properties": {
}
},
UI:
Ausgabe:
{}
Schema:
"configSchema": {
"type": "object",
"properties": {
"code": {
"type": "string",
"widget": "code",
"lang": "python",
"title": {
"de": "Quellcode",
"en": "Sourcecode"
},
"description": {
"de": "Trage hier deinen Python 3-Code ein. Er muss ...",
"en": "Enter your Python 3 code here. It must return ..."
}
}
}
},
UI:
Ausgabe an den Korrekturdienst:
{
"code": "...Python Code..."
}
"configSchema": {
"properties": {
"settings": {
"type": "object",
"title": {
"de": "Einstellungen",
"en": "Settings"
},
"properties": {
"execError":
{
"type": "boolean",
"title": "Automatisch 0 Punkte bei Kompilierfehlern vergeben?"
},
"syntaxError":
{
"type": "boolean",
"title": "Automatisch 0 Punkte bei Syntaxfehlern vergeben?"
}
}
},
"form": {
"type": "array",
"title": "Python Unit Test Grader",
"items": {
"type": "object",
"properties": {
"weight": {
"type": "number",
"description": "Gewichtung",
"default": 1,
"minimum": 0.1,
"maximum": 100
},
"essential": {
"type": "boolean",
"title": "Muss erfüllt sein um weitere Tests auszuführen"
},
"prependCode": {
"type": "string",
"title": {
"en": "Prepend Code (optional)",
"de": "Vorangestellter Code (optional)"
},
"description": {
"en": "You can add additional code, e.g. variables o ...",
"de": "Hier kann zusätzlicher Code angegeben werden, ..."
},
"widget": "code",
"lang": "python"
},
"test": {
"type": "string",
"description": {
"de": "Hier kann der Python 3 Testcase eingegeben werden. Erstelle ...",
"en": "You can add the python 3 testcase here. Please create one o ..."
},
"title": {
"de": "Testcase",
"en": "Testcase"
},
"widget": "code",
"lang": "python"
}
}
}
}
},
"type": "object"
}
UI:
Ausgabe:
{
"settings": {
"execError": true,
"syntaxError": false,
},
"form": [{
"weight": 10,
"essential": true,
"prependCode": "...",
"test": "..."
}, {
"weight": 5,
"essential": false,
"prependCode": "...",
"test": "..."
}]
}
Wenn eine Korrektur gemacht werden soll, wird eine Anfrage an gradeURI
geschickt.
/// POST /grade (Request)
{
// Das Feld `answer` enthält die rohe Antwort des Studierenden.
"answer": string | number | ...
// Das Attribut `config` enthält die vom Benutzer erstellte Konfiguration
// durch das `configSchema`.
"config": {
// ...
}
// Wenn `useBindings` aktiv ist, werden hier die verbundenen Elemente zugeschickt.
"bindings": {
"elementNameA": "value of element",
"elementNameB": "value of element",
}
// Wenn `useVariables` aktiv ist, werden hier die Variablen aus der Parametrisierung
// zugeschickt.
variables: {
"typ": "Auto",
"x": "122,5",
"y": ["1", "b", "5"]
}
}
/// RESPONSE 200 OK
{
// Das Attribut `data` enthält die zentralen Ergebnisdaten.
"data": {
// Das Ergebnis aus der Korrektur als Wert zwischen 0 und 1 oder "null".
// Wenn "null" zurückgegeben wird, wird das Element als "nicht korrigierbar" markiert
// und eine manuelle Korrektur wird angefordert.
"result": 0 - 1 | null,
// Kommentar, welcher dem Studenten angezeigt wird.
comment: string | null,
// Detaillierte Ergebnisdaten, welche vom Elementyp abhängen.
// (Wir empfehlen diesen Punkt vorerst zu ignorieren!)
"customData": null,
},
// Dieser Fehler wird gesetzt, wenn etwas nicht geklappt hat.
// Kann in Kombination mit "result: "null" verwendet werden, umn
// Gründe für die manuelle Korrektur zu liefern.
"error": string | null,
// Optionaler Debug-Output durch den Korrekturdienst.
"output": [{
"line": number | null,
"msg": string
}, {
"line": 12,
"msg": "Hier hat etwas nicht funktioniert"
}]
}
/// RESPONSE != 200
{
// Es gab einen Fehler welcher nicht vom Autor oder Korrektor ausgelöst wurde.
// Die Korrektur gilt nicht als abgeschlosen und wird erneut versucht, bis das
// Problem (ggf. durch Eingriff der Entwickler oder Admins) behoben wurde.
"error": string | null,
}
Soll Code von Studierenden oder Dozierenden ausgeführt werden, muss die Maschine von anderen Prozessen im System abgegrenzt werden. Sollen nur einfache, feste Berechnungen durchgeführt werden, ist typischerweise kein striktes Sandboxing notwendig.
Da wir diverse Dienste anbieten müssen, welche z.B. unbekannten Python Code ausführen, haben wir ein eigenes Hilfsmittel geschaffen, welches die Logik des Korrekturdienstes von der Außenwelt abschirmt und gleichzeitig möglichst viele Elemente zulässt.
Anstatt selbst einen HTTP-Webserver anzubieten, wird ein Management-Container gestartet, welcher von sich aus einen neuen Container startet. Der neue Container hat dabei keinen Netzwerkzugang und die Informationen werden nur über limitierte Schreibrechte auf die Festplatte erlaubt.
/// Eintrag aus der `docker-compose.yml`
python-unit-grader:
image: registry.dynexite.rwth-aachen.de/dynexite/http-docker-runner:v2
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./grading_service_data/manager:/manager
environment:
IMAGE: registry.dynexite.rwth-aachen.de/dynexite/grader/python-unit-grader:v6
# This path is used by the unit grader to write it's files.
TMPFS: 1
TMPFS_PATH: /srv/data
# Forwarded Service Configuration
# SVC__TIMEOUT: 10
Die Dienste müssen angepasst werden und sprechen nun nicht mehr über HTTP-Endpunkte, sondern über die Umgebungsvariablen REQUEST_URI
REQUEST_DATA
und OUTPUT_PATH
.
/// Auszüge aus `main.py`.
def generate_output(data):
result_dir = os.environ['OUTPUT_PATH']
with open(result_dir, 'w') as f:
res = json.dumps(data)
f.write(res)
if __name__ == '__main__':
# [... Validierung ....]
requestURI = os.environ["REQUEST_URI"]
# Get the data from the request
requestData = os.environ["REQUEST_DATA"]
if requestURI == "/info":
generate_output(config())
exit(0)
if requestURI == "/grade":
res = generate(requestData)
generate_output(res)
exit(0)
Sie wünschen Zugriff zu den Docker-Images oder auf das private Repository? Dann kontaktieren Sie bitte unser Helpdesk unter dynexite@medien.rwth-aachen.de.
https://git.rwth-aachen.de/dynexite/http-docker-runner