OpenAI官方金融研究示例存在两条fail-open路径,运行正常结束但日志实际有误;通过六案例测试框架捕获并用单文件补丁修复,上游约10小时后响应。
当一个失败的 Agent 步骤看起来已经完成
多 Agent 研究工作流可能以一种安静的方式失败:某个步骤返回了无用内容,编排层继续推进,最终运行以一份成熟的报告收尾。日志里仍然记录了某些地方出了问题,但人们实际会看的部分——摘要、终端输出、退出路径——都没有体现。
我在 openai/openai-agents-js 的 commit 710cccfd8fd26b395f8e3470419852d76de80967 冻结版本上评估了 OpenAI 官方的金融研究示例。一个六用例测试框架捕获了两个 fail-open 路径。一份单文件补丁关闭了它们。上游在报告后约十小时修复了这个示例。这是该路径的案例研究——不是对 SDK 的批评,也不是关于这个模式在生产中发生频率的声明。
目标切片是 examples/financial-research-agent。该示例规划网络搜索,并发运行搜索 Agent,丢弃失败的结果,撰写结构化报告,向验证器请求 { verified, issues },最多修订两次,然后发出终端输出。
这里的所有结果都标记为 synthetic-orchestration:真实的 FinancialResearchManager 控制流运行,但模型和网络搜索边界是确定性伪造。不需要模型 API key。这使得证据廉价且可重复。这也意味着这不是模型质量基准测试、金融准确性研究、安全发现,也不是关于整个 Agents SDK 的证据。
六个用例覆盖:搜索聚合、部分搜索失败、完全搜索失败、首次草稿被接受、通过最终重试的报告,以及重试耗尽。
标准基线运行:4 通过,2 失败。
两种失败共享一种控制流模式。状态中存在否定前提条件,但没有守卫将该状态连接到终端报告路径:
完全搜索失败。两次搜索都失败。search() 将异常转为 null;performSearches() 将它们过滤掉;run() 仍然调用写入器。管理器报告成功并发出一份没有可用来源的报告。
验证耗尽。验证器拒绝初始报告和两次允许的修订。重试循环在两次尝试后正确停止,然后输出仍然继续。管理器发出了仍然被拒绝的报告。
基线通过的四个用例中有两个复现了示例自身附带测试(首次草稿被接受;通过最终重试)。在这四个用例中,这个拆解设计的基线因此是 2 通过 / 2 失败。标题中的 4/6 包括了两个复现用例。
单文件补丁之后:6/6
补丁只修改了 examples/financial-research-agent/manager.ts:
当零个可用搜索摘要剩余时,在写入器之前抛出异常;
在有界重试循环之后验证仍为负时,在终端输出之前抛出异常。
相同的语料库、相同的伪造、相同的控制流:
FR-003(所有搜索失败):基线调用了写入器并发出了报告;打补丁后在写入器之前以受控错误停止。
FR-006(重试预算耗尽):基线保留了三个负面验证和两次修订,然后仍然发出了报告;打补丁后保持相同的循环形状,但在发出之前以受控错误停止,而不是发出。
其他四个用例保持绿色。打补丁运行下,示例自身的两个重点测试仍然通过。
上游关闭了该路径
该行为被报告为 openai/openai-agents-js#1546。维护者 pull request #1546("在没有金融研究来源时 fail closed")在大约十小时后合并到 main 为 6483fef,并将会话标记为已完成。
拆解仓库中的复现有意固定在 710cccf——一个早于修复的提交——以便在代码继续演进后记录的基线仍然可检验。这里描述的路径已在当前 main 中关闭。
同一类问题——失败的步骤被当作已完成的——也出现在 google/adk-python 的定时维护 Agent 中。一位协作者复现了它;修复 pull request 仍在审核中。那是一个独立的冻结路径,不是关于 ADK 已有解决方案的证据。
无需 key 即可复现
npm ci
npm run verify
前置条件:Git、Node.js 22+、Corepack,以及用于初始克隆和锁定安装的网络访问。不需要模型 API key、付费 API 或数据库。在全新克隆上完整 gate 测量为 184 秒,其中包括克隆冻结目标;有了这个缓存的克隆后,后续三次运行测量分别为 90、117 和 119 秒。时间会随机器负载变化,因此将这些作为观察结果而非保证。
npm run verify 设置独立的冻结目标,复现标准基线(4/6),为修复运行应用补丁(6/6 加上上游兼容性检查),反转补丁,并验证哈希值和声明。完整的建设性分析位于拆解仓库的 TEARDOWN.md。
在十分钟内探测你自己的 Agent
你不需要这个测试框架。方法只有一句话:用确定性伪造故意破坏一个步骤,看看下游是否有任何东西变红。
针对一个真实代码路径进行这些操作:
让工具边界抛出异常。将一个客户端或包装器替换为立即抛出的替身。如果摘要仍然将该项目计为已处理,或者最终产物仍然被产生,则该路径是 fail-open 的。
读取退出状态,而非日志。用你的调度器运行入口点的方式运行,并检查状态。记录错误后状态为零通常意味着 catch-and-fall-through。
耗尽重试预算。强制验证或重试在每次尝试中都保持负面。如果循环结束而下一阶段仍然运行,则耗尽被当作继续的许可。
返回空而非抛出异常。许多管道只处理异常。流入报告的空列表或 null 是同样的缺陷,只是症状更安静。
运行零个工作单元。让运行器指向空的输入集。零测量下的绿色通过率不是通过——它是报告为成功的不可度量运行。
在多个中失败一个。部分失败是"尝试"和"成功"分叉的地方。如果摘要计了整个批次,则账目是错误的。
让守卫失明。删除检查读取的文件、撤销凭证、或向其提供格式错误的输入。一个在没有什么可检查时仍然通过的守卫只是装饰。
对空进行聚合。请求对空结果集进行度量。一个数字而诚实答案是"未测量"——尤其是当越低越好的指标报告理想的空值时——是上一层的 fail-open。
如果任何探测在应该变红的地方保持绿色,你已经有了一个确定性的复现:你刚刚写的那个伪造。
这不说明什么
这些结果不衡量模型质量、金融正确性、安全性、生产频率,或超出一个冻结示例的 Agents SDK。伪造边界使每个数字都成为 synthetic-orchestration 证据。这个限制是刻意的:被研究的失败是在失败步骤的账目中,而非模型智能中。
一个无法区分失败步骤和已完成步骤的系统会 fail-open。这种失败最令人信服的形式不是崩溃。它是在证据消失之后撰写的流畅报告。
在综合之前定义最小可用证据。将重试耗尽视为终止状态。断言副作用——下游调用、发出、退出状态——而不仅仅是返回的对象。并且当你故意破坏一个步骤时,只有当其他所有表面都同意时,才相信说"完成"的表面。
Kerem Turhan。复现和撰写:agent-reliability-teardown-openai-agents-js。