☰
OpenAI Agents SDK 工程笔记:AgentsException 族、失败分类与生产恢复禁区
2026/10/14 3:42:54 网站建设 项目流程


千笔-AIWritePaper · https://www.aiwritepaper.com

线上 Agent 挂掉时,最危险的操作是「反正再跑一遍」。OpenAI Agents SDK 把运行失败收进AgentsException族:回合耗尽、模型行为异常、调用方误配、模型超时、工具超时、护栏触发等。它们有的挂着run_data快照,有的根本不在这个族里(例如缺OPENAI_API_KEY时的openai.OpenAIError)。能不能重试,取决于异常类型 + 是否幂等 + 是否已经产生写副作用,不取决于值班同学的耐心。

本文在openai-agents 0.23.1下用脚本化假模型离线实跑:故意触发回合耗尽、未知工具、坏结构化 JSON、误配call_model_input_filter、缺密钥,以及超时后盲目整段重试导致charge副作用翻倍。全部结果进_w/agents-exception-smoke/的 CSV 与smoke_output.txt。


图:上排为 AgentsException 族与恢复分类;中部为 Runner.run 成功/抛错路径与 run_data 字段;底部为 F1至F5 实测禁区。

目标说明

读完你应能独立完成五件事。

  1. 按类型把失败分进三桶:条件可重试、不可重试、必须人工介入;并把缺密钥等基础设施错误单独隔离。
  2. 在except AgentsException里读取exc.run_data(若有),把raw_responses、new_items、last_agent写入结构化日志。
  3. 用max_turns故意打爆一轮,确认抛出MaxTurnsExceeded且run_data.raw_responses条数等于预算。
  4. 对写工具路径禁用「整段 Runner.run 重试」;超时只允许在幂等读路径或带幂等键的补偿路径重试。
  5. 把 5 条失败样本写进禁区表和告警分级:盲目重试翻倍、原样重跑耗尽、吞异常伪造成功、缺密钥空转、坏工具 JSON 伪健康。

适用与边界

适合:已经在跑Runner.run/run_sync/run_streamed,需要统一异常出口、日志字段与告警;要把「再试一次」写成可执行策略,写进口头习惯不够。

不讲:tool 循环与单工具超时调参细节、as_tool、MCP 接线、guardrails 设计、lifecycle hooks、ModelSettings、RunContext、SQLiteSession 落盘(见 10-02 A)。护栏触发类异常只在分类表里占位,本文不展开 tripwire 业务规则。

失败含义:假模型按脚本返回调用,用来固定触发路径;「有没有挂上 run_data」「缺密钥是否属于 AgentsException」「坏工具 JSON 会不会默默回灌错误串」由 SDK 与 Runner 决定。ModelTimeoutError的分类用例直接构造异常对象验证分桶;真实超时依赖ModelSettings.timeout,以你安装版本为准。

机制:异常族、返回路径、日志字段

类型典型触发恢复分桶告警建议
MaxTurnsExceededmax_turns用尽人工介入:加预算或拆任务P1/P2,带 run_data
ModelBehaviorError未知工具、坏结构化 JSON、非法工具载荷不可重试:修工具表或输出约束P1,禁止自动重跑
UserError调用方误配(如 filter 返回类型错)不可重试:修代码/配置P1,走发布门禁
ModelTimeoutError模型调用超过 timeout仅幂等可读路径可重试P2,写路径要幂等键
ToolTimeoutError工具timeout_behavior="raise_exception"视工具是否幂等P2
护栏 tripwire 诸类输入/输出/工具护栏触发默认人工或降级策略按业务定级
openai.OpenAIError等缺密钥、凭据、网络基建不在AgentsException 族;禁盲目重试P0 配置/密钥

Runner.run成功时返回RunResult(读final_output)。失败时抛上述异常;Runner 会尽量把当时的input、new_items、raw_responses、last_agent、护栏结果塞进exc.run_data(RunErrorDetails)。日志至少落四类字段:exc_type、exc_message、run_id/trace_id、run_data摘要(响应条数、最后 agent 名、new_items 类型直方图)。告警分级建议:配置/密钥 → P0;Behavior/User → P1 且禁止自动重跑;MaxTurns → P1/P2 并附预算;Timeout → P2,由幂等标记决定是否进入重试队列。

