Prhub

#2228 examples: make the agent-server trial timeout configurable

原始 PR 作者 Shi-Dong 合并时间 2026-08-07 05:56 文件变更 2 提交数 1 评论 0 代码增减 +24 / -2

执行摘要

agent-server 调用超时改为可配置,默认 7200s 修复超时倒置

PR body 明确指出:swe_agent_function.run 硬编码 3600s 天花板,而 README 都建议用 --agent-timeout 5400 启动 agent server,且 httpx 客户端 timeout=None,因此 3600s 就是每个 trial 的实际上限,任何高于 3600s 的 server 配置都不生效。该倒置的代价不只是常量名错位:60-90 分钟的 trial 会被 trainer 记为 aborted 并返回 None;agent server 仍继续运行该 trial,沙箱和 --max-concurrent 槽位最多再被占用 30 分钟,在配额受限的沙箱后端会推高后续 trial 的创建失败率;更严重的是 aborted-sample 动态过滤器会丢弃该 sample 所属的整个 GRPO group,在 --rollout-batch-size 4 时一次迟到 trial 就移除该 step 四分之一的梯度信号。问题由 nblintao 在评审 #1919 时报告。

值得精读:虽然只是 2 个文件的示例级修复,但 PR body 对超时倒置的因果链分析(客户端先超时 → trial 记 aborted → 沙箱与并发槽位继续占用 → 整个 GRPO group 被过滤 → 梯度信号损失)非常清晰,是理解 agentic rollout 跨层故障代价的好材料。值得关注的设计决策:权威超时与兜底超时的分层、通过 --extra-env-vars 向 rollout actor 透传配置的通用手法。可结合 #919 与 #2233(同目录后续重命名)一起看示例线的演进。

讨论亮点

该 PR 没有评论线程,唯一 review 来自 nblintao(APPROVED,无正文)。核心问题由 nblintao 在评审 #1919 时发现并报告到 PR body 中:

"swe_agent_function.run hardcoded a 3600 s ceiling on the agent-server /run call, while both READMEs start the agent server with --agent-timeout 5400 ... any --agent-timeout above 3600 was dead configuration."
"At --rollout-batch-size 4 one late trial removes a quarter of the step's gradient signal."

PR body 还明确了设计决策:server 自己的 --agent-timeout 应该先触发并自行清理 trial/沙箱,客户端超时只是针对 server 失联的兜底,因此默认值 7200s 必须高于示例的 5400s。这个层级关系此前被 README 中"keep --agent-timeout generous"的建议直接矛盾,本次一并修正。

实现拆解

变更共 2 个文件、+24/-2,单一 commit,无 review 评论,属于小范围示例修复。

  1. 新增默认常量与读取函数:在 examples/swe-agent/swe_agent_function.py 中新增 _DEFAULT_AGENT_TRIAL_TIMEOUT_S = 7200 和 _agent_trial_timeout_s(),后者从 AGENT_TRIAL_TIMEOUT 环境变量读取,未设置时回退到 7200s。
  2. 替换 run() 中的硬编码超时:在发起 _post_agent_server 调用前先读取 trial_timeout_s = _agent_trial_timeout_s(),再传给 asyncio.wait_for 的 timeout 参数;超时日志从写死的 "3600s" 改为动态值。设计上客户端超时只作为 agent server 失联时的兜底(backstop),正常情况下 server 的 --agent-timeout 应先行触发。
  3. 更新 README 文档:在 examples/swe-agent/README.md 中补充两级超时的顺序要求——--agent-timeout 是权威超时,触发后 server 结束 trial 并释放沙箱;客户端 AGENT_TRIAL_TIMEOUT(默认 7200s)必须高于它,否则客户端先超时会记录 aborted 且 sandbox 与并发槽位继续被占用、整个 GRPO group 被过滤。文档给出通过 launcher 通用环境变量钩子透传的用法:--extra-env-vars 'AGENT_TRIAL_TIMEOUT=10800',无需新增专用启动参数。
  4. 测试配套:无新增测试文件。作者在测试计划中验证了 black/isort 格式、AGENT_TRIAL_TIMEOUT 未设置/不同取值下的解析,以及 --extra-env-vars 能正确透传到 rollout actor;但未对真实 agent server 做端到端验证,因为触发超时路径需要超过两小时的 trial。作者也明确说明默认值是对原硬编码值的严格放宽,现有运行只会停止误 abort。
文件 模块 状态 重要度
examples/swe-agent/swe_agent_function.py 智能体 modified 6.22
examples/swe-agent/README.md 示例文档 modified 2.83

关键符号

_agent_trial_timeout_s run

关键源码片段

examples/swe-agent/swe_agent_function.py core-logic

核心变更文件:将 run() 中对 agent-server /run 调用硬编码的 3600s 超时替换为 AGENT_TRIAL_TIMEOUT 环境变量(默认 7200s),并新增 _DEFAULT_AGENT_TRIAL_TIMEOUT_S 与 _agent_trial_timeout_s() 两个符号。这是修复超时倒置、避免 trial 被误 abort 的关键逻辑所在。

