Agent Harness(Agent 脚手架 / 运行时控制层)
让模型能稳定作为 Agent 运行的一层工程化控制系统,负责调度、约束、反馈与恢复。
Agent 调用外部服务失败后,按策略自动重新尝试的容错机制。
内容摘要
重试机制(Retry)是指当 Agent 调用外部服务(LLM API、数据库、搜索引擎等)失败时,系统不立即放弃,而是**按照预设策略自动重新尝试**的容错手段。可以类比为"打电话没人接,等一会儿再打一次"。
重试机制(Retry)是指当 Agent 调用外部服务(LLM API、数据库、搜索引擎等)失败时,系统不立即放弃,而是按照预设策略自动重新尝试的容错手段。可以类比为"打电话没人接,等一会儿再打一次"。
Agent 系统大量依赖网络调用,而网络本身就不稳定——API 服务器可能短暂过载、网络可能抖动、请求可能被限流(Rate Limit,速率限制)。据 OpenAI 官方文档记录,429 Too Many Requests(请求过多)是生产环境中最常遇到的 API 错误码。如果每次遇到这类临时性故障就直接报错,用户体验和任务成功率都会大打折扣。
重试机制与传统的"出错就抛异常"做法的核心区别在于:它能自动区分"临时故障"和"永久错误",只对有恢复可能的故障进行重试,并通过逐渐加长等待时间来避免给已经不堪重负的服务器雪上加霜。
| 结构 | 作用 | 说明 |
|---|---|---|
| 错误分类 | 判断是否值得重试 | 区分可重试错误(429、503、超时)和不可重试错误(401、404) |
| 退避策略 | 控制重试间隔 | 决定每次重试之间等待多久,常用指数退避 |
| 重试上限 | 防止无限循环 | 设定最大重试次数(通常 3-5 次)和最大等待时间 |
| Jitter(随机抖动) | 打散重试时间 | 加入随机偏移,防止多个客户端同时重试造成二次拥堵 |
并非所有错误都值得重试。错误分类是重试机制的第一道关卡:
ConnectionError(连接失败)、TimeoutError(超时)。这些属于临时性故障,等一会儿可能就好了。AWS 的重试最佳实践明确指出:只对网络抖动、超时、限流(HTTP 429 带 Retry-After 头)和临时过载(HTTP 503)进行重试。
退避策略决定两次重试之间等待多长时间。常见的策略有三种:
没有上限的重试等于无限循环。AWS 推荐的参数范围:
即使使用了指数退避,如果 100 个客户端在同一时刻失败,它们的重试时间仍然会"对齐"。Jitter 通过给等待时间加上一个随机偏移量来打散重试时间点。AWS 在其 Builder's Library 中将 Full Jitter(完全随机化)列为推荐的默认策略。
指数退避 + Jitter 的计算过程:
wait = min(base * 2^n, max_delay) + random(0, jitter),其中 n 是当前重试次数n + 1 后重复步骤 2以初始延迟 1 秒、退避因子 2、最大延迟 30 秒为例:
| 重试次数 | 基础延迟 | 加 Jitter 后(示意) |
|---|---|---|
| 第 1 次 | 1 秒 | 0.8 ~ 1.2 秒 |
| 第 2 次 | 2 秒 | 1.6 ~ 2.4 秒 |
| 第 3 次 | 4 秒 | 3.2 ~ 4.8 秒 |
| 第 4 次 | 8 秒 | 6.4 ~ 9.6 秒 |
| 第 5 次 | 16 秒 | 12.8 ~ 19.2 秒 |
图中的核心分叉点在"错误类型"判断处:不可重试错误直接跳出循环,可重试错误才进入退避等待。每次重试前都会检查剩余次数,防止无限循环。
import time
import random
from functools import wraps
def retry(max_attempts=3, base_delay=1.0, backoff_factor=2.0, max_delay=30.0,
retryable_errors=(ConnectionError, TimeoutError)):
"""带指数退避和 Jitter 的重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
delay = base_delay
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except retryable_errors as e:
if attempt == max_attempts - 1:
raise # 最后一次失败,抛出异常
# 指数退避 + Jitter
wait = min(delay, max_delay) + random.uniform(0, delay * 0.1)
print(f"第 {attempt+1} 次失败: {e},等待 {wait:.1f}s 后重试")
time.sleep(wait)
delay *= backoff_factor
return wrapper
return decorator
# 使用示例
@retry(max_attempts=3, base_delay=1.0)
def call_api(prompt):
"""模拟可能失败的 API 调用"""
if random.random() < 0.5:
raise ConnectionError("服务暂时不可用")
return f"回复: {prompt}"
装饰器内部用 delay *= backoff_factor 实现指数增长,random.uniform(0, delay * 0.1) 加入 Jitter。max_delay 防止等待时间无限增长。实际生产中通常使用 tenacity 库(pip install tenacity)而非手写,它提供了 wait_exponential_jitter 等现成策略。
| 概念 | 与重试机制的区别 | 更适合关注的重点 |
|---|---|---|
| 熔断器(Circuit Breaker) | 重试是"失败后再试",熔断器是"故障太多就别试了" | 防止对已崩溃的服务持续发送请求 |
| 超时控制(Timeout) | 超时控制的是"单次请求最多等多久",重试控制的是"失败后要不要再来" | 单次请求的时间边界 |
| 降级(Fallback) | 降级是"正路不通走备路",重试是"正路堵了等一等再走" | 提供替代方案保证基本可用 |
| 幂等性(Idempotency) | 幂等性不是容错机制,但它是重试安全执行的前提 | 确保同一操作重复执行不会产生副作用 |
核心区别:
| 常见误区 | 正确理解 |
|---|---|
| 所有错误都应该重试 | 只有临时性错误(429、503、超时)值得重试,永久性错误(401、404)重试无意义 |
| 失败后立即重试效果最好 | 立即重试可能给已过载的服务雪上加霜,必须使用退避延迟。AWS 和 OpenAI 均推荐指数退避 + Jitter |
| 重试次数越多越可靠 | 过多重试会大幅增加延迟并浪费资源,3-5 次是业界推荐值。超过上限应走降级方案 |
| 只要加了重试就万事大吉 | 重试只解决临时故障,不能替代容量规划、服务监控和根因修复 |
参考答案:
401 是认证失败,属于永久性错误,不会因为重试而变好。对它重试只会白白浪费时间(假设 3 次重试 + 指数退避 = 7 秒),还会给服务器增加无意义的请求负担。正确做法是立即失败并提示用户检查 API Key。
参考答案:
不加 Jitter 时,所有在同一时刻失败的客户端会按相同的退避时间表重试(比如都在第 1 秒、第 3 秒、第 7 秒重试),形成周期性的请求洪峰,即"雷鸣羊群"效应。加 Jitter 后,每个客户端的重试时间被随机打散,服务器收到的请求从"脉冲式"变为"均匀分布式",大幅降低二次过载风险。AWS Builder's Library 对此有详细分析。
参考答案:
不安全。支付转账是非幂等操作,直接重试可能导致重复扣款。正确做法是:(1) 为每笔交易生成唯一的幂等键(Idempotency Key),随请求发送给支付 API;(2) 支付 API 服务端根据幂等键判断是否为重复请求,如果是则直接返回上次的结果而不重复执行;(3) 在客户端,超时后先查询交易状态,确认未成功后再携带相同幂等键重试。Stripe 等支付平台的 API 原生支持幂等键机制。
优先展示同分类且标签更接近的内容,方便继续串联学习。
让模型能稳定作为 Agent 运行的一层工程化控制系统,负责调度、约束、反馈与恢复。
微软开源的多智能体协作框架,通过异步消息驱动多个 Agent 角色分工完成复杂任务。
开源自主 Agent 平台,支持可视化搭建和部署持续运行的 AI Agent 工作流。