简介:本文对比 Agent 系统幂等设计中“入口层稳定键约束”与“执行层操作记录约束”两类方案,解析二者在职责边界、拦截时机、参数校验、状态管理及适用场景上的差异,帮助开发者在客户端重试、消息重投与模型重复规划等复杂条件下,选择更贴合业务不变量的幂等实现路径。
在 Agent 系统中,同一业务操作被重复触发几乎是常态:客户端超时重试、消息队列重投、故障恢复回放、模型重新规划后再次生成工具调用,都会让一个原本只应生效一次的业务动作在技术上执行多次。对纯计算任务,重复通常只是资源浪费;但对发券、下单、补发、扣款等写操作,重复执行会直接改变业务账目。
于是问题变成:幂等控制应该放在哪一层?是入口收到请求时就拦截,还是在真正产生业务效果的位置阻止重复生效?实践中常见两类方案:
本文围绕这两类方案展开对比,重点回答“区别是什么、如何组合、怎么选”。
入口层稳定键约束关注的是“同一个请求意图不要生成第二个任务”。它为每次业务提交分配一个稳定请求键,入口收到重复请求时,用该键查询是否已有任务记录,若有则返回原任务,而不是新建任务。
执行层操作记录约束关注的是“同一个业务操作不要产生第二份业务效果”。它不假设上游只提交一次,而是允许任务被多次触发、多次编排、多次调用工具,但在真正写入业务结果前,依据业务唯一关系判断该操作是否已经生效。
两者的关键差异在于:入口层保护的是“任务身份”,执行层保护的是“业务身份”。任务身份可以因重试、重投、重新规划而产生多个,业务身份却必须唯一。
入口层在请求进入系统时拦截,能最早避免重复任务创建,节省编排与工具调用资源。执行层在业务写入前拦截,虽然晚一些,但能覆盖入口层看不到的重复来源,例如模型重新规划生成的新工具调用、上游误建的第二个任务。
入口层依据稳定请求键,通常由客户端或上游生成并透传;执行层依据业务不变量,例如“同一租户的一张售后补发单,某类权益最多补发一次”。前者是技术身份,后者是业务身份。仅靠参数哈希或“一个用户只能发一次券”这类粗粒度规则,会误拦合法的第二次业务操作。
执行层方案强调区分三个层次:
同时明确不能把模型的 tool_call_id 当作幂等键,因为它标识的是一次工具调用,而非业务操作。入口层方案通常只有一个请求键,重试语义相对简单,但也更容易把“同一请求键的不同业务意图”混为一谈。
执行层在同键命中时会检查参数指纹,并在租户和权限范围内读取原结果,避免跨租户泄露或越权返回。入口层若只做键存在性判断,可能忽略参数变化,导致“同键不同参”被错误地当作同一操作返回。
执行层要求操作记录按“执行中、成功、未知、最终失败”分别处理,不能只存一个“已处理”标记。因为“未知”状态需要后续对账或补偿,“执行中”需要防止并发重复进入,“最终失败”才允许按策略重试。入口层通常只记录任务是否存在及其结果引用,状态粒度较粗。
| 对比维度 | 入口层稳定键约束 | 执行层操作记录约束 |
|---|---|---|
| 拦截时机 | 请求进入时 | 业务效果写入前 |
| 保护对象 | 任务身份 | 业务身份 |
| 核心标识 | 稳定请求键 | 业务操作号 + 尝试号 |
| 重试语义 | 同键返回原任务 | 换尝试号,业务操作号不变 |
| 参数处理 | 需校验参数指纹 | 同键检查参数指纹并按权限读取结果 |
| 状态粒度 | 较粗,任务级 | 较细,执行中/成功/未知/最终失败 |
| 覆盖重复来源 | 客户端重试、消息重投 | 额外覆盖模型重新规划、上游误建任务 |
| 资源节省 | 更早,节省编排与调用 | 稍晚,但业务安全性更强 |
| 实现复杂度 | 较低 | 较高,需要业务唯一关系建模 |
tool_call_id,必须在执行层用业务操作号兜底。tool_call_id 标识调用,不标识业务操作。从单一入口幂等迁移到入口加执行层组合时,需要评估:历史任务记录是否可补齐业务操作号;并发场景下“执行中”状态的锁与超时策略;未知状态的对账机制;参数指纹算法变更带来的兼容性;以及跨租户读取结果的权限校验。若只做入口层,需接受模型重新规划导致的漏拦风险;若只做执行层,则可能浪费编排与调用资源。组合方案复杂度更高,但边界更清晰。
入口层稳定键约束与执行层操作记录约束并非替代关系,而是互补关系。前者用稳定请求键保护任务身份,拦截客户端重试与消息重投;后者用业务操作号保护业务身份,覆盖模型重新规划与上游误建任务,并在租户与权限范围内安全返回原结果。幂等的目标不是禁止执行两次,而是允许重复尝试、在约定范围内不产生第二份业务效果。选型时应先确定业务不变量,再决定约束放在哪一层、组合到何种程度。