长任务不是"跑完",是"交卷":任务预算与超时怎么设计
一句话结论:长任务最危险的不是慢,是两种死法——跑飞了不停止,或撞上限直接失败。把"任务预算耗尽"定义成"交卷"而不是"失败",长任务才敢真交给用户。
长任务(写代码、跑调研、生成报告)和"问一句答一句"的短对话,对系统的要求完全不同。短对话超时就重来,长任务超时必须把已经做的部分保住——否则用户等了四十分钟,最后拿到"任务失败",下次不敢再把重要的活交给它。
预算分三层
长任务的预算不是一个数,是三道防线:
- L1 任务预算(TaskBudget)。 这是"交卷线"。预算耗尽时,任务不是失败,而是交卷——把已经完成的产出作为草稿交回给用户。用户拿到的是"做到这步的结果",而不是"任务失败,啥也没有"。
- L2 提醒线。 预算快满但还没满时,先提醒,不打断。给用户一个"要不要继续"的选择,而不是悄悄停或硬撑。
- L3 上下文上限。 上下文窗口不够时,不走各处自己判断,统一走
chat.ResolveContextLimit解析。避免"这里觉得还能塞、那里已经爆了"的不一致。
三层各管一段:L1 决定能不能收尾,L2 决定要不要问用户,L3 决定上下文还装不装得下。
超时不能只看过期时间
最容易写错的是"超时"的算法。如果只看"距离上次操作过了多少秒",会遇到假死:一个任务在等外部 IO(下载、调接口),长时间没动作,但没真卡死。
更稳的判据是累计活跃时长:一个任务被判定"卡死",是"累计节点运行时间 ≥ 1800 秒,且期间空档 ≤ 300 秒"——也就是它一直在忙、没真正停过,却始终没产出。这样等 IO 的任务不会被误杀,真死循环的任务会被抓住。
节点时长要按 (task_id, node_id) 累计,不能笼统按任务计。否则并行节点会重复计时或漏计时,预算数字失真,后面这批饿死前面那批撑死。
批次上限是全局约束
长任务常被拆成多批执行(比如一次生成 36 条)。批次上限是全局约束,不是每批独立。五批全生效时,预算要在批次之间正确流转,不能第一批就把预算用光、后面四批饿死。
失败式 vs 交卷式
| 失败式 | 交卷式 | |
|---|---|---|
| 预算耗尽 | 任务失败,结果丢弃 | 交回已完成的草稿 |
| 用户感受 | 白等,不敢再交重要活 | 拿到阶段成果,可控 |
| 续跑 | 重头跑 | 从草稿续 |
几个已知边界
这部分是实打实还没打磨完的地方,写清楚比藏着好:
- 双路节点(同一任务两条并行路径)的超时计数还不齐,可能出现一路判定活跃、一路判定空闲的不一致。
- 算力点是定值,没随任务复杂度浮动,简单任务会"显得"预算紧张。
- 个别基线测试还红着,说明"交卷"的边界在某些组合下没完全稳定。
这些不影响核心结论:预算耗尽=交卷,不是失败。 方向定了,边界是打磨问题。
一个实现例子
Lyncore 的长任务跑 DAG,每到一个阶段边界就检查预算;满了就收尾交卷,把已完成的草稿交回原会话,用户决定要不要加预算续跑。