Agent 失败恢复:重试、补偿、回滚与人工接管

0阅读11分钟

多步任务一定会失败

Agent 工作流包含模型调用、工具调用、外部系统集成和用户确认。任何一个环节都可能失败——网络超时、API 限流、模型幻觉、权限不足、参数格式变化,甚至只是下游服务临时不可用。问题只在于什么时候出错。

MAST(Multi-Agent Systems Failure Taxonomy)的研究识别出 14 种失败模式,分布在规格与系统设计、Agent 间对齐、任务执行三个大类中 [1]。Microsoft 的 Agentic AI 失败模式白皮书进一步归纳了 Agent 滥用、决策错误、拒绝服务等六类系统性风险 [2]。这些研究的核心结论是一致的:多步 Agent 系统的失败是常态,而非边缘情况。

生产系统不能只设计成功路径。失败恢复的目标是让任务停在可理解、可继续、可撤销的位置——用户可以看懂发生了什么,可以选择继续还是回退,可以信任系统没有在背后留下不一致的状态。

失败分类:从 transient 到 permanent

在讨论恢复策略之前,需要先把失败分清楚。不同类型的失败对应不同的恢复路径,用同一种重试策略处理所有失败是最常见的工程错误。

按恢复可能性分类

失败类型特征是否可重试典型示例
瞬态失败(Transient)临时性、外部依赖、预期会自行恢复是,带退避网络超时、429 限流、第三方服务 503
永久失败(Permanent)确定性错误、重试不会改变结果否,需要修正输入或流程权限不足、参数无效、资源不存在
部分成功(Partial)前序步骤已执行,后续步骤失败视情况,需要补偿已执行步骤数据库写入成功但通知发送失败
输出不可信(Unreliable)模型返回结果但置信度不足是,但需要换策略而非简单重试低置信度分类、缺少证据的推理、格式不符
高风险暂停(High-risk Pause)操作不可逆或影响范围大,需人工确认不自动重试,等待人工删除资源、发送外部通知、金额变更

这个分类直接决定了后续选择哪种恢复策略。瞬态失败适合指数退避重试,永久失败需要修正根因,部分成功需要补偿机制,输出不可信需要 Reflection 或人工校验,高风险操作需要前置确认。

按失败层级分类

另一种视角是从系统层级来看:

层级失败原因影响范围恢复责任
模型层幻觉、格式错误、推理链断裂单次调用当前 Agent 自纠正
工具层工具调用超时、返回异常、Schema 不匹配单个工具调用工具调用框架
编排层步骤依赖失败、状态不一致、并行任务部分超时整个工作流工作流引擎
系统层基础设施故障、密钥过期、配额耗尽全局运维 / 平台团队

分层理解失败有助于把恢复逻辑放在正确的架构位置——模型层的自纠正不应该由工作流引擎处理,系统层的基础设施故障也不应该让 Agent 自己消化。

重试策略:不只是「再试一次」

重试是最直觉的恢复策略,也是被误用最多的策略。一个简单的 retry(3) 在生产环境中既可能不够用,也可能造成雪崩。

指数退避与抖动

对于瞬态失败,指数退避(Exponential Backoff)是基本模式。每次重试间隔翻倍,加上随机抖动(Jitter)防止多个客户端同时重试造成的「惊群效应」。

// ❌ 简单重试:固定间隔,无抖动,容易造成惊群
async function simpleRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (e) {
      if (i === maxRetries - 1) throw e
      await sleep(1000) // 固定 1 秒,所有客户端同步重试
    }
  }
  throw new Error('unreachable')
}
 
// ✅ 指数退避 + 抖动:间隔递增,随机偏移,避免惊群
async function backoffRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 5,
  baseDelay = 100,
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (e) {
      if (i === maxRetries - 1) throw e
      if (!isRetryable(e)) throw e // 永久失败立即放弃
      const jitter = Math.random() * baseDelay
      const delay = baseDelay * Math.pow(2, i) + jitter
      await sleep(delay)
    }
  }
  throw new Error('unreachable')
}

关键差异不只是间隔策略——是否区分可重试与不可重试错误决定了重试是否会浪费时间。对 403 Forbidden 重试 5 次毫无意义,但对 429 Too Many Requests 不重试则会不必要地丢弃任务。

上下文感知重试

Agent 工作流中的重试和普通 API 调用有一个重要区别:重试时需要把之前的错误信息注入到模型上下文中,让模型有机会调整策略,而不是简单地重复相同的调用。