import asyncio
import logging
import oslogger = logging.getLogger(__name__)# 客户端超时只是兜底(backstop):正常情况下 agent server 自己的
# --agent-timeout 会先触发,由 server 主动结束 trial 并释放 sandbox。
# 所以默认值 7200s 必须高于示例中 agent server 的 5400s 配置,
# 否则任何高于客户端上限的 --agent-timeout 都会成为死配置:客户端先超时,
# 把正常 trial 误判为 aborted,并拖垮整个 GRPO group。
_DEFAULT_AGENT_TRIAL_TIMEOUT_S = 7200
​
​
def _agent_trial_timeout_s() -> int:
    """每个 trial 调用 agent server /run 的超时上限,可通过 AGENT_TRIAL_TIMEOUT 环境变量覆盖。    未设置时回退到 7200s;若设置为非数字,int() 会抛 ValueError,
    让调用方尽早暴露配置错误。
    """
    return int(os.environ.get("AGENT_TRIAL_TIMEOUT", _DEFAULT_AGENT_TRIAL_TIMEOUT_S))
​
​
async def run(base_url: str, prompt, request_kwargs=None, metadata=None):
    ... # 构造 agent_server_url / request 等前置逻辑省略
​
    # 在 try 块之前读取本次调用的超时值
    trial_timeout_s = _agent_trial_timeout_s()
    try:
        response = await asyncio.wait_for(
            _post_agent_server(f"{agent_server_url}/run", request),
            timeout=trial_timeout_s,
        )
    except asyncio.TimeoutError:
        # 超时后 trainer 会把该 sample 记为 aborted,aborted-sample 过滤器
        # 会丢弃它所属的整个 GRPO group;因此客户端超时必须高于 server 端
        # --agent-timeout,让 server 先自行清理并释放沙箱。
        logger.error(f"Agent server call timed out after {trial_timeout_s}s")
        return None
    except asyncio.CancelledError:
        logger.warning("Agent server call cancelled (sibling task failure?)")
        return None
    except Exception as e:
        logger.error(f"Agent server call failed: {e}")
        return None
​
    return {
        "reward": response.get("reward", 0.0),
        "exit_status": response.get("exit_status", ""),
        "eval_report": response.get("eval_report", {}),
        "agent_metrics": response.get("agent_metrics", {}),
    }

评论区精华

客户端 3600s 硬编码与 server --agent-timeout 5400 的倒置 正确性

nblintao 在评审 #1919 时报告:swe_agent_function.run 对 agent-server /run 硬编码 3600s 超时,而 README 指导 --agent-timeout 5400,实际任何高于 3600s 的 server 配置都不生效;60-90 分钟的 trial 被 trainer 记为 aborted,且其整个 GRPO group 会被动态过滤器丢弃。

结论:客户端超时改为 AGENT_TRIAL_TIMEOUT 环境变量,默认 7200s 高于 5400s;README 明确 --agent-timeout 为权威超时、客户端为兜底,并通过 --extra-env-vars 透传。 · 已解决

风险与影响

  1. 环境变量非法值:AGENT_TRIAL_TIMEOUT 在 _agent_trial_timeout_s() 中通过 int() 转换且读取位置在 try 块之外,若用户误设为非数字(如 abc),ValueError 会向上抛出而不是被 run() 的 except Exception 捕获返回 None,可能导致 rollout 调用直接失败。
  2. 默认等待时间放宽:客户端超时从 3600s 放宽到 7200s,若 agent server 失联,客户端最长等待增加一小时,拖尾 trial 对训练进度的阻塞和沙箱/并发槽位占用时间变长;不过正常路径由 server 5400s 先行触发,实际影响可控。
  3. 无自动化测试覆盖:超时路径依赖真实 agent server 和超过两小时的 trial,CI 无法覆盖;本次变更也没有新增任何测试文件。
  4. 影响范围有限:变更只在 examples 目录,不触及 miles 核心训练/rollout 代码。
  • 用户侧:使用 swe-agent 示例做 agentic RL 训练的用户不再因客户端 3600s 硬编码而误 abort 60-90 分钟的正常 trial,GRPO group 不再被整组误过滤,训练梯度信号质量提升;沙箱配额释放也更符合预期。
  • 系统侧:客户端超时上限放宽到 2 小时,最坏情况下 rollout actor 等待时间变长,但正常路径由 server 侧 --agent-timeout(5400s)主导,两级超时的职责划分更清晰。
  • 团队侧:README 明确了 "server 权威超时 + client 兜底超时" 的层级要求,并通过 --extra-env-vars 展示了一种免新增启动参数的通用配置透传模式,避免后续示例再次引入同类 dead configuration。
示例目录变更,无自动化测试覆盖 AGENT_TRIAL_TIMEOUT 非数字时 int() 异常位于 try 之外 默认超时放宽至 7200s,失联时最坏等待变长

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论