Agent 应用开发会员账号
知识目录选择核心方向与细分内容
知识单元 32进阶实现约 12 分钟

理解 → 实现 → 排错 → 取舍

重试、期限、取消与预算的统一状态机

将总截止时间、单次超时、错误分类、预算预留与协作取消纳入同一个执行策略。

重试取消超时预算asyncio

知识内容核对 2026-10-03 · 原题来源核对 2026-10-02

这个知识点,你想学到哪一步?

按当前基础选择起点,也可以依次深入。遇到不熟悉的概念,先回到核心原理;完成后用知识练习检查理解。

先理解

刚接触这个知识点

补齐先备概念,读原理与反例,再用自己的话解释为什么。

从核心原理开始 →

再实现

准备把原理写进代码

理解实现步骤与边界,完成小任务,对照验收要求检查结果。

查看代码示例 →

会排错

需要处理故障与条件变化

沿连续追问定位失效前提,再比较迁移案例,说明方案应如何调整。

沿问题继续深入 →

能取舍

需要设计或评审方案

结合工程推演与资深自评标准,解释方案的适用条件、代价和替代选择。

分析工程场景 →
知识单元目录

LEARN · PRACTICE · REFLECT

知识学习与个人记录

我的笔记与复习 ↗

先沿着原理、问答和迁移案例阅读。需要检查理解时,再切换巩固练习或展开个人记录。

作答与个人记录

每次修改后的提交会保留为独立历史。掌握程度由你对照标准自评。

核心知识 · 重试、期限、取消与预算的统一状态机

先理解核心原理

先备概念:HTTP 超时语义、协程取消、原子计数与预算

重试是对同一逻辑操作的又一次尝试,必须受总期限、预算和当前授权约束。超时不证明失败,取消不撤销已发生动作,所有边界都要进入状态机。

四个控制量约束不同资源

run deadline 限制任务总时间,单次 timeout 限制等待一回调用,重试策略决定哪些错误可再尝试,预算限制资源消耗。只给工具十秒超时,却不限重试总数,任务仍能无限运行;退避等待也占用总期限。临时故障可有限重试,校验失败或权限拒绝通常不靠重试改善。

失败确定性决定能否直接重试

写请求超时可能在远端成功,恢复沿同一操作键查询或幂等重试。带退避和随机抖动避免同时重试制造洪峰,但它不解决重复副作用。设置最大尝试、最大总等待和熔断,预算预留在调用前完成;超时用量未知时不能立即释放所有预留,让其他分支把额度花掉。

并发把普通检查变成竞态

两个分支都读到剩余十元,各自花八元,就超预算。用共享权威账本原子检查并预留,记录 reservation_id,调用后按可核对用量结算。预计成本不是上限,严格预算还需限制最大输出与工具额度;预留不足要追加批准或停止,不能只在事后告警。

取消要穿过执行结构

asyncio 取消在协程能处理的点生效,清理后继续抛 CancelledError;阻塞 SDK 会延迟事件循环,移到线程也不意味着线程可被强杀。长远端调用需要 SDK 取消、目标查询或隔离进程。Temporal Activity 的取消依赖心跳交付,具体行为应按 SDK 验证。验收覆盖取消前、发送后、响应丢失和结算前,分别记录任务状态与实际效果。

用一个问题检查理解

如何统一设计 Agent 的重试、取消、超时和成本预算?

先区分整个 run 的 deadline 和每次工具的 timeout,再只对暂时性错误做有限指数退避。所有重试沿用幂等键,等待时间也计入总期限;每次调用前原子预留预算,调用后按真实用量结算。取消要从调度层传播到节点与工具,停止新调用并记录已发生副作用,不能把取消等同于回滚。Python 协程清理资源后应继续抛出 CancelledError;长耗时 Activity 还要心跳才能及时接收取消。

实现与取舍

四种约束应共享同一执行上下文

上下文至少包含 deadline、cancel_requested、attempts_left、money_reserved 和 operation_id。总期限覆盖排队、退避、工具执行和结果收集;单次超时只限制一次尝试。设置三次重试却每次允许十分钟,会让用户等待远超预期。取消是控制信号,不是工具异常的一种重试理由。任务状态可以经过 cancel_requested 再进入 cancelled,期间允许必要清理与对账,但禁止继续规划新操作。

按错误语义重试而不是捕获所有异常

网络临时不可用、限流等可以有限退避并尊重服务端等待提示;鉴权失败、参数错误和业务拒绝应直接失败或请求补充信息。写操作超时可能意味着已完成而响应丢失,必须先查询或使用幂等重试。Temporal 文档区分 Activity 重试与 Workflow 重试,默认行为并不能替代应用的最大期限和错误分类。重试次数、总等待及工具账单应写入轨迹,便于定位成本和服务问题。并发高时加入抖动可以降低同一时刻集体重试的冲击。

预算要先预留再结算,取消不能自动退款

两个分支若都读取剩余预算后开始调用,会共同超额。调用前以原子条件扣减可用额度、增加预留额度;拿到真实计费数据后释放差额。请求取消后已经发出的调用仍可能计费,不能先把全部预留额度退回再允许其他分支消费。若账单暂时未知,保留保守预留并异步核对。模型 token、外部搜索、存储或代码沙箱成本可能分别计量,应把允许估计误差和硬限额策略写清楚,无法实时准确计费时不能承诺绝对分毫不超。

取消传播要用故障测试检查

Python asyncio 的取消在协程下一次可响应机会触发,清理应放进 finally;吞掉 CancelledError 可能破坏结构化并发。同步阻塞函数、线程任务和远端服务不一定随协程取消停止。Temporal 长运行 Activity 通过心跳获得取消通知,也可能有传播延迟。验收包括退避期间取消、调用中取消、结果返回与取消同时发生、预算恰好耗尽和失败响应未知。下面代码只限制标准库协程尝试次数及总时间;没有模拟真实计费、跨进程原子预算或远端撤销。

代码示例

同时限制重试次数与总 deadline

需要 Python 3.11+。只对显式暂时性异常重试;单次超时默认直接失败,取消不会被吞掉。远端副作用与计费预算另行实现。

import asyncio
class TransientError(Exception): pass
async def run(tool, max_attempts=3, total_seconds=1):
    async with asyncio.timeout(total_seconds):
        for attempt in range(1, max_attempts + 1):
            try:
                async with asyncio.timeout(0.2):
                    return await tool(attempt)
            except TransientError:
                if attempt == max_attempts: raise
                await asyncio.sleep(min(0.01 * 2**(attempt-1), 0.05))
    raise RuntimeError('unreachable')
async def tool(attempt):
    if attempt == 1: raise TransientError('temporary outage')
    return 'ok after 2 attempts'
print(asyncio.run(run(tool)))

预期输出

ok after 2 attempts

工程推演

场景
假设工程场景:研究 Agent 调用搜索 API 遇到限流,用户随后取消。
设计决策
共享截止时间和取消信号,退避可取消,已发出请求保留计费预留并核对。
验证目标
验收目标是在取消后不再发起新搜索,任务保留已完成证据和真实尝试次数。
适用边界
服务端不支持撤销时,已发出的搜索仍可能完成并计费。

连续追问与解答

沿着问题的前提和约束继续向下读。先理解参考解答,再尝试收起答案,用自己的话解释因果和取舍。

举一反三:条件变了,怎样推导?

先找出改变的条件,再判断原方案中哪些前提仍成立。下面的案例是教学推演,便于将原理迁移到新问题。

只读昂贵检索

改变的条件:没有写副作用,但每次请求计费

延伸问题:超时后重试还要幂等和预算吗?

推导与参考解答

读请求通常没有重复业务写入,但会重复消耗费用、限流和时间。预算按每次实际尝试预留,缓存或请求去重是否能省费用取决于服务契约。总 deadline 和退避仍要统一限制;不应因只读而无限重试。

保持不变的原理:可重试性与资源可负担性是不同条件,所有尝试仍受总约束。

两个分支一胜出就取消另一条

改变的条件:取消用于降低延迟与成本

延伸问题:被取消分支的预留是否立刻释放?

推导与参考解答

若尚未发送且已确认停止,可以释放;已发送但用量未确认则保留并对账,避免重复分配同一额度。已成功的外部效果记录下来,不能以另一路胜出为由忽略。结算幂等,避免取消回调和成功回调重复归还预算。

保持不变的原理:任务不再需要结果,不代表费用和副作用没有发生。

易错点

  • 对权限错误和取消一律重试
  • 每次重试重置总 deadline
  • 取消后立即认定远端操作没有发生
  • 预算先检查后调用,没有预留原子性

参考资料

依据公开技术资料设计;参考资料支持技术机制,场景与评分标准为本站设计,不代表某公司面试原题。 新增问答与迁移案例用于原理讲解,来源核查与案例运行验证分别记录。

检查自己理解到哪一步

读完后可以对照这些标准解释原理、边界和取舍。掌握程度由你自评;需要进一步验证时,再完成下方小任务。

基础达标
能按错误类型区分可重试、永久失败与用户取消。
中高级信号
统一截止时间、退避、次数和费用预算。
资深信号
处理并发预算预留、跨层重试放大与外部动作核对。

查看独立示例的校验记录

继续做进阶研究实验

进程真的退出后,还能怎样继续?

从两份数据库的幂等反例,继续验证租约、检查点和独立服务方回执。解压可靠性实验 v3,在独立目录执行。

阅读全文与故障分析 → · 下载可靠性实验 v3 ↓

python3 cli.py submit --db crash.sqlite
python3 cli.py run --db crash.sqlite --lease-seconds 2 --fault after_collect
python3 cli.py inspect --db crash.sqlite
# 首次运行预期退出码 75;等待至少 2 秒后分别执行
python3 cli.py run --db crash.sqlite
python3 evaluate.py --db crash.sqlite

保留证据,逐条核对

  • 首次 inspect 显示 running,已保存 collect 检查点。
  • 租约过期后 succeeded,generation=2,collect 只提交一次。
  • 按 README 再跑 after_effect,核对独立 publisher 数据库只有一个 receipt。

固定 collect → draft → verify → publish 流程;验证本地持久协议与真实进程退出,不证明任意远程服务恰好一次执行。

动手验证 按需完成 · 建议 15 分钟

为模型限流、权限拒绝和工具超时写处理矩阵。

展开验收要求与检查点
  • 权限拒绝不盲重试
  • 重试不重置总截止时间
  • 取消后不启动新动作

重点检查

  • 能划分暂时错误、永久错误和结果未知
  • 能同时限制单次执行与完整任务的时间
  • 能解释取消的传播和已发生操作的处理
  • 能说明并发调用预算预留为何需要原子性