Agent 失败恢复:重试、补偿、回滚与人工接管
多步任务一定会失败
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-After | 3 | Retry-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]:
- 补偿不要求精确逆操作——比如「发送取消邮件」不是「发送邮件」的精确逆操作,但它是合理的业务补偿。
- 补偿本身也可能失败——需要为补偿失败设计独立的处理路径,而不是无限递归。
- 幂等性是前提——补偿操作可能被执行多次(因为补偿失败后的重试),必须保证幂等。
Agent 工作流中的补偿特殊性
Agent 工作流中的补偿比传统微服务 Saga 多了一层复杂度:有些操作的「补偿」需要模型参与判断。
| 操作类型 | 补偿方式 | 是否需要模型参与 | 示例 |
|---|---|---|---|
| 确定性 API 调用 | 调用逆操作 API | 否 | 预留库存 → 释放库存 |
| 内容生成 | 删除或标记失效 | 否(但需要追踪生成的内容 ID) | 生成文档 → 删除文档 |
| 外部通信 | 发送撤销/更正消息 | 视情况 | 发送邮件 → 发送取消邮件 |
| 决策类操作 | 回滚决策状态,重新评估 | 是 | 选择方案 A → 回到决策点重选 |
| 多轮对话 | 截断对话历史,重新规划 | 是 | 已完成 5 轮对话 → 回退到第 3 轮 |
人工接管:最后的恢复手段
自动恢复不是万能的。有些失败需要人工介入:不可逆操作的风险确认、模型持续输出低置信度结果、补偿操作本身失败、或者任务超出 Agent 的能力范围。
人工接管的设计核心是给接管者足够的上下文。不要只告诉用户「Agent 失败了」——要告诉他们:
- 任务目标是什么——用户最初要求做什么
- 已完成哪些步骤——工作流执行到哪里了
- 失败原因是什么——具体哪个步骤失败、错误信息
- 当前状态是什么——已经产生了哪些副作用、是否已补偿
- 可选操作有哪些——用户可以做什么选择
// ❌ 信息不足的人工接管通知
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 工作流中,从失败检测到恢复的完整决策过程如下:
这个流程覆盖了四种主要的失败路径:
- 瞬态失败路径: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 |
关键实践:
- 每个工作流实例分配唯一 traceId,贯穿所有步骤和补偿操作。
- 记录每次重试的输入差异——尤其是上下文感知重试时注入的错误历史。
- 补偿操作必须独立记录——不能和正向操作混在同一个日志条目中。
- 人工接管事件单独标记——便于统计人工介入率和原因分布。
工程落地检查清单
以下清单可以直接用于评审 Agent 工作流的失败恢复设计:
重试设计
- 每个工具调用是否声明了可重试性(
retryable: true/false) - 重试策略是否区分了瞬态失败和永久失败
- 是否使用了指数退避 + 抖动,而非固定间隔
- 重试时是否注入了错误上下文,让模型调整策略
- 是否遵守了外部服务的 Retry-After 头
幂等与补偿
- 所有写操作是否实现了幂等(幂等键或去重逻辑)
- 每个有副作用的步骤是否定义了对应的补偿动作
- 补偿操作是否按逆序执行
- 补偿操作本身失败时是否有独立处理路径
- 补偿操作的幂等性是否得到保证
人工接管
- 人工接管的触发条件是否在设计阶段明确定义
- 接管通知是否包含任务目标、已完成步骤、失败原因、当前状态和可选操作
- 用户是否可以从中断点继续,而非从头开始
- 高风险不可逆操作是否在执行前强制人工确认
可观测性
- 每个工作流实例是否有唯一 traceId
- 重试、补偿、人工接管事件是否有独立的结构化日志
- 是否对重试次数、补偿次数、成功率设置了监控告警
- Trace 是否能还原失败前的完整执行链路
模型层恢复
- 模型输出是否有格式校验(Schema validation)
- 输出不可信时是否有 Reflection 或自纠正机制
- 是否设置了单任务的 Token 消耗上限
- 模型降级策略是否定义(主模型不可用时切换到备用模型)
小结
Agent 多步任务的失败恢复是一组工程决策的组合:什么值得重试、什么应该补偿、什么需要人工介入、怎么让失败过程可追踪。
核心原则可以归纳为四条:
- 区分失败类型——瞬态重试、永久修正、部分补偿、高风险确认。不同类型用不同策略。
- 副作用必须可逆——每个有副作用的步骤都要有补偿动作。没有补偿的操作,要么前置确认,要么不要做。
- 人工接管要提供上下文——不要让用户猜发生了什么。任务目标、已完成步骤、失败原因、可选操作,一次给全。
- 让失败可见——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