Eval

Eval 按统一的 Setup、Agent Run、Verify 流程评测 Task,并保存评测结果、轨迹和日志。

进行一道题的评测需要 task、agent 和 config。

流程伪代码
import json
import os
import warnings
from copy import copy, deepcopy
from dataclasses import replace
from envweaver.agents.execution import AgentExecution
from envweaver.evaluation.store import inspect_task
from envweaver.evaluation.failure import stop_reason
from envweaver.evaluation.runner import _error_record
from envweaver.errors import AgentStopReason

# 下文“阶段时限”到期时抛出 StageTimeout;底层操作的 TimeoutError 不视为阶段到期。

def evaluate(task, agent, config, output_dir):
    execution = AgentExecution(agent.new())  # 为本次尝试创建独立 Agent,并管理其执行流程。
    # 此处 output_dir 是当前机器上的工作目录。
    # 使用 Verifier 已绑定的 Runtime,用来操作沙箱。
    runtime = task.verifier.runtime

    # 先建立结果记录,便于中途失败时仍能保存任务信息和错误。
    result = {
        "name": task.name,           # 任务名称。
        "task_ref": task.task_ref,   # 任务在 Task Pool 中的地址,用于追溯和重新加载。
        "metadata": {},  # 本次 rollout 的自定义信息。
        "agent_stop_reason": None,  # Agent 退出原因;尚未运行时为 None。
        "agent_result": None,       # agent.run 返回的结果,或失败后读取的已有结果快照。
        "verification_result": None, # 验证程序返回的 JSON,包含 score 及其他评测信息。
        "score_valid": False,       # 该次得分是否可以计入统计。
        "errors": [],   # 评测过程中的问题,按发生顺序记录。
    }

    task_instance = None      # Agent 执行任务的沙箱实例。
    verifier_instance = None  # 运行验证程序的沙箱实例。
    stop_attempted = False  # 是否已请求停止 Agent。

    stage = "preflight"  # 当前评测阶段:preflight(启动前检查)、setup、agent 或 verify。
    output_dir.mkdir(parents=True, exist_ok=False)  # 每次尝试使用新目录;目录已存在时在创建资源前报错。

    try:
        execution.preflight(goal=task.goal)
        _, input_error = inspect_task(task)  # 检查并计算输入材料指纹;批量执行时复用已计算的结果。
        if input_error is not None:
            raise input_error

        # 任务的 Verifier 对象
        verifier = task.verifier

        # 从 os.environ 读取必需变量,config.verifier_env 补充或覆盖。
        env = dict(config.verifier_env)
        for name in verifier.required_env:
            if name not in env and (value := os.environ.get(name)) is not None:
                env[name] = value
            if name not in env:
                raise ValueError(f"Missing verifier environment variable: {name}")

        setup_timeout = config.setup_timeout
        if task.setup is not None and setup_timeout < task.setup["timeout"]:
            warnings.warn("setup_timeout is less than task.setup.timeout; Setup may time out before the setup command finishes")

        # Setup
        stage = "setup"
        with 阶段时限(setup_timeout):
            # 递归合并本次任务环境配置,得到启动任务沙箱的引用。
            task_ref = task.task_runtime_ref
            if config.task_runtime_config:
                task_ref = replace(
                    task_ref, config=merge_config(task_ref.config, config.task_runtime_config)
                )

            # 递归合并本次验证环境配置,得到启动验证沙箱的引用。
            verifier_ref = task.verifier_runtime_ref
            if config.verifier_runtime_config:
                verifier_ref = replace(
                    verifier_ref, config=merge_config(verifier_ref.config, config.verifier_runtime_config)
                )

            # 准备做题实例和验证实例
            task_instance = runtime.start(
                ref=task_ref,
                context=task.context,
                setup=task.setup,
                connection=config.task_connection,
            )
            verifier_instance = runtime.start(
                ref=verifier_ref,
                connection=config.verifier_connection,
            )

            # 准备 Agent 执行任务所需的环境和工具,具体操作由各 Agent 适配代码实现。
            execution.prepare(
                runtime=runtime,
                instance=task_instance,
                output_dir=output_dir / "agent",
                cwd=task.cwd,
            )

        # Agent Run
        stage = "agent"
        agent_timeout = config.agent_timeout
        try:
            with 阶段时限(agent_timeout):
                result["agent_result"] = execution.run(
                    goal=task.goal,
                    runtime=runtime,
                    instance=task_instance,
                    output_dir=output_dir / "agent",
                )
        except BaseException as error:
            reason = stop_reason(error)
            result["agent_stop_reason"] = reason.value
            if not reason.should_verify:
                raise
            result["errors"].append(_error_record("agent", error))
            stop_attempted = True
            try:
                execution.stop(timeout=30)
            except Exception as stop_error:
                result["errors"].append(_error_record("cleanup", stop_error, operation="stop"))
                return result  # finally 仍会保存结果并关闭沙箱。
        else:
            result["agent_stop_reason"] = AgentStopReason.COMPLETED.value

        # Verify:正常完成或达到执行限制,均对已有成果评分。
        stage = "verify"
        # 在副本上设置本次验证超时,不修改 Task。
        if config.verify_timeout is not None:
            if config.verify_timeout < verifier.timeout:
                warnings.warn("verify_timeout is less than task.verifier.timeout")
            verifier = copy(verifier)
            verifier.timeout = config.verify_timeout
        result["verification_result"] = verifier.run(
            verifier_instance=verifier_instance,
            task_instance=(
                task_instance if verifier.requires_task_instance else None
            ),
            env=env,
        )

    except BaseException as error:
        result["errors"].append(_error_record(stage, error))
        if not isinstance(error, Exception):
            raise

    finally:
        # 停止本次 Agent
        if not stop_attempted:
            try:
                execution.stop(timeout=30)
            except Exception as error:
                result["errors"].append(_error_record("cleanup", error, operation="stop"))

        # run 未返回结果时,读取已保存的 Agent 结果快照。
        if result["agent_result"] is None:
            try:
                with (output_dir / "agent" / "result.json").open("r", encoding="utf-8") as file:
                    result["agent_result"] = json.load(file)
            except FileNotFoundError:
                pass
            except Exception as error:
                result["errors"].append(_error_record("cleanup", error, operation="snapshot"))

        # 关闭验证实例和做题实例
        for instance in (verifier_instance, task_instance):
            if instance is not None:
                try:
                    runtime.close(instance=instance)
                except Exception as error:
                    result["errors"].append(_error_record("cleanup", error, operation="close"))

        result["score_valid"] = (
            result["verification_result"] is not None
            and result["agent_stop_reason"] is not None
            and AgentStopReason(result["agent_stop_reason"]).should_verify
        )

        # 保存本次评测结果
        with (output_dir / "result.json").open("w", encoding="utf-8") as file:
            json.dump(result, file, ensure_ascii=False, indent=2, allow_nan=False)

    return result


def merge_config(base, overrides):
    merged = deepcopy(base)
    for key, value in overrides.items():
        if isinstance(merged.get(key), dict) and isinstance(value, dict):
            merged[key] = merge_config(merged[key], value)
        else:
            merged[key] = deepcopy(value)
    return merged


task = pool.load(task_ref=task_ref)
result = evaluate(task=task, agent=agent, config=config, output_dir=output_dir)

eval:评测任务

Parameters:

Name Type Description Default
tasks Task | Iterable[Task]

一个 Task,或 Task 的可迭代对象。

required
agent Agent

执行任务的 Agent 对象,例如 CodexAgent(model=model_name)。

required
config EvalConfig | None

任务使用的评测配置,不填使用 EvalConfig 默认值。

None
output_dir str | Path

评测产物的保存目录,默认 eval。

'eval'
concurrency int | dict[str, int]

正整数限制同时评测的任务数,默认 1;或填写 {"setup": 4, "agent": 40, "verify": 4},分别限制三个阶段的并发。阶段排队不计入超时,同时进行中的任务最多为三个上限之和。

1
traj_pool TrajPool | None

自动归档 rollout 的 TrajPool;默认 None,不入池。归档失败会报错,评测产物保留在 output_dir。

None
resume bool

是否继续上次评测,默认 False;已完成任务保留原结果,未完成任务使用本次 Agent 和 EvalConfig。 历次尝试须包含 configuration 配置快照。

False

Returns:

Type Description
list[dict]

按输入顺序排列的评测结果列表,每道题一个字典;errors 按发生顺序记录各阶段的问题;每次新执行的 metadata 默认为独立的空字典。

from envweaver import eval

results = eval(
    tasks=tasks,
    agent=agent,
    config=config,
    output_dir="./runs/batch-001",
    concurrency={"setup": 4, "agent": 40, "verify": 4},
)

返回列表中的每个字典包含以下字段:

字段 含义
name 任务名称
task_ref 任务在 Task Pool 中的存储地址
metadata 本次 rollout 的自定义信息,默认 {}
output_dir 本次尝试的产物目录,相对于传给 eval 的批次目录
agent_result Agent 运行结果,无结果时为 None
agent_stop_reason Agent 退出原因,如 completed、timeout;尚未运行时为 None
verification_result Verify 脚本返回的 JSON 结果,无结果时为 None
score_valid 得分是否可计入统计;需取得有效验证结果、Agent 正常结束或达到执行限制
errors 评测过程中的问题列表,按发生顺序记录,无问题时为 []
agent_stop_reason 退出原因

Eval 结果的 agent_stop_reason 保存 AgentStopReason 的字符串值;尚未进入 Agent Run 时为 null。退出原因如下:

Attributes:

Name Type Description
COMPLETED str

正常结束,值为 completed;是否完成题目由 Verifier 判断。

TIMEOUT str

达到 Agent 执行时间上限,值为 timeout。

CONTEXT_LIMIT str

耗尽模型上下文,值为 context_limit。

STEP_LIMIT str

达到执行步数上限,值为 step_limit。

COST_LIMIT str

达到费用上限,值为 cost_limit。

TOKEN_LIMIT str

达到输出 Token 上限,值为 token_limit。

OTHER_LIMIT str

已确认达到其他限制或无法细分原因,值为 other_limit;在 message 中说明。

CANCELLED str

被用户取消或主动停止,值为 cancelled。

ERROR str

认证、连接、环境等执行异常,值为 error。

errors 中每条记录的字段
字段 含义
stage 发生阶段:preflight、setup、agent、verify 或 cleanup
type ErrorType 的字符串值,具体类型见下方
message 具体问题说明

ErrorType:问题类型

Attributes:

Name Type Description
TIMEOUT str

阶段或 Agent 执行时间达到上限,值为 timeout。

CONTEXT_LIMIT str

模型上下文耗尽,值为 context_limit。

STEP_LIMIT str

达到执行步数上限,值为 step_limit。

COST_LIMIT str

达到费用上限,值为 cost_limit。

TOKEN_LIMIT str

达到输出 Token 上限,值为 token_limit。

OTHER_LIMIT str

已确认的其他执行限制,值为 other_limit。

PROVIDER_TRANSIENT str

模型服务连接中断、请求超时、限流或临时不可用,值为 provider_transient。

TRANSPORT str

非模型服务的连接或传输故障,值为 transport。

AUTHENTICATION str

认证或权限错误,值为 authentication。

CONFIGURATION str

参数、输入或配置错误,值为 configuration。

SANDBOX str

沙箱启动或操作失败,值为 sandbox。

VERIFIER str

验证执行失败或结果无效,值为 verifier。

CANCELLED str

用户取消或主动停止,值为 cancelled。

UNKNOWN str

未分类的问题,值为 unknown。

config:本次评测配置

Parameters:

Name Type Description Default
setup_timeout float

Setup 总时限,单位秒,默认 600;小于 task.setup.timeout 时 warning,继续执行。

600
agent_timeout float

Agent Run 总时限,单位秒,默认 10800(3 小时)。

10800
verify_timeout float | None

验证程序时限,不包含材料复制;默认使用 task.verifier.timeout,小于它时 warning,继续执行。

None
task_runtime_config dict

与 task_runtime_ref.config 递归合并,同名值以本次为准,默认 {}。

dict()
verifier_runtime_config dict

与 verifier_runtime_ref.config 递归合并,同名值以本次为准,默认 {}。

dict()
task_connection dict

与任务引用的 connection 递归合并,同名值以本次为准,默认 {}。

dict()
verifier_connection dict

与验证引用的 connection 递归合并,同名值以本次为准,默认 {}。

dict()
verifier_env dict[str, str]

传给 run.py 的环境变量,默认 {};task.verifier.required_env 从 os.environ 读取,此处补充或覆盖。

dict()
max_retries int

整题重试次数,非负整数,默认 0(不重试);不影响 Agent 内部的请求重试。

0
retry_delay float

整题重试的初始等待秒数,默认 1;指数退避并加入随机抖动,最长 60 秒,可被取消。

1

Traj Pool

TrajPool 用于保存和管理 rollout 的轨迹、评测结果及相关产物,传入 eval(traj_pool=...) 即可自动归档。

Traj Pool:轨迹池

Parameters:

Name Type Description Default
root str | Path

轨迹池目录。

required
completed_only bool

是否只保存已有 ATIF、正常结束或达到执行限制、取得有效评分且无其他故障的 rollout,默认 True;零分也保存。

True
from envweaver import TrajPool

traj_pool = TrajPool(root="/data/traj-pool", completed_only=True)