可选的error_handlers(如"max_turns"/"model_refusal"/"invalid_final_output")可以把部分错误收成受控final_output,它替代抛错,不会替你撤销已经执行过的写工具。把它当成产品文案降级可以,当成「副作用自动回滚」不行。

fromagentsimportAgent,Runner,MaxTurnsExceeded,ModelBehaviorError,UserErrorfromagentsimportModelTimeoutError,AgentsExceptiondefclassify(exc:BaseException)->str:ifisinstance(exc,MaxTurnsExceeded):return"human_intervene"ifisinstance(exc,ModelTimeoutError):return"retryable_if_idempotent"ifisinstance(exc,(ModelBehaviorError,UserError)):return"non_retryable"ifisinstance(exc,AgentsException):return"agents_other_no_blind_retry"return"infra_config_no_retry"try:result=awaitRunner.run(agent,user_input,max_turns=8)exceptAgentsExceptionasexc:log.exception("agent_failed",extra={"exc_type":type(exc).__name__,"recover":classify(exc),"raw_n":len(exc.run_data.raw_responses)ifexc.run_dataelse0,"agent":exc.run_data.last_agent.nameifexc.run_dataelseNone,})raise

离线 smoke:6 条通过 + 5 条失败

P1 PASS 回合耗尽 MaxTurnsExceeded+run_data -> raw_responses=3 new_items=6 last_agent=budget side_effects=3 P2 PASS 未知工具名触发 ModelBehaviorError -> Tool no_such_tool not found P3 PASS output_type 解析失败 ModelBehaviorError -> Invalid JSON when parsing model output P4 PASS call_model_input_filter 误配 UserError -> must return a ModelInputData instance P5 PASS 缺 OPENAI_API_KEY 落在 infra 层 -> openai.OpenAIError agents_exc=False P6 PASS ModelTimeoutError 分类为条件可重试 -> timeout_seconds=1.5 F1 FAIL 超时后盲目整段重试导致写副作用翻倍 -> charge:99 × 2 F2 FAIL 回合耗尽后原样重跑仍耗尽 -> second MaxTurnsExceeded F3 FAIL 吞掉 ModelBehaviorError 伪造成功 -> returned_ok=True F4 FAIL 缺密钥盲目重试空转 -> OpenAIError × 3 F5 FAIL 坏工具 JSON 未抛异常仅回灌错误串 -> raised=False saw_tool_error_string=True CSV rows: 11

逐条读法。

  • P1:max_turns=3时模型每轮都调charge,抛MaxTurnsExceeded;run_data.raw_responses恰好 3,且副作用日志已有 3 条。预算耗尽前写工具可能已经发生。
  • P2:模型点名不存在的工具,默认行为抛ModelBehaviorError(可改为tool_not_found_behavior="return_error_to_model"回灌,那是另一条产品策略)。
  • P3:output_type为 Pydantic 模型时,助手只回非 JSON 文本,抛ModelBehaviorError: Invalid JSON when parsing model output。
  • P4:call_model_input_filter返回了普通字符串,抛UserError。这是调用方代码病,重试无意义。
  • P5:不设密钥走默认真模型路径,得到openai.OpenAIError,它落在 AgentsException 族外。只写except AgentsException会漏掉它。
  • P6:ModelTimeoutError(1.5)进入「条件可重试」桶;是否真重试还要看路径幂等。
  • F1:工具已charge:99,随后模拟超时,调用方把整段Runner.run再跑一遍,副作用变成两条。这是写路径盲目重试的标准事故。
  • F2:第一次max_turns=2耗尽后,不改预算、不拆任务,原样再跑,第二次仍然MaxTurnsExceeded。
  • F3:except ModelBehaviorError后直接return {"ok": True},监控显示成功,用户拿到空结果。
  • F4:缺密钥连打三次,三次同一基建错误,告警风暴与空耗。
  • F5:工具参数是{not-json时,SDK 默认把错误串写进function_call_output让模型继续,整次 run 成功返回。没有异常不等于工具健康。

生产禁区表

编号禁区实测证据替代做法
D1超时后整段重跑含写工具的 runF1:charge:99× 2写工具带幂等键;超时只重试未确认的读步骤
D2MaxTurns 后原样同预算重跑F2:第二次仍耗尽升预算、拆子任务,或走error_handlers["max_turns"]给受控文案
D3吞掉 Behavior/User 当成功F3:ok=True必须记账并失败返回;CI 禁止裸 except
D4缺密钥/凭据当可重试F4:空转 × 3启动期校验密钥;该类进 P0,重试队列直接拒绝
D5「没抛异常」当成工具成功F5:错误串回灌扫描function_call_output错误模式;指标单独计数
D6只 catchAgentsExceptionP5:OpenAIError 漏网外层再接基建异常,字段进同一日志模式

告警与重试对照

分桶谁进自动动作人工动作
条件可重试幂等读上的ModelTimeoutError有限次退避重试超限后升级
不可重试ModelBehaviorError/UserError不重试,记 P1修工具表、输出约束或配置
人工介入MaxTurnsExceeded可选受控降级文案加预算或拆任务,并看 run_data
基建禁重试缺密钥等非 Agents 异常拒绝入队修密钥与网络

最小复现:断言「写副作用不会因超时翻倍」

smoke 里用内存列表当账本。你的项目里换成数据库幂等表即可。

SIDE=[]@function_tooldefcharge(amount:int)->str:SIDE.append(f"charge:{amount}")returnf"charged:{amount}"# 业务层:同一 payment_id 只允许成功落账一次asyncdefrun_payment(payment_id:str):ifalready_charged(payment_id):return"duplicate-ignored"awaitRunner.run(pay_agent,f"pay{payment_id}",max_turns=5)mark_charged(payment_id)

CI 断言:模拟「成功扣费 + 随后超时 + 调用方重试」后,SIDE.count("charge:99") == 1(或账本行数唯一)。谁把整段 run 当重试单元,谁就红。

常见误判

  • 「catch 住了 AgentsException 就等于覆盖所有运行失败」:P5 证明缺密钥走另一条继承树。
  • 「run 返回了 final_output 说明工具都成功」:F5 证明坏 JSON 可以只回灌错误串仍算跑完。
  • 「error_handlers 接住 max_turns 就等于没副作用」:P1 在抛错前已经执行了 3 次charge;handler 只改返回,不回滚。
  • 「超时都可以重试」:只有幂等读或带幂等键的写才可以;F1 是反例。

与已写主题的咬合

相邻主题关系一句话
tool 循环 / 超时专文单工具超时策略本篇管异常出口与是否允许整段重跑
guardrails 专文tripwire 如何设计本篇只把 tripwire 放进分桶表
ModelSettingstimeout 等旋钮来源本篇消费超时异常的恢复边界
SQLiteSession(10-02 A)失败时历史是否仍在异常与 session 正交;run_data 不替代会话库

日志字段最小集

生产里建议每条失败日志固定带这些键,方便和 trace 对上。

字段来源用途
exc_typetype(exc).__name__分桶与看板
recover上文classify(exc)决定是否入重试队列
messagestr(exc)截断值班可读
raw_nlen(run_data.raw_responses)对照 max_turns
item_histnew_items 的 type 直方图看失败前是否已调工具
last_agentrun_data.last_agent.name多 agent 时定位
side_effect_hint业务侧幂等表查询判断能否安全重试
trace_idRunConfig / 追踪系统串联模型与工具跨度

缺side_effect_hint时,超时重试决策会退化成猜。P1 已经证明回合耗尽前charge可以发生三次;你若只看异常类型、不看账本,就会把「已经扣过」的请求再次送进 Runner。

与 error_handlers 的边界

SDK 允许为"max_turns"、"model_refusal"、"invalid_final_output"注册 handler,返回受控final_output。这在产品层很有用,例如告诉用户「本轮步数不够,请缩小范围」。工程上要同时记住三件事。

  1. handler 成功后,调用方看到的是正常RunResult,监控若只盯异常率会漏报。
  2. include_in_history默认可能把降级文案写入会话;设False可避免污染下一轮上下文。
  3. 已执行的写工具不会因为 handler 而回滚。P1 的三次charge在抛MaxTurnsExceeded之前已经进了副作用日志。

因此 handler 属于对外文案与流程降级,不属于恢复禁区的豁免条款。禁区表里的 D1/D2 仍然成立。

验收清单

  • 所有Runner.run调用点有统一classify+ 结构化日志(含run_data摘要)。
  • 写工具具备幂等键;超时重试测试断言副作用不翻倍。
  • MaxTurnsExceeded不进入无脑重试队列。
  • ModelBehaviorError/UserError禁止自动重跑,CI 有裸 except 检查。
  • 启动期校验 API 密钥;缺密钥进 P0 而非重试。
  • CI 跑_w/agents-exception-smoke/smoke.py,P 系列 PASS,F 系列保持可复现。

当天 30 分钟落地

  1. 5 分钟:搜仓库里所有Runner.run,列出 except 范围是否覆盖非 Agents 异常。
  2. 10 分钟:接入上文classify,日志加上exc_type/recover/raw_n。
  3. 5 分钟:给支付/写库类工具补幂等键,加一条「超时重试不翻倍」测试。
  4. 5 分钟:MaxTurns 与 BehaviorError 的告警从「自动重跑」改成「工单」。
  5. 5 分钟:把agents-exception-smoke.csv存进仓库,升级 SDK 时复跑对照。

配置对照:四个问题定恢复策略

问题是否
失败路径上是否已执行写工具?禁止整段重跑;查幂等表才可考虑有限次退避
异常是否为 Behavior / User?修代码与工具表再看超时或 MaxTurns
是否 MaxTurnsExceeded?人工改预算或拆任务不要用同预算重跑碰运气
是否族外基建错误?P0 修密钥与网络不要丢进 Agents 重试队列

这四问可以直接做成网关中间件的分支。谁跳过第一问,谁就会复现 F1。

踩坑

  • 把「再跑一遍」写进 on-call 手册的第一步:F1/F2/F4 都会被手册合法化。
  • 只在网关层 catch Exception 并返回 200:F3 的假阳性会直接进用户报表。
  • 看见 tool output 里有英文 error 仍展示「已完成」:F5 的静默失败最常见于演示环境。
  • 用升max_turns掩盖未知工具或坏 JSON:那是把 BehaviorError 熬成 MaxTurns,run_data 更难看。
  • 假设error_handlers会撤销工具副作用:SDK 文档写明它不重放、不回滚副作用。

总结

AgentsException族给出的是可观测的失败类型。它本身不附带「自动恢复许可证」。Runner.run成功走RunResult,失败走带(或不带)run_data的异常;缺密钥等基建错误还在族外。生产能不能扛住,取决于分桶、幂等与告警:超时后整段重跑、耗尽后原样重跑、吞掉 BehaviorError、缺密钥空转、把错误串回灌当成健康,都会在账本或监控里留下可复现伤口。把 5 条失败写进禁区表,升级 SDK 时复跑同一份 smoke。本文实跑版本为openai-agents 0.23.1,字段名以你安装的版本为准;若run_data或error_handlers语义有变,先以官方 Running agents / Exceptions 文档与本目录 CSV 对照。

写作与实验流程可用千笔-AIWritePaper 整理实验记录与禁区表,站点见文首链接。

P1 PASS 回合耗尽 MaxTurnsExceeded+run_data -> msg='Max turns (3) exceeded' run_data=True raw_responses=3 new_items=6 last_agent=budget side_effects=3 P2 PASS 未知工具名触发 ModelBehaviorError -> type=ModelBehaviorError msg='Tool no_such_tool not found in agent tools' class=non_retryable P3 PASS output_type 解析失败 ModelBehaviorError -> type=ModelBehaviorError msg='Invalid JSON when parsing model output' P4 PASS call_model_input_filter 误配 UserError -> type=UserError msg='call_model_input_filter must return a ModelInputData instance' P5 PASS 缺 OPENAI_API_KEY 落在 infra 层 -> type=openai.OpenAIError agents_exc=False class=infra_config_no_retry

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询