订阅更新

多Agent调用链很长,失败了怎么重试才能幂等不重复?

Agent手册 第 15 题 约 10 分钟读完

本文目录
  1. 30秒版回答
  2. 面试官在考什么
  3. 展开一:区分操作类型,分别设计重试策略
  4. 展开二:幂等键——由业务语义生成,而不是由模型生成
  5. 展开三:流程状态持久化——从失败的地方继续
  6. 展开四:无法幂等的操作——补偿机制
  7. 数据工程经验的迁移
  8. 追问链
  9. 加分说法 vs 减分说法
  10. 怎么挂到你的项目上
  11. 总结

30秒版回答

核心思路是把“推理”和“副作用”分开处理。

推理步骤(查数、分析、生成文本)没有外部副作用,失败了可以直接重试。真正需要保证幂等的是有副作用的工具调用——发邮件、建工单、写表、重跑任务。

我的做法有四点:第一,每个有副作用的操作都带幂等键,由业务语义生成(比如“任务ID + 步骤 + 目标对象”),下游根据幂等键去重。第二,流程状态持久化,每一步完成后记录状态和产出,重试时从失败的步骤继续,而不是从头再来。第三,先记录意图再执行,类似预写日志,执行结果不确定时可以查询确认。第四,无法幂等的操作要有补偿方案,比如发错了邮件要能撤回或发更正。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

面试官:「我们有一个多 Agent 的流程:一个 Agent 拆任务,几个子 Agent 分别去查数、生成报告、发邮件、创建工单。中间某一步失败了要重试,你怎么保证不会重复发邮件、重复创建工单?」

这道题把 Agent 和分布式系统的经典问题结合到了一起。对数据开发来说,它和“ETL 任务怎么设计幂等和可重跑”“Kafka 怎么保证不重复消费”“Flink 的 Exactly-Once 怎么实现”是同一类问题。

Agent 场景让这个问题更难了:模型的输出不是确定的,同一个任务重跑一次,生成的 SQL、报告、甚至调用工具的顺序都可能不一样。所以不能指望“重跑一遍得到同样结果”,而是要在副作用层面保证幂等。

面试官在考什么

• 是否区分有副作用和无副作用的操作,针对性设计重试策略。

• 是否知道幂等键、状态持久化、补偿机制这些分布式系统的标准做法。

• 是否理解模型输出不确定性带来的额外挑战。

• 能否迁移数据工程中的可靠性经验:Checkpoint、两阶段提交、幂等写入。

展开一:区分操作类型,分别设计重试策略

操作类型例子重试策略关键手段
只读推理分析、总结、规划直接重试限制次数,避免死循环
只读工具查数、检索、读日志直接重试超时、退避
幂等写UPSERT、覆盖写分区可重试天然幂等的写法
非幂等写发邮件、建工单、发消息必须带幂等键幂等键 + 下游去重
不可逆操作删除数据、对外付款不自动重试,人工确认审批 + 补偿方案

这张表的意义在于:不是所有失败都该重试,也不是所有操作都能重试。很多 Agent 框架的默认行为是“工具报错就让模型再试一次”,这对只读工具没问题,对发邮件就是灾难。

展开二:幂等键——由业务语义生成,而不是由模型生成

幂等键的关键是:同一个业务意图,无论重试多少次,生成的幂等键都必须一样。所以它不能依赖模型的输出(模型每次生成的邮件正文可能不同),而要由确定的业务字段生成。

# 幂等键由业务语义确定,不依赖模型生成的内容
def idempotency_key(run_id: str, step: str, target: str) -> str:
    # 同一个流程实例、同一个步骤、同一个目标对象 → 同一个键
    return sha256(f"{run_id}:{step}:{target}".encode()).hexdigest()
 
def send_report_email(run_id, to, subject, body):
    key = idempotency_key(run_id, "send_report", to)
    record = side_effect_log.get(key)
    if record and record.status == "DONE":
        return record.result                 # 已执行过,直接返回上次结果
    side_effect_log.upsert(key, status="PENDING", payload=hash_of(body))  # 先记意图
    result = mail_api.send(to, subject, body, idempotency_key=key)        # 下游也支持去重时一并传入
    side_effect_log.upsert(key, status="DONE", result=result)
    return result

这段代码有一个容易被追问的细节:如果在“发送成功”和“记录 DONE”之间进程挂了怎么办?这时状态是 PENDING,重试时不知道邮件到底发没发。解决办法是:下游接口本身支持幂等键(很多消息和工单系统都支持),把同一个键传过去由下游去重;如果下游不支持,就在重试前先查询下游确认(比如按幂等键查工单是否已存在)。

展开三:流程状态持久化——从失败的地方继续

长流程失败后从头再来,不仅浪费成本,还会因为模型输出不确定导致前后不一致(第一次生成的报告和第二次的不一样,但邮件已经发了第一版)。所以要像 Flink 的 Checkpoint 一样,把每一步的状态和产出持久化。

