mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 00:48:00 +00:00
feat(auto): AUTO-only priority chain with tiered slots + live source probing; CSV export w/ key names; audit persistence; fix prompt token accounting & deepseek thinking
This commit is contained in:
@ -1,6 +1,9 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@ -55,6 +58,7 @@ type Stats struct {
|
||||
byKeySrc map[string]map[string]*Stat
|
||||
recs []Req
|
||||
maxRecs int
|
||||
auditPath string
|
||||
modelHour map[string]map[int64]int64 // model -> unix-hour bucket -> tokens
|
||||
}
|
||||
|
||||
@ -109,6 +113,30 @@ func inc(m map[string]*Stat, name string, r Req) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stats) LoadAudit(path string) {
|
||||
f, err := os.Open(path)
|
||||
if err == nil {
|
||||
var recs []Req
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
var r Req
|
||||
if json.Unmarshal(sc.Bytes(), &r) == nil {
|
||||
recs = append(recs, r)
|
||||
}
|
||||
}
|
||||
_ = f.Close()
|
||||
if len(recs) > s.maxRecs {
|
||||
recs = recs[len(recs)-s.maxRecs:]
|
||||
}
|
||||
for _, r := range recs {
|
||||
s.Record(r)
|
||||
}
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.auditPath = path
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// Record appends a finished request to the aggregates and ring buffer.
|
||||
func (s *Stats) Record(r Req) {
|
||||
s.mu.Lock()
|
||||
@ -128,14 +156,18 @@ func (s *Stats) Record(r Req) {
|
||||
s.byKeySrc[r.Key] = ks
|
||||
}
|
||||
inc(ks, r.Source, r)
|
||||
// window bucket for quota enforcement (per model, per unix hour)
|
||||
// window bucket for quota enforcement (per source-model pair, per unix hour)
|
||||
tok := r.Prompt + r.Compl
|
||||
if tok > 0 && r.Model != "" {
|
||||
key := r.Model
|
||||
if r.Source != "" {
|
||||
key = r.Source + "::" + r.Model
|
||||
}
|
||||
h := r.Time / hourSec
|
||||
hm := s.modelHour[r.Model]
|
||||
hm := s.modelHour[key]
|
||||
if hm == nil {
|
||||
hm = map[int64]int64{}
|
||||
s.modelHour[r.Model] = hm
|
||||
s.modelHour[key] = hm
|
||||
}
|
||||
hm[h] += tok
|
||||
if len(hm) > 24*40 {
|
||||
@ -150,6 +182,14 @@ func (s *Stats) Record(r Req) {
|
||||
if len(s.recs) > s.maxRecs {
|
||||
s.recs = s.recs[len(s.recs)-s.maxRecs:]
|
||||
}
|
||||
if s.auditPath != "" {
|
||||
if f, err := os.OpenFile(s.auditPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644); err == nil {
|
||||
if b, err := json.Marshal(r); err == nil {
|
||||
_, _ = f.Write(append(b, '\n'))
|
||||
}
|
||||
_ = f.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ModelTokens returns the tokens consumed per model for one gateway key id
|
||||
@ -197,11 +237,17 @@ func AutoPeriodSeconds(period string, hours int64) int64 {
|
||||
|
||||
// WindowTokens returns the tokens billed for the model within the last `sec`
|
||||
// seconds (0 = since forever).
|
||||
func (s *Stats) WindowTokens(model string, sec int64) int64 {
|
||||
// WindowTokens returns the tokens consumed for one model (optionally pinned
|
||||
// to a single source) within the window; sec <= 0 means all time.
|
||||
func (s *Stats) WindowTokens(model, source string, sec int64) int64 {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
key := model
|
||||
if source != "" {
|
||||
key = source + "::" + model
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
hm := s.modelHour[model]
|
||||
hm := s.modelHour[key]
|
||||
if len(hm) == 0 {
|
||||
return 0
|
||||
}
|
||||
@ -240,6 +286,26 @@ type StatsRow struct {
|
||||
Stat
|
||||
}
|
||||
|
||||
// Records returns request records filtered by unix-millisecond time range and key.
|
||||
func (s *Stats) Records(from, to int64, key string) []Req {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
out := make([]Req, 0, len(s.recs))
|
||||
for _, r := range s.recs {
|
||||
if key != "" && r.Key != key {
|
||||
continue
|
||||
}
|
||||
if from > 0 && r.Time < from {
|
||||
continue
|
||||
}
|
||||
if to > 0 && r.Time > to {
|
||||
continue
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Snapshot returns the whole dashboard payload; when key != "" the records
|
||||
// and aggregate views are restricted to that gateway key.
|
||||
func (s *Stats) Snapshot(limit int, key string) map[string]interface{} {
|
||||
|
||||
Reference in New Issue
Block a user