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 对象,例如 |
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)