qinghuan
← 评测、复现与研究实践 / 全部笔记

单元 43 / 评测、复现与研究实践

P0 · 必会主线L3 · 深入

Agent 基准与可执行环境:定义成功和失败

把 Agent 评估变成能重复运行的环境试验,明确从哪里开始、允许多少动作、怎样算成功,以及异常试验怎样计入分母。

本篇目录 · 按需要跳转

这篇重点学什么

本篇重点:固定运行环境、任务规则和结果检查器,检查训练或调试是否提前使用了测试内容;分别说明全部计划任务和实际有效运行有多少,不能悄悄丢掉异常任务。

开始读正文 ↓
学过相近内容?查看哪些可以跳过

两条路线有些基础相同。已经学会的部分可以跳过,另一条路线只用来补你当前需要的内容。

数据划分与评测集

共同基础这组原则只学一次:先确定以后要处理哪些新数据,再据此给相关样本分组;同组不跨数据集,测试集不用来调参,并划清时间先后,避免测试内容的相同或近似版本提前进入训练。

另一条路线补充什么?

写清系统允许和禁止做什么,把同一原始案例的变体归为一组,记录失败类型,并固定回归测试的版本与通过条件。

读完这篇,你能做什么

运行修复与回归检查的小环境,Java拒绝缺失或重复试验记录并计算两种聚合指标。

学习理由、准备要求与知识点

为什么学它

只看最终文字或成功截图,无法判断实际执行能力。遗漏失败或异常记录,还可能让成功率看起来比实际更高。

先读这些内容

知识点清单

  • 任务与环境快照
  • 独立成功oracle
  • 修复与回归检查
  • 重置及种子
  • 终止和预算耗尽
  • 基础设施异常
  • 微平均与宏平均
  • 测试泄漏与隔离

运行前先确认SWE-bench论文v3与WebArena论文v3;Java17、Python3.14标准库的独立教学环境。未运行公开基准、容器或外部模型。

01先弄懂它是什么

比较两个 Agent 之前,要先确认它们在做同一种任务。例如“真的修改订单总额”和“说出总额应该怎样修改”,验收方法完全不同。一个可执行基准需要规定任务从哪里抽取、环境起点是什么、允许哪些动作、预算多少、怎样算成功。最好把这些约定写成程序可检查的规则;否则最终文字看起来都合理,实际完成的工作却可能不一样。

判断任务是否完成,应由独立于被测策略的检查器负责,这常叫成功 oracle。代码任务可以运行指定测试,网页任务可以检查页面或后台状态。但检查器也可能漏掉要求,测试全过仍不一定涵盖所有真实目标。因此要保存检查器版本和它能看到的信息,并单独分析那些“通过了检查,却没有完成用户目的”的情况。

试验需要区分成功、策略失败、预算超时与基础设施异常。基础设施异常可能让任务未真正开始,和模型做错不是一回事,但不能悄悄丢掉这些记录后只展示剩余高分。预先定义重跑规则、报告有效试验覆盖率,并保持每个计划任务都有记录,才能解释分数的真实分母。

文中这几个词,先认一认

Oracle
按任务实际状态判断成功或失败的一套独立规则或测试,不由被测策略自己宣布结果。
回归检查
修改或修复之后,重新确认以前已经正确的行为没有被破坏。
宏平均
先分别算每个预先定义分组的指标,再给各组相同权重求平均,避免大组单独主导总分。

还有陌生词?到术语小抄里看一个具体例子 ↗

02看看它怎么工作

SWE-bench把问题描述和代码快照作为输入,对候选补丁执行测试;修复相关失败测试与保持原有通过测试都重要。WebArena提供可交互网页环境,并按任务结果设计评价。二者说明Agent能力应落在环境行为与结果上,但它们的任务范围不同,不能用其中一个分数推断所有软件或网页工作都能胜任。

