Agent Runtime 工程 · 工程面试系列 04/05

想象一个 Agent 正在比较两版 API 文档:已经读完规范、筛出三条兼容性风险,最后一步需要在团队系统创建审查任务。任务系统收到请求并返回成功之前,Agent 进程刚好被重启。恢复后,数据库里只有“准备创建审查任务”,没有“创建完成”。

从头运行会多付一次模型和工具的成本;直接重试,团队可能收到两个相同任务;跳过这一步,第一次调用却可能根本没有成功。恢复最难的是判断外部世界已经发生了什么。 以下以这个任务为贯穿案例,设计可恢复执行和用户中途改需求的处理方式。

Checkpoint 存到能够解释下一步为止

可靠的状态最少要回答四个问题:现在执行的是哪版任务、已完成什么、下一步准备做什么、有没有结果尚未确认的副作用。只保留自然语言对话,很难在重启后可靠回答这些问题。一个最小运行快照可以这样组织:

run_id: r-42
goal_rev: 3
checkpoint_rev: 18
status: RUNNING
next_step: create_review_task
evidence_refs: [ev_12, ev_13]
pending_action:
  action_id: review-r42-g3-s7
  idempotency_key: review-r42-g3-s7
  request_fingerprint: <canonical payload hash>
  status: PREPARED

goal_rev 标识用户目标版本;checkpoint_rev 用于比较并更新运行状态。两个进程同时接手时,只有符合当前版本的更新可以推进状态。还要保存足够的输入版本、证据引用和已执行工具结果,才能判断哪些计算可以复用。下一步由运行时从明确的状态转换恢复。

Checkpoint 的频率需要权衡。每次 token 都持久化既贵又难处理;只在任务结束时保存,则无法恢复中间步骤。常见取点是模型或工具步骤的逻辑边界。对带副作用的工具,特别要区分“发请求前”和“拿到结果后”。

把工具调用分成准备、执行和确认

  1. 在本地事务中写入 PREPARED、稳定的幂等键及请求摘要,同时提交 checkpoint;若需要跨进程派发,可同事务写入 outbox。
  2. 执行器调用外部工具,将同一个幂等键传给真正拥有副作用的服务。
  3. 拿到可验证的结果后,提交 CONFIRMED、外部对象 ID,并推进下一步。
  4. 若请求可能已发出但未收到响应,进入待对账状态。优先用动作 ID 或业务唯一键查询;确认没有落地且重放安全时,再使用原幂等键重试。
崩溃位置恢复时看到什么怎样处理
写入准备状态之前没有动作记录从安全边界重新决策
准备已写入,尚未发出或无法判断PREPARED查询外部状态,或按对方支持的幂等契约重放
外部创建已发生,本地确认丢失本地仍是 PREPARED查询并回填对象 ID;不要用新键发第二次
本地确认已提交CONFIRMED 和对象 ID复用结果,直接推进

外部服务必须支持相应语义,例如同键同请求返回第一次结果,或通过业务唯一约束避免创建两条。如果去重窗口已过、无法查询或不支持幂等,未知结果的高风险动作需要人工对账。仅用本地数据库事务或队列至少一次投递,不能证明端到端恰好执行一次。纯读操作一般更容易重试,但也要判断新结果是否对应同一输入版本。

用户说“我刚才说错了”,给它一次明确的状态转换

假设用户改口:“不用开团队任务了,改成只返回报告,范围只查 /orders。”把这句话静默追加在聊天历史里,可能无法阻止已排队的旧动作。更稳妥的是为修改生成事件,把任务从版本 3 推进到版本 4:

用户修订 → 暂停旧版本派发 → 记录 goal_rev=4
       → 按新范围校验证据 → 生成新计划 → 恢复执行

状态推进和后续派发应有版本校验,排队消息带着旧 goal_rev 时,执行器在发起动作前拒绝它。已经开始执行的动作要尝试取消,记录取消是否得到确认;旧结果即使晚到,也不能直接推进新版本。外部服务若支持版本令牌或条件写入,可以进一步防止旧执行器提交;否则检查与外部请求之间仍有竞态,必须对账。

已经创建的审查任务不会因为内部状态回滚而消失。若业务允许,应设计显式撤回或关闭作为补偿,并按授权边界执行。反过来,版本 3 采集的 /orders 证据若来源和版本都未变,可以留用;与 /users 有关的中间结论则不进入新报告。这样避免把“用户改了目标”误解为“所有工作都重做”。

面试追问:你会怎样验证这套恢复设计?

用可控假工具记录每一次创建,然后分别在请求前、服务执行后但回包前、本地确认之后注入崩溃。验证恢复后状态能收敛、外部对象不会重复、旧版本结果不会污染新版本。再加一个负例:服务完全不支持幂等且无法对账,此时系统应保留不确定状态,不能自动重放。这是一组故障注入建议,本文未声称运行过生产验证。

面试时可以先说:“我会把决策、执行和结果确认拆成持久状态;能安全重试的用同一个动作键,未知副作用先对账。用户改需求用版本事件推进,隔离旧任务和晚到结果。”接着挑一个最难的窗口讲清楚:外部成功、本地失败。比笼统说“写入 Redis,重启接着跑”更能体现你知道系统会在哪儿出错。

参考:LangGraph Persistence;AWS Builders’ Library:Making retries safe with idempotent APIs。

Logo

更多推荐