先说背景。我在办公室用一台旧台式搭了个本地 Agent,接了日历和待办两个工具,平时帮组里几个同事把聊天里约好的事情落成日程。用了两周,大家反馈还行,直到周一早上,产品组的阿杰在群里发了张截图。
截图里,他周三下午三点的日历上,整整齐齐躺着两个一模一样的“需求评审”。
他私下问我:“它是不是把我的话听了两遍?我就说了一次啊。”
我当时的回答是:“它没听两遍,但它可能做了两遍。”说完我自己也不确定,于是开始查。
一、先翻日志,不猜
我给 Agent 开了比较详细的调用日志,每次调用工具都会记一行:时间、工具名、参数、返回结果、耗时。翻到阿杰那条请求,很快就看到了问题:
第一次调用“创建日程”,耗时 31 秒,返回的是超时错误。 紧接着,Agent 又调用了一次“创建日程”,参数完全一样,耗时 2 秒,返回成功。
也就是说,第一次其实已经建成功了,只是日历那边响应太慢,超过了我设的 30 秒超时。Agent 看到失败,就按我写的规则“失败重试一次”,结果又建了一个。
阿杰听完说:“那你把重试关了不就行了?”
这是我当时也想过的办法,但我想了想没这么做。关掉重试,下次真的失败了,用户以为建好了其实没有,那比多一个会更糟。重复的会一眼就能看到,漏掉的会只有到点了才发现。
二、我改了三个地方
1. 写操作重试前先查一遍
对“创建”“修改”这类会改东西的工具,我加了一个规则:重试前先用同样的标题和时间去查一下,如果已经有了,就当成功处理,不再创建。读操作(查日程、查待办)还是照常重试,反正读多少遍都不会多出东西。
2. 超时时间按工具分开设
以前是一刀切 30 秒。我把日志里过去两周每个工具的耗时拉出来看了看,日历创建偶尔会到二十多秒,查询基本一两秒。于是创建类放宽到 60 秒,查询类保持短一些。
3. 回复里把“做了什么”说清楚
以前 Agent 回复就一句“已为您创建日程”。现在改成带上它实际调用的结果,比如“已创建:周三 15:00 需求评审(检测到同名日程已存在,未重复创建)”。这样用户自己也能发现不对劲。
三、怎么验证改好了
我没有在真实日历上试,而是建了一个测试日历,手动把创建接口的响应人为拖慢到 40 秒,模拟超时。改之前,十次请求出现了七次重复;改之后十次都只建了一个,日志里能看到“重试前检测到已存在,跳过”。
然后又反过来测了一下:让接口直接返回失败、实际也没建。确认这种情况下它还会正常重试并建成功,没有因为“先查一遍”把真失败给吞掉。
两种情况都过了,我才换回正式日历,又让阿杰帮忙用了三天,没再出现重复。
四、几点体会
- Agent 的“失败”不一定是真失败,超时尤其要小心
- 读和写的重试策略应该分开
- 先看日志再动手,比猜原因快得多
- 测试要同时覆盖“假失败”和“真失败”两种情况
说实话,这次如果不是阿杰随口一问,我可能还要过很久才发现。用户的一句吐槽,往往就是最好的测试用例。
想问问各位:你们的 Agent 接写操作工具的时候,重试是怎么处理的?有没有遇到过类似的重复执行?欢迎评论区交流,我这套做法比较土,肯定有更好的方案。
相关的折腾记录我也放在 evafang.com ,有兴趣可以随便翻翻。