先分清请求与调用尝试
一个客户端请求,可能经历主模型调用、失败重试或备用模型调用。如果只保存一个最终成功状态,中间发生过的调用和费用就很容易丢失。
在网关实践中,使用 request_id 标识客户端请求,使用 attempt_id 标识每一次调用尝试。前者串起一次访问,后者记录实际发往模型的操作。两者分开后,最终结果和中间尝试都能保留各自的上下文。
请求受理→调用尝试→收集用量→账本记录
费用需要有可以说明的来源
模型响应中的 usage 是厂商提供的用量信息。网关记录用量时,同时保留模型身份、价格版本和用量来源,使后续的核对有明确依据。金额采用整数微单位保存,减少浮点数累计带来的偏差。
超时和网络断开只说明客户端没有拿到完整结果,不能据此判断厂商是否处理了请求。用量缺失时,应将估算与已确认记录区分开,并保留待核实状态。
未知用量需要保留为未知。
把超时调用写成零费用,会让表面上完整的报表失去可信的依据。
把超时调用写成零费用,会让表面上完整的报表失去可信的依据。
恢复能力从持久化边界开始
账本的作用是保留可以追溯的事实。项目将调用开始记录写入持久化存储,再通过完成事件更新结算状态。后台处理事件时,需要识别已经处理过的记录,避免消息重复投递造成重复记账。
这里的幂等,指的是同一操作执行多次,最终只产生一次有效的业务结果。实现它需要稳定的标识和明确的状态推进规则,而不是仅依赖消息“通常只到达一次”。
数据库和消息队列可以帮助恢复已经写下的事实。对于从未收到的厂商用量,仍需要其他核对渠道;重放事件无法凭空补出不存在的信息。
边界明确,才能讨论取舍
路由、重试、流式输出和计量之间存在联动。流式输出已经开始后,直接切换模型继续拼接,可能让一个回答混入两次不同推理的内容,也会让请求归属和费用解释更加困难。
在这类系统中,我更关注三件事:每一步是否留下了足够的信息;失败后哪些状态可以恢复;仍然未知的部分是否被如实展示。吞吐与容量则需要独立的负载验证,不能从功能演示推导。