File size: 4,317 Bytes
6d60378 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | package upstream
import "encoding/json"
// normalizeUsageCacheAliases keeps cache-hit aliases consistent before the
// response leaves the gateway. Some WorkBuddy responses carry the real hit in
// prompt_tokens_details.cached_tokens while also emitting
// cache_read_input_tokens: 0 and cached_tokens: 0 compatibility aliases.
// Strict downstream parsers may prefer those zero aliases and lose the hit.
func normalizeUsageCacheAliases(usage map[string]any) map[string]any {
best, ok := bestUsageCacheHitTokens(usage)
if !ok || best <= 0 {
return usage
}
out := cloneUsageMap(usage)
out["cache_read_input_tokens"] = best
out["cached_tokens"] = best
out["prompt_cache_hit_tokens"] = best
promptDetails := cloneUsageDetails(out, "prompt_tokens_details")
promptDetails["cached_tokens"] = best
out["prompt_tokens_details"] = promptDetails
// Responses API consumers use this nested form. Preserve it when the
// upstream already supplies it, but do not invent it for Chat-only clients.
if _, exists := out["input_tokens_details"]; exists {
inputDetails := cloneUsageDetails(out, "input_tokens_details")
inputDetails["cached_tokens"] = best
out["input_tokens_details"] = inputDetails
}
return out
}
func bestUsageCacheHitTokens(usage map[string]any) (float64, bool) {
paths := []struct {
section string
key string
}{
{"prompt_tokens_details", "cached_tokens"},
{"", "prompt_cache_hit_tokens"},
{"", "cache_read_input_tokens"},
{"", "cached_tokens"},
{"input_tokens_details", "cached_tokens"},
}
for _, path := range paths {
var value any
if path.section == "" {
value = usage[path.key]
} else if details, ok := usage[path.section].(map[string]any); ok {
value = details[path.key]
}
if tokens, ok := positiveUsageNumber(value); ok {
return tokens, true
}
}
return 0, false
}
func positiveUsageNumber(value any) (float64, bool) {
switch n := value.(type) {
case float64:
return n, n > 0
case float32:
value := float64(n)
return value, value > 0
case int:
return float64(n), n > 0
case int64:
return float64(n), n > 0
case int32:
return float64(n), n > 0
case uint:
return float64(n), n > 0
case uint64:
return float64(n), n > 0
case uint32:
return float64(n), n > 0
default:
return 0, false
}
}
func cloneUsageMap(usage map[string]any) map[string]any {
out := make(map[string]any, len(usage))
for key, value := range usage {
out[key] = value
}
return out
}
func cloneUsageDetails(usage map[string]any, key string) map[string]any {
out := make(map[string]any)
details, _ := usage[key].(map[string]any)
for detailKey, value := range details {
out[detailKey] = value
}
return out
}
// UsageCacheHitTokens 返回 usage 里的缓存命中 token 数(多别名取最优,口径与
// 回写给客户端的 normalizeUsageCacheAliases 一致)。供网关统计层(usage 桶 /
// reqlog)观测命中率使用;usage 缺失该维度时 ok=false。
func UsageCacheHitTokens(usage map[string]any) (float64, bool) {
if usage == nil {
return 0, false
}
return bestUsageCacheHitTokens(usage)
}
// UsageCacheMissTokens 返回 usage 里的缓存未命中 token 数:优先读上游显式的
// prompt_cache_miss_tokens,缺失时按 prompt_tokens - 命中 推导(推导值为负时
// 视为不可信,返回 ok=false)。
func UsageCacheMissTokens(usage map[string]any) (float64, bool) {
if usage == nil {
return 0, false
}
if miss, ok := usageNumber(usage, "prompt_cache_miss_tokens"); ok {
return miss, true
}
prompt, okP := usageNumber(usage, "prompt_tokens")
hit, okH := UsageCacheHitTokens(usage)
if okP && okH && prompt-hit >= 0 {
return prompt - hit, true
}
return 0, false
}
// usageNumber 从 usage 顶层取数值字段(JSON 数字可能是 float64 / json.Number 形态)。
func usageNumber(usage map[string]any, key string) (float64, bool) {
v, ok := usage[key]
if !ok {
return 0, false
}
switch n := v.(type) {
case float64:
return n, true
case int:
return float64(n), true
case int64:
return float64(n), true
case json.Number:
f, err := n.Float64()
return f, err == nil
}
return 0, false
}
|