我最早以为,子 Agent 的超时管理就是个简单的定时器——给每个子任务设一个 60 秒的 deadline,超了就杀掉,这样系统至少不会卡死。结果上个月我在调试一个多 Agent 写作系统时,一个子 Agent 负责去查资料,它调了一个外部搜索 API,那个 API 卡了 10 分钟没返回,我的主 Agent 也就这么傻等。日志里每 30 秒重复一次“正在等待搜索结果”。那一刻我才意识到:超时管理不是设个 60 秒就完事,而是要回答一个问题——你怎么知道一个 Agent 是“还在干活”还是“已经卡死”?

子 Agent 卡住的原因,比你想的多
你可能会说,不就是 LLM 偶尔不响应吗?真实系统里的卡法千奇百怪:
- 工具调用挂起:子 Agent 调用的外部 API 一直没有返回,比如 HTTP 请求没有设置超时。
- 无限循环:子 Agent 在同一个错误计划上反复重试,每次都失败,每次都以为自己是首次尝试。
- 上下文爆炸:子 Agent 把超长历史全部塞给 LLM,生成速度急剧下降,一个 token 要好几秒。
- 模型退化:模型陷入重复输出,同一个句子刷屏,停不下来。
这些卡住并不是简单的“不干活”,有些甚至是在疯狂干活,但干的是无用功。
超时的本质:给系统一个“放弃的资格”
单 Agent 场景下,超时只是防挂死;但在多 Agent 场景下,超时还承担着协调功能——主 Agent 需要知道子 Agent 失败了,才能选择重试、换方案,或者自己接手。
这就像你和同事协作:你让同事去查个数据,他迟迟不回。你不可能无限等,但也不会 30 秒就催一次。你心里有个“查数据大概需要多久”的预期,这个预期就是超时依据。
静态超时为什么不够?
很多框架默认就是静态超时:每个子 Agent 给一个固定时间,比如 60 秒。配置起来也很简单:
# 伪代码:固定超时
result = main_agent.run_subagent(task, timeout=60)
但它有个根本问题:任务耗时差异太大。让子 Agent 算一下 1234×5678,3 秒足够;让它调研“过去三年的 AI 芯片市场变化”,5 分钟不一定够。固定一个值,要么频繁误杀复杂任务,要么让简单任务在失败后白白多等 50 秒。
你说那就按任务类型来设?查询类 30 秒,调研类 5 分钟。但同一类任务的波动也很大。调研一个热门话题和调研一个冷门话题,时间能差一个数量级。静态超时本质上是在用“猜”代替“监测”。
动态超时:不要只等时间,要盯进展
更好的做法是,把超时建立在“进展”而不是“时间”上。核心是心跳 + 进展检测:
- 子 Agent 启动时,主 Agent 给它一个心跳周期(比如 10 秒)。子 Agent 每个周期报告一次状态,包括“当前在干什么”和“已完成什么”。
- 主 Agent 记录每一次心跳的时间戳和内容摘要。
- 如果超过心跳周期 2 倍没有新心跳,判定为挂起。
- 如果连续 N 次心跳内容都没有变化,比如反复报告“正在等待 API”,判定为循环空转。
- 发生上述情况,主 Agent 不再等待,转入超时处理流程。
心跳机制最大的好处是,把“时间到没到”的问题变成了“还有没有进展”的问题。我踩过的一个坑是:只做心跳,没做内容变化判断。结果子 Agent 一直发“我还活着”,但内容一模一样,还是卡了 5 分钟。后来加上了内容摘要的哈希值,才真正管用。
业界框架的超时配置,长什么样?
我梳理了四个常见框架的超时机制,它们都比你项目里的裸循环强一些,但也都有明显的“静态思维”:
| 框架 | 超时参数 | 控制粒度 | 局限 |
|---|---|---|---|
| LangChain | timeout(毫秒) | 单次模型调用 | Agent 级循环要自己包 |
| CrewAI | max_execution_time(秒) | 整个 Agent | 固定时间,没有进展检测 |
| AutoGPT | max_execution_time + max_cycles | 循环级别 | 时间与次数都是静态 |
| Semantic Kernel | CancellationToken | 任意异步操作 | 偏底层,需要自己组织 |
注意一个趋势:不少框架都同时提供“时间上限”和“迭代次数上限”。因为 LLM 的单次迭代可能很快也可能很慢,只限时间会在慢环境下误杀,只限次数会在短迭代下失效。
超时之后,才是重头戏
超时本身不是目的,超时后干什么才是。一个比较实用的流程是:
- 先尝试优雅中断:给子 Agent 发送 cancel 信号,让它清理正在进行的工具调用,然后返回当前已产出的部分结果。
- 如果优雅中断在 N 秒内没响应,再强制终止,并回收线程/会话。
- 拿到部分结果后,主 Agent 基于部分结果继续,或者重试整个子任务,或者把它拆成更小的子任务。
- 如果子 Agent 是被外部 API 卡住,那问题在工具层。所有工具调用都要有自己的超时,比如 HTTP 请求 15 秒必须返回。
我之前犯过一个错误:只给 Agent 设了超时,没给工具设。结果子 Agent 卡在 HTTP 请求上,Agent 超时中断了,但底层那个 HTTP 请求还挂着,占着线程池。直到给所有工具调用都加上超时,才算彻底解决。
关于超时的三个常见误解
1. 超时越长,任务完成率越高?
不一定。卡死的 Agent 占着 token 配额和 API 并发,超时越长,整个系统的吞吐越差。一个子 Agent 卡 10 分钟,可能把其他 10 个任务的并发都挤掉了。
2. 超时短一点,就能快速失败?
太短会误杀正常任务。主 Agent 被迫重试,反而增加总耗时。你设 5 秒,LLM 一个 2048 token 的响应可能都回不来。
3. 杀掉子 Agent 就万事大吉?
不是。子 Agent 启动的副作用不会自动回滚——可能已经写了数据库、发了邮件。超时管理必须配合事务性设计,或者你接受“至少执行一次”的语义,并设计幂等接口。
主 Agent 到底等多久?我的答案是分层超时
回到最初的问题:等多久算超时?我现在的答案是:不要问“等多久”,要问“等到什么程度不算白等”。静态超时是底线,动态心跳和进展检测是核心。你至少需要三层超时:
| 层级 | 参考范围 | 判断依据 |
|---|---|---|
| 工具调用层 | 15–30 秒 | 外部 API 的统计耗时 |
| 单次 LLM 调用层 | 30–120 秒 | 模型 + 上下文长度 |
| Agent 整体层 | 5–30 分钟 | 任务类型 + 心跳进展 |
每个层级超时后都要有明确的恢复路径:工具层重试或降级,LLM 层换模型,Agent 层拿部分结果或转人工。
说到底,超时管理不是纯技术难题,而是对不确定性的接受。你没法预知每个子 Agent 会走多远,但你可以确保它突然停下时,系统不会陪着它一起停。
原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/686.html