咱们做开发的都知道:单线程没问题的地方,并发一上准出事。
Agent 也一样。这两天万能 AI 盒里出了个 BUG,排查过程挺有意思,是一个教科书级的并发问题——只不过"并发线程"换成了"并行的工具调用"。把这个坑记下来,给正在做 Agent 应用的老铁提个醒。
现象:一个工单,三条记录
用户提交了一条售后工单,Agent 判断需要同时做三件事:创建工单、给负责人发通知、写入日志。
从 Agent 的视角看,这是三个独立操作,它并行发起了三个工具调用——这在技术上完全正确,还能省时间。
然后数据库里出现了三条一模一样的工单。
用户收到了三次通知。
排查:五分钟定位,但汗流浃背了
第一反应是"创建工单"这个工具被调了三次?看日志——不是,三次调用参数完全相同。
再想,明白了:Agent 的规划器把"创建工单"这个动作放了三次——一次在主任务里,两次在两个子任务的上下文里。并行执行时谁也不知道谁的存在。
这暴露了两个层面的坑:
坑一:工具的无副作用声明是谎言
我们的工具描述里写着"创建售后工单"。在 Agent 的理解里,这跟"查询工单列表"没区别——都是"操作"。它不知道"创建"这个词意味着调三次就有三条。
人类开发者看到 `createOrder` 和 `listOrders` 会本能地分化对待,但模型只看描述文本。副作用信息不在描述里,对 Agent 就不存在。
坑二:并行调用之间没有隔离机制
传统服务里我们防这种事靠的是幂等键、分布式锁、唯一索引。但 Agent 的工具调用层如果只是裸包了一层 API,这些防线一个都没带上去。
解法:三道锁,一层都不能少
第一道:工具描述里写明副作用与幂等约定
改前:
```
创建售后工单,传入用户ID和问题描述
```
改后:
```
创建售后工单(⚠️写操作,有副作用)。
- 同一请求上下文内只允许调用一次
- 幂等键 idempotency_key 必传,重复提交返回原工单号
```
第二道:服务端幂等键兜底
工具对应的 API 加幂等逻辑:相同 key 的请求 10 分钟内返回首次结果。这是老手艺了,但做 Agent 工具封装时特别容易忘——因为"包一层就行"的心态。
第三道:规划器层加"写操作互斥"
在 Agent 的任务规划器里加规则:同一批次并行调用中,写操作(create/update/delete/submit 类)最多出现一次;需要多个写操作时,改为串行确认。
第三道是我们最后加的,也是最有效的——前两道防的是"重复的相同调用",这道防的是"重复的等价调用"(参数不同但语义重复)。
顺手整理的 Agent 工具设计检查单
这轮踩坑后我把工具设计规范重新整理了一遍,核心就四条:
1.写操作必须在描述里显式标注副作用——"有副作用""不可重入""需幂等键",字越少越要写
2.所有写操作工具,幂等键服务端强制校验——不依赖 Agent 自觉
3.并行批次内写操作互斥——规划器层面控制,别等数据库报错
4.危险操作加确认步骤——删除、支付、对外发送类,工具描述里写明"执行前需用户确认",让 Agent 学会先问再做
结尾
这个 BUG 表面上是"Agent 不懂事",本质是我们把"给人用的 API"直接丢给了 Agent,而人有的常识(创建类操作不能乱点),Agent 没有。
工具描述就是 Agent 的世界说明书。说明书里没写的规矩,它真的不知道。
上回聊了怎么让 Agent 选对工具(写清边界),这回补上了"选对了也不能瞎调"(幂等与互斥)。凑齐了工具治理的另一半拼图。
下回咱们聊 Agent 的"记忆"设计——多轮对话里哪些该记住、哪些该扔,这又是一个上下文预算的精细活。感兴趣的老铁蹲一下。