本文Python只有受控字典状态:总额最初为零,要求改成数量乘单价,同时币种保持USD。动作可修改指定字段,自述成功被忽略;每次重新复制初始状态,按动作步数限制预算。检查器分别验证修复与回归,因此“改对总额却改坏币种”仍失败。它没有执行不受信任代码,也没有提供安全沙箱。

Java按预声明任务清单核对结果映射,缺失、未知或重复ID直接拒绝。operational使用成功数除所有计划任务,反映完整运行的可交付比例;validRate排除显式INFRA后给出条件成功率,并同时报告异常数量。macro先对每个分组算完整分母比率再平均,两种指标回答不同问题,不能任意挑高者作为主结论。

先跑通一个最小例子

Python3.14标准库执行experiment.py,字典状态模拟修复和回归检查;没有执行生成代码或真实网页动作。

experiment.py · python
from copy import deepcopy
TASK={"quantity":2,"price":3,"currency":"USD"}
INITIAL={"total":0,"currency":"USD"}
def evaluate(actions,budget=2,healthy=True):
    if type(budget) is not int or budget<1: raise ValueError("budget")
    if not healthy: return "INFRA",None
    state=deepcopy(INITIAL)  # 每次试验从同一初始状态开始。
    for index,action in enumerate(actions):
        if index>=budget: return "TIMEOUT",state  # 这是步数预算,不是墙钟超时隔离。
        if not isinstance(action,dict) or len(action)!=1: return "FAILURE",state
        key,value=next(iter(action.items()))
        if key=="claim_success": continue  # 代理自述不替代独立结果判定。
        if key=="total" and type(value) is int: state[key]=value
        elif key=="currency" and isinstance(value,str): state[key]=value
        else: return "FAILURE",state
    repaired=state["total"]==TASK["quantity"]*TASK["price"]
    unchanged=state["currency"]==INITIAL["currency"]
    return ("SUCCESS" if repaired and unchanged else "FAILURE"),state

assert evaluate([{"total":6}])[0]=="SUCCESS"
assert evaluate([{"total":5}])[0]=="FAILURE"
assert evaluate([{"total":6},{"currency":"EUR"}])[0]=="FAILURE"
assert evaluate([{"claim_success":True}])[0]=="FAILURE"
assert evaluate([{"total":6},{"claim_success":True}],budget=1)[0]=="TIMEOUT"
assert evaluate([{"total":6}],healthy=False)[0]=="INFRA"
assert evaluate([{"total":6}])[1] is not evaluate([])[1] and INITIAL["total"]==0
assert evaluate([{"total":True}])[0]=="FAILURE"
print("PASS 8 checks; reset, repair, regression, budget and infrastructure separated")

核对运行结果PASS 8 checks,覆盖正确修改、错误结果、回归、预算、重置和基础设施分类。

正在加载流程图…

环境重置、受控执行和独立结果判定共同定义一次有效试验,超时也必须入账。
查看流程图源码(Mermaid)
flowchart TD
  N0["固定任务初始状态和预算"]
  N1["重置环境并记录可见输入"]
  N2["受控执行并区分结束原因"]
  N3["独立检查目标和回归条件"]
  N4["核对记录完整性后聚合报告"]
  N0 --> N1
  N1 --> N2
  N2 --> N3
  N3 --> N4

按顺序看,每一步做了什么

  1. 固定任务初始状态和预算
  2. 重置环境并记录可见输入
  3. 受控执行并区分结束原因
  4. 独立检查目标和回归条件
  5. 核对记录完整性后聚合报告

03对照实现代码

SWE-bench执行式任务判定 (新标签页)↗

SWE-bench论文 arXiv:2310.06770v3 · Section 2.2与Appendix A.4评价流程

原创执行判定模型,参考固定论文;未运行SWE-bench容器或WebArena服务。

核心机制代码 · python
def resolved(after, fail_to_pass, pass_to_pass):
    eligible = bool(fail_to_pass)  # 本教学任务要求至少有一个待修复测试。
    repaired = all(after.get(name, False) for name in fail_to_pass)  # 缺失测试结果不视为通过。
    preserved = all(after.get(name, False) for name in pass_to_pass)  # 保持原有功能也属于判定条件。
    return eligible and repaired and preserved

