ELAI S.r.l.

AI 智能体说“完成了”:动作真的只执行了一次吗?

一次备件预订,一次响应丢失:用十五个可复现案例解释发件箱模式、幂等性、事务边界与限制。

AI 智能体说“完成了”:动作真的只执行了一次吗?

一次预订,两套记录,一个未到达的响应

AI 智能体要为机器预订一个备件。它先在自己的记录中标记“已处理”,再调用仓库,两步之间却中断了。重启后看见“完成”,不再重试,于是根本没有预订。把顺序反过来:仓库已经预订,但响应在更新本地记录前丢失,这时重试可能预订两件。同样是消息未到,结果却可能完全相反。

摘要。如何区分已记录意图、远端效果和已观察到的确认?本文用两个独立 SQLite 数据库、三种协议、五个中断边界建立可执行模型。发件箱保护意图,却不能单独阻止重复,还需要接收方契约。结果来自十五个确定性案例,不是工业可靠性测量;没有真实下单、预订或外部服务调用。

再有说服力的文字,也不能消除事务边界

本地事务让同一系统中的一组修改不可分割:一起提交,或不生效。但它不会自动包含另一服务的写入。语言模型即使选对备件、生成完美 JSON,也无法扩大事务边界。问题出在执行协议,而不是“操作完成”这句话是否可信。

用 I 表示持久化意图,N 表示模拟仓库中的效果次数。对于已接受且可以完成的请求,目标是 N=1;N=0 表示遗漏,N=2 表示重复。这些是无量纲计数,不是概率。本地“done”只是协议记录的状态,若不明确它如何产生,就不能证明任何一种效果次数。

发件箱保存待办意图,而非执行证明

第一步是在同一本地事务中,一起记录业务决策和待发送消息;这条持久化队列就是 outbox(发件箱)。转发进程 relay 只读取已提交意图,再发给接收方。若提交后中断,重启时 pending 行仍在。“还记得要尝试什么吗”不再依赖智能体的易失内存。

这并不会让两个数据库整体原子化。仓库产生效果后,relay 可能在记录送达前中断;pending 行还在,于是再次发送。如果把每次到达都当新请求,教学实现的远端计数器就会增加两次。发件箱填补了决策与消息之间的空隙,却没有消除响应丢失的歧义。

身份属于意图,而不是某次尝试

给预订一个稳定键 tenantA:reserve42,同一意图的每次尝试都使用它。接收方保留键、参数与结果的对应凭据;若凭据存在且参数一致,就返回先前结果,不再产生效果。参数不同应拒绝含糊复用:预订两件与一件不是同一个命令。

same intent → same key new accepted intent → new key receipt(key, parameters, result) + effect → one remote transaction

最后一行是关键。如果先写凭据,再用另一事务产生效果,就把最初的漏洞搬到了接收端;顺序相反,又会出现有效果却无凭据的间隙。模型中两者一起提交。并发服务还需要正确的隔离与唯一性约束处理,防止两个 worker 都看到“键不存在”后分别执行。

十五个案例:实际中断了什么

Python 附件为每个案例创建两个新的 SQLite 文件,分别代表编排器和仓库,保留在 runs 目录便于检查。仓库只有计数器,没有真实库存。异常在预定边界打断控制流程,恢复路径使用新连接;没有断电,也没有测试存储设备抗断电能力。

边界为:0 本地记录之前;1 记录之后、发送之前;2 远端效果之后、尚未观察响应;3 响应之后、本地最终完成标记之前;4 完成标记之后。边界 0 假定调用方重新提交同一意图,否则发件箱不可能记住从未记录的请求。其他情况恢复 pending 工作,而不重做 done 行。

协议 / 边界01234
mark-first10111
outbox-no-dedup11221
outbox-dedup11111

每个单元格是恢复后的效果次数。mark-first 发送前就写 done,因此边界 1 为零,本地记录阻止了必要尝试。无去重 outbox 在边界 2、3 为两次,因为接收方已执行,relay 却未记录。outbox 加原子凭据在这五个建模案例中均为一次。

十五个已执行案例的计数。0–4 是协议边界编号,不是时间或概率;虚线表示期望一次效果,没有测量真实故障频率。
十五个已执行案例的计数。0–4 是协议边界编号,不是时间或概率;虚线表示期望一次效果,没有测量真实故障频率。

不能把图读成成功率:这里选择五个边界,而非从代表性故障分布中抽样。实验用于寻找反例,并在模型内检验性质。代码用断言检查三组结果;文末片段调用同一模拟器输出表格,附件也含绘图代码,数值不是手绘出来的。

性质,以及支撑它的假设

对每个键 k,期望不变量是 N(k)≤1,即同一意图没有第二次效果。接收方事务支撑它:效果与凭据要么都没有,要么都存在,保留的凭据阻止再次增加。还要得到 N(k)≥1,则需要意图持久化、持续尝试、接收方恢复可用、请求有效。前者是协议安全性,后者是活性或进展性;系统可能不重复,却永远卡住。

“恰好一次”只有明确边界才有意义:在声明条件下,每个键一个逻辑效果。它不意味着只有一个网络包、一次尝试,也不保证业务正确。两个不同键的意图可分别预订同一备件,协议上正确,用户看来却错了。因此应在重试循环之前确定已接受动作的身份;每次恢复都让模型重新生成键会使保护失效。

教学模型在哪些地方不再足够

凭据有生命周期。若旧尝试到达前已删除凭据,消息可能被视为新请求。保留期应与延迟、队列及重试策略匹配,或用已关闭的时期标识拒绝过期请求;实验没有实现此策略。租户也是身份的一部分,不同客户使用相同键,不应混为一个意图。

真实系统还要处理并发 worker、过期租约、同一对象更新顺序、部分响应和永久拒绝。远端接受也可能只是启动异步任务,“接受”与“完成”不同。若接收方不支持稳定身份和结果查询,超时后可能必须对账,而不能宣称成功。提示词本身无法补出缺失的执行信息。

结论:智能体究竟可以声称什么

本地记录证明意图已保存,经验证的远端凭据证明接收方契约规定的结果。在模型中,发件箱、稳定重试身份和原子去重同时消除边界 1 的遗漏及 2、3 的重复。支撑“完成”的是这条证据路径,不是日志里的那个词。教学实验不证明 EL-AI 已有相关功能或运营成果。

主要来源解释发件箱模式和幂等 API 契约,也已阅读实现示例、迟到请求和参数变化章节。模拟器、反例和表格是本文实际执行的教学分析,不是来源作者的基准。每个案例处理一个意图和常数次状态转移,控制工作随案例数线性增长;没有估算数据库成本、网络延迟或大型队列。下一步可在具体服务中注入故障,加入并发与凭据过期,但这里只是实验提议。

来源与可复现性

AWS Prescriptive Guidance — Transactional outbox pattern.

Malcolm Featonby — Making retries safe with idempotent APIs, Amazon Builders’ Library.

# Run beside experiment.py; all effects are local simulated counters.
from experiment import run
for protocol in ("mark-first", "outbox-no-dedup", "outbox-dedup"):
    print(protocol, [run(protocol, cut)["effects"] for cut in range(5)])

代码、数据与说明 · JSON. 教学计算使用 Python 3.14.0,图使用 Matplotlib 3.11.2。分析由 AI 辅助,不声称经过同行评审或人工审核。原创 ImageGen 封面仅作示意,不记录 EL-AI 人员、场所或实际安装。来源查阅于 2026 年 10 月 3 日。