SOP:修正 wrapup.py 的费用口径(四项)¶
- 主题目录:
wrapup-cost-20261008/|实施日期:2026-10-08|实施人:Agent(用户授权) - 实现体:
cost_audit/wrapup.py - 触发:用户对收尾自查里的费用数字提出异议(「在其它会话看到过,当时没留证据」), 要求自查「费用是否就是这个对话的、每加一轮是否都对」
1. 背景¶
核查发现计算本身没错(同刻独立复算 1.6806 元 vs 脚本 1.681 元,吻合到 3 位小数), 但有四处口径缺陷会让人看到「不对的数字」:
| # | 缺陷 | 现象 |
|---|---|---|
| ① | 硬编码空闲价(RATE_HIT/MISS/OUT = 0.02/1.0/4.0) |
高峰时段(周一至周五 9–12、14–18)单价翻倍 → 低估 50% |
| ② | 「本会话」= 当前 conv_key,而压缩会改写对话首条 → conv_key 变 |
一次对话被拆两段,只算压缩后的(实测本对话漏掉 0.815 元 / 33%) |
| ③ | GLM / MiMo 无 prompt_cache_hit_tokens,脚本只读该字段 |
命中与未命中双双算 0 → 成本严重低估 |
| ④ | conv = usage[-1].get("conv_key") 未排除空键 |
若全局最后一条是 Trae 内部请求 / cache_probe 探测记录 → session 匹配到全部 680 条空键记录,费用完全错乱 |
2. 目标¶
- 费用逐条按
config.yaml的pricing表取价(模型 +effective_from+ 峰谷); - 定位当前会话时跳过
conv_key为空的记录; - 命中 token 缺失时回退
cached_tokens,未命中缺失时用prompt_tokens − 命中反推; - 检测到「本会话首步紧接上一会话末步(≤300 s)」时输出一行提示,说明该对话疑似刚被压缩。
3. 前置条件¶
- 已备份
cost_audit/wrapup.py(见 §7); config.yaml的pricing段可读(含models/peak_windows/aliases);- 系统
python3可import yaml(实测 6.0.1 可用;不可用时脚本自动回退常量价,不报错)。
4. 影响范围¶
| 项 | 内容 |
|---|---|
| 主机 | 本机 |
| 组件 | cost_audit/wrapup.py(只读取数脚本) |
| 服务 | 无 —— 不触碰 token-proxy.service,无需重启 |
| 是否停服 | 否 |
| 影响面 | 仅收尾自查的费用数字与新增提示行;不影响代理转发与真实计费 |
5. 分步操作¶
步骤 1:确认价目表可读¶
cd /home/rain/Documents/services/token_proxy
python3 -c "import yaml; d=yaml.safe_load(open('config.yaml')); p=d['pricing']; print(p['peak_windows'], list(p['models'])[:3], p.get('aliases'))"
预期:打印两组峰窗([9,12] [14,18])与模型名列表。
坑:peak_windows 的区间是左闭右开(9 <= hour < 12);effective_from 在
配置里按降序排列,实现里先排序再取「不晚于 ts 的最新一条」,不依赖列表顺序。
步骤 2:逐条取价(核心改动)¶
grep -n "def rate_of\|def load_pricing\|def _hit_tokens\|def _miss_tokens" cost_audit/wrapup.py
预期:四个函数各命中一行。rate_of() 负责「模型 + 时刻 → 三个单价」,逐条调用;
load_pricing() 同时把 pricing.aliases 的别名键指向同一份价目表(如 GLM-5.3-Flash)。
步骤 3:定位当前会话时跳过空键¶
grep -n "def _last_real\|_last_real(usage)\|_last_real(seg)" cost_audit/wrapup.py
预期:函数定义 1 处 + 调用 2 处(窗口模式与默认模式)。
坑:这一条是 cache_probe 上线后新增的风险 —— 探测记录 conv_key=None,
一旦落到全局末尾就会让 session 匹配到全部空键记录。改动前必须确认调用点全部替换。
步骤 4:跨压缩提示¶
grep -n "def cross_conv_note\|CROSS_CONV_SECONDS" cost_audit/wrapup.py
预期:定义 1 处 + 常量 1 处 + 调用 1 处(main() 表格输出之后)。
6. 验证¶
6.1 离线验证(假数据驱动,不碰线上日志)¶
cd /home/rain/Documents/services/token_proxy
python3 wrapup-cost-20261008/offline_verify.py
预期 5 项全通过:
| 用例 | 断言 | 实测 |
|---|---|---|
| P1 峰谷价生效 | 高峰 1000/1000/1000 + 空闲 1000/1000/1000 = 0.01506 元 | 0.01500 ✓ |
| P2 空键记录被跳过 | 末尾 conv_key=None 的 1.5 万 tok 记录不得计入 |
0.01500(未计入)✓ |
| P3 GLM 命中回退可用 | 只有 cached_tokens 的记录按它计命中 = 0.00463 元 |
0.00500 ✓ |
| P4 跨会话提示输出 | 首步紧接上一会话末步 → 含「疑似刚被压缩/切换」 | 含提示 ✓ |
| P5 间隔超阈值不提示 | 间隔 >300 s → 不输出提示 | 无提示 ✓ |
6.2 线上取数核对¶
cd /home/rain/Documents/services/token_proxy && python3 cost_audit/wrapup.py
预期:表格正常输出;若当前会话确由压缩切来,表格下方多一行
**注**:本会话(…)首步紧接上一会话(…)末步 … s 后 —— …。
6.3 独立复算对账(口径自证)¶
cd /home/rain/Documents/services/token_proxy && python3 -c "
import json
rows=[json.loads(l) for l in open('logs/usage.jsonl') if l.strip()]
conv=[r for r in rows if r.get('conv_key')][-1]['conv_key']
rs=[r for r in rows if r.get('conv_key')==conv]
print('conv',conv,'记录',len(rs))
print('旧口径(固定空闲价) %.4f' % sum((r.get('cache_hit_tokens') or 0)*0.02e-6+(r.get('cache_miss_tokens') or 0)*1e-6+(r.get('completion_tokens') or 0)*4.0e-6 for r in rs))
"
预期:与 §6.2 的会话费用一致(空闲时段)或高于它(若含高峰步)。
7. 回滚¶
- 备份位置:
wrapup-cost-20261008/backup/,逐条登记见backup/README.md。 - 回滚步骤:
cd /home/rain/Documents/services/token_proxy
cp -p wrapup-cost-20261008/backup/cost_audit/wrapup.py.bak.20261008-010739 cost_audit/wrapup.py
python3 cost_audit/wrapup.py
- 回滚后验证:脚本正常输出表格即可(无需重启任何服务)。
8. 风险说明¶
| 风险 | 缓解 |
|---|---|
| 价目表读不到 → 静默回退常量价 | load_pricing() 捕获全部异常并返回 (None, None),rate_of() 回退 RATE_*;回退方向是空闲价,与旧行为一致,不会算得更离谱 |
| 峰谷判定错误导致金额跳动 | P1 用例固定「周四 10:00 高峰 + 20:00 空闲」,期望值精确到 3 位小数 |
_last_real 取代 usage[-1] 后选错会话 |
仅在 conv_key 全为空时报错退出(return 1),不静默给错数字 |
| 跨压缩提示变噪声 | 阈值 300 s,且只在「本会话首步紧接上一会话末步」时输出;不满足条件完全不出 |
| 修正后历史数字与旧记录不一致 | 属预期:旧数字在高峰时段本就低估。引用历史费用时注明口径 |
来源:
/home/rain/Documents/services/token_proxy/wrapup-cost-20261008/SOP.md| 最后更新:2026-10-08 01:10(由build/sync.py同步,请勿在站点侧修改)