Skip to content

Evaluator SDK

Evaluator SDK 运行在 Evaluator 容器中,用于调用用户解答并输出评测结果。

导入

python
from noj_evaluator_sdk import SolutionCallError, SolutionRunner, result

调用用户函数

创建 runner:

python
runner = SolutionRunner()

调用用户函数:

python
answer = runner.call("solve", 1, 2)

runner.call() 会向 Solution Host 发起一次 RPC 调用。如果调用成功,返回用户函数的返回值。

调用级超时runner.call() 支持可选 timeout_ms 参数,每次调用可指定独立超时(毫秒)。缺省(None)时由 Judge Worker 回退到题目的 runtime_config.solution.call_timeout_ms

python
answer = runner.call("solve", 1, 2)                    # 用题目级默认超时
answer = runner.call("solve", 1, 2, timeout_ms=5000)   # 本次调用 5s 超时

timeout_ms 必须为正整数或 None,其他值(0 / 负数 / 非整数)抛出 ValueError。超时后 runner.call() 抛出 SolutionTimeoutError,可捕获后记为失败用例继续评测。

调用参数会经过 Neuro OJ codec 编码后通过 RPC 传递。支持的类型和限制见 RPC 与可传递数据

处理调用错误

如果函数不存在、函数不可调用、用户代码抛异常或 RPC 通道异常,runner.call() 会抛出 SolutionCallError

python
try:
    answer = runner.call("solve", 1, 2)
except SolutionCallError as exc:
    error = exc.error

exc.error 是结构化错误对象,通常包含:

  • type:错误类型,例如 FunctionNotFound
  • message:错误消息。
  • traceback:可选,截断后的 traceback。
  • stderr:可选,Solution stderr 的尾部片段,用于调试用户输出或异常前日志。

常见错误类型:

类型含义
FunctionNotFound目标函数不存在
NotCallable同名对象存在,但不可调用
InvalidFunctionName函数名不是非空字符串
用户异常类名用户函数执行时抛出了该异常
CallTimeout单次调用超过 call_timeout_ms。若 evaluator 未捕获(evaluate.py 异常退出、无 ---RESULT---),最终状态为 TimeLimitExceeded
InvalidRpcResponseJudge Worker 返回给 evaluator 的响应不是合法 JSON
RpcChannelClosedevaluator 无法继续从 Judge Worker 读取 RPC 响应

参数与返回值类型校验(RejectedError)

runner.call() 在发出 RPC 帧之前会递归校验参数类型:只允许 None / bool / int / float / str / bytes / list / dict(dict 的 key 必须是 str),任何其他类型——包括嵌套在 list / dict 中的自定义对象、settuple、函数、生成器、文件句柄等——都会直接抛出 RejectedError,错误消息带路径与类型名:

text
arg[0]: 不支持的类型 MyClass(仅 None/bool/int/float/str/bytes/list/dict)

此时 RPC 帧不会发出,Solution 侧完全不知情。帧序列化超过 1 MiB 软上限时同样抛出 RejectedError

返回值路径对称:Solution 返回不支持类型时,Judge Worker 以 code="Rejected" 的错误帧返回,Evaluator 侧同样收到 RejectedError

出题人可以用 try/except RejectedError 把这类调用按失败用例处理;不捕获则 evaluate.py 异常退出,该次评测落为 SystemError

注册 capability(供 Solution 调用)

当题目需要让 solution 使用网络等能力时,用 register_capability 暴露一个精确封装的 handler:

python
from noj_evaluator_sdk import register_capability

def request_llm_completion(prompt: str) -> str:
    # evaluator 已联网(runtime_config.evaluator.network.enabled = true)
    # ... 调用固定 URL 的外部 API,参数校验由 handler 负责
    return completion_text

register_capability("request_llm_completion", request_llm_completion)

capability 默认超时register_capability(name, handler, timeout_ms=None) 可配置 solution 每次调用该 capability 的超时(毫秒)。注册时经 cap_reg 帧上报 Judge,缺省(None)回退题目级 call_timeout_ms

python
register_capability("request_llm_completion", handler, timeout_ms=10000)
  • Solution 通过 noj_solution_sdk.call_capability(name, *args) 调用;请求经 judge 转发到 evaluator,在 runner 的 reader 线程中同步执行 handler,结果以 result 帧返回。
  • 返回值与 runner.call() 相同约束(None / bool / int / float / str / bytes / list / dict);返回值类型非法或帧超限(> 1 MiB)→ code="Rejected"(参数类型已在 solution 侧校验,不会到达 evaluator);handler 异常 → code="Exception"(含清洗后 trace),未注册 → code="NotFound"
  • 不要嵌套双向调用:capability handler 在 reader 线程中同步执行,若 handler 内再调用 runner.call()(回调 solution),双方会互相等待而死锁,只能等评测总超时兜底——不支持这种嵌套。
  • 重复注册同名 capability:最近一次生效。
  • 安全模型:capability 是 solution 使用网络的唯一入口,不要注册通用 URL 转发(如 fetch_url(url));应封装固定目标的业务函数并做参数校验。详细指南见 如何提供受限网络能力

输出评测结果

Evaluator 使用 result 模块输出最终结果。

python
result.accept(score=1000, details={"passed": 10})
result.wrong_answer(score=500, details={"passed": 5})
result.runtime_error(score=0, message="用户代码运行错误")
result.system_error(message="评测脚本配置错误")

分数是整数,当前样例题使用“实际分数乘以 100”的方式。例如满分 10 分时,1000 表示 10.00 分。

details

details 会作为结构化结果透传给前端。建议内容稳定、可序列化,并注意不要泄露隐藏用例数据。

常见结构:

python
details = {
    "visible": {
        "passed": 3,
        "total": 3,
        "cases": [],
    },
    "hidden": {
        "passed": 7,
        "total": 10,
    },
}

restart

runner.restart() 会请求重启 Solution Host。首版文档只建议在确有隔离状态需求时使用;普通题目优先让 evaluator 设计成可重复调用同一个用户模块。

重启后,用户模块会重新导入,全局变量状态会重置。重启失败时会抛出 SolutionCallError

Neuro OJ 是一个独立社区项目,与 CCF 及 LMCC 无官方关系。