Skip to content

[Feature] ReActAgent 运行级 deadline:墙钟总预算、每次调用向下夹逼、soft→hard 两段收尾 #356

Description

Paired: GitHub #356 ↔ GitCode #188

来自 agent-solution 仓 smartresearch-alpha(agent-core-java 0.1.16 消费者,真 LLM e2e 实测背景)。
涉及分支:930(0.1.16 血统)。

标题:[Feature] ReActAgent 运行级 deadline:墙钟总预算、每次调用向下夹逼、soft→hard 两段收尾

1. 问题

即使有了 per-call timeout(Issue A),maxIterations × per-call 仍无总上界:以 smartresearch-alpha 为例,三相位 ReAct 各 ≤120/60 迭代 × 单调用上界 1200s ⇒ 名义运行上界 40+ 小时。真 LLM 实测中单次任务 10–90 分钟,目前只能靠部署外层的墙钟 timeout 进程级 kill 兜底——无法优雅收尾,任务已写的中间工件状态不可控,消费者也无法在框架内回答「这次调用还剩多少预算」。

具体三个缺口:

  1. 总预算:引擎没有「这次 run 最多跑多久」的概念;
  2. 向下传播:每次模型调用与工具执行拿不到「剩余预算」,无法做 min(per-call, remaining) 夹逼;
  3. 优雅收尾:预算耗尽只有硬中断一种结局——模型没有「知道快到点了、把手头结论写完」的机会。

2. 建议设计(三层,默认关闭零破坏)

① 总预算入口(Builder/引擎配置)

ReActAgent agent = new ReActAgent(card);
// 墙钟总预算(二选一语义均可,建议 Duration 为主、Instant 便于对齐外部期限)
agent.configureDeadline(java.time.Duration.ofMinutes(90));
// 可选:软线比例,默认 0.2
agent.configureSoftDeadlineRatio(0.2);

② 向下夹逼传播(ReAct 主循环内,每次迭代重算)

循环头:remaining = deadline − now
callModel 传给 client 的 callTimeout = min(配置的 per-call, remaining)   // 依赖 Issue A 的配置口
remaining ≤ 0 → 不再派发新迭代,进入 hard 收尾

③ soft → hard 两段收尾

soft(一次性):remaining < ratio × budget 且未通知过 → 向对话注入一条 system/tool 消息
      (建议常量:"Time budget nearly exhausted; finalize with what you have.")
      ——给模型最后一轮收敛机会(写终稿/摘要),此后照常迭代直至自然结束或 hard
hard:remaining ≤ 0 → 抛 AgentDeadlineExceededException
      (建议继承 java.util.concurrent.TimeoutException,语义现成)
      ——fail-fast:已产生的对话与外部工件保持可读,重试/部分交付由调用方决定

④(可选第二步)工具侧可见性:ToolCallInputs(或工具执行上下文)增加 deadlineRemaining()——长退避类工具(如限流检索的 30s×N 退避 sleep)可自行夹逼 min(backoff, remaining),避免预算耗尽前「睡死」。

3. 兼容性

  • 不配置 deadline = 现行为逐字节不变(默认关闭;这是本提案的硬约束);
  • soft 通知是消息注入——对无状态工具无影响;消息文本收在常量里,便于消费者禁用/替换/本地化;
  • hard 异常是新类型,不与既有异常混淆;建议 hard 后再 invoke 抛 IllegalStateException(与一次性任务的 fail-fast 模式一致);
  • Issue A 的 per-call 配置是②夹逼公式的前置,但本 Issue 可独立先行(per-call 暂用全局配置值参与 min)。

4. 测试建议(消费者视角的最小验证集)

  • 无 deadline 对照:stub 模型 N 轮迭代,行为/消息序列与现版本零差异;
  • soft 恰一次:极短预算下软通知只注入一次(第二次迭代起不再注入);
  • 夹逼断言:per-call=10s、remaining=1s 时传到 client 的 timeout=1s(Recording 断言参数);
  • hard 语义:超限抛新异常、已写工件可读、再次 invoke 抛 IllegalStateException。

5. 开源先例

  • langgraph(MIT):图级 recursion_limit 是迭代数总量上界;TimeoutPolicy 提供 run_timeout(总墙钟)+ idle_timeout(进度停滞)双闸——「总量上界 + 停滞检测」的公开组合设计;
  • OpenAI codex(codex-rs,Apache-2.0):重试睡眠与握手预算夹逼(timeout(remaining, sleep(delay)) 形态——退避永远不越过剩余预算);会话级超时旋钮原生配置;
  • openclaw:turn 级超时在任务预算内夹逼计算(min(perTurnMs, deadline − started))——与本提案②的公式同形。

6. 不含(明确出界)

工具实现内部的退避策略本身(各工具自治);分布式/跨进程 deadline 传播(gRPC-style context cancellation 是更重的方案,若本 Issue 落地后再评估)。



(关联:per-call timeout 配置化 Issue A 随后提交,为本文夹逼公式的前置,可独立评审。)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions