外部分级¶
ZJUI-Learn 允许您在指定的环境中安全地运行自定义评分脚本。这主要用于对提交的代码进行自动评分,但它足够灵活,可以支持各种用例。
高级概述¶
您可以为外部评分过程定义许多资源:
- 用于执行测试的 Docker 映像,可能是:
- ZJUI-Learn 提供的图像。
- 您已构建并推送到 Docker Hub 的 ZJUI-Learn 提供的映像的自定义版本。
- 来自 Docker Hub 的标准公共映像(例如
python:3.13、node:24等),可以执行特定于课程的评分脚本。 - 一个完全自定义的映像,包含您构建并推送到 Docker Hub 的脚本和资源。
- 问题之间共享的文件、脚本和其他资源。
- 特定于各个问题的文件、脚本、测试和其他资源。
当学生在外部评分问题上单击“提交”时,ZJUI-Learn 会将您定义的所有资源汇总到存档中。该存档将被提交以在您指定的环境中运行。然后将结果发送回ZJUI-Learn;一旦处理完成,学生将立即看到他们的分数、单独的测试用例、stdout/stderr 以及您想要向他们显示的任何信息。
外部评分时间戳和阶段¶
当学生提交使用外部评分的问题时,系统会创建评分作业来处理提交。有一批评分员可以处理这些工作。然后,分级工作将遵循一系列步骤:
- ZJUI-Learn 创建评分作业并将其放入评分队列中。
- 该作业在评分队列中等待,直到被评分者接收。
- 池中的评分者接收作业,读取评分信息,拉取适当的评分 Docker 映像,并启动评分容器。
- 课程评分代码在 Docker 容器内运行。
- 评分者将评分结果发送回ZJUI-Learn。
- ZJUI-Learn 处理结果、记录成绩并向学生显示他们的结果。
评分过程每个阶段的时间戳都会被记录下来,教师可以在每次提交的提交信息框中以及评分作业页面中查看。这对于调试性能评分问题很有用。
配置和启用外部分级机支持¶
外部评分配置是根据每个问题完成的。问题需要设置为使用“外部”评分方法。所有配置都可以使用问题设置页面或通过问题的 info.json 中的 externalGradingOptions 对象来完成。外部评分问题的最低配置包括以下选项:
image:应该用于该问题的 Docker 映像。这可以是 Docker Hub 上公开托管的任何镜像。启用外部放坡时需要此属性。
其他选项可用于进一步定制:
entrypoint:容器启动时将运行的脚本或命令行。如果未提供此属性,则将使用 Docker 映像的默认入口点。- 如果指定,这应该是 Docker 映像中可执行文件的绝对路径。这可以是 shell 脚本、Python 脚本、已编译的可执行文件或任何其他可以执行的内容。该文件可以内置到您的映像中,该文件必须在映像本身中可执行;或者它可以是要安装到
/grade中的文件之一(稍后会详细介绍),在这种情况下,入口点文件在运行之前由分级进程本身授予可执行权限(即chmod +x /path/to/entrypoint && /path/to/entrypoint)。 entrypoint还可以提供额外的命令行参数。如果通过info.json设置,这些可以作为字符串(例如"/path/to/entrypoint -h")或数组提供,每个元素对应一个参数(例如["/path/to/entrypoint", "-h"])。
- 如果指定,这应该是 Docker 映像中可执行文件的绝对路径。这可以是 shell 脚本、Python 脚本、已编译的可执行文件或任何其他可以执行的内容。该文件可以内置到您的映像中,该文件必须在映像本身中可执行;或者它可以是要安装到
serverFilesCourse:指定将从课程的serverFilesCourse复制到评分作业中的文件或目录列表。如果您想在许多问题之间共享标准资源(例如脚本、库和数据文件),这会很有用。该属性是可选的。
timeout:指定评分作业的超时时间(以秒为单位)。如果在该时间过后评分尚未完成,作业将被终止并向学生报告为失败。此属性是可选的,默认值为 30 秒。它应该尽可能小,以适合您的工作,并且不能超过 600 秒(10 分钟)。
enableNetworking:允许容器访问公共互联网。默认情况下禁用此功能,以使安全、隔离的执行成为默认行为。
environment:在评分容器内设置的环境变量。使用{"VAR": "value", ...}设置变量,并使用{"VAR": null}取消设置变量(null周围没有引号)。该属性是可选的。
以下是问题 info.json 的完整 externalGradingOptions 部分的示例:
{
"externalGradingOptions": {
"image": "prairielearn/grader-python",
"serverFilesCourse": ["my_libraries/"],
"timeout": 5
}
}
该配置文件指定以下内容:
- 外部分级已启用。
- 将使用
prairielearn/grader-python图像。 serverFilesCourse/my_libraries下的文件/目录将在分级时复制到您的图像中。- 当容器启动时,将执行图像设置的默认入口点脚本。
- 如果分级时间超过 5 秒,容器将被杀死。
Info
有关如何在 info.json 文件中设置这些字段的详细信息,请参阅问题 info.json 文件的 externalGradingOptions 架构。
写作问题¶
有多种方式允许学生提交文件进行外部评分:
pl-file-editor元素 为学生提供了一个浏览器内编辑器,可用于编写代码。pl-file-upload元素 允许学生从自己的计算机上传文件。pl-order-blocks元素 使用grading-method="external"属性,允许学生通过以正确的顺序排列预定义的代码块来提交代码。pl-rich-text-editor元素 允许学生创建 HTML 文档。pl-image-capture元素 允许学生提交使用其设备的相机拍摄的图像。- 对于使用工作空间 的问题,
gradedFiles选项 标识可供外部评分者使用的工作空间文件。
有关允许学生提交的问题示例,您可以查看 ZJUI-Learn/exampleCourse/questions/demo/autograder/codeEditor 和 ZJUI-Learn/exampleCourse/questions/demo/autograder/codeUpload。
如果您想编写自己的提交机制(例如,作为自定义元素),您也可以这样做。通过将文件包含在 submitted_answers 字典上的 _files 数组中,可以将文件提交给外部评分者。这可以通过在问题或自定义元素的 parse() 方法中调用 pl.add_submitted_file() 来完成。有关此操作的示例,请参阅pl-file-upload 的实现。
特殊目录¶
在问题目录中,您可以创建一个 tests 目录,其中包含您想要供外部评分容器使用的任何问题特定文件。这些可能是评分过程中将使用的单独测试、输入文件、代码文件或库文件。这些文件的格式可能会有所不同,具体取决于您使用的分级图像。
此外,如上所述,您可以指定 serverFilesCourse 中应复制到容器中的文件或目录。一个常见的用例是,如果您想在课程中的不同问题之间共享库、脚本或数据文件。
Warning
每次提交时,tests 或指定 serverFilesCourse 目录中包含的任何文件都将被复制到评分作业中。如果包含大文件或目录,这可能会显着减慢评分过程。我们建议仅包含评分所需的文件。如果您有许多问题之间共享的大文件,请考虑将它们构建到自定义 Docker 映像中。
评分过程¶
所有问题和学生提交的代码都将出现在容器内 /grade 的各个子目录中。
- 如果您在
serverFilesCourse中指定任何文件或目录,它们将被复制到/grade/serverFilesCourse。 - 如果您的问题有
tests目录,它将被复制到/grade/tests。 - 学生提交的文件将被复制到
/grade/student。 - 通常提供给问题服务器文件的
grade方法的data对象将在/grade/data/data.json处序列化为 JSON。
当容器启动时,将执行入口点脚本(图像设置的默认脚本或问题设置中指定的脚本)。唯一的要求是,当脚本完成时,它应该已将评分作业的结果写入 /grade/results/results.json。下面指定了该文件的格式。该文件的内容将被发送回 ZJUI-Learn 以记录成绩,并可能向学生显示。
Note
/grade/results目录不会自动创建,所以在写入results.json之前必须自己创建它。
特别是,评分器的文件系统结构如下所示:
/grade # Root directory of the grading job
+-- /data # JSON dump of the data object from server.py
| `-- data.json
|
+-- /results # Report of test output formatted for ZJUI-Learn
| `-- results.json
|
+-- /serverFilesCourse # Files from serverFilesCourse/ in the course directory
| +-- /my_testing_framework
| | +-- testfile # Test framework configuration
| | `-- run.sh # Entrypoint called in the container
|
+-- /student # Files submitted by student
| +-- studentfile1
| `-- studentfile2
|
+-- /tests # Files found in the question's tests/ directory
| +-- test1
| `-- test2
评分结果¶
您的评分过程必须将其结果写入 /grade/results/results.json。
- 如果提交是可评分的,则结果只有一个必填字段:
score,这是提交的尝试的分数,并且应该是 [0.0, 1.0] 范围内的浮点数。可以选择包含字段gradable并将其设置为true以指示提交是可评分的,但这不是必需的,因为省略该字段相当于假设输入是可评分的。 - If the submission is not gradable, the field
gradableis required, and must be set tofalse. In this case, thescorefield is not needed.这表示无法对提交的内容进行评分,例如由于语法错误,或者输入丢失、无效或格式不正确。在这种情况下,提交的内容将被标记为“无效,不可评分”,不会奖励或失去任何分数,并且学生不会因为尝试该问题而受到处罚。
除了 score 和 gradable 之外,您还可以向该对象添加所需的任何其他数据。这可能包括详细的测试结果、stdout/stderr、编译器错误、渲染图等信息。但请注意,该文件应限制为 1 MB,因此您必须确保对数据的任何广泛使用都考虑到此限制。
如果 gradable 设置为 false,则可以通过设置 format_errors 键将与答案格式相关的错误消息添加到评分结果中。这可以是字符串或字符串数组,具体取决于错误消息的数量。
<pl-external-grader-results> 元素 能够呈现测试列表以及关联的测试名称、描述、点值、输出、消息和图像。以下是此元素可以呈现的格式正确的结果的示例。请注意,除 score(或 gradable)之外的所有字段都是可选的。
{
"gradable": true,
"score": 0.25,
"message": "Tests completed successfully.",
"output": "Running tests...\nTest 1 passed\nTest 2 failed!\n...",
"images": [
{
"label": "First Image",
"url": "data:image/png;base64,..."
},
{
"label": "Second Image",
"url": "data:image/jpeg;base64,..."
}
],
"tests": [
{
"name": "Test 1",
"description": "Tests that a thing does a thing.",
"points": 1,
"max_points": 1,
"message": "No errors!",
"output": "Running test...\nYour output matched the expected output!"
},
{
"name": "Test 2",
"description": "Like Test 1, but harder, you'll probably fail it.",
"points": 0,
"max_points": 3,
"message": "Make sure that your code is doing the thing correctly.",
"output": "Running test...\nYour output did not match the expected output.",
"images": [
{
"label": "First Image",
"url": "data:image/gif;base64,..."
},
{
"label": "First Image",
"url": "data:image/png;base64,..."
}
]
}
]
}
每个测试点的显示是全有或全无:如果在任何单个测试中省略 points 或 max_points,则针对所有测试禁用详细的每个测试点显示(徽章、颜色和点列表),并显示警告横幅。要获得详细显示,请在每次测试中都包含 points 和 max_points。
将 max_points 设置为 0 的测试以中性方式呈现(灰色,带有信息图标),而不是正确或不正确。
可以通过将 base64 编码数据 URL 添加到各自的 images 数组(如上面的示例中所列),将绘图或图像添加到单个测试用例或主输出中,前提是生成的文件遵守上面列出的 1 MB 的大小限制。数组的每个元素应该是包含以下键的对象:
url:图像的来源,通常格式为标准数据 URL,如"data:[mimetype];base64,[contents]"。label:图像的可选标签(默认为“图形”)。
为了与旧版本的外部评分器兼容,该对象可以替换为仅包含 URL 的字符串。
本地运行以进行开发¶
为了在本地 Docker 环境中运行外部分级器,docker 命令必须包含支持创建本地“同级”容器的选项。关于如何运行Docker的详细说明可以在安装说明中找到。有关在本地测试自定义映像的更多详细信息,请参阅Docker 映像文档。
当不在 Docker 中运行时,事情会更容易。 Docker套接字可以正常使用,并且我们可以自动存储作业文件,而无需设置HOST_JOBS_DIR。默认情况下,它们存储在 $HOME/.pljobs 中。但是,如果您使用环境变量 JOBS_DIR=/abs/path/to/my/custom/jobs/directory/ 运行 ZJUI-Learn,则将使用该目录。请注意,此环境变量在 Docker 上运行时不起作用,在这种情况下,使用 HOST_JOBS_DIR 而不是 JOBS_DIR 指定作业目录。