把创建工单的超时从5秒调到30秒,或许能减少慢响应造成的超时,却解决不了响应丢失后的问题:没拿到结果,你还敢让Agent再创建一次吗?
写操作超时后,先保留“结果未知”,再由Runtime依据业务契约查单或重试;模型不应凭一句报错重新发起业务动作。Runtime是负责工具执行与恢复的运行层,业务接口则必须提供可核对的结果和去重边界。
这里真正要设计的不是重试次数,而是第二次写请求凭什么被允许发出。
5秒超时,工单可能已经存在
用一个假设场景贯穿全文:用户要求为租户A的设备E17创建故障工单。服务端已经生成W731,成功响应却在网络中丢失;Runtime等待5秒后超时。下文接口、状态和预算都是为这个场景提出的设计,并非某个框架的内建功能。
从业务端看,工单已创建;从调用端看,只知道没有拿到结果。此时若把工具输出写成“创建失败,请重试”,模型可能再次选择创建工具,第二张工单就有了入口。
调用方要区分三种判断:有可靠证据证明未执行;有权威结果证明已执行;证据不足,结果未知。前两种是业务判断,第三种是调用方的知识状态。一次连接断开,通常只足以支持第三种。
只有确认请求尚未离开本地、也没有其他发送尝试时,才能凭发送侧证据认定未执行。已经交给代理、SDK或网关的请求,即使本地没有读到任何响应字节,也可能继续向业务端推进。
所以,给模型的结果应是“op-204正在核验,请勿重新创建”,而非笼统失败。这段提示只是展示,真正的拦截必须在执行层完成。恢复过程仍要继续,只是控制对象从这次HTTP调用转成了这次业务操作。
![]()
▲ 成功响应丢失只让调用方失去证据:工单已创建,Runtime仍须保留结果未知。
给业务意图一个跨回合的身份
在第一次发送前,Runtime先持久记录业务操作ID:op-204。它代表用户这次被批准的创建意图,绑定租户、设备、动作和原始参数摘要。恢复执行、切换工作进程或进入下一轮对话,都沿用这条记录。
这里至少有四种身份。工具调用ID负责关联模型的一次调用与返回;业务操作ID标识要完成的业务意图;幂等键供接口识别同一次操作的重复提交;尝试ID则区分每次实际发送,用于排障。
OpenAI的Function calling流程里,应用执行工具,再通过function_call_output的call_id关联返回。这解决的是对话协议配对,没有替工单接口承诺持久去重。把call_id直接放进幂等键字段,仍需额外保证它在所有恢复路径上稳定。
例如第一次调用是c1,模型下一轮又生成c2。若每个调用各自产生一个新键,接口会看到两个合法的新操作。正确处理是让c2先经过执行层,识别它是在恢复op-204,再返回该操作状态,而不是重新申请创建。
这种识别要依赖任务中的操作引用或明确的恢复入口,不能靠“两个工单标题相似”猜测。用户确实要求再建一张时,应确认新的业务意图并生成新操作ID,避免过度去重吞掉合法请求。
Runtime保存的也应是冻结后的请求参数,而不是重试时让模型重新组织一遍。设备仍为E17,但优先级被重新推理成另一个值,这已经是参数修改,需要单独判断授权和操作语义。
![]()
▲ c1、c2只有被明确识别为同一意图的恢复,才能关联op-204;恢复沿用原键与冻结参数,每次发送另记尝试。
幂等契约要回答范围、时间和并发
幂等在这里指重复提交同一操作,不额外产生同类业务副作用。一个名为Idempotency-Key的请求头只是入口;接口究竟如何识别、保留和处理重复请求,才决定恢复是否安全。
Stripe会保存首次执行的状态码与响应体,连500也在其中;键至少存在24小时后可以被清理,清理后同键再来可被当作新请求。同键参数改变会报错。但参数校验失败或与执行中的请求并发冲突时,Stripe不会保存这次幂等结果,因为端点尚未开始执行。“首次请求”因此不能一概理解为首次到达服务器的请求。
作用域同样会改变结论。AWS EC2的RunInstances存在区域和可用区幂等语义;指定Placement.AvailabilityZone或SubnetId时使用可用区幂等,未显式或隐式指定可用区时使用区域幂等。同一ClientToken跨相应边界可以创建不同资源。把请求故障切到另一区域,并不天然延续原去重保证。
回到工单接口,建议把唯一性约束写成“同租户、同创建动作、同业务操作ID”。请求重放键k-204只是这个操作的传输标识,服务端还应检查规范化参数摘要,不能只看键相同就接受不同内容。
最小请求与查询接口可以长这样:
POST /tickets
Idempotency-Key: k-204
{"operation_id":"op-204","device_id":"E17","priority":"P2"}
GET /operations/op-204
{"status":"SUCCEEDED","resource_id":"W731","state_version":4,"replay_allowed":false}
租户从可信身份上下文取得,不能相信模型传来的tenant字段。查询返回业务状态、工单ID、状态版本和允许的下一步;重放许可也应由服务端校验,不能把客户端回传的布尔值当授权。
保留期至少拆成两项:请求重放保护可先约定不少于24小时,操作查询记录保留不少于7天。前者过期不意味着后者允许再次创建。只要op-204仍有成功记录,就返回W731;若已失去去重证据,则拒绝自动写入,交给核验流程。
若连操作记录也会清理,还须留下能拒绝旧请求的依据。例如由服务端签发绑定租户、动作、操作ID与截止时间的执行凭据,并在业务执行入口校验;或者保留足够长的已用操作标记。截止时间覆盖允许的恢复窗口,过期操作只能核验,不能当作首次创建。否则“查无记录”无法区分新操作与已遗忘的旧操作。
还要约定并发行为:两个执行者同时提交op-204时,只有一个获得业务执行资格,另一个得到处理中或已有结果。同键异参则明确拒绝。若实现允许两个请求都先创建、事后再比较缓存,契约就没有兑现。
本文只要求业务方能证明这项接口保证,不展开其存储实现。工单创建后的通知、扣减等其他系统动作,各有自己的去重范围;一个幂等键不能保证跨系统“恰好执行一次”。
把查询优先写进Runtime状态机
先分开两类状态:业务结果记录UNKNOWN、PROCESSING、SUCCEEDED或明确拒绝等证据;调度状态记录是否待发送、可重试或已转人工。RETRY_READY与MANUAL_REVIEW不改变业务结果,转人工后收到成功证据,也不自动恢复写权限。
Runtime应在发送前持久化PREPARED记录,并在交给网络层前,以条件更新登记IN_FLIGHT及本次尝试。登记与实际发送无法天然原子化:恢复时凡无法证明从未发送的记录,都按UNKNOWN处理。
即使本地仍显示PREPARED,也要核对发送路径是否严格遵守先登记、后发送的约束,不能仅凭状态名称认定未执行。进程崩溃后必须沿原操作ID核验,而不是因缺少成功记录重新开始。
对op-204,UNKNOWN触发按操作ID查询。查到SUCCEEDED和W731,就保存结果并向模型返回成功;查到PROCESSING,按服务端建议间隔等待;查到明确的业务拒绝,保存失败原因,停止无意义重放。
若查询返回404,先保持UNKNOWN。它可能来自读副本延迟、错误租户、记录清理,甚至原请求还在路上。查询接口必须明确读取哪个权威状态;普通搜索工单列表的“没找到”,无法充当未执行证明。
Runtime进入RETRY_READY有两条路径:接口确认NOT_EXECUTED,并给出有效的安全重放条件;或者结果仍未知,但契约保证当前作用域和保留期内的同操作重放安全。
前一路径必须排除旧尝试稍后生效,或由服务端同操作唯一性覆盖竞争;后一路径仍保留结果未知,不能改记为“未执行”。两条路径都须重新检查当前权限和剩余预算。
这也解释了为什么查询优先不是绝对规则。若API已经承诺在当前作用域和保留期内,同键重放一定安全且会返回原结果,Runtime可以直接按契约重放。有可用的操作查询时,先查能避免把处理中误当失败,也能拿到业务终态。
恢复还需要一个不随进程重启刷新的预算。这个场景可先配置自动恢复总窗口60秒,写尝试最多3次且包含首次;查询单独设限并做退避与抖动。到期仍未知就转MANUAL_REVIEW,不把“次数用完”标成业务失败。
剩余时间不足以完成下一次请求时不再启动;遇到Retry-After超过剩余窗口,也转接管。SDK、HTTP客户端与Runtime中的自动重试要统一计入预算,最好让Runtime成为唯一调度者,避免外层以为只发一次、内层已重复发送。
多工作进程恢复同一条记录时,还需通过版本比较或租约竞争执行权。租约是临时的调度所有权,不替代业务幂等:暂停的旧进程仍可能苏醒,最终是否重复创建必须由业务接口拦住。
![]()
▲ 查询404仍是未知;确认未执行或契约覆盖的安全重放,均须再过权限与预算门禁,才可进入RETRY_READY。
按执行证据分流,而不是按状态码重试
错误分类最有用的输出,是下一步允许做什么。单独给模型一个retryable=true,既丢掉证据,也把执行控制重新交给了文本推理。建议工具适配层返回结构化状态,由Runtime应用固定策略。
观察结果
能证明什么
Runtime动作
本地校验失败且从未发送
本次未执行
修正输入,必要时重新授权
401/403或业务校验拒绝
该响应对应的拒绝;旧尝试另核对
停止原样重试,处理权限或参数
超时、断连、网关502/504
通常只知道结果未知
查询;满足幂等契约才有限重放
429/503
是否执行要看接口约定
确认安全后按退避及预算执行
202或PROCESSING
已受理或仍处理中
查询进度,不新建业务操作
同键异参、并发冲突
参数不一致或已有请求竞争
异参停止;竞争查询原操作
已确认SUCCEEDED
业务已完成
返回原工单,不再写入
对W731而言,最危险的是把网关504归成“暂时性失败,可以再试”。网关没有等到上游回包,并没有替上游撤销工单。此时应沿op-204查业务结果,而不是用一个新键绕开原请求。
另一种误判是把500当成“重试后总能成功”。Stripe同键会返回已缓存的500,说明某些接口里的重放只是重取首次结果。若业务结果仍不清楚,就转查询或核验;换键虽然可能得到不同响应,也可能再制造一次副作用。
授权错误也不能触发静默提权。刷新过期凭证可以是受控恢复动作,但重新提交前仍要校验当前权限;若之前已有未知尝试,拿到新凭证后先恢复那次操作,而非启动新的创建。
人工接管必须同时收走自动写权限
转人工不是给群里发一条告警。Runtime需要把op-204标记为MANUAL_REVIEW,停止发放新的自动发送资格,并明确接管人。交接包至少包含业务操作ID、参数摘要、历次发送记录、已获得的结果、预算消耗与当前授权状态。
查询权限和写入权限要分开设计。任务原本获准创建工单,恢复时授权可能已被撤销;若仍有查询权限,可以核对W731,但不能据旧批准再次写。查询也必须按租户和资源权限过滤,知道操作ID不应成为越权查看入口。
接管人认领时用状态版本做条件更新;自动发送入口也要校验相同状态与执行代次。执行代次是用于识别当前执行者的递增版本,旧进程据此失去领取发送资格的权利。
认领时的条件更新无法撤销已经取得资格、尚未发出的请求,这些尝试仍须纳入在途核验。若要求接管生效后连这类请求也不得执行,需要在业务执行边界校验可撤销的执行令牌,并协调撤销与执行资格判定。只在发送入口检查,仍拦不住此前已经放行的请求;已经执行的副作用更不会被撤销令牌回滚。
人工不得把MANUAL_REVIEW当成旧请求已经取消的证据,服务端同操作去重仍是必要保护。
如果接管后迟到的响应返回W731,系统应将它登记为新增证据,通知接管人收尾,而不是让自动分支重新获得执行权。若人员只看“超时”二字就另建工单,软件状态机再严也挡不住人工重复。
人工最终可以确认已完成、确认未执行后按批准方案恢复,或继续保留待核验。结果未知本身就是可交付的状态,不必为了让任务列表全绿而强行宣布失败或成功。
![]()
▲ 人工接管阻断新发送资格,不代表取消在途请求;迟到成功只追加证据,不能自动恢复写权限。
用丢响应和过期重放验收这条链
接入演练要故意让“业务成功”与“调用成功”分离。下面是针对这套契约的测试设计,不是已经取得的测试结果;验收时同时看状态记录与实际工单数量,不能只看HTTP日志。
先在服务端创建W731之后、成功响应返回之前丢弃回包。预期Runtime进入UNKNOWN,按op-204查到W731,再转成功;租户A的这次操作只对应一张工单,模型没有获得第二次创建入口。
然后在第一次查询中注入404,让后续查询恢复正常。预期404期间仍保留未知;若此时就生成新操作ID,测试直接失败。在独立演练中,把同一创建流程阻塞在处理中,验证查询退避以及60秒后转人工,而不是循环创建;这不是让已经完成的W731退回PROCESSING。
另做一条过期分支:使k-204的重放保护到期并清理,保留op-204的成功记录,再恢复旧任务。预期返回W731或拒绝重放,不能新建。接着模拟操作记录也已清理,预期停止自动写并要求人工核验;另将旧请求直接送到业务入口,验证过期执行凭据或已用操作标记仍能拒绝它。
这条测试要覆盖SDK自行重放的路径,不能只测试Runtime表面上的按钮。尤其要检查恢复代码是否因为“旧键过期”而自动生成k-205;对同一未知业务意图,换键不是修复,是绕开已有保护。
重置演练数据后,再并发发送两个同操作同参数请求,验证一个执行、另一个查询或复用结果;把第二个请求的设备改成E18,则应明确拒绝。最后在UNKNOWN阶段撤销写权限,确认系统还能在获准范围内查结果,却不再创建。
再补三组竞争演练:持久登记尝试后、实际发送前崩溃;旧进程暂停至租约过期,待新进程恢复后再唤醒;自动恢复准备发送时让人工同时认领。预期均沿原操作核验,不产生额外工单。
接管后不得新领取自动发送资格;已登记的在途尝试及迟到结果只追加证据,不恢复自动执行权。保留操作ID、尝试ID、执行代次、状态迁移与业务记录关联。没有权威成功证据却报告成功,或接管后仍新领取发送资格,都应判为未通过。
从一个写工具迁移,不从万能重试器开始
迁移到自研系统时,先选create_ticket这一条工具:冻结输入,补持久操作记录,让适配层输出“成功、明确未执行、未知、处理中”,再把上述故障注入跑通。这个改动比给所有工具统一加三次重试更有价值。
若第三方接口只有创建、没有稳定操作身份和结果查询,就应缩小自动化范围:未知后暂停写入,走人工对账。可以评估用外部引用字段查找原记录,但模糊搜索、最终一致列表都不能直接升级成重放许可。
架构师要交付的因此是一条可恢复的写操作契约,而不只是一个可被模型调用的函数。模型负责表达意图,Runtime负责在证据与预算内推进,业务方负责给出结果和去重保证,人工负责承接证据不足的剩余风险。
这套分工适用于副作用必须可追踪、重复成本高的写操作。若接口无法兑现身份、查询和去重边界,宁可让一次任务停在“待核验”,也不要让一次超时变成两张工单。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.