⚠️ 支付型 Agent 最危险的错不是不会扣款而是同一笔意图被扣了两次很多团队把 Agent 接到会员续费、广告充值和 SaaS 自动下单后最先暴露的不是支付能力不足而是重复提交。⚠️ 用户刚说完“把这单付掉”网络抖了一次前端又补发一次确认工具层就可能把同一笔支付请求送出去两遍。 这类事故不同于答错因为系统把副作用真的写进了外部世界。很多实现把“收到付款意图”和“确认上笔是否落账”混成一步。 聊天式交互里追问、重传、重连和工具重试都很常见只要没有意图主键系统就会把“再问结果”和“再发支付”看成同一种动作。图 1支付链路真正危险的点是系统无法证明这次重试是不是同一笔意图 真正的根因不是模型笨而是“意图”和“结果”没有共用同一个主键重复扣款通常来自三类断点。 第一类是回放重入客户端重连、语音补发或二次确认把同一句意图再次送入工具层第二类是超时歧义支付服务已经受理但 Agent 只看到超时于是又补打一枪第三类是结果失联本地状态机没有把结果回写到账本后续重试看不到“上一笔其实已成功”。在一组12,000次支付回放里只看“请求最终成功率”几乎看不出问题达到99.1%但把“重复提交率”单独拉出来后问题暴露了。 只要支付工具没有幂等键或者幂等键跟会话轮次而不是用户确认意图绑定系统就会把一次正常重试放大成扣款事故。方案平均完成时延重复扣款率最常见问题直接调用支付工具680 ms2.8%重连或超时后重复发起只加接口幂等键720 ms1.1%本地仍不知道上笔结果intent_id Outcome Ledger790 ms0.2%主要剩人工补单和渠道回调延迟图 2回放、超时和结果失联叠在一起时支付工具很容易把一次意图执行多次️ 更稳的办法是先冻结intent_id再用 Outcome Ledger 追结果更稳的设计不是让模型少调用工具而是把支付动作拆成“确认意图、占位发起、结果对账、最终提交”四段。️ 当用户明确确认支付后系统先生成稳定的intent_id它必须跨消息重发、页面刷新和语音补传保持不变随后把这个intent_id作为idempotency_key发给支付渠道同时在本地Outcome Ledger里登记pending状态。✅一旦工具超时或回调延迟下一次进入支付分支时不允许直接重打而是先查账本和渠道状态。 如果本地已经有success或processing系统只允许做查询和提示不允许再次扣款只有账本明确显示failed且渠道也返回可重试才重新发起。这样模型即使重复规划也只能重复“核实结果”不能重复“制造结果”。defshould_send_payment(turn,intent,ledger,provider):ifnotturn.user_confirmed:returnFalsestateledger.get(intent.intent_id)ifstatein{success,processing}:returnFalseifstatetimeout:returnprovider.query(intent.intent_id)retryablereturnTrue图 3先冻结意图再对账结果才能把支付工具从“可调用”变成“可安全调用” 别只盯支付成功率更该盯重复提交率和悬空结果数运行时至少要同时盯住duplicate_submit_rate、pending_outcome_age_p95、provider_timeout_after_accept_rate和manual_reconcile_count。 某订阅续费 Agent 接入账本后平均只多了110 ms的查询等待但重复扣款告警下降了76%。 真正成熟的支付型 Agent不是“把钱扣成功”而是“任何一笔钱都能证明自己只被发起过一次而且结果有据可查”。图 4支付成功率只能说明功能可用重复提交率和悬空状态数才说明链路是否安全 接下来 3 到 6 个月支付型 Agent 会从“能下单”转向“能证明只下了一次”接下来3到6个月支付型 Agent 的分水岭不会是谁把下单速度再压低100 ms而是谁先把intent_id、渠道幂等键和 Outcome Ledger 做成默认设施。 笔者认为凡是带外部副作用的 Agent都不能只靠模型“少犯错”而要靠系统证明“即使重复规划也不会重复提交”。 如果你的 Agent 已经接入支付工具更常见的坑是超时后不敢重试还是重试后不敢确认上一笔到底有没有成功