固定论文描述从真实问题与补丁构建任务,再在指定代码状态上应用候选补丁并运行测试。本文短函数是修复加回归判定的原创布尔模型,after中的值被约定为已经解析好的布尔通过状态,不能把任意非空错误字符串直接传入并依赖Python真值判断。

真实harness还处理补丁是否可应用、环境构建、命令执行、日志解析和测试状态分类。作者grading.py可对照get_eval_report、get_resolution_status等流程;main链接是阅读入口,基线固定到论文,不把几行all判断冒称完整框架源码或安全执行器。

环境失败发生在不同阶段时要保留原因:构建失败、日志缺失、运行超时与断言失败各自需要诊断。本文选择单列INFRA并报告两种分母,不宣称这是任何官方榜单默认规则。复现实验应遵守所使用基准的明确规则,不能自行换算后继续沿用官方指标名称。

可重复环境也不能自动避免污染。模型可能见过公开问题、测试或解决补丁,实验设计还需说明数据时间范围与可见材料。本文不下载公开任务集,不运行真实仓库补丁,独立小环境只验证重置和判定逻辑,不能报告为SWE-bench或WebArena成绩。

代码先调用谁,再调用谁

  1. 准备指定任务快照
  2. 应用候选修改并运行测试
  3. 解析修复与保持状态
  4. 生成含失败原因的任务报告

04动手写一个例子

预声明任务清单的完整性审计与聚合

对任务ID和分组清单逐一匹配结果,计算所有计划任务成功率、分组宏平均、排除基础设施异常的条件成功率。

输入 1
A组三题成功1,B组一题成功1
输出 1
整体.5,宏平均2/3
输入 2
两题一成功一INFRA
输出 2
完整比例.5,条件比例1,异常1

先扫描任务清单建立唯一ID集合与分组计数,再从结果映射读取每项状态。每个计划任务必须有且只有一个结果;清单重复ID、结果缺失和额外未知ID都拒绝。这样可以阻止用不完整日志计算看似精确的成功率,输入清洗错误不会被静默隐藏。

每完成一个任务,success是此前成功项数,infra是基础设施异常项数,每组数组保存成功和总数。遍历结束后整体比率按任务等权,宏平均按组等权;超时在这两项和条件成功率中都作为未成功保留。全部INFRA时条件分母为零,返回OptionalDouble.empty而非伪造零或一。

哈希容器的期望常数查找使期望时间为O(N+G),其中N为任务数、G为组数;额外ID集合和组表为O(N+G)。这个复杂度不包括生成或运行任务,也不保证恶意哈希分布下严格线性。统计汇总再快,也不能补救不一致的执行预算。

为什么这样写是对的已处理任务恰好一次计入对应组与全局计数,结果映射不允许缺失状态。

时间 期望O(N+G)额外空间 O(N+G)

扫描N条任务并汇总G个分组;依赖哈希集合与映射的平均查找成本。

保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。

展开收起代码 · Main.java · 完整可运行程序Java · 44 行

长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。

Main.java · 完整可运行程序 · Java
import java.util.*;
public class Main {
    static void near(double expected,double actual) {
        if (!Double.isFinite(actual) || Math.abs(expected-actual)>1e-9*Math.max(1,Math.abs(expected)))
            throw new AssertionError(expected+" != "+actual);
    }
    static void check(boolean ok) { if(!ok) throw new AssertionError(); }
    static void bad(Runnable r) {
        try { r.run(); } catch(IllegalArgumentException e) { return; }
        throw new AssertionError("invalid input accepted");
    }
enum Status {SUCCESS,FAILURE,TIMEOUT,INFRA}
    record Task(String id,String group) {}
    record Report(double operational,double macro,OptionalDouble validRate,int infrastructure) {}
    static Report report(List<Task> tasks,Map<String,Status> results) {
        if(tasks==null||tasks.isEmpty()||results==null) throw new IllegalArgumentException("empty");
        Set<String> ids=new HashSet<>();Map<String,int[]> groups=new HashMap<>();int success=0,infra=0;
        for(Task t:tasks) {
            if(t==null||t.id()==null||t.group()==null||t.id().isBlank()||t.group().isBlank()||!ids.add(t.id())) throw new IllegalArgumentException("task");
            Status s=results.get(t.id());if(s==null) throw new IllegalArgumentException("missing"); // 缺失日志不静默当作有效完成。
            int[] counts=groups.computeIfAbsent(t.group(),x->new int[2]);counts[1]++;
            if(s==Status.SUCCESS) {success++;counts[0]++;}
            if(s==Status.INFRA) infra++; // 基础设施异常单列,同时保留预声明任务总分母。
        }
        if(results.size()!=ids.size()) throw new IllegalArgumentException("unknown task");
        double macro=0;for(int[] c:groups.values()) macro+=(double)c[0]/c[1]; // 先求每组比率再等权平均,与全体任务比例不同。
        int valid=tasks.size()-infra;
        return new Report((double)success/tasks.size(),macro/groups.size(),valid==0?OptionalDouble.empty():OptionalDouble.of((double)success/valid),infra);
    }
    public static void main(String[] args) {
List<Task> ts=List.of(new Task("a1","A"),new Task("a2","A"),new Task("a3","A"),new Task("b1","B"));
        Report r=report(ts,Map.of("a1",Status.SUCCESS,"a2",Status.FAILURE,"a3",Status.TIMEOUT,"b1",Status.SUCCESS));
        near(.5,r.operational());near(2.0/3,r.macro());near(.5,r.validRate().orElseThrow());
        Report i=report(List.of(new Task("a","G"),new Task("b","G")),Map.of("a",Status.SUCCESS,"b",Status.INFRA));
        near(.5,i.operational());near(1,i.validRate().orElseThrow());check(i.infrastructure()==1);
        check(report(List.of(new Task("a","G")),Map.of("a",Status.INFRA)).validRate().isEmpty());
        near(0,report(List.of(new Task("a","G")),Map.of("a",Status.TIMEOUT)).operational());
        bad(()->report(ts,Map.of()));
        bad(()->report(List.of(new Task("a","G")),Map.of("a",Status.SUCCESS,"x",Status.SUCCESS)));
        bad(()->report(List.of(new Task("a","G"),new Task("a","H")),Map.of("a",Status.SUCCESS)));
        bad(()->report(List.of(),Map.of()));
        System.out.println("PASS 8 cases");
    }
}

这些特殊情况也要试一试

  • 全部INFRA返回空条件指标
  • 超时保留在失败分母
  • 缺失和额外ID拒绝
  • 重复ID拒绝
进阶选读 · 并行可执行基准的隔离与成本对照

场景任务可能运行不受信任代码、耗尽资源或污染后续环境。

做法使用独立受限运行环境、固定镜像和数据快照,实施墙钟与资源上限,结束后回收;并行度和重试协议纳入实验配置。

复杂度N次执行耗时取决于单题时长与并行度,调度隔离和镜像准备需另外测量,不能由O(N)汇总算法推断吞吐。

好处与代价隔离提高可控性但增加启动成本;真实安全边界需要容器或更强机制评估,普通字典与子进程不是完整沙箱。

05项目里怎么用

建立自有基准时先从少量人工核对的任务开始,确认失败版本确实失败、参考修复确实成功、回归条件确实能抓住破坏。之后再扩大规模,避免一千个错误标签比十个正确任务更“客观”的错觉。记录任务来源和覆盖边界,固定主指标及预算后再比较方法。

要把执行层日志与模型可见观察分开存放。隐藏检查可用于最终评价,但不应在调参循环中不断反馈具体答案;开发集可反复调试,最终保留集只用于确定配置后的评价。公开测试的通过情况可作为反馈,仍需防止修改测试或利用检查器漏洞绕过目标。

报告中要同时列出原计划测试多少题、实际跑了多少题、各类结束原因,以及总体和分组指标。若基础设施总在难题上出故障,只统计跑完的题,就可能让成功率偏高,不能拿它代表全部任务能力。本篇仅验证合成状态机和汇总公式;真实环境能否恢复、是否隔离,以及模型调用是否可靠,还需分别验收。

容易踩的坑

  • 代理自述成功替代环境检查
  • 每轮未重置导致状态污染
  • 只跑修复测试忽略回归
  • 删掉超时和异常后夸大成功率
  • 把测试全过说成完整语义证明

几种做法,怎么选

表格有四列,窄屏可左右滑动查看代价和适用条件。

方案优势代价与限制适用条件
纯文本评价成本低、易扩展不能证明动作生效解释和生成质量初筛
可执行任务结果有直接状态证据环境与oracle维护复杂代码和业务操作评估
人工复核加自动检查能发现检查器盲区成本高且需一致标准高价值失败分析与标签审计

06检验是否读懂了

先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。

先用自己的话解释

01为什么检查最终环境?

展开参考答案与推导

可执行任务要求状态变化,语言承诺本身不证明动作生效。

02宏平均为何不同?

展开参考答案与推导

它给每个分组相同权重,整体比例给每个任务相同权重。

03全部基础设施异常能算能力零吗?

展开参考答案与推导

本协议的条件能力指标不可计算,应报告无有效试验;完整交付比例可为零但含义不同。

换个情况,还会处理吗

01只修新测试破坏旧测试?

展开参考答案与推导

判为未满足完整要求,同时记录修复与回归结果帮助定位。

02一半任务日志缺失?

展开参考答案与推导

先拒绝聚合并恢复完整记录,不能将剩余任务默认代表总体。

03基准分数很好却真实工作差?

展开参考答案与推导

检查任务覆盖、污染、预算和oracle漏洞,并在目标场景建立新保留集。

写代码试一试

01计算宏微指标。

输入 / 条件
A组三题1成功,B组一题1成功
输出 / 预期
微.5,宏2/3

提示 先分组再平均

展开参考答案与推导

组成功率分别1/3和1,再求等权均值。

02处理基础设施分母。

输入 / 条件
一成功一INFRA
输出 / 预期
完整.5,条件1且异常1

提示 同时报告覆盖

展开参考答案与推导

不静默删除异常,也不将两种指标混名。

03阻止自述作弊。

输入 / 条件
动作只包含claim_success
输出 / 预期
FAILURE

提示 最终状态oracle

展开参考答案与推导

忽略声明,根据总额和币种检查目标。

读完之后,独立完成

课后作业:自己做一遍

本篇共 12 道作业,先完成 9 道基础与综合任务,再按前置知识挑战进阶题。其中包含 2 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。

这次练什么

运行修复与回归检查的小环境,Java拒绝缺失或重复试验记录并计算两种聚合指标。

本篇重点加练

独立判定、异常归因和隔离决定基准可信度,重点测试高分是否依赖验收漏洞。

  • 独立成功oracle
  • 基础设施异常
  • 测试泄漏与隔离

重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。

先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。

基础必做

  1. 作业 01原理问答

    为一个可在临时目录运行的文件修复任务定义初始快照、提交物、独立成功 oracle 和回归检查,列出代理自述不能替代的证据。

    提交内容
    任务规格与验收表
    怎样算完成
    • oracle 独立于代理叙述
    • 旧功能回归包含在内

    练到的知识点任务与环境快照 · 独立成功oracle · 修复与回归检查

  2. 作业 02代码推演

    自建六次运行记录,包含成功、功能失败、预算耗尽、环境启动失败与日志缺失;声明每类如何进入各报告分母。

    提交内容
    结果账本与分母表
    怎样算完成
    • 未知与失败区分
    • 未完成运行没有被静默删除

    练到的知识点重置及种子 · 终止和预算耗尽 · 基础设施异常

  3. 作业 03原理问答

    同一任务存在多个合法实现。自行构造两个外观不同但满足需求的提交,审查结果判定器(oracle)是否把参考实现的细节误当成任务要求。

    提交内容
    合法实现对与结果判定器(oracle)审阅
    怎样算完成
    • 先规定功能要求,再考虑实现形式
    • 误拒绝案例可执行

    练到的知识点独立成功oracle

综合应用

  1. 作业 04编程实践

    实现 CPU 基准执行器,在专用临时目录内重置三个小任务并执行只读验收;保存环境版本、种子和产物摘要。

    提交内容
    执行器与重复运行报告
    怎样算完成
    • 任务间状态隔离
    • 临时路径与超时边界明确

    练到的知识点任务与环境快照 · 独立成功oracle · 重置及种子

  2. 作业 05排错实验

    向基准注入过宽测试、残留文件和可读取标准答案三类漏洞,用反例检查原有高分是否可信。

    提交内容
    故障实验与修补验证
    怎样算完成
    • 成功判定能识别作弊
    • 修补保留合法解覆盖

    练到的知识点修复与回归检查 · 基础设施异常 · 测试泄漏与隔离

  3. 作业 06面试场景

    两个任务家族的题数分别为 3 和 9,各方法成功分布不同。自建逐题结果,比较微平均与宏平均并写出适用目标。

    提交内容
    逐题数据与两种统计
    怎样算完成
    • 任务家族权重公开
    • 样本量差异不被掩盖

    练到的知识点基础设施异常 · 微平均与宏平均

  4. 作业 07编程实践

    实现 CPU oracle 自测集,包含正确解、局部错误解、超时及验收器自身异常,输出任务结果与评价器健康状态两个字段。

    提交内容
    自测程序与分类记录
    怎样算完成
    • 任务失败与判定失败区分
    • 已知样例独立于被测代理

    练到的知识点独立成功oracle · 基础设施异常

  5. 作业 08排错实验

    让两个临时目录任务共享一个缓存文件,注入前一任务残留答案,比较隔离前后的评价;所有测试仅使用自建无敏感数据。

    提交内容
    污染复现及隔离验证
    怎样算完成
    • 数据读写路径可追踪
    • 状态重置验证实际发生

    练到的知识点基础设施异常 · 测试泄漏与隔离

  6. 作业 09设计评审

    基准允许代理看到单元测试,但要求隐藏最终验收。设计公开与保留测试的职责边界,说明哪些修改属于合理修复、哪些会破坏评价身份。

    提交内容
    测试资产清单与访问契约
    怎样算完成
    • 公开信息合法范围明确
    • 最终 oracle 身份稳定

    练到的知识点独立成功oracle · 测试泄漏与隔离

进阶挑战 · 掌握前置后选做

  1. 作业 10设计评审

    制定基准升级后的可比性协议,包括旧新环境交叉运行、任务变更记录和保留集隔离;明确何时不能沿用历史排名。

    提交内容
    版本比较协议
    怎样算完成
    • 难度和环境变化分开
    • 结论依据可审计

    练到的知识点任务与环境快照 · 终止和预算耗尽 · 测试泄漏与隔离

思维拓展

先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。

  1. 作业 11思维拓展 · 条件变化

    如果 oracle 本身有小概率误判,任务成功率与模型能力的关系如何变化?自建已知真实标签的 CPU 模拟,比较不同误判率下的观测。

    提交内容
    误判模拟与识别边界
    怎样算完成
    • 真实标签与观测标签分开
    • 无法辨识参数明确列出

    练到的知识点独立成功oracle · 基础设施异常

  2. 作业 12思维拓展 · 跨域迁移

    把基准隔离迁移到编译器测试或数据库压测:缓存热身、残留状态与系统故障分别怎样影响可比性?选择一个领域做最小对照方案。

    提交内容
    迁移设计与证据清单
    怎样算完成
    • 状态因素具体可控
    • 业务错误与环境错误分开

    练到的知识点基础设施异常 · 测试泄漏与隔离

完成后,再检验一次

给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。

继续阅读

先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。

打开资料库,找下一份学习材料 ↗