问题出现得很突然,日志倒是留得挺全。自建网关统计缓存命中率 81%,账单按 token 计费却显示缓存只省了 34%。网关的分子是命中请求数,分母是总请求数,而命中的多是短请求,未命中的多是长文档请求,两个口径差了一倍多。试过把分母改成 token 数,网关又拿不到准确的 usage。

刚开始盯着「流式请求结束才回 usage,中途断连的请求计不进分母」,折腾了半个多小时,排掉了。

这类报错背后常见的原因有几处,逐个排除就行。命中率按请求数算,长请求的权重被低估。少数情况下是网关透传后 usage 字段被裁剪,只能看有没有缓存标记。

不容易想到的是,重试请求被算成两次,命中与未命中各记一条。

上来先把 usage 完整透传到日志再统计

python -c "import json;print(json.dumps({'cached_tokens':0,'prompt_tokens':0}))"

再往下按 token 口径重算一遍命中率

python -c "import json;rows=[json.loads(l) for l in open('gw.jsonl')];print(sum(r['cached'] for r in rows)/sum(r['prompt'] for r in rows))"

随后把重试请求按 request_id 去重

python -c "import json;rows=[json.loads(l) for l in open('gw.jsonl')];print(len({r['rid'] for r in rows}))"

流式请求在断连时补一条估算记录

python -c "print('flush usage on disconnect')"

怎么算真的好了,看的是同一批现象。网关统计的缓存节省比例与账单误差在 3 个百分点内,去重后的请求数与原请求数对得上。

提醒一点。命中率是比例,分子分母的统计单位必须一致,请求数口径适合看流量,token 口径才对得上钱。同一份配置在测试环境跑通,不代表线上没事,两边的默认值经常不一样。

作者 9823189