-- 流程状态表:每一步的状态和产出都持久化
CREATE TABLE agent_workflow_step (
  run_id          STRING,      -- 流程实例
  step_id         STRING,      -- 步骤,如 plan / query / report / email
  status          STRING,      -- PENDING / RUNNING / DONE / FAILED / COMPENSATED
  attempt         INT,         -- 第几次尝试
  input_ref       STRING,      -- 输入的引用(对象存储路径)
  output_ref      STRING,      -- 产出的引用:生成的 SQL、报告文件
  idem_key        STRING,      -- 有副作用步骤的幂等键
  error_type      STRING,      -- 可重试/不可重试
  updated_at      TIMESTAMP,
  PRIMARY KEY (run_id, step_id)
);
-- 重试时:DONE 的步骤直接复用 output_ref,不重新调用模型

关键点是“DONE 的步骤直接复用产出,不重新生成”。报告已经生成过了,重试时直接用上次生成的文件去发邮件,而不是让模型重新写一份。这就消除了模型不确定性对一致性的影响。

工程上可以借助持久化执行(Durable Execution)类的工作流引擎来实现,它们会自动记录每一步的执行结果,进程重启后从断点恢复。数据团队常用的调度系统其实也有类似的能力,只是粒度更粗。

展开四:无法幂等的操作——补偿机制

有些操作天生无法撤销,比如邮件已经发出去了。这时候要用 Saga 模式的思路:每个有副作用的步骤都定义一个补偿动作,流程失败时按相反顺序执行补偿。

步骤正向操作补偿操作
创建工单create_ticket关闭工单并备注“流程失败自动关闭”
写入结果表INSERT 分区删除该 run_id 写入的数据 / 覆盖回旧版本
发送通知send_message发送更正通知(无法撤回时)
重跑调度任务trigger_rerun无法撤销,因此前置人工确认

补偿方案的存在,反过来会影响流程设计:把不可逆的操作尽量放到流程的最后。比如先生成报告、校验报告、写入结果表,全部成功后最后一步再发邮件。这样前面任何一步失败都不会产生对外可见的副作用。

数据工程经验的迁移

这道题里的每一个概念,数据开发都有对应的老经验:

Agent 场景数据工程对应共同原则
幂等键ETL 按分区覆盖写、MERGE INTO 按主键 UPSERT重复执行结果一致
流程状态持久化Flink Checkpoint、调度系统的任务状态从断点恢复,不从头再来
先记意图再执行数据库预写日志、Kafka 事务执行结果不确定时可确认
不可逆操作放最后先写临时表、校验通过再切换失败时不产生对外可见的副作用
补偿机制回滚分区、回切旧版本无法原子化时用补偿兜底

我在某出海硬件品牌把维度表的更新逻辑从 UNION 改成了 MERGE INTO,代码量减少了 70%。当时选择 MERGE INTO 的一个重要原因就是它天然幂等:同一批数据跑多少次结果都一样,任务失败可以放心重跑。Agent 的写操作工具,我也会优先设计成这种幂等语义。

追问链

追问一:模型不断重试同一个失败的工具调用,怎么办?

要对错误分类:工具返回的错误信息里明确告诉模型这是“可重试”(超时、限流)还是“不可重试”(参数错误、权限不足、业务规则拒绝)。可重试的错误由框架层做退避重试,不需要模型参与;不可重试的错误把原因和修改建议返回给模型,让它换个方案;同时设置单个工具的重试上限和整体步数上限,超过就终止并上报。

追问二:子 Agent 执行超时,主 Agent 不知道它到底完成没有,怎么办?

这就是分布式系统里的“结果未知”问题。子 Agent 的任务要有唯一 ID 和可查询的状态,主 Agent 超时后先查询状态,而不是直接重新派发。子 Agent 那边按任务 ID 做幂等,即使收到重复派发也只执行一次。这和第 02 题讲的 A2A 协议里任务有生命周期和状态查询是一致的。

追问三:怎么测试这些可靠性设计?

做故障注入测试:在每个步骤之间随机模拟进程崩溃、工具超时、下游返回错误,然后检查最终结果——邮件是否只发了一次、工单是否只建了一个、结果表数据是否正确。这些测试要自动化,放进持续集成里,每次修改流程都跑一遍。

加分说法 vs 减分说法

✔ 加分说法✘ 减分说法
推理和副作用分开,只对副作用做幂等失败了让模型再试一次
幂等键由业务语义生成,不依赖模型输出整个流程从头重跑
流程状态持久化,DONE 步骤复用产出依赖模型生成相同内容来保证一致
不可逆操作放最后,必要时用补偿发邮件这类操作没有去重
迁移 ETL 幂等、Checkpoint 的经验不区分可重试和不可重试错误

怎么挂到你的项目上

话术模板:“多 Agent 流程的可靠性,我的核心思路是把推理和副作用分开:推理可以随便重试,有副作用的工具调用必须带业务语义生成的幂等键,并且流程状态像 Checkpoint 一样持久化,重试时复用已完成步骤的产出,不重新生成。这和我做 ETL 幂等设计的思路一样,比如我把维表更新从 UNION 改成 MERGE INTO,一个重要原因就是天然幂等、可以放心重跑。”

总结

多 Agent 可靠性的核心是:在副作用层面保证幂等,而不是指望模型输出一致。幂等键、状态持久化、先记意图、补偿机制,这四个手段配合起来,再加上“不可逆操作放最后”的流程设计原则。数据开发对幂等和可重跑的理解,是回答这道题的最大底气。

下一篇聊合规:Agent 读到的数据里有个人信息、有跨境数据,合规怎么做?

你们的 AI 应用有没有出过重复发消息、重复操作的事故?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器