Files
vikingowl c4fde583f5 chore(lint): gofmt sweep + errcheck cleanups in router discovery
Apply gofmt -w across the codebase (struct field comment realignment
only — no semantic changes) and silence two errcheck warnings on
fmt.Sscanf / fmt.Fprintf return values in internal/router/discovery
with explicit `_, _ =` discards. Required so `make check` is green
before tagging v0.1.0.
2026-05-20 03:13:05 +02:00

128 lines
3.5 KiB
Go

package router
import (
"fmt"
"time"
"somegit.dev/Owlibou/gnoma/internal/provider"
)
// PoolsFromRateLimits creates limit pools for an arm based on rate limits.
// Each non-zero limit dimension becomes a pool attached to the arm.
func PoolsFromRateLimits(armID ArmID, rl provider.RateLimits) []*LimitPool {
now := time.Now()
var pools []*LimitPool
if rl.RPS > 0 {
pools = append(pools, &LimitPool{
ID: fmt.Sprintf("%s/rps", armID),
Kind: PoolRPS,
TotalLimit: rl.RPS,
ResetPeriod: time.Second,
ResetAt: now.Add(time.Second),
ArmRates: map[ArmID]float64{armID: 1.0 / 1000.0}, // 1 request per call, normalized
ScarcityK: 3.0,
})
}
if rl.RPM > 0 {
pools = append(pools, &LimitPool{
ID: fmt.Sprintf("%s/rpm", armID),
Kind: PoolRPM,
TotalLimit: float64(rl.RPM),
ResetPeriod: time.Minute,
ResetAt: now.Add(time.Minute),
ArmRates: map[ArmID]float64{armID: 1.0 / 1000.0},
ScarcityK: 2.0,
})
}
if rl.RPD > 0 {
pools = append(pools, &LimitPool{
ID: fmt.Sprintf("%s/rpd", armID),
Kind: PoolRPD,
TotalLimit: float64(rl.RPD),
ResetPeriod: 24 * time.Hour,
ResetAt: nextMidnight(),
ArmRates: map[ArmID]float64{armID: 1.0 / 1000.0},
ScarcityK: 2.0,
})
}
if rl.TPM > 0 {
pools = append(pools, &LimitPool{
ID: fmt.Sprintf("%s/tpm", armID),
Kind: PoolTPM,
TotalLimit: float64(rl.TPM),
ResetPeriod: time.Minute,
ResetAt: now.Add(time.Minute),
ArmRates: map[ArmID]float64{armID: 1.0}, // 1 token per token
ScarcityK: 2.0,
})
}
// Anthropic-style split: ITPM+OTPM. Use TPM pool kind for the more
// restrictive one (output) since that's what throttles agentic use.
if rl.ITPM > 0 && rl.TPM == 0 {
pools = append(pools, &LimitPool{
ID: fmt.Sprintf("%s/itpm", armID),
Kind: PoolTPM,
TotalLimit: float64(rl.ITPM),
ResetPeriod: time.Minute,
ResetAt: now.Add(time.Minute),
ArmRates: map[ArmID]float64{armID: 0.6}, // ~60% of tokens are input
ScarcityK: 2.0,
})
}
if rl.OTPM > 0 {
pools = append(pools, &LimitPool{
ID: fmt.Sprintf("%s/otpm", armID),
Kind: PoolTPM,
TotalLimit: float64(rl.OTPM),
ResetPeriod: time.Minute,
ResetAt: now.Add(time.Minute),
ArmRates: map[ArmID]float64{armID: 0.4}, // ~40% of tokens are output
ScarcityK: 3.0, // output is more precious
})
}
if rl.TokensMonth > 0 {
pools = append(pools, &LimitPool{
ID: fmt.Sprintf("%s/tokens-month", armID),
Kind: PoolTokensMonth,
TotalLimit: float64(rl.TokensMonth),
ResetPeriod: 30 * 24 * time.Hour,
ResetAt: nextMonthStart(),
ArmRates: map[ArmID]float64{armID: 1.0},
ScarcityK: 4.0, // aggressive hoarding for monthly budgets
})
}
if rl.SpendCap > 0 {
pools = append(pools, &LimitPool{
ID: fmt.Sprintf("%s/spend-cap", armID),
Kind: PoolCostMonth,
TotalLimit: rl.SpendCap,
ResetPeriod: 30 * 24 * time.Hour,
ResetAt: nextMonthStart(),
ArmRates: map[ArmID]float64{}, // cost tracked externally per arm
ScarcityK: 4.0,
})
}
return pools
}
func nextMidnight() time.Time {
now := time.Now()
return time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
}
func nextMonthStart() time.Time {
now := time.Now()
if now.Month() == 12 {
return time.Date(now.Year()+1, 1, 1, 0, 0, 0, 0, now.Location())
}
return time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location())
}