mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
fix(providers): support lookup-based fallback candidate resolution
This commit is contained in:
@@ -43,11 +43,26 @@ func NewFallbackChain(cooldown *CooldownTracker) *FallbackChain {
|
||||
|
||||
// ResolveCandidates parses model config into a deduplicated candidate list.
|
||||
func ResolveCandidates(cfg ModelConfig, defaultProvider string) []FallbackCandidate {
|
||||
return ResolveCandidatesWithLookup(cfg, defaultProvider, nil)
|
||||
}
|
||||
|
||||
func ResolveCandidatesWithLookup(
|
||||
cfg ModelConfig,
|
||||
defaultProvider string,
|
||||
lookup func(raw string) (resolved string, ok bool),
|
||||
) []FallbackCandidate {
|
||||
seen := make(map[string]bool)
|
||||
var candidates []FallbackCandidate
|
||||
|
||||
addCandidate := func(raw string) {
|
||||
ref := ParseModelRef(raw, defaultProvider)
|
||||
candidateRaw := strings.TrimSpace(raw)
|
||||
if lookup != nil {
|
||||
if resolved, ok := lookup(candidateRaw); ok {
|
||||
candidateRaw = resolved
|
||||
}
|
||||
}
|
||||
|
||||
ref := ParseModelRef(candidateRaw, defaultProvider)
|
||||
if ref == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -453,6 +453,75 @@ func TestResolveCandidates_EmptyPrimary(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCandidatesWithLookup_AliasResolvesToNestedModel(t *testing.T) {
|
||||
cfg := ModelConfig{
|
||||
Primary: "step-3.5-flash",
|
||||
Fallbacks: nil,
|
||||
}
|
||||
|
||||
lookup := func(raw string) (string, bool) {
|
||||
if raw == "step-3.5-flash" {
|
||||
return "openrouter/stepfun/step-3.5-flash:free", true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
candidates := ResolveCandidatesWithLookup(cfg, "", lookup)
|
||||
if len(candidates) != 1 {
|
||||
t.Fatalf("candidates = %d, want 1", len(candidates))
|
||||
}
|
||||
if candidates[0].Provider != "openrouter" {
|
||||
t.Fatalf("provider = %q, want openrouter", candidates[0].Provider)
|
||||
}
|
||||
if candidates[0].Model != "stepfun/step-3.5-flash:free" {
|
||||
t.Fatalf("model = %q, want stepfun/step-3.5-flash:free", candidates[0].Model)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCandidatesWithLookup_DeduplicateAfterLookup(t *testing.T) {
|
||||
cfg := ModelConfig{
|
||||
Primary: "step-3.5-flash",
|
||||
Fallbacks: []string{"openrouter/stepfun/step-3.5-flash:free"},
|
||||
}
|
||||
|
||||
lookup := func(raw string) (string, bool) {
|
||||
if raw == "step-3.5-flash" {
|
||||
return "openrouter/stepfun/step-3.5-flash:free", true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
candidates := ResolveCandidatesWithLookup(cfg, "", lookup)
|
||||
if len(candidates) != 1 {
|
||||
t.Fatalf("candidates = %d, want 1", len(candidates))
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCandidatesWithLookup_AliasWithoutProtocolUsesDefaultProvider(t *testing.T) {
|
||||
cfg := ModelConfig{
|
||||
Primary: "glm-5",
|
||||
Fallbacks: nil,
|
||||
}
|
||||
|
||||
lookup := func(raw string) (string, bool) {
|
||||
if raw == "glm-5" {
|
||||
return "glm-5", true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
candidates := ResolveCandidatesWithLookup(cfg, "openai", lookup)
|
||||
if len(candidates) != 1 {
|
||||
t.Fatalf("candidates = %d, want 1", len(candidates))
|
||||
}
|
||||
if candidates[0].Provider != "openai" {
|
||||
t.Fatalf("provider = %q, want openai", candidates[0].Provider)
|
||||
}
|
||||
if candidates[0].Model != "glm-5" {
|
||||
t.Fatalf("model = %q, want glm-5", candidates[0].Model)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFallbackExhaustedError_Message(t *testing.T) {
|
||||
e := &FallbackExhaustedError{
|
||||
Attempts: []FallbackAttempt{
|
||||
|
||||
Reference in New Issue
Block a user