给 Agent 的工具加上重试之前,先想想会不会做两遍

用创建笔记的例子解释超时、幂等键、任务查询,以及重试为什么可能造成重复写入。

目录

请求超时以后,再发一次,大多数时候看起来都没问题。可如果刚才那次已经创建了文件,只是回复没传回来,重试就会再创建一份。

Agent 很容易遇到这种情况。它看见工具没有返回成功,于是换个参数、换个标题,继续尝试。最后任务完成了,文件夹里也多了三个版本。

超时只说明没有按时收到答复

假设工具叫 create_note,负责把整理结果存成一条笔记。一次调用可能经过这些步骤:收到请求、写入数据库、提交事务、返回笔记编号。

如果断线发生在提交之后,客户端看到失败,服务端却已经做完。模型再聪明,也不能仅凭“timeout”判断那条笔记存在不存在。

因此我会让工具明确区分:确定没有执行、确定已经完成、结果未知。最后一种状态需要查询,不能直接等同于失败。

把同一次操作认出来

幂等键的作用,是给一次业务操作一个稳定编号。比如“把本次任务的最终摘要存为笔记”对应一个键。它在网络重试时不变,用户明确要求另建一份时才创建新键。

POST /notes
Idempotency-Key: task-7f3a-final-note

{"title": "胶片扫描记录", "body": "……"}

这个接口是设计示例,不对应现成服务。服务端应把键、请求内容摘要、执行状态和结果编号关联起来。同一个键配同一份内容,再次请求时返回已有结果;同一个键配不同内容,拒绝并要求调用方核对。

不要让模型每次重试都生成随机键,那只是给重复操作换了身份证。键最好由掌握任务状态的程序生成。

最容易漏的是两个请求同时到达

先查询“有没有这个键”,再写入,还不够。两个请求可能同时查到没有,然后各创建一份。

需要数据库唯一约束、事务或服务端等价机制保证只有一个操作获得执行权。如果创建笔记还依赖另一个外部系统,单个数据库事务未必能覆盖整个过程,就要保存中间状态,并使用对方提供的幂等能力或事后对账。

记录也不能刚成功就删除。键的保留时长至少要覆盖可能发生的重试窗口,过期后旧请求再次出现的行为需要有明确约定。

给模型的说明要能直接指导下一步

工具说明里写“创建一条笔记”太少。更有用的是补上三句话:会产生外部写入;超时后先按操作编号查询;只有确认未执行才考虑重新提交。

返回值也应包含可用信息:

{
  "status": "completed",
  "operation_id": "task-7f3a-final-note",
  "note_id": "note-42",
  "replayed": true
}

replayed 表示这次拿到的是已有操作的结果,不是又写了一条。这样的字段比“操作成功啦”更容易交给后续程序处理。

对于文件编辑,也可以记录原文件版本或内容摘要,写入前检查是否发生变化。这里解决的是另一类冲突:任务暂停时,人可能已经改过文件。防止重复写入和防止覆盖新内容,都需要工具层的约束。

Agent 的重试策略最后仍要落回普通软件工程。读操作可以按错误类型重试;写操作先确认有没有执行。把这条线画清楚,比在提示词后面多加几个“务必谨慎”更有用。