
openai-agents-python 沙箱重试机制全解析深入agents.sandbox.util.retry模块源码与实战【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python本文以 openai-agents-python 仓库中沙箱sandbox子系统的重试工具模块agents.sandbox.util.retry为核心完整讲解其设计动机、核心 API、退避backoff算法、异常链判定工具以及它在 Docker、Blaxel、E2B、Modal、Vercel 等多家沙箱提供方实现中的真实用法。读完本文你将能够理解该框架如何处理瞬时故障transient failure并能在自己的异步代码中复用它提供的retry_async装饰器与异常判定工具。该模块的 API 参考文档位于 docs/ref/sandbox/util/retry.md其源码实现位于 src/agents/sandbox/util/retry.py对应的单元测试位于 tests/sandbox/test_retry.py。文档参考页由 docs/scripts/generate_ref_files.py 自动生成即# \Retry标题加::: agents.sandbox.util.retry 的 mkdocstrings 指令真正的技术细节全部沉淀在源码模块中本文即以此为据展开。为什么沙箱子系统需要一个独立的重试模块在 openai-agents-python 中沙箱Sandbox用于为 Agent 提供隔离、可复现、可快照的执行环境涉及 Docker 本地沙箱以及 Blaxel、Cloudflare、Daytona、E2B、Modal、Runloop、Vercel 等远程托管沙箱提供方对应src/agents/extensions/sandbox/下的各子目录。这类场景有两个显著特点外部依赖多创建沙箱、持久化工作区persist_workspace、打包上传 tar 归档等操作都依赖网络与远端服务瞬时故障不可避免HTTP 5xx、网关超时、asyncio.TimeoutError、SDK 抛出的aiohttp.ClientError等往往是短暂的、重试即可恢复的。如果每次遇到这类故障就直接失败Agent 的执行体验会很不稳定。因此框架把一套可配置的重试逻辑 异常链判定工具抽成独立模块src/agents/sandbox/util/retry.py供各沙箱提供方复用。从源码结构看重试策略什么时候重试、间隔多久、最多几次、退避方式被设计为可插拔的装饰器参数而异常到底算不算瞬时故障则由各提供方通过 lambda 自由定义。核心 API 一览整个模块只依赖标准库asyncio、functools、inspect、enum不引入任何第三方依赖所有公开符号如下符号类型作用BackoffStrategystr, Enum退避策略枚举FIXED/LINEAR/EXPONENTIALDEFAULT_TRANSIENT_RETRY_INTERVAL_Sfloat默认重试间隔0.25秒DEFAULT_TRANSIENT_RETRY_MAX_ATTEMPTint默认最大尝试次数3DEFAULT_TRANSIENT_RETRY_BACKOFFBackoffStrategy默认退避策略EXPONENTIALTRANSIENT_HTTP_STATUS_CODESfrozenset[int]视为瞬时故障的 HTTP 状态码集合{500, 502, 503, 504}iter_exception_chain(exc)生成器沿__cause__/__context__遍历整条异常链防环exception_chain_contains_type(exc, types)bool异常链中是否存在指定类型的异常exception_chain_has_status_code(exc, codes)bool异常链中是否存在携带指定 HTTP 状态码的异常retry_async(...)装饰器为异步函数注入重试逻辑其中BackoffStrategy继承自str, Enum并重写了__str__返回枚举值本身src/agents/sandbox/util/retry.py#L14-L20因此str(BackoffStrategy.EXPONENTIAL)得到字符串exponential方便序列化与日志输出——这一点在单元测试test_retry_async_retries_with_expected_backoff_and_async_hook中也有断言覆盖。retry_async装饰器参数语义与校验规则retry_async的完整签名src/agents/sandbox/util/retry.py#L65-L75如下def retry_async( *, interval: float DEFAULT_TRANSIENT_RETRY_INTERVAL_S, max_attempt: int DEFAULT_TRANSIENT_RETRY_MAX_ATTEMPT, backoff: BackoffStrategy DEFAULT_TRANSIENT_RETRY_BACKOFF, retry_if: Callable[..., bool], on_retry: Callable[..., object] | None None, ) - ...注意所有参数都是关键字参数*其中retry_if为必填。各参数语义interval默认0.25秒基础重试间隔也是 FIXED 策略下的固定等待时长max_attempt默认3最大尝试次数含首次调用即最多失败max_attempt - 1次backoff默认EXPONENTIAL退避策略决定每次重试前的等待时长如何随尝试次数增长retry_if判定函数形如retry_if(exc, *args, **kwargs)接收捕获到的异常以及被装饰函数的原始参数返回True表示该异常属于可重试的瞬时故障on_retry可选回调在每次决定重试之后、asyncio.sleep之前被调用形如on_retry(exc, attempt, max_attempt, delay_s, *args, **kwargs)它可以是普通函数也可以是协程函数框架通过inspect.isawaitable识别并await。参数校验装饰器在创建阶段即做三组校验src/agents/sandbox/util/retry.py#L83-L95非法配置直接抛ValueErrorif max_attempt 1: raise ValueError(max_attempt must be 1) if interval 0: raise ValueError(interval must be 0) if backoff not in {FIXED, LINEAR, EXPONENTIAL}: raise ValueError(backoff must be BackoffStrategy.FIXED, ...)对应的测试test_retry_async_validates_configurationtests/sandbox/test_retry.py分别验证了max_attempt0、interval-1以及传入非法枚举值quadratic三种情况都会抛出带相应消息的ValueError。重试循环与退避算法被装饰的函数被替换为如下循环逻辑src/agents/sandbox/util/retry.py#L100-L125for attempt in range(1, max_attempt 1): try: return await fn(*args, **kwargs) except Exception as exc: if attempt max_attempt or not retry_if(exc, *args, **kwargs): raise if backoff is BackoffStrategy.EXPONENTIAL: delay_s interval * (2 ** (attempt - 1)) elif backoff is BackoffStrategy.LINEAR: delay_s interval * attempt else: delay_s interval if on_retry is not None: hook_result on_retry(exc, attempt, max_attempt, delay_s, *args, **kwargs) if inspect.isawaitable(hook_result): await hook_result await asyncio.sleep(delay_s)三种退避策略的计算方式模块 docstring 与实现一致策略第attempt次重试前等待时长说明FIXEDinterval恒定延迟每次都等同样长的时间LINEARinterval * attempt线性增长第一次重试等 1 倍间隔第二次等 2 倍依此类推EXPONENTIALinterval * 2 ** (attempt - 1)指数翻倍第 n 次重试等待2^(n-1)倍间隔对网络抖动最友好以interval0.5、max_attempt3为例三次尝试之间两次等待的时长测试test_retry_async_retries_with_expected_backoff_and_async_hook给出了精确断言tests/sandbox/test_retry.pyFIXED[0.5, 0.5]LINEAR[0.5, 1.0]EXPONENTIAL[0.5, 1.0]该测试用monkeypatch.setattr(asyncio, sleep, fake_sleep)把真实休眠替换成记录延迟的假函数从而在毫秒级验证了三种策略的等待序列同时验证了on_retry异步钩子被调用两次、参数为(attempt, max_attempt, delay_s)且最终装饰器返回成功结果ok:sandbox。值得注意的细节retry_if返回False时立即raise不会调用asyncio.sleep。测试test_retry_async_stops_without_sleep_when_retry_is_rejected用一旦 sleep 就抛AssertionError的假函数验证了这一点——函数只执行一次重试被判定拒绝后原异常原样抛出tests/sandbox/test_retry.py循环外的raise AssertionError(unreachable)是类型系统的收尾保护正常情况下不会执行装饰器使用functools.wraps(fn)保留被装饰函数的元信息__name__、__doc__等采用ParamSpec与TypeVar做类型标注保证装饰器不会破坏被装饰函数的调用签名类型检查。异常链判定工具穿透__cause__/__context__真实故障往往不是孤立异常SDK 抛出的aiohttp.ClientError可能被包装成带__cause__的SandboxError而 HTTP 状态码可能藏在异常的status_code、http_code或response.status_code属性里。因此模块提供了三个配套工具。iter_exception_chain安全的异常链遍历def iter_exception_chain(exc: BaseException) - Iterable[BaseException]: seen: set[int] set() current: BaseException | None exc while current is not None and id(current) not in seen: yield current seen.add(id(current)) current getattr(current, __cause__, None) or getattr(current, __context__, None)它沿__cause__优先或__context__逐层向上遍历整条异常链并用id()集合防止异常链出现环导致死循环。测试test_iter_exception_chain_supports_context_and_stops_on_cyclestests/sandbox/test_retry.py验证了两点外层异常的__context__指向内层异常时能依次遍历到两者构造互为 cause的环形链时遍历能正确终止且不重复。exception_chain_contains_type按类型判定def exception_chain_contains_type(exc, error_types) - bool: if not error_types: return False return any(isinstance(candidate, error_types) for candidate in iter_exception_chain(exc))空元组直接返回False否则对链上每个异常做isinstance判定。这解决了包装层异常类型不对、但根源异常类型正确的常见问题。exception_chain_has_status_code按 HTTP 状态码判定def exception_chain_has_status_code(exc, status_codes) - bool: for candidate in iter_exception_chain(exc): for value in ( getattr(candidate, status_code, None), getattr(candidate, http_code, None), getattr(getattr(candidate, response, None), status_code, None), ): if isinstance(value, int) and value in status_codes: return True return False它同时探测三种常见的状态码存放位置异常自身的status_code属性、http_code属性以及response.status_code兼容封装了 HTTP 响应对象的 SDK 异常。测试用自定义的_ErrorWithHttpMetadata分别构造了三种携带方式并验证500、502、504均能被识别、503不会被误判tests/sandbox/test_retry.py。实战用法如何在自己的异步代码中使用参考各沙箱提供方源码中的真实用法复用一个最小示例。假设你的函数会调用一个不稳定的远端接口import asyncio from agents.sandbox.util.retry import ( BackoffStrategy, exception_chain_contains_type, exception_chain_has_status_code, retry_async, TRANSIENT_HTTP_STATUS_CODES, ) retry_async( interval0.25, max_attempt3, backoffBackoffStrategy.EXPONENTIAL, retry_iflambda exc, *args, **kwargs: ( exception_chain_contains_type(exc, (asyncio.TimeoutError,)) or exception_chain_has_status_code(exc, TRANSIENT_HTTP_STATUS_CODES) ), on_retrylambda exc, attempt, max_attempt, delay_s, *args, **kwargs: ( print(fattempt {attempt}/{max_attempt} failed: {exc!r}, retrying in {delay_s}s) ), ) async def persist_snapshot(remote_url: str) - bytes: # ... 调用远端接口可能抛 TimeoutError 或携带 5xx 状态码的异常 ...要点归纳retry_if是要不要重试的唯一决策入口——它既决定是否重试也天然过滤掉业务性错误如 4xx、校验失败这类错误应直接上抛不做无意义的重试retry_if会收到被装饰函数的原始参数因此可以结合参数做更精细的判断例如根据目标环境决定是否重试on_retry支持同步与异步两种形态适合在重试前做日志、指标上报或清理工作默认值就是为瞬时故障调好的间隔 0.25 秒、最多 3 次、指数退避多数场景可直接采用。仓库中的真实应用场景该模块并非孤立的工具代码而是被沙箱子系统的多家提供方广泛复用可作为学习何时该重试的最佳范本Docker 本地沙箱src/agents/sandbox/sandboxes/docker.py中的persist_workspace方法使用retry_asyncretry_if仅依据exception_chain_has_status_code(exc, TRANSIENT_HTTP_STATUS_CODES)判定是否重试docker.py——即工作区持久化遇到 5xx 这类瞬时 HTTP 错误时自动重试Blaxelsrc/agents/extensions/sandbox/blaxel/sandbox.py的persist_workspace将asyncio.TimeoutError与瞬时 HTTP 状态码合并作为重试条件blaxel/sandbox.pyDaytonasrc/agents/extensions/sandbox/daytona/sandbox.py对持久化命令_run_persist_workspace_command同时检查可重试的提供方错误类型与瞬时 HTTP 状态码daytona/sandbox.pyE2Bsrc/agents/extensions/sandbox/e2b/sandbox.py在错误分类逻辑中直接用TRANSIENT_HTTP_STATUS_CODES判定 transient_http_status并结合exception_chain_contains_type识别可重试的提供方超时Modalsrc/agents/extensions/sandbox/modal/sandbox.py用iter_exception_chain逐层检查SandboxError.retryable标志再结合ExecTransportError类型与瞬时 HTTP 状态码综合判定modal/sandbox.py并用retry_async包装_persist_workspace_via_tarVercelsrc/agents/extensions/sandbox/vercel/sandbox.py定义_vercel_provider_retryability对提供方错误分类后用retry_async包装_create_sandbox_with_retry与文件写入操作vercel/sandbox.pyRunloopsrc/agents/extensions/sandbox/runloop/sandbox.py使用iter_exception_chain遍历异常链并匹配可重试错误类型集合。可以看到各家提供方的共同模式是把分类判定与重试执行分离——分类逻辑retry_iflambda可以非常复杂、因提供方而异而重试的节流、次数控制与退避计算统一由retry_async完成职责清晰、无重复代码。测试覆盖与质量保障tests/sandbox/test_retry.py是理解该模块行为契约的最佳入口覆盖了四类场景异常链遍历__context__链遍历、环形链防死循环test_iter_exception_chain_supports_context_and_stops_on_cycles判定工具类型匹配、三种 HTTP 状态码属性位置探测、负例不误判test_exception_chain_helpers_detect_types_and_status_codes配置校验三种非法参数均抛ValueErrortest_retry_async_validates_configuration重试行为三种退避策略的精确等待序列、异步on_retry钩子参数、重试被拒绝时不 sleep 且原样抛错两个pytest.mark.asyncio测试。这套测试使用monkeypatch替换asyncio.sleep使重试等待在测试中被短路既保证了测试速度又精确断言了延迟计算逻辑——这也是异步重试类代码值得借鉴的测试手法。总结与使用建议agents.sandbox.util.retry是 openai-agents-python 沙箱子系统的容错基础设施它以极小的 API 表面积一个枚举、三个默认常量、三个工具函数、一个装饰器为所有沙箱提供方统一解决了瞬时故障自动重试这一横切关注点。使用时的关键决策可以概括为三点选对重试条件用exception_chain_contains_type匹配超时/传输类异常用exception_chain_has_status_codeTRANSIENT_HTTP_STATUS_CODES匹配 5xx 瞬时错误不要对业务性错误重试选对退避策略网络抖动场景优先EXPONENTIAL默认需要稳定节奏可改用FIXED或LINEAR善用on_retry钩子在重试间隙输出日志或上报指标能显著提升故障可观测性。如果你正在为 Agent 沙箱、远端 SDK 调用或任何不可靠的外部依赖编写容错逻辑这个模块的源码与测试本身就是一份高质量的实现参考。【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考