diff --git a/db/llm_usage.go b/db/llm_usage.go index 92009e5..288296e 100644 --- a/db/llm_usage.go +++ b/db/llm_usage.go @@ -371,7 +371,15 @@ func applyCostEur(d *DB, out []ProfileUsage) error { if !ok || (pr.in == 0 && pr.outp == 0 && pr.cr == 0 && pr.cw == 0) { continue } - cost := float64(out[i].InputTokens)/1e6*pr.in + float64(out[i].OutputTokens)/1e6*pr.outp + + // InputTokens includes the cached subset (Anthropic and OpenAI usage + // semantics), so the fresh share is input minus cache reads; charging + // both buckets at full input price double-counts the cache. Clamped at + // zero for gateways that report the buckets disjointly. + fresh := out[i].InputTokens - out[i].CacheReadTokens + if fresh < 0 { + fresh = 0 + } + cost := float64(fresh)/1e6*pr.in + float64(out[i].OutputTokens)/1e6*pr.outp + float64(out[i].CacheReadTokens)/1e6*pr.cr + float64(out[i].CacheWriteTokens)/1e6*pr.cw c := cost out[i].CostEur = &c diff --git a/server/cost_ledger_test.go b/server/cost_ledger_test.go index a7b4add..da5bec5 100644 --- a/server/cost_ledger_test.go +++ b/server/cost_ledger_test.go @@ -54,7 +54,7 @@ func TestCostLedgerEurOnUsage(t *testing.T) { if got == nil { t.Fatal("priced profile has no cost_eur") } - want := 2.0*1.5 + 0.5*4.0 + 1.0*0.15 // 3.0 + 2.0 + 0.15 = 5.15 + want := 1.0*1.5 + 0.5*4.0 + 1.0*0.15 // fresh input 1M (2M incl. 1M cached) + 0.5M out + 1M cache-read = 3.65 if d := *got - want; d > 0.001 || d < -0.001 { t.Fatalf("cost_eur = %v, want %v", *got, want) }