// ❌ 盲目重试:重复相同的 prompt 和参数
async function blindRetry(agent: Agent, prompt: string) {
  for (let i = 0; i < 3; i++) {
    try {
      return await agent.run(prompt)
    } catch (e) {
      // 忽略错误,原样重试——大概率得到相同的结果
    }
  }
}
 
// ✅ 上下文感知重试:累积错误历史,让模型调整策略
async function contextAwareRetry(
  agent: Agent,
  prompt: string,
  maxRetries = 3,
) {
  const errorHistory: string[] = []
 
  for (let i = 0; i < maxRetries; i++) {
    try {
      const context = errorHistory.length > 0
        ? `${prompt}\n\n之前的尝试遇到了以下错误,请调整策略:\n${errorHistory.map((e, idx) => `尝试 ${idx + 1}: ${e}`).join('\n')}`
        : prompt
      return await agent.run(context)
    } catch (e) {
      errorHistory.push(formatError(e))
    }
  }
  throw new Error(`重试 ${maxRetries} 次后仍然失败:\n${errorHistory.join('\n')}`)
}

SitePoint 关于概率性代码执行的错误处理研究指出,在重试之间改变修正上下文——把错误信息、堆栈跟踪、之前的输出喂回 Agent 的下一个 prompt——是提高恢复成功率的关键 [3]

重试决策矩阵

错误类型HTTP 状态码重试策略最大重试次数退避策略
网络超时N/A重试5指数退避 + 抖动
服务端限流429重试,遵守 Retry-After3Retry-After 优先
服务端临时故障502/503/504重试3指数退避
认证失败401/403不重试0触发人工接管
参数无效400/422不重试0修正输入后重新提交
资源不存在404不重试0检查前置步骤
模型幻觉N/A带上下文重试3注入错误反馈
工具调用失败N/A带上下文重试2切换工具或降级

补偿与回滚:Saga 模式在 Agent 工作流中的应用

当多步任务已经执行了若干步骤后失败,简单的重试已经不够——需要处理已经产生的副作用。这正是分布式系统中 Saga 模式要解决的问题 [4]

Saga 模式的核心思想

Saga 模式把一个长事务拆分为一组本地事务,每个本地事务都有对应的补偿操作。如果某个步骤失败,系统逆序执行已完成步骤的补偿操作,把系统状态恢复到一致状态。

Saga 有两种实现方式:

模式结构优点缺点适用场景
编排式(Choreography)每个服务监听事件、发布事件,去中心化松耦合、易扩展流程难以追踪、补偿逻辑分散步骤少、团队独立
编排器式(Orchestration)中央协调器控制整个流程流程清晰、便于管理补偿协调器是单点、复杂度集中步骤多、需要全局视图

Agent 工作流通常更适合编排器式 Saga——有一个明确的 Worklow Engine 控制步骤顺序、管理状态、执行补偿。

补偿动作设计

每个 Agent 工具调用如果会产生副作用,就需要定义对应的补偿动作。

// ❌ 只关心正向操作,失败后状态不一致
async function createOrderWorkflow(agent: Agent, order: Order) {
  await inventoryService.reserve(order.items)    // 库存已预留
  await paymentService.charge(order.total)        // 已扣款
  await notificationService.send(order.userId)    // ← 这里失败了
  // 库存已预留、款已扣,但通知没发出去——状态不一致
}
 
// ✅ 每个步骤定义补偿动作,失败时逆序补偿
async function createOrderWithCompensation(agent: Agent, order: Order) {
  const compensations: Array<() => Promise<void>> = []
 
  try {
    await inventoryService.reserve(order.items)
    compensations.push(() => inventoryService.release(order.items))
 
    await paymentService.charge(order.total)
    compensations.push(() => paymentService.refund(order.total))
 
    await notificationService.send(order.userId)
    compensations.push(() => notificationService.cancel(order.userId))
  } catch (e) {
    // 逆序执行补偿
    for (const compensate of compensations.reverse()) {
      try {
        await compensate()
      } catch (compensationError) {
        // 补偿本身也可能失败,需要记录并告警
        logger.error('补偿操作失败', { error: compensationError })
        await alertService.notify('compensation-failed', {
          originalError: e,
          compensationError,
          order,
        })
      }
    }
    throw e
  }
}

