Files
team-api/relaykit/dispatch/score.go
T

130 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dispatch
import (
"math"
)
// EffectiveWeight 权重合成函数 W(c)(纯函数)。
//
// W(c) = baseWeight × tierFactor × healthFactor × headroom^γ × costFactor × rampFactor × protoFactor
//
// 返回合成权重与分解明细(供决策日志 / ForwardingTrace)。
// pref 为可选的入站协议偏好(省略 = ProtoAny,protoFactor 恒为 1)。
func EffectiveWeight(c Channel, pol *RoutingPolicy, pref ...ProtoPreference) (float64, WeightBreakdown) {
var proto ProtoPreference
if len(pref) > 0 {
proto = pref[0]
}
bd := WeightBreakdown{
Base: c.BaseWeight,
Tier: tierFactor(c.Tier, pol),
Health: healthFactor(c, pol),
Headroom: headroomFactor(c, pol),
Cost: costFactor(c, pol),
Ramp: rampFactor(c, pol),
Proto: protoFactor(c, proto, pol),
}
if bd.Base <= 0 {
bd.Base = 0
}
bd.Effective = bd.Base * bd.Tier * bd.Health * bd.Headroom * bd.Cost * bd.Ramp * bd.Proto
return bd.Effective, bd
}
// tierFactor 层级偏置。未配置的层级视为 0(不参与,由扩组兜底救回)。
func tierFactor(t Tier, pol *RoutingPolicy) float64 {
f, ok := pol.TierFactors[t]
if !ok {
return 0
}
return math.Max(f, 0)
}
// healthFactor 健康因子:
//
// clamp(succEwma, 0.01, 1)^α × latencyPenalty
// latencyPenalty = clamp(latRef / max(latEwma, latRef), 0.5, 1.0)
func healthFactor(c Channel, pol *RoutingPolicy) float64 {
succ := c.SuccEwma
if succ <= 0 && c.LatEwmaMs == 0 {
// 无任何健康数据(新渠道)视为满分
succ = 1
}
succ = clamp(succ, 0.01, 1.0)
f := math.Pow(succ, pol.Health.Alpha)
latRef := pol.Health.LatRefMs
if latRef > 0 && c.LatEwmaMs > latRef {
f *= clamp(latRef/c.LatEwmaMs, 0.5, 1.0)
}
return f
}
// headroomFactor 负载余量因子:(1 - inflight/softLimit)^γ。
// 无容量信息(SoftLimit<=0)时恒为 1。
func headroomFactor(c Channel, pol *RoutingPolicy) float64 {
if c.SoftLimit <= 0 {
return 1
}
headroom := 1 - float64(c.Inflight)/float64(c.SoftLimit)
if headroom <= 0 {
return 0
}
return math.Pow(headroom, pol.Load.Gamma)
}
// costFactor 成本因子:clamp((1/costRatio)^β, min, max)。
// CostRatio 是无量纲比例(非金额,float64 不受 decimal 强约束),<=0 视为 1.0。
func costFactor(c Channel, pol *RoutingPolicy) float64 {
ratio := c.CostRatio
if ratio <= 0 {
ratio = 1.0
}
return clamp(math.Pow(1.0/ratio, pol.Cost.Beta), pol.Cost.Min, pol.Cost.Max)
}
// rampFactor 爬坡因子:新渠道 / 熔断恢复后从小流量爬坡。
func rampFactor(c Channel, pol *RoutingPolicy) float64 {
if c.RampElapsedMs < 0 {
return 1
}
window := pol.Ramp.WindowSeconds * 1000
if window <= 0 {
return 1
}
return clamp(float64(c.RampElapsedMs)/float64(window), pol.Ramp.Floor, 1.0)
}
// protoFactor 协议偏好因子(软偏好,只降权不排除,恒 > 0):
// - ProtoResponses:上游原生支持 Responses 协议(supports_responses 或
// chat_via_responses,后者定义上即 responses-only 上游)的渠道为 1,其余降权
// (经 chat 转换会丢失 previous_response_id 有状态多轮等 Responses 专属特性,
// 损失大故降权较重);
// - ProtoChat:原生 chat 渠道为 1,ChatViaResponses 桥接渠道降权(桥接基本无损,
// 仅响应细节略有出入,降权较轻)。
func protoFactor(c Channel, pref ProtoPreference, pol *RoutingPolicy) float64 {
switch pref {
case ProtoResponses:
if c.SupportsResponses || c.ChatViaResponses {
return 1
}
return pol.Proto.ResponsesMismatch
case ProtoChat:
if !c.ChatViaResponses {
return 1
}
return pol.Proto.ChatBridgeMismatch
}
return 1
}
func clamp(v, lo, hi float64) float64 {
if v < lo {
return lo
}
if v > hi {
return hi
}
return v
}