Files
ttstd-infinite-canvas/backend/internal/app/cloud_agent_step_limits.go
AetherNo2332 63c9e32259 feat(cloud-agent): 单步输出上限与秒级墙钟,超时/空输出可自恢复
画布 Agent 一轮要跑很多步模型调用,此前只有"按任务类型的分钟级超时":某一步卡住只能等
整轮判死;更根本的是每一步请求根本不带输出上限,不写 max_tokens 时上游按剩余上下文放行,
思考模型可以把单步拖到几分钟,且偶发把整个输出预算花在推理上、正文与工具调用皆空。

- 新增运行时策略 task.agentStepTimeoutSeconds 与 task.agentStepMaxOutputTokens:秒级墙钟
  与单步输出上限,都支持环境变量给部署级默认值、管理端热更新;旧配置 JSON 缺字段按默认值
  回填,无需数据迁移。
- 单步墙钟到点中止这一步时不再把整轮判死:以可识别标记记为该步失败,运行期关掉上游思考
  重试同一步一次(model_failure_recovered / step_timeout_retried),重试仍超时才以可读原因
  (model_step_timeout)结束本轮。
- 空输出催办用尽后新增一级升级:关思考 + 放大输出预算重试同一步
  (model_failure_recovered / empty_output_escalated),而不是直接判死。放大档在原值上翻倍
  并以 131072 封顶,策略值为 0 时用 32768 兜底,让重试仍然有界。
- 输出上限真正下发到上游:textOptions.maxOutputTokens 成为任务信封字段,Agent 的两条请求
  路径(内置协议与声明式协议)组装请求体时写入 max_tokens / max_output_tokens,与既有的
  input.MaxOutputTokens 取较小值,避免策略上限超过模型声明的物理上限。
- task_worker:执行时限改用带画布 Agent 分支的 taskExecutionTimeout;执行时限到点本身不再
  被当成"租约丢失",否则任务会停在 running 等租约过期后被反复重跑(实测一次调用被重跑成
  三次上游请求)。续租仍用上游已合入的"不继承父 context 取消"的独立 context。
2026-09-22 01:20:36 +08:00

56 lines
2.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package app
import (
"time"
"infinite-canvas/backend/internal/platform"
)
// 画布 Agent 单步的执行边界:一次模型调用最多输出多少 token、最多等多久。
// 两个值都来自运行时策略(管理端可改、环境变量可给部署级默认值),"0"是各自的关闭档位。
// cloudAgentStepOperation 是一次"模型调用"任务的操作名。它同时是任务中心里区分
// "这一条任务是 Agent 的某一步"的依据。
const cloudAgentStepOperation = "cloud_agent_step"
// cloudAgentStepBoostFallbackTokens 是"不限制输出"(策略值为 0)时放大重试用的预算:
// 不限制的本意是"让模型写完",重试却必须有个上界,否则一次卡住的调用会一直占着单步墙钟。
const cloudAgentStepBoostFallbackTokens = 32_768
// cloudAgentStepLimits 是一次模型调用实际生效的执行边界。
type cloudAgentStepLimits struct {
// OutputTokens 是本次调用的输出上限;0 表示不限制(只有 Timeout 兜底)。
OutputTokens int
// Timeout 是单步墙钟:策略给了秒级值时用它,否则沿用文本任务超时。
Timeout time.Duration
}
// cloudAgentStepLimits 解析当前生效的单步边界。策略读取失败时退回出厂默认值:
// 拿到一个确定的上界,好过让一次调用无限跑下去。
func (s *Service) cloudAgentStepLimits() cloudAgentStepLimits {
policy, err := s.runtimeConcurrencySetting()
if err != nil {
policy = defaultRuntimePolicy().Task
}
limits := cloudAgentStepLimits{OutputTokens: policy.AgentStepMaxOutputTokens}
if policy.AgentStepTimeoutSeconds > 0 {
limits.Timeout = time.Duration(policy.AgentStepTimeoutSeconds) * time.Second
} else {
limits.Timeout = time.Duration(policy.TextTimeoutMinutes) * time.Minute
}
return limits
}
// cloudAgentStepOutputBudget 把生效上限折算成本步请求要带的 maxOutputTokens:
// 放大档(空输出升级重试)在原值上翻倍并以硬上限封顶;原值为 0(不限制)时用兜底值,
// 让重试仍然有界。
func cloudAgentStepOutputBudget(limits cloudAgentStepLimits, boosted bool) int {
if !boosted {
return limits.OutputTokens
}
if limits.OutputTokens <= 0 {
return cloudAgentStepBoostFallbackTokens
}
return min(limits.OutputTokens*2, platform.MaxRuntimeAgentStepOutputTokens)
}