更新于

Agent 工具调用的超时预算设计


Agent 跑一个复杂任务时,可能连续调用十几次工具:搜索、读文件、跑代码、调外部 API。任何一次单独超时不算大事,但如果没有整体预算设计,常见的失败模式是这样的:第 8 次工具调用超时报错,前面 7 次的中间结果全部作废,用户从头再来一遍,体验和成本都很差。本文讲清楚怎么给 Agent 的工具调用链设计一套合理的超时预算体系。

工具集合本身的设计原则可以参考 AI Agent 工具集合的设计原则,本文聚焦时间维度的预算控制。

一、单工具超时不是唯一维度

大多数人第一反应是给每个工具调用设一个超时时间,比如”网络请求 10 秒超时”。这只解决了单点问题,没解决系统问题:

  • 如果 Agent 一个任务要调用 15 次工具,每次都卡在超时上限的边缘,总耗时可能远超用户能接受的范围,即便每次单独看都”合规”
  • 不同工具的合理超时天差地别:一次简单的文件读取应该在毫秒级完成,一次代码执行可能需要几十秒——用同一个超时阈值套所有工具,要么误杀慢工具,要么放纵本该快速失败的调用

合理的设计需要至少两层预算:单工具超时(防止某一次调用无限挂起)+ 任务总预算(防止 Agent 在多轮工具调用里把时间耗尽还没产出结果)。

二、预算分层设计

层级控制目标典型阈值参考超出后的行为
单次工具调用防止单个调用无限挂起依工具类型而定,5–60 秒该次调用失败,返回错误给 Agent 决策下一步
单轮 Agent 决策防止模型在一轮里选择耗时工具后无响应覆盖单工具超时 + 一定余量视为该轮失败,进入重试或降级逻辑
任务总预算防止多轮工具调用无限循环,总耗时失控依任务复杂度,30 秒到几分钟强制终止,返回已有的部分结果 + 说明

三层预算不是相互独立的,而是层层收紧的关系:任务总预算减去已消耗时间,才是留给后续每一次工具调用的实际可用时间。如果只设单工具超时不设总预算,Agent 完全可能把”允许失败重试”用成”无限重试直到系统资源耗尽”。

三、总预算耗尽时怎么办:优雅降级而不是硬报错

超时预算设计里最容易被忽视的一点:预算耗尽不等于任务失败,尤其当 Agent 已经完成了大部分工作。常见的三种降级策略:

  1. 返回部分结果 + 说明:如果 Agent 已经收集到足够回答用户问题的信息,直接基于已有信息生成回复,附一句”部分数据获取超时,以下基于已获取信息回答”
  2. 跳过非关键工具:预先标记哪些工具调用是”锦上添花”(比如补充引用来源)、哪些是”必须完成”(比如核心计算结果),预算紧张时优先保证必须项
  3. 异步转同步降级:如果某个工具本身支持异步查询(先提交任务再轮询结果),预算不够时改为返回”任务已提交,稍后可查询”,而不是硬等到超时

哪种策略合适取决于任务性质——面向用户的实时交互场景应该倾向于快速返回部分结果,后台批处理任务可以给更宽松的预算并允许失败后完整重试。

四、伪代码示例

import time

class TaskBudget:
    def __init__(self, total_seconds):
        self.deadline = time.monotonic() + total_seconds

    def remaining(self):
        return max(0, self.deadline - time.monotonic())

    def exhausted(self):
        return self.remaining() <= 0


def run_agent_task(user_query, tools, total_budget_seconds=60):
    budget = TaskBudget(total_budget_seconds)
    collected_results = []

    for step in agent_loop(user_query, tools):
        if budget.exhausted():
            return finalize_with_partial_results(collected_results, note="任务超时,以下为部分结果")

        # 单次工具调用超时不超过剩余预算,也不超过工具自身的合理上限
        tool_timeout = min(budget.remaining(), step.tool.max_reasonable_timeout)
        try:
            result = step.tool.call(step.args, timeout=tool_timeout)
            collected_results.append(result)
        except TimeoutError:
            if step.tool.is_critical:
                return finalize_with_partial_results(collected_results, note=f"关键工具 {step.tool.name} 超时")
            continue  # 非关键工具超时,跳过继续

    return finalize_with_partial_results(collected_results)

关键点是 tool_timeout = min(budget.remaining(), step.tool.max_reasonable_timeout)——单次调用的超时永远不应该超过任务剩余预算,否则总预算形同虚设。

五、给不同工具设置差异化超时的参考原则

  • 只读、幂等的查询类工具(搜索、读取):超时可以设得相对宽松,因为重试成本低,超时后直接重试通常安全
  • 有副作用的工具(写入、调用外部业务 API):超时要更保守,且超时后不应自动重试,需要先确认上一次调用是否已经生效(参考幂等设计),避免重复副作用
  • 模型自身推理耗时较长的子任务(如调用另一个 Agent 做子任务分解):预算要单独留出余量,不能和普通工具调用共用同一套阈值,否则复杂推理任务会被误判为超时

六、相关阅读

如果你的 Agent 应用在国内网络下经常因为链路延迟触发超时,先排查是不是中转环节的问题——YoTradeApi 提供低延迟稳定连接,减少因网络本身导致的超时误判。