多Agent调用链很长,失败了怎么重试才能幂等不重复?
本文目录
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年大数据老兵,聊技术、聊职场、聊成长