55d50417a9
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>
46 lines
1.4 KiB
Go
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)
|
|
}
|
|
}
|