mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-14 05:18:54 +00:00
feat(admin): report per-model detail, including free models
GET /admin/api/usage/models accepts detail=1. Each entry then carries requests, tokensIn, tokensOut, costMicroUsd and a free flag. A model is listed when any of those four totals is non-zero, so a free model with traffic appears with value 0 even when ranked by cost. The free flag is true when modelMeta marks the model free, or its resolved price is zero. Without detail the response is unchanged. The image and audio routes now write the per-model request counter after a successful upstream call, so media models show up in the ranking. They still record no tokens or cost. Claude-Session: https://claude.ai/code/session_01V5jYJNxpDFdJkyyExpADrv
This commit is contained in:
@@ -2300,6 +2300,52 @@ func (l *limiter) modelTotals(ids []string, metric, window string) ([]int64, boo
|
||||
return l.modelSpanTotals(ids, metric, window, l.now(), 1)
|
||||
}
|
||||
|
||||
// modelSpanTotalsMulti is modelSpanTotals generalized to several metrics
|
||||
// read in ONE storeGetMulti round trip — GET /admin/api/usage/models'
|
||||
// detail=1 mode (free-models feature) needs all four metrics
|
||||
// (req/tokin/tokout/cost) for every candidate id, not just the one the
|
||||
// ranking sorts by, and a per-metric modelSpanTotals call would cost 4
|
||||
// round trips instead of 1. Keys are built id-major, metric second, span
|
||||
// innermost — out[m][i] is metrics[m]'s span-sum for ids[i], positional
|
||||
// in both dimensions, mirroring modelSpanTotals' own result[i] contract.
|
||||
// ok is false on any store read failure, exactly like modelSpanTotals —
|
||||
// a detail read that only partially succeeds is never presented as
|
||||
// complete data. An empty ids slice is not a store read at all, matching
|
||||
// modelSpanTotals' own contract.
|
||||
func (l *limiter) modelSpanTotalsMulti(ids, metrics []string, window string, now time.Time, span int) ([][]int64, bool) {
|
||||
if len(ids) == 0 {
|
||||
return nil, true
|
||||
}
|
||||
allKeys := make([]string, 0, len(ids)*len(metrics)*span)
|
||||
for _, id := range ids {
|
||||
for _, metric := range metrics {
|
||||
keys, _ := historyBucketKeys(kindModel, id, metric, window, now, span)
|
||||
allKeys = append(allKeys, keys...)
|
||||
}
|
||||
}
|
||||
vals, ok := l.storeGetMulti(allKeys)
|
||||
if !ok || len(vals) != len(allKeys) || l.configuredStoreDown() {
|
||||
return nil, false
|
||||
}
|
||||
out := make([][]int64, len(metrics))
|
||||
for m := range out {
|
||||
out[m] = make([]int64, len(ids))
|
||||
}
|
||||
stride := len(metrics) * span
|
||||
for i := range ids {
|
||||
base := i * stride
|
||||
for m := range metrics {
|
||||
mBase := base + m*span
|
||||
var sum int64
|
||||
for _, v := range vals[mBase : mBase+span] {
|
||||
sum += v
|
||||
}
|
||||
out[m][i] = sum
|
||||
}
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
// historyStepBack returns the instant window's bucket was current i steps
|
||||
// before now: i=0 is now's own (current, possibly partial) bucket, i=1
|
||||
// the previous one, and so on. hour and day step by a fixed duration/day
|
||||
|
||||
Reference in New Issue
Block a user