Files
sundynix-agentix/sundynix-dispatcher/internal/llm/pool_health_test.go
T
Blizzard 55d50417a9 feat: 模型健康/熔断态 surface 到管理端(T4.F 可观测)
failover/熔断的运行时态原来只在 dispatcher 日志、admin 看不到 —— 本次接到概览可见:
- harness: CircuitBreaker.Snapshot() 只读观测访问器(state + fails,不动状态机)
- llm: Pool.ModelHealth() 上报主备链每模型 {provider,model,role,state,fails};
  buildWithFallbacks 把模型名↔breaker 配对(同包直接读 failoverModel.breakers);
  newFailoverModel 改返回具体类型以便读 breakers
- dispatcher 心跳 payload 加 models[]
- gateway /admin/overview 独立超时 Ping dispatcher,合并进 models.health
- admin 概览「模型路由」新增「运行时链路态(实时)」:逐模型状态点
  (🟢在线/🔴熔断中+失败数/🟡半开探测/单点)
- 单测:Snapshot、Pool.ModelHealth(名字↔态配对/单点/空)
- live:配坏主→提交任务打熔断→概览显示 broken-demo「熔断中·失败3」,备用在线

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:00:13 +08:00

46 lines
1.4 KiB
Go

package llm
import (
"testing"
"github.com/sundynix/sundynix-shared/contract"
)
// Pool.ModelHealth 应把主备链各模型的名字/角色与其熔断态正确配对上报。
func TestPool_ModelHealth(t *testing.T) {
p := NewPool()
// 主 + 1 备 → failover 链,两模型都在线(closed)。
p.SetConfig(&contract.ModelConfig{
Provider: "openai-compatible", BaseURL: "http://primary", APIKey: "k", Model: "primary-m",
Fallbacks: []contract.ModelConfig{
{Provider: "openai-compatible", BaseURL: "http://backup", APIKey: "k", Model: "backup-m"},
},
})
h := p.ModelHealth()
if len(h) != 2 {
t.Fatalf("主+备应 2 条, got %d: %+v", len(h), h)
}
if h[0].Role != "primary" || h[0].Model != "primary-m" || h[0].State != "closed" {
t.Errorf("主模型态错: %+v", h[0])
}
if h[1].Role != "fallback" || h[1].Model != "backup-m" || h[1].State != "closed" {
t.Errorf("备模型态错: %+v", h[1])
}
// 单模型 → 无 failover 链,state=single。
p.SetConfig(&contract.ModelConfig{
Provider: "openai-compatible", BaseURL: "http://only", APIKey: "k", Model: "only-m",
})
h = p.ModelHealth()
if len(h) != 1 || h[0].State != "single" || h[0].Role != "primary" {
t.Fatalf("单模型应 1 条 single/primary, got %+v", h)
}
// 未配置 → 空。
p.SetConfig(nil)
if h := p.ModelHealth(); len(h) != 0 {
t.Fatalf("未配置应空, got %+v", h)
}
}