Temporal 关于补偿事务的分析强调了几个关键原则 [5]

  1. 补偿不要求精确逆操作——比如「发送取消邮件」不是「发送邮件」的精确逆操作,但它是合理的业务补偿。
  2. 补偿本身也可能失败——需要为补偿失败设计独立的处理路径,而不是无限递归。
  3. 幂等性是前提——补偿操作可能被执行多次(因为补偿失败后的重试),必须保证幂等。

Agent 工作流中的补偿特殊性

Agent 工作流中的补偿比传统微服务 Saga 多了一层复杂度:有些操作的「补偿」需要模型参与判断。

操作类型补偿方式是否需要模型参与示例
确定性 API 调用调用逆操作 API预留库存 → 释放库存
内容生成删除或标记失效否(但需要追踪生成的内容 ID)生成文档 → 删除文档
外部通信发送撤销/更正消息视情况发送邮件 → 发送取消邮件
决策类操作回滚决策状态,重新评估选择方案 A → 回到决策点重选
多轮对话截断对话历史,重新规划已完成 5 轮对话 → 回退到第 3 轮

人工接管:最后的恢复手段

自动恢复不是万能的。有些失败需要人工介入:不可逆操作的风险确认、模型持续输出低置信度结果、补偿操作本身失败、或者任务超出 Agent 的能力范围。

人工接管的设计核心是给接管者足够的上下文。不要只告诉用户「Agent 失败了」——要告诉他们:

  1. 任务目标是什么——用户最初要求做什么
  2. 已完成哪些步骤——工作流执行到哪里了
  3. 失败原因是什么——具体哪个步骤失败、错误信息
  4. 当前状态是什么——已经产生了哪些副作用、是否已补偿
  5. 可选操作有哪些——用户可以做什么选择
// ❌ 信息不足的人工接管通知
interface FailureNotification {
  status: 'failed'
  message: '任务执行失败,请联系管理员'
}
 
// ✅ 结构化的人工接管上下文
interface HandoffContext {
  taskGoal: string           // 原始任务目标
  completedSteps: Array<{    // 已完成步骤及其副作用
    step: string
    status: 'completed' | 'compensated'
    sideEffects?: string[]
  }>
  failedStep: string         // 失败步骤
  failureReason: string      // 失败原因
  failureCategory: 'transient' | 'permanent' | 'unreliable' | 'high-risk'
  retryCount: number         // 已重试次数
  options: Array<{           // 可选操作
    action: string
    description: string
    risk: 'low' | 'medium' | 'high'
  }>
  traceId: string            // 用于追踪完整执行链路
}

人工接管的触发条件应该在设计阶段明确定义,而不是事后补救:

触发条件说明示例
重试次数耗尽瞬态错误重试 N 次后仍失败API 连续 5 次超时
不可重试错误确定性错误,重试无意义403 权限不足
补偿失败回滚操作本身出错退款 API 不可用
输出置信度低模型结果不确定分类置信度 < 0.6
高风险操作确认不可逆操作需要人工授权删除生产数据
成本阈值累计 API 调用成本超标Token 消耗超过预算
超时任务执行时间超过预期工作流运行超过 30 分钟

端到端流程:一个完整的失败恢复流程

一个多步 Agent 工作流中,从失败检测到恢复的完整决策过程如下:

流程图画布 · 115%
Mermaid 流程图加载中...

这个流程覆盖了四种主要的失败路径:

  • 瞬态失败路径:A → B → E → F → G → A(退避重试)
  • 永久失败路径:A → B → E → I → M → O(直接补偿)
  • 输出不可信路径:A → B → E → J → K → A(上下文注入重试)
  • 高风险暂停路径:A → B → E → L → A 或 H(人工确认后继续或补偿)

可观测性:让失败可追踪

失败恢复的前提是能看到失败发生了什么。Agent 工作流的可观测性需要覆盖三个层面:

层面采集内容用途示例工具
Trace每个步骤的输入、输出、耗时、错误还原执行链路OpenTelemetry、Langfuse
Metrics重试次数、补偿次数、成功率、延迟分布监控和告警Prometheus、Grafana
Logs结构化日志,包含 traceId、stepId、错误详情问题排查ELK、Loki

关键实践:

  1. 每个工作流实例分配唯一 traceId,贯穿所有步骤和补偿操作。
  2. 记录每次重试的输入差异——尤其是上下文感知重试时注入的错误历史。
  3. 补偿操作必须独立记录——不能和正向操作混在同一个日志条目中。
  4. 人工接管事件单独标记——便于统计人工介入率和原因分布。

工程落地检查清单

以下清单可以直接用于评审 Agent 工作流的失败恢复设计:

重试设计

  • 每个工具调用是否声明了可重试性(retryable: true/false
  • 重试策略是否区分了瞬态失败和永久失败
  • 是否使用了指数退避 + 抖动,而非固定间隔
  • 重试时是否注入了错误上下文,让模型调整策略
  • 是否遵守了外部服务的 Retry-After 头

幂等与补偿

  • 所有写操作是否实现了幂等(幂等键或去重逻辑)
  • 每个有副作用的步骤是否定义了对应的补偿动作
  • 补偿操作是否按逆序执行
  • 补偿操作本身失败时是否有独立处理路径
  • 补偿操作的幂等性是否得到保证

人工接管

  • 人工接管的触发条件是否在设计阶段明确定义
  • 接管通知是否包含任务目标、已完成步骤、失败原因、当前状态和可选操作
  • 用户是否可以从中断点继续,而非从头开始
  • 高风险不可逆操作是否在执行前强制人工确认

可观测性

  • 每个工作流实例是否有唯一 traceId
  • 重试、补偿、人工接管事件是否有独立的结构化日志
  • 是否对重试次数、补偿次数、成功率设置了监控告警
  • Trace 是否能还原失败前的完整执行链路

模型层恢复

  • 模型输出是否有格式校验(Schema validation)
  • 输出不可信时是否有 Reflection 或自纠正机制
  • 是否设置了单任务的 Token 消耗上限
  • 模型降级策略是否定义(主模型不可用时切换到备用模型)

小结

Agent 多步任务的失败恢复是一组工程决策的组合:什么值得重试、什么应该补偿、什么需要人工介入、怎么让失败过程可追踪。

核心原则可以归纳为四条:

  1. 区分失败类型——瞬态重试、永久修正、部分补偿、高风险确认。不同类型用不同策略。
  2. 副作用必须可逆——每个有副作用的步骤都要有补偿动作。没有补偿的操作,要么前置确认,要么不要做。
  3. 人工接管要提供上下文——不要让用户猜发生了什么。任务目标、已完成步骤、失败原因、可选操作,一次给全。
  4. 让失败可见——Trace、Metrics、Logs 三层可观测性。不可追踪的失败等于不可恢复的失败。

这些原则不依赖于特定的 Agent 框架或工作流引擎。无论使用 LangGraph、CrewAI、Temporal 还是自研系统,失败恢复的设计思路是相通的。

参考资料

<a id="ref-1"></a> [1] Grigoryan, A. A. (2025). Why Do Multi-Agent LLM Systems Fail? arXiv:2503.13657. https://arxiv.org/pdf/2503.13657

<a id="ref-2"></a> [2] Microsoft. (2025). Taxonomy of Failure Mode in Agentic AI Systems. https://cdn-dynmedia-1.microsoft.com/is/content/microsoftcorp/microsoft/final/en-us/microsoft-brand/documents/Taxonomy-of-Failure-Mode-in-Agentic-AI-Systems-Whitepaper.pdf

<a id="ref-3"></a> [3] SitePoint. (2026). Error Handling Strategies for Probabilistic Code Execution. https://www.sitepoint.com/error-handling-strategies-for-probabilistic-code-execution/

<a id="ref-4"></a> [4] Microsoft Azure. Saga Design Pattern. https://learn.microsoft.com/en-us/azure/architecture/patterns/saga

<a id="ref-5"></a> [5] Temporal. (2023). Saga Compensating Transactions: The Key to Handling Failures. https://temporal.io/blog/compensating-actions-part-of-a-complete-breakfast-with-sagas

<a id="ref-6"></a> [6] Towards AI. (2026). Building Retries in Agents: How to Build AI Agents That Survive Failures. https://pub.towardsai.net/building-retries-in-agents-how-to-build-ai-agents-that-survive-failures-32eedd2623f0

<a id="ref-7"></a> [7] newline. (2025). 5 Recovery Strategies for Multi-Agent LLM Failures. https://www.newline.co/@zaoyang/5-recovery-strategies-for-multi-agent-llm-failures--673fe4c4

<a id="ref-8"></a> [8] AWS. Saga Patterns - Prescriptive Guidance. https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/saga.html

评论 0

0 / 1000