READ · UNDERSTAND · TRANSFER
阅读理解,按需巩固
先沿着原理、问答和迁移案例阅读。需要检查理解时,再切换巩固练习或展开个人记录。
核心知识 · 重试、期限、取消与预算的统一状态机
先理解核心原理
先备概念:HTTP 超时语义、协程取消、原子计数与预算
重试是对同一逻辑操作的又一次尝试,必须受总期限、预算和当前授权约束。超时不证明失败,取消不撤销已发生动作,所有边界都要进入状态机。
四个控制量约束不同资源
run deadline 限制任务总时间,单次 timeout 限制等待一回调用,重试策略决定哪些错误可再尝试,预算限制资源消耗。只给工具十秒超时,却不限重试总数,任务仍能无限运行;退避等待也占用总期限。临时故障可有限重试,校验失败或权限拒绝通常不靠重试改善。
失败确定性决定能否直接重试
写请求超时可能在远端成功,恢复沿同一操作键查询或幂等重试。带退避和随机抖动避免同时重试制造洪峰,但它不解决重复副作用。设置最大尝试、最大总等待和熔断,预算预留在调用前完成;超时用量未知时不能立即释放所有预留,让其他分支把额度花掉。
并发把普通检查变成竞态
两个分支都读到剩余十元,各自花八元,就超预算。用共享权威账本原子检查并预留,记录 reservation_id,调用后按可核对用量结算。预计成本不是上限,严格预算还需限制最大输出与工具额度;预留不足要追加批准或停止,不能只在事后告警。
取消要穿过执行结构
asyncio 取消在协程能处理的点生效,清理后继续抛 CancelledError;阻塞 SDK 会延迟事件循环,移到线程也不意味着线程可被强杀。长远端调用需要 SDK 取消、目标查询或隔离进程。Temporal Activity 的取消依赖心跳交付,具体行为应按 SDK 验证。验收覆盖取消前、发送后、响应丢失和结算前,分别记录任务状态与实际效果。
回到问题:怎样回答?
先区分整个 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 遇到限流,用户随后取消。
- 设计决策
- 共享截止时间和取消信号,退避可取消,已发出请求保留计费预留并核对。
- 验证目标
- 验收目标是在取消后不再发起新搜索,任务保留已完成证据和真实尝试次数。
- 适用边界
- 服务端不支持撤销时,已发出的搜索仍可能完成并计费。
连续追问与解答
沿着问题的前提和约束继续向下读。先理解参考解答,再尝试收起答案,用自己的话解释因果和取舍。
第 1 层HTTP 超时为什么不能直接认定工具失败?
从统一策略深入到重试最关键的结果不确定性。
参考解答
超时只说明调用方在期限内没拿到确认,远端可能未开始、已成功或正在执行。读取操作可按策略重试;写操作保持 operation_id 并查询回执或幂等重试,不能生成新键。若目标不可核对,标 unknown 并暂停,而不是把请求异常统一记为业务失败。
第 1 层两个 Agent 分支怎样原子预留剩余预算?
多个分支使预算读后写变成双花风险。
参考解答
在共享账本用条件更新或短事务检查 available >= reservation,扣减可用并建立唯一预留记录。并发分支竞争同一权威余额,成功预留者才调用;重试沿用或明确追加预留,不能重复扣同一个 reservation。返回真实用量后结算差额,未知用量保留额度直到对账。
沿着这个回答继续深入
第 2 层预留五元但实际用了七元,原子预留还算有效控制吗?
父问解决并发预留后,成本估算误差成为新的约束。
参考解答
它防并发重复分配,但若预留只是估算就不构成严格上限。调用前限制最大 Token 或工具资源以形成保守上界,或明确允许有限超额并设计追加预留。实际结算超过预留时阻止新调用并告警,不能用事后扣负数声称从未超预算。
沿着这个回答继续深入
第 3 层响应丢失,实际用量查不到,预留能一直卡着吗?
成本上界之外,结算信息不完整要求定义可观察终态。
参考解答
设 unknown 用量状态、对账期限和人工处理路径;严格预算下在确认前保守占用,避免再次花费。若业务允许风险准备金,明确上界与核销策略,但不能默默归零。持续未知需要告警或停止该提供者新调用,而不是靠重试同时扩大不确定费用。
第 1 层同步 SDK 阻塞事件循环时怎么保证可取消?
取消协程与取消底层操作有不同边界。
参考解答
优先使用异步可取消 SDK;I/O 阻塞可用 asyncio.to_thread 保持事件循环响应,但取消等待不会自动终止线程内函数。给 SDK 设置实际请求超时、协作取消信号,必要时用隔离进程;远端已提交仍要对账。不能用 wait_for 证明底层动作已停止。
举一反三:条件变了,怎样推导?
先找出改变的条件,再判断原方案中哪些前提仍成立。下面的案例是教学推演,便于将原理迁移到新问题。
只读昂贵检索
改变的条件:没有写副作用,但每次请求计费
延伸问题:超时后重试还要幂等和预算吗?
推导与参考解答
读请求通常没有重复业务写入,但会重复消耗费用、限流和时间。预算按每次实际尝试预留,缓存或请求去重是否能省费用取决于服务契约。总 deadline 和退避仍要统一限制;不应因只读而无限重试。
保持不变的原理:可重试性与资源可负担性是不同条件,所有尝试仍受总约束。
两个分支一胜出就取消另一条
改变的条件:取消用于降低延迟与成本
延伸问题:被取消分支的预留是否立刻释放?
推导与参考解答
若尚未发送且已确认停止,可以释放;已发送但用量未确认则保留并对账,避免重复分配同一额度。已成功的外部效果记录下来,不能以另一路胜出为由忽略。结算幂等,避免取消回调和成功回调重复归还预算。
保持不变的原理:任务不再需要结果,不代表费用和副作用没有发生。
易错点
- 对权限错误和取消一律重试
- 每次重试重置总 deadline
- 取消后立即认定远端操作没有发生
- 预算先检查后调用,没有预留原子性
参考资料
依据公开技术资料设计;参考资料支持技术机制,场景与评分标准为本站设计,不代表某公司面试原题。 新增问答与迁移案例用于原理讲解,来源核查与案例运行验证分别记录。
检查自己理解到哪一步
读完后可以对照这些标准解释原理、边界和取舍。掌握程度由你自评;需要进一步验证时,再完成下方小任务。
- 基础达标
- 能按错误类型区分可重试、永久失败与用户取消。
- 中高级信号
- 统一截止时间、退避、次数和费用预算。
- 资深信号
- 处理并发预算预留、跨层重试放大与外部动作核对。
巩固练习 按需完成 · 建议 15 分钟
为模型限流、权限拒绝和工具超时写处理矩阵。
展开验收要求与检查点
- 权限拒绝不盲重试
- 重试不重置总截止时间
- 取消后不启动新动作
重点检查
- 能划分暂时错误、永久错误和结果未知
- 能同时限制单次执行与完整任务的时间
- 能解释取消的传播和已发生操作的处理
- 能说明并发调用预算预留为何需要原子性