跳到主要内容

超时感知的智能体设计:如何返回部分结果而非静默失败

· 阅读需 11 分钟
Tian Pan
Software Engineer

一个智能体成功创建了 GitHub Issue、开启了 Jira 工单,并更新了共享表格。然后在发送 Slack 通知之前超时了。框架将此次运行记录为"已交付"。用户从未收到通知。副作用存在于三个系统中,而对人类真正重要的结果却没有送达。

这是生产智能体系统中最常见的超时失败模式,但几乎从来不是团队预先准备好的那种。大多数智能体实现把超时当作普通异常处理:捕获、记录、返回错误。即使智能体完成了 90% 的工作,用户也什么都得不到。问题不在于是否设置超时——每个生产系统都需要超时。问题在于当时钟走完时,智能体该如何应对。

截止失败的解剖

智能体在截止时间处的失败分为两大类。第一类是显式的:智能体崩溃、抛出异常,调用方看到错误。令人烦恼,但可以恢复。第二类才是真正造成破坏的:智能体完成了不可逆的副作用——写入、通知、外部 API 调用——然后在向用户交付结果之前失败了。

这种区别至关重要,因为这两种失败模式需要完全不同的缓解方案。显式失败需要重试逻辑和熔断器。静默的部分完成失败需要事务纪律:理解哪些操作可以回滚、哪些不能,并据此排序。

还有第三种失败模式是团队很少考虑到的:初始化税。一个资源充足的智能体在初始化内存存储、凭证系统和技能注册表时,可能在 300 秒的预算里烧掉 75 秒,然后才开始做任何实质性工作。你的超时看起来是 300 秒,但实际表现得像 225 秒。按挂钟测试(跳过冷启动)来计算大小的系统,往往会在生产中以令所有人惊讶的速度失败。

为什么智能体会完全失败而非部分失败

根本原因是架构性的。大多数智能体实现将完整任务建模为单一原子单元。任务要么成功要么失败;没有中间状态的表示。当超时触发时,运行时没有检查点可以返回,没有部分模式可以填充,除了"超时"之外没有任何信号可以发送给调用方。

这与成熟分布式系统处理截止时间的方式形成鲜明对比。超时的数据库查询仍然可以返回取消之前已扫描的行。流式 API 会向客户端发出信号以优雅关闭。文件下载可以从字节偏移处恢复。这些模式之所以有效,是因为底层协议在设计时就考虑了部分完成——模式允许它,客户端期待它,超时成为进度门控而非硬停止。

智能体循环没有继承任何这些。ReAct 循环——观察、推理、行动、重复——没有自然的提前退出钩子。LangChain 的 AgentExecutor 添加了 max_iterationsmax_execution_time 参数,有助于控制失控行为。但它们没有添加在中断时刻打包已有进度的结构化方式。智能体在第 4 步(共 10 步)停下来,而它在这四步中学到的一切都消失了。

三种真正有效的模式

检查点优先执行

最健壮的方法将每个智能体步骤视为持久状态转换而非短暂函数调用。在每次 LLM 调用和每次工具返回之后,系统写入检查点:当前上下文、累积结果、待处理操作。当超时触发时,执行在最近的检查点处干净停止,而不是在任意的中间步骤中断。

像 Temporal 这样的持久化执行框架会自动实现这一点。每个工作流步骤都捕获在事件历史中;如果进程在运行中途崩溃,新的工作节点会从事件日志重放并从上一个工作节点停止的地方恢复。智能体永远不会重做已完成的工作,已完成的结果也永远不会丢失。

这种模式有成本。检查点写入会为每个步骤增加延迟,事件日志的增长与执行时长成正比。对于短时运行的智能体(30 秒以内),开销通常不值得。对于预计运行数分钟的智能体,检查点是使部分进度对调用方可见的唯一方式。

结构化部分结果模式

检查点存储进度以供恢复。部分结果模式将该进度传达给调用方。区别在于受众:检查点是为系统服务的,部分结果是为用户服务的。

为部分完成设计的模式将字段标记为可选,并包含状态信封。与其返回完整的分析对象或什么都不返回,智能体返回:

{
"status": "partial",
"completed_steps": ["competitor_pricing", "market_share"],
"missing_steps": ["financial_projections"],
"reason": "timeout",
"results": { ... }
}

调用方——无论是 UI、另一个智能体还是编排器——现在可以对要显示的内容做出明智的决定。10 家竞争对手中有 8 家的定价信息比什么都没有要有用得多。当管理者在决定是否下紧急订单时,部分库存报告比没有要好。

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates