有一类故障在聊天窗口里完全看不出来:模型决定调工具,工具执行完了,但客户端没收到结果,流断了。用户点重试,第二个请求把同样的活又干一遍——重复建工单、重复发通知、同一份数据写两次。这跟"回答缺了几个字"不是一种失败:前者带外部动作,重发提示词救不了。
先分清断在哪一段
一次带工具调用的任务链路:提交请求 → 模型生成工具调用 → 应用收到参数 → 执行业务动作 → 结果回传模型 → 模型答复用户。断线如果发生在"执行业务动作"之前,核对状态后重试安全;发生在执行之后、回传之前,盲目重试就产生第二个动作。仅凭聊天窗口最后的文字,分不清这两个时刻。
排查时把三件事分开记录:模型响应有没有正常结束、工具有没有执行、业务动作有没有提交。HTTP 200 只说明连接开始返回;业务是否完成,只认自己的数据库和目标系统。
动作 ID:幂等的地基
调用模型前,应用先给用户这次提交生成 operation_id——它代表"用户本意上的一次操作",重试可能换 request_id,但必须指向同一个 operation_id:
| 字段 | 用途 |
|---|---|
| operation_id | 业务动作稳定标识,应用生成并持久化 |
| request_id | 单次请求排错标识,重试可变 |
| tool_name | 实际执行的工具 |
| arguments_hash | 规范化参数摘要,发现同 ID 参数被改 |
| status | pending/running/succeeded/failed |
| external_result_id | 外部系统返回的任务标识 |
不能拿整个工具调用 JSON 当幂等键——字段顺序、无关描述、模型重新生成的调用 ID 都会变。业务键必须来自用户提交和应用状态。同一 operation_id 带不同关键参数到达时,直接拒绝转人工,别把旧任务当新请求。
提交边界:唯一约束是底线
业务表对 operation_id 加唯一约束,处理流程按状态分支:已成功直接返回旧结果;执行中返回处理中状态不再启动第二次;未执行才占有并调用外部系统。"先查再插"没有唯一约束时,并发重试同时到达照样重复执行——占有动作必须原子完成。外部系统若支持幂等键,把 operation_id 传过去,恢复时按键查询;不支持就准备对账步骤。支付、发货这类高风险动作,绝不能用"没收到响应"推定"没执行"。
四种中断逐一演练
用不发真实通知、不扣费的测试工具,人为中断在四个位置各演练一次:工具执行前(恢复后只执行一次);外部系统成功后、写成功状态前(最容易漏的窗口,验证能否按业务标识找回旧结果);已保存成功、模型未收到结果前(重试应直接复用结果);模型已答、客户端未收到结束事件前(界面要能同时展示"响应未确认完整"与"业务已完成")。
每次演练记录 operation_id、请求次数、工具启动次数、外部系统对象数。验收标准不是"模型最后说成功",而是目标系统里确实只有一个业务对象。换模型、换中转入口都不影响这套机制的正确性——幂等在应用层,不在模型层。

评论0