
用户点了一次提交网关重试两次模型流又在结束前断开。看板上的“一次请求”到底应该对应几笔成本这个问题先于Token单价也先于Micrometer标签怎么命名。可以用requestId关联用户任务用attemptId标识每次实际调用再保存供应商返回的请求ID。request并不能充当费用去重键同一个任务的两次尝试可能都产生费用同一尝试收到重复回调也不能记两笔。封面中的多张单据表示同一attempt重复到达不同attempt必须分别记账。缺usage是一种状态Spring AI观测文档描述模型与工具的观测信息但监控输出不是供应商账单。usage缺失、迟到、字段口径不同都需要明细流水承接。观测参考为持续维护文档。建议至少保留UNKNOWN、ESTIMATED、REPORTED三态。缺usage时既不要填0也不要从客户端收到的字数反推成确定费用。估算可以用于预算提示但要带估算方法、版本和价格快照晚到实报覆盖估算时留下修订轨迹。一个去重与迟到处理的离线例子为了只检验状态迁移下面金额均为手写的“教学计价单位”不是任何模型的真实价格。实际账本还需存Token分类、币种、价目生效时间、区域和折扣。import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; public class AttemptLedger { static class Entry { final BigDecimal amount; final boolean reported; Entry(String amount, boolean reported) { this.amount new BigDecimal(amount); this.reported reported; if (this.amount.signum() 0) throw new IllegalArgumentException(); } } final MapString, Entry rows new HashMap(); void observe(String attempt, Entry incoming) { if (attempt null || attempt.isBlank()) throw new IllegalArgumentException(); Entry old rows.get(attempt); if (old ! null old.reported) { if (!incoming.reported) return; if (old.amount.compareTo(incoming.amount) ! 0) throw new IllegalStateException(conflicting report); return; } rows.put(attempt, incoming); } BigDecimal reportedTotal() { return rows.values().stream().filter(x - x.reported) .map(x - x.amount).reduce(BigDecimal.ZERO, BigDecimal::add); } public static void main(String[] args) { AttemptLedger l new AttemptLedger(); l.observe(r1/a1, new Entry(0.10, false)); l.observe(r1/a1, new Entry(0.12, true)); l.observe(r1/a1, new Entry(0.12, true)); l.observe(r1/a1, new Entry(9.00, false)); l.observe(r1/a2, new Entry(0.20, true)); assert l.rows.size() 2; assert l.reportedTotal().compareTo(new BigDecimal(0.32)) 0; boolean conflict false; try { l.observe(r1/a1, new Entry(0.13, true)); } catch (IllegalStateException expected) { conflict true; } assert conflict; System.out.println(ledger: duplicate, late estimate, conflict checked); } }保存为AttemptLedger.java执行javac --release 11 AttemptLedger.java java -ea AttemptLedger。没有出现在Map中的尝试仍可能是UNKNOWN不能因不在求和中就宣称费用为零。这个例子没有并发或持久化保证真实服务应使用数据库唯一键和事务处理同一attempt的更新。看板和账本分工高基数的requestId、完整Prompt、用户邮箱不适合作Prometheus标签。明细放受权限控制的流水指标按provider、模型版本线、结果类别等有限维度聚合。估算金额与实报金额也要分别命名避免迟到修正把累计Counter倒减。Prometheus的rate(counter[1h])表示该窗口内估计的每秒速率不是一小时金额需要窗口增量时应理解increase的外推与采样条件账单核对仍回到明细。函数说明支持这种量纲区分。本例仅验证合成流水的去重规则没有调用模型或取得实际账单。十月SDK/API变化提醒我们保存版本身份但不能据此填造usage字段或节费比例。最终对账还要确认供应商实报、价格快照与财务账单分别覆盖什么。本地验证记录Oracle JDK 12.0.1javac --release 11与java -ea运行成功输出ledger: duplicate, late estimate, conflict checked。仅覆盖本例的离线输入框架、供应商和生产集成仍未测试。