Skip to content

Agent 为什么会重复执行?幂等、重试与断点恢复 ​

内容摘要:Agent 会重试、会崩溃、会并发。若写操作没有幂等保护,重试就会重复扣款、重复建单。本章讲幂等、超时、重试与退避、熔断、限流、补偿与断点续跑,并给出可运行、可测试的实现。 难度:高级 |前置知识:06 工具调用、09 状态

常见误区

工具调用超时就代表没有执行成功,可以直接重试;任务恢复时从失败节点重新跑一遍最稳妥。

先给结论 ​

超时只表示调用方没有及时得到结果,不代表副作用未发生。写操作必须先设计幂等键与结果查询,再决定重试、补偿或人工处理;Checkpoint 恢复也要避免重放已完成副作用。

带着这三个问题阅读:

  1. 循环、并发、超时与进程恢复如何造成重复执行?
  2. 幂等、重试、熔断、限流和补偿分别解决什么问题?
  3. 哪些错误可重试,哪些错误重试只会放大故障?

一、为什么 Agent 特别需要可靠性工程 ​

Agent 的循环、恢复、并发都会导致同一写操作被执行多次:

解药是幂等(idempotency):用幂等键保证"同一操作执行多次,效果等于一次"。

二、幂等键:写操作的护身符 ​

python
"""idempotency.py —— 幂等执行器(可离线测试)。"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Callable

@dataclass
class IdempotencyStore:
    """记录已执行的幂等键与结果。生产用 DB 唯一约束/Redis SETNX。"""
    _done: dict[str, dict] = field(default_factory=dict)

    def run_once(self, key: str, op: Callable[[], dict]) -> dict:
        if key in self._done:
            return {**self._done[key], "_deduped": True}   # 命中:直接返回旧结果,不重复执行
        result = op()
        self._done[key] = result
        return {**result, "_deduped": False}

# 示例:创建工单,重复调用不会建两张单
store = IdempotencyStore()
def create_ticket(): return {"ticket_id": "T-1001", "status": "created"}
print(store.run_once("key-abc", create_ticket))   # 首次执行
print(store.run_once("key-abc", create_ticket))   # 重复:_deduped=True

幂等键怎么生成:由业务语义决定(如 hash(用户+动作+目标+时间窗)),而非随机——随机键无法识别"同一操作"。跨请求要持久化。

三、超时、重试与退避 ​

只对可恢复错误重试,且必须有上限:

错误类型可重试?处理
网络抖动、5xx、超时是(有限次)指数退避 + 抖动
限流 429是退避,尊重 Retry-After
认证 401/权限 403否直接失败,别刷
参数错误 4xx否修参数,别重试
python
import random, time
def retry_with_backoff(op, *, max_attempts=3, base=0.2, is_retryable=lambda e: True):
    """指数退避 + 抖动;只重试可恢复错误;有上限。"""
    for attempt in range(1, max_attempts + 1):
        try:
            return op()
        except Exception as e:
            if attempt == max_attempts or not is_retryable(e):
                raise
            sleep = base * (2 ** (attempt - 1)) + random.uniform(0, base)
            time.sleep(sleep)

关键:写操作重试前必须先有幂等键,否则"重试成功"可能是"重复执行"。

四、熔断与限流 ​

  • 熔断(Circuit Breaker):某依赖连续失败到阈值,暂时"断开",快速失败一段时间再半开试探,避免雪崩。
  • 限流(Rate Limiting):限制对模型/工具的调用速率,防止刷爆额度或触发下游限流。
  • Agent 循环内也要有"连续失败上限"(08 的熔断停止)。

五、补偿与 Saga ​

不可用分布式事务时,用补偿:每个写步骤配一个"撤销"动作,失败时反向补偿。适合"建单→通知→扣配额"这类多步写。补偿动作本身也要幂等。

六、长任务与断点续跑 ​

结合状态持久化:把任务切成可 checkpoint 的步骤,崩溃后从最近 checkpoint 恢复,已完成步骤靠幂等键跳过。异步长任务用队列 + 定时器驱动,人工审批等待用带外回调而非阻塞。

七、失败路径 ​

  • 无幂等的重试 → 重复副作用。
  • 无限重试 → 放大故障、烧额度。
  • 对不可重试错误重试 → 浪费且掩盖问题。
  • 恢复不跳过已完成步骤 → 重复执行。

八、测试与验证 ​

python
"""test_idempotency.py"""
from idempotency import IdempotencyStore
def test_run_once_dedup():
    store = IdempotencyStore(); calls = []
    def op(): calls.append(1); return {"v": 1}
    r1 = store.run_once("k", op); r2 = store.run_once("k", op)
    assert r1["_deduped"] is False and r2["_deduped"] is True
    assert len(calls) == 1        # 实际只执行一次
  • 重试:注入前 N 次失败的假依赖,断言最终成功且不超上限。
  • 熔断:连续失败后断言进入 open 状态、快速失败。

九、安全与权限 ​

  • 幂等键、重试日志不得含敏感数据。
  • 补偿动作同样是写操作,需权限与审计(13、14)。

十、性能与成本 ​

  • 重试与退避影响尾延迟;限流保护成本上限(18)。

十一、框架差异 ​

  • LangGraph 的持久化/重放需配合幂等;多数框架不自动保证工具幂等,这是你的责任。

十二、关联章节 ​

面试复述 ​

Agent 可靠性的核心是把不确定调用变成可判定状态。写操作先幂等,再设置超时和分类重试;长任务通过 Checkpoint 恢复,但不能重复已确认的副作用。

复述时先给结论,再补机制、适用边界和一个可验证指标;不要只罗列框架名称或产品功能。

迁移练习 ​

为创建工单接口定义幂等键生成规则和结果查询接口,模拟“服务端成功但客户端超时”,验证重试不会生成第二张工单。

检索关键词 ​

幂等键 idempotency 指数退避 重试上限 熔断 限流 补偿 Saga 断点续跑 长任务队列

官方来源 ​

  • 云厂商幂等/重试最佳实践;Temporal 等工作流引擎文档作为工程对照(verified_at: 2026-09-07)。

ICP 备案号待补充 · Gitee 主页