Prhub

#3084 fix(verifiers): cap renderers before pool API removal

原始 PR 作者 guapisolo 合并时间 2026-09-04 11:40 文件变更 3 提交数 2 评论 4 代码增减 +45 / -11

执行摘要

修复 Verifiers 适配器因 Renderers 0.1.11 移除 pool API 导致的启动超时问题。

修复因依赖版本不兼容导致的 CI 超时。根据 PR body,stage-c-2-gpu-h200 上的测试运行在安装了 renderers==0.1.12.dev2 后超时,因为 Renderers 0.1.11 移除了 RendererPool API。根因是 requirements.txt 仅在特定环境中安装时起约束作用,而 _import_verifiers() 在适配器启动时只检查了 Renderers 的最低版本,未对上限进行检查,导致不兼容版本的导入错误发生在 episode 执行内部的 resampling 阶段,最终引发超时。

此 PR 是一个高质量的 bugfix,值得精读。其价值不仅在于修复了具体的 CI 问题,更在于展示了如何通过「依赖约束 + 运行时版本守卫」的组合策略,来健壮地应对上游依赖的破坏性变更。推荐关注以下设计决策:

1) 如何将具体的错误原因(maximum_reason)与通用的版本检查函数结合;
2) 测试如何从直接测试内部函数转向测试公共入口点以验证集成行为。修复逻辑清晰,测试充分,对现有代码的侵入性低。

讨论亮点

核心讨论聚焦于如何提供更全面的保护。Reviewer nblintao 在 requirements.txt 文件的评论中指出:"This only guards the venv install path — suggest also passing maximum to the renderers _check_version in verifiers_rollout.py, so a host env with a newer renderers fails loud instead of resampling until timeout." 这指出了仅修改 requirements.txt 的局限性,并建议在代码层面实施防御。作者 guapisolo 在后续提交中响应了该建议,实现了相同的上限检查,并说明:"Apply the same upper bound during adapter startup so incompatible host installs fail before episode resampling, while preserving package-specific compatibility diagnostics." 这表明团队对"防御性启动检查"与"及时反馈"的重视。

实现拆解

  1. 文件与常量更新:在 examples/experimental/verifiers/requirements.txt 中,将 renderers 的依赖约束从 >=0.1.8 修改为 >=0.1.8,<0.1.11,并添加注释说明上限原因。在核心模块 examples/experimental/verifiers/verifiers_rollout.py 中,新增模块级常量 _MAX_RENDERERS_VERSION = Version("0.1.11")。
  2. 版本检查逻辑增强:修改核心的 _check_version 函数,为其添加 maximum_reason: str | None = None 关键字参数,使版本上限不满足时的错误信息更具体、更可诊断。
  3. 适配器启动时强制检查:在 _import_verifiers() 函数中,对 renderers 包的调用从仅检查最低版本 (_MIN_RENDERERS_VERSION) 改为同时检查最高版本 (_MAX_RENDERERS_VERSION),并传递具体的 maximum_reason 字符串。这确保了即使在不受 requirements.txt 管理的主机环境中,不兼容的 Renderers 版本也会在适配器构造阶段立即被拒绝。
  4. 测试覆盖强化:更新测试文件 tests/fast/examples/experimental/verifiers/test_verifiers_rollout.py。新增测试 test_renderers_0_1_11_is_rejected_with_compatibility_reason,通过 monkeypatch 模拟安装了 Renderers 0.1.11 的环境,验证 _import_verifiers() 会抛出包含特定原因消息的 RuntimeError。同时,重构了已有测试 test_verifiers_0_2_1_is_rejected_with_compatibility_reason,使其也通过 monkeypatch 调用完整的 _import_verifiers 入口,从而测试集成后的版本检查逻辑。
文件 模块 状态 重要度
examples/experimental/verifiers/verifiers_rollout.py 适配器 modified 6.92
tests/fast/examples/experimental/verifiers/test_verifiers_rollout.py 测试 modified 6.01
examples/experimental/verifiers/requirements.txt 示例依赖 modified 1.82

关键符号

_check_version _import_verifiers

关键源码片段

examples/experimental/verifiers/verifiers_rollout.py core-logic

核心源码文件,修改了版本检查逻辑 `_check_version` 和适配器入口 `_import_verifiers`,实施了对 Renderers 版本上限的运行时强制检查。

# 在 _check_version 函数中新增 maximum_reason 参数,使错误信息更具诊断性
def _check_version(
    package: str,
    raw_version: str,
    minimum: Version,
    maximum: Version | None = None,
    *,
    maximum_reason: str | None = None,
) -> None:
    try:
        installed = Version(raw_version)
    except InvalidVersion as error:
        raise RuntimeError(f"Could not parse installed {package} version {raw_version!r}.") from error
    if installed < minimum:
        raise RuntimeError(f"Verifiers rollouts require {package}>={minimum}; found {installed}.")
    if maximum is not None and installed >= maximum:
        # 构造包含具体原因的错误消息
        message = f"Verifiers rollouts require {package}>={minimum},<{maximum}; found {installed}."
        if maximum_reason is not None:
            message += f" {maximum_reason}"
        raise RuntimeError(message)
​
# 在 _import_verifiers 中,对 renderers 包的检查增加了上限和具体原因
def _import_verifiers():
    # ... ( 省略 verifiers 版本检查 )
    _check_version(
        "renderers",
        _installed_version("renderers"),
        _MIN_RENDERERS_VERSION,
        _MAX_RENDERERS_VERSION,
        # 提供具体的上限原因,指向 Renderers 0.1.11 的 breaking change
        maximum_reason="Renderers 0.1.11 removes the renderer pool APIs used by Miles and Verifiers 0.2.0.",
    )
    # ... ( 后续导入逻辑 )
tests/fast/examples/experimental/verifiers/test_verifiers_rollout.py test-coverage

关键测试文件,新增并重构了版本检查测试,验证了对 Renderers 0.1.11 不兼容版本的拒绝逻辑,确保修复有效。

# 新增测试:验证 Renderers 0.1.11 会在适配器启动时被拒绝,并给出特定原因
def test_renderers_0_1_11_is_rejected_with_compatibility_reason(monkeypatch):
    # 模拟安装了 verifiers 0.2.0 和 renderers 0.1.11 的环境
    versions = {"verifiers": "0.2.0", "renderers": "0.1.11"}
    # monkeypatch 内部版本查询函数,返回模拟的版本字符串
    monkeypatch.setattr(
        "examples.experimental.verifiers.verifiers_rollout._installed_version",
        versions.__getitem__,
    )
​
    # 调用完整的适配器入口函数,并断言其抛出预期的 RuntimeError
    with pytest.raises(
        RuntimeError,
        match=r"renderers>=0\.1\.8,<0\.1\.11.*removes the renderer pool APIs",
    ):
        _import_verifiers()

评论区精华

对版本检查范围的建议 正确性

Reviewer **nblintao** 在评论中指出 `requirements.txt` 的修改仅保护了通过该文件安装的虚拟环境(venv install path),并建议同时在 `verifiers_rollout.py` 的 `_check_version` 调用中传递 `maximum` 参数,以确保在不受管理的主机环境(host env)中遇到更新版本的 Renderers 时能立即失败,而不是进入后续的 resampling 循环直至超时。

结论:建议被采纳。作者在后续提交中实现了运行时版本检查,使不兼容的主机环境在适配器构造阶段就失败。 · 已解决

风险与影响

  1. 兼容性限制:对 Renderers 设置 <0.1.11 的硬性上限意味着未来 Renderers 的新版本将无法与当前的 Verifiers 适配器配合使用,需要依赖上游 API 恢复或 Miles 侧进行适配迁移。Review 中已创建 Issue #3088 来跟踪此上限的移除。
  2. 版本检查路径:_import_verifiers() 是适配器的主要入口点,版本检查在此处进行是合理的。但如果存在其他代码路径直接导入 renderers(如 _renderer_identity 中的 from renderers.base import MODEL_RENDERER_MAP),则可能绕过此检查。不过,由于 _import_verifiers() 是适配器的唯一入口,风险较低。
  3. 测试隔离:测试通过 monkeypatch _installed_version 函数来模拟不同版本,这有效验证了版本检查逻辑。但 monkeypatch 的目标是模块内部函数,属于实现细节测试,如果未来重构 _import_verifiers 的内部调用,测试可能需要同步更新。
  1. 用户影响:对于使用 Verifiers 实验性功能的用户,此变更确保了他们在运行 Rollout 前会收到明确的版本不兼容错误,而不是遇到难以调试的超时或 API 缺失异常。这提升了故障排查体验。
  2. 系统影响:修复了 CI 的一个阻塞性问题,保证了 stage-c-2-gpu-h200 上与 Verifiers 相关的 E2E 测试套件能够正常运行。
  3. 团队影响:展示了处理第三方依赖破坏性变更的标准流程:1)在依赖声明中设置版本上限以保护新安装;2)在运行时检查版本并给出清晰错误信息以保护现有环境。这为处理类似问题提供了范例。
依赖版本上限 上游 API 不兼容

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论