fix(admin): 模型级健康分统一为 succ^α 口径,探测/重置结果即时落盘
能力列表的模型级健康分用线性 succ×100,而调度 healthFactor、维护快照落盘、健康变化 日志三处都用 succ^α×100。前端两处共用同一套 80/50 阈值,于是 succ=0.89 时模型行显示 89「健康」、渠道行显示 79「降级」,同一个渠道点进去自相矛盾。改为经 dispatchadapter.HealthAlpha() 取当前生效策略的 α(策略支持热更新,不硬编码 2)。 该入口刻意不走 Coordinator(),避免管理后台读接口有装配整个调度引擎的副作用。 渠道测试与重置健康度后请求即时重算快照:维护循环 5 分钟一轮,此前测试成功后 Redis 立刻变、能力列表(直读 Redis)立刻变,唯独渠道健康分要等下一轮,看起来像「测试没 生效」。重置单模型的分支此前完全不写 chn_health_scores,展示分永远停在旧值,一并修复。
This commit is contained in:
1 parent
a5e2ecf527
commit
4f07b64ac7
2 files changed
+18
-3
No files matched your search
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
do "github.com/qianfree/team-api/internal/model/do"
|
||||
@@ -791,6 +792,9 @@ func (s *sAdmin) batchGetModelHealth(ctx context.Context, channelID int64, abili
|
||||
}
|
||||
|
||||
redis := g.Redis()
|
||||
// 健康分折算口径必须与调度 healthFactor / 维护快照落盘一致(succ^α×100),
|
||||
// α 取当前生效的路由策略而非硬编码——策略支持热更新。
|
||||
alpha := dispatchadapter.HealthAlpha()
|
||||
for _, ab := range abilities {
|
||||
healthKey := fmt.Sprintf("dispatch:v1:health:%d:%s", channelID, ab.ModelName)
|
||||
breakerKey := fmt.Sprintf("dispatch:v1:breaker:%d:%s", channelID, ab.ModelName)
|
||||
@@ -808,8 +812,8 @@ func (s *sAdmin) batchGetModelHealth(ctx context.Context, channelID int64, abili
|
||||
var succEWMA float64
|
||||
if _, err := fmt.Sscanf(succStr, "%f", &succEWMA); err == nil && succEWMA >= 0 {
|
||||
data.SuccEWMA = &succEWMA
|
||||
// 健康分 = succ_ewma * 100
|
||||
healthScore := succEWMA * 100
|
||||
// 健康分 = succ_ewma^α × 100(与调度 healthFactor 同源)
|
||||
healthScore := math.Pow(succEWMA, alpha) * 100
|
||||
data.HealthScore = &healthScore
|
||||
}
|
||||
}
|
||||
@@ -959,13 +963,17 @@ func (s *sAdmin) ResetChannelHealth(ctx context.Context, req *v1.ChannelResetHea
|
||||
|
||||
// 调度层:只复位指定模型的熔断 + 成功率恢复
|
||||
dispatchadapter.ResetModelHealth(ctx, req.ID, req.ModelName)
|
||||
// 单模型重置不直接写 chn_health_scores(渠道分是全模型聚合值,不能按单模型拍值),
|
||||
// 改为请求重算——否则展示分永远停在旧值,管理员会以为重置没生效
|
||||
dispatchadapter.RequestHealthSnapshot(req.ID)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 未指定模型名,重置整个渠道(含所有模型)
|
||||
|
||||
// 展示层:健康分落库为 80(调度不读此表,仅供仪表盘/审计展示;下次维护快照会按策略重算)
|
||||
// 展示层:健康分落库为 80(调度不读此表,仅供仪表盘/审计展示;随后的按需重算/
|
||||
// 维护快照会按 Redis 实际读值重算,此处只是让界面立即有反馈)
|
||||
affected, err := dao.ChnHealthScores.Ctx(ctx).
|
||||
Where("channel_id", req.ID).
|
||||
Data(do.ChnHealthScores{
|
||||
@@ -998,6 +1006,9 @@ func (s *sAdmin) ResetChannelHealth(ctx context.Context, req *v1.ChannelResetHea
|
||||
|
||||
// 调度层:复位渠道级/模型级熔断 + 成功率恢复,渠道立即恢复被选择能力
|
||||
dispatchadapter.ResetChannelHealth(ctx, req.ID)
|
||||
// 请求按 Redis 实际读值重算落盘:上面写死的 80 只是调度引擎未组装时的兜底,
|
||||
// 重算后会收敛到与调度同源的 0.9^α×100
|
||||
dispatchadapter.RequestHealthSnapshot(req.ID)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -121,6 +121,10 @@ func (s *sAdmin) TestChannel(ctx context.Context, req *v1.ChannelTestReq) (*v1.C
|
||||
// 渠道启用时 MarkChannelRecovered 会复位熔断,无需在禁用期间预热。
|
||||
if ch.Status == "active" {
|
||||
dispatchadapter.ReportProbeOutcome(ctx, channelID, testModel, result.Success, float64(latencyMs))
|
||||
// 请求立即重算渠道健康快照:维护循环 5 分钟一轮,不触发的话管理员会看到
|
||||
// "测试成功了但渠道健康度纹丝不动"(模型级能力列表直读 Redis 会立刻变,
|
||||
// 渠道健康分读的是 chn_health_scores 落盘值)
|
||||
dispatchadapter.RequestHealthSnapshot(channelID)
|
||||
if dispatchadapter.CatalogHasModel(channelID, testModel) {
|
||||
g.Log().Infof(ctx, "[ChannelProbe] 渠道 %s (%d) 探测结果已投递健康上报 | 模型: %s | success: %v | 延迟: %dms(EWMA 变化将打印 [ChannelHealth] 日志,5 分钟内落盘刷新)",
|
||||
ch.Name, channelID, testModel, result.Success, latencyMs)
|
||||
|
||||
Reference in new issue
Block a user