Prhub

#2526 fix(swe-agent example): stop logging unmeasured agent metrics as zero

原始 PR 作者 Shi-Dong 合并时间 2026-08-14 06:01 文件变更 3 提交数 2 评论 1 代码增减 +116 / -1

执行摘要

SWE-agent 示例不再把未上报的 agent 指标记为 0

PR body 说明了两类失败模式:不同 agent harness 上报的键集不同,_collect_values 使用 m.get(key, 0) 后,只上报 wall-clock 总量的 agent 在 wandb 面板上出现 agent/tool_calls_mean = 0.0 等 12 个假零,而真实轨迹每条含 23–47 次 bash_command 调用,面板却显示 agent 从未调用工具;混合批次中单个样本缺键会把上报 47 次的样本均值拉低成 23.5。作者强调 A zero sitting next to real numbers reads as a measurement,假零会伪装成真实测量,这是必须修复的语义问题。

值得快速精读:核心逻辑只有 7 行改动,但把“缺失”与“真零”的语义区别处理得很干净,测试五场景覆盖完整,适合作为指标上报聚合的最佳实践模板;如果未来把 SWE-agent 示例的指标逻辑提升为通用 agent metrics 模块,本 PR 的缺失值处理方式可直接复用。示例使用者无需额外操作,重新拉取代码即可获得正确指标。

讨论亮点

唯一的 review 评论是作者 Shi-Dong 的自评,针对 _collect_values 的新 docstring:Shorten the explanation here. This is too long. Make it clear and concise.。作者随即在第二个提交中压缩注释,只保留“跳过未上报键、避免伪造测量”的核心说明。整个过程没有其它争议或未解决问题,nblintao 直接给出 APPROVED,说明修复逻辑本身得到认可,唯一打磨点在可读性。

实现拆解

  1. 修复聚合入口 _collect_valuesexamples/swe-agent-harbor-docker/generate.py):将 [m.get(key, 0) for m in all_metrics] 替换为带海象表达式的列表推导,只有当键存在且值不为 None 时才收集;agent 真正测得的值 0 仍会被保留并正常上报。
  2. 依托调用方已有的空列表跳过逻辑:_agg_mean 等聚合函数对空列表不产出统计项,因此 aggregate_agent_metrics 输出中未上报的键会整体消失,wandb 直接不显示对应 series,行为从“伪造零”变为“诚实的缺失”。
  3. 新增回归测试 tests/fast/examples/swe_agent_harbor_docker/test_agent_metrics.py:由于目录名含连字符不是合法 Python 标识符,测试通过 importlib.util.spec_from_file_location 按路径加载 generate.py;5 个用例分别验证全上报无回归、未上报键被省略、混合批次均值只统计上报者、真实 0 保留、无指标样本被忽略。作者验证修复前 2 个用例失败、修复后全部通过。
  4. 自审打磨:作者 self-review 指出新 docstring 过长后,第二个提交 Shorten the _collect_values docstring 完成精简,随后由 nblintao 批准合入。
文件 模块 状态 重要度
examples/swe-agent-harbor-docker/generate.py 示例脚本 modified 3.69
tests/fast/examples/swe_agent_harbor_docker/test_agent_metrics.py 指标测试 added 6.87
tests/fast/examples/swe_agent_harbor_docker/__init__.py 测试包 added 2.31

关键符号

_collect_values aggregate_agent_metrics _agg_mean sample_with test_mixed_batch_averages_only_over_agents_that_measured_the_key

关键源码片段

examples/swe-agent-harbor-docker/generate.py core-logic

核心修复点:`_collect_values` 从 `m.get(key, 0)` 兜底零改为跳过未上报键,消除假零与混合批次均值偏差,真实 0 仍保留。

def _collect_values(all_metrics: list[dict], key: str) -> list[float]:
    """Values agents reported for ``key``; agents that omit it are skipped.    Defaulting to 0 would log a fake measurement and drag the batch mean down.
    Callers skip empty lists, so an unreported key stays out of the log.
    """
    # 海象表达式同时完成取值与非 None 判断:
    # 只有键存在且值不为 None 时收集,agent 真正测到的 0 仍会被保留。
    return [value for metrics in all_metrics if (value := metrics.get(key)) is not None]
tests/fast/examples/swe_agent_harbor_docker/test_agent_metrics.py test-coverage

新增 5 个用例固化“缺失 ≠ 0”语义,覆盖全上报、仅总量、混合批次、真 0、空样本,是本次修复唯一的回归保障。

# FULLY_INSTRUMENTED 上报 13 个键(turns=30、tool_calls=47...);
# TOTALS_ONLY 只上报 3 个 wall-clock 键(eval_time、agent_run_time、total_time)。@pytest.fixture(scope="module")
def generate_module() -> ModuleType:
    # 目录名含连字符,不是合法 Python 标识符,因此像其它 example 测试
    # 一样用 importlib 按路径加载 generate.py。
    spec = importlib.util.spec_from_file_location("swe_agent_harbor_docker_generate", GENERATE_SCRIPT)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module
​
​
def sample_with(agent_metrics: dict) -> SimpleNamespace:
    # 构造带 metadata.agent_metrics 的最小样本,供聚合函数直接消费。
    return SimpleNamespace(metadata={"agent_metrics": agent_metrics})
​
​
def test_unreported_keys_are_omitted_rather_than_logged_as_zero(generate_module: ModuleType) -> None:
    # 0 与“未测量”不可混为一谈:未上报的键不应出现在日志里,
    # 否则会伪装成一次真实的零测量。
    metrics = generate_module.aggregate_agent_metrics([sample_with(TOTALS_ONLY)])
​
    for key in UNREPORTED_BY_TOTALS_ONLY:
        assert key not in metrics, f"{key} was logged despite never being measured"
​
    # 真正上报的键仍然正常输出。
    assert metrics["agent/eval_time_mean"] == pytest.approx(42.8)
    assert metrics["agent/agent_run_time_mean"] == pytest.approx(486.8)
    assert metrics["agent/total_time_mean"] == pytest.approx(559.3)
​
​
def test_mixed_batch_averages_only_over_agents_that_measured_the_key(generate_module: ModuleType) -> None:
    # 修复前:一个样本缺 tool_calls 会把 47 次调用拉低成 23.5;
    # 修复后:均值只对真正上报该键的 agent 计算。
    metrics = generate_module.aggregate_agent_metrics([sample_with(FULLY_INSTRUMENTED), sample_with(TOTALS_ONLY)])
​
    assert metrics["agent/tool_calls_mean"] == 47
    assert metrics["agent/turns_mean"] == 30
    assert metrics["agent/time_per_turn"] == pytest.approx(13.3)
    # 两个 agent 都上报的键,仍然按整个批次求均值。
    assert metrics["agent/total_time_mean"] == pytest.approx((500.0 + 559.3) / 2)

评论区精华

缩短 _collect_values 的 docstring style

作者自评:“Shorten the explanation here. This is too long. Make it clear and concise.” 指出新 docstring 过长,应更清晰简洁。

结论:第二个提交 `Shorten the _collect_values docstring` 完成精简,保留核心语义说明。 · 已解决

风险与影响

行为变更:aggregate_agent_metrics 输出中未上报键会消失,任何按固定键读取结果的消费方会触发 KeyError;当前 _agg_mean 等调用方已具备空列表跳过逻辑,未发现受影响的下游,但后续新增消费方需记住该语义。测试加载方式存在脆弱性:test_agent_metrics.py 通过 spec_from_file_location 按路径加载 generate.py,若脚本将来引入依赖同包相对导入的模块,此加载方式会失效,目前验证可正常加载。真实 0 语义依赖 is not None 判断,约定“None = 未测量”自洽;若未来 agent 用 NaN 表达缺失则不会被跳过,需要同步扩展。整体风险低,且影响范围仅限示例脚本。

直接受影响的是 examples/swe-agent-harbor-docker 的 wandb 指标输出:只报总时长的 agent 不再出现 12 个假零指标;完全插桩的 agent 全部 17 个指标前后完全一致;混合批次中 tool_calls_mean 从错误的 23.5 修正为 47.0。对使用该示例做 agentic RL / SWE-bench 类实验的团队,面板可读性和可信度提升。改动不触及 miles 核心训练、rollout、dashboard 代码,核心用户无感知;示例维护者获得 5 个可持续回归的测试用例。

指标键缺失属行为变更 下游消费方需容忍键消失 测试依赖按路径加载脚本

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论