From 112d80396e7d78481e0483e5461202d871cf14bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=92=E6=9F=A0?= <865274218@qq.com> Date: Tue, 10 Mar 2026 21:41:17 +0800 Subject: [PATCH] fix(providers): resolve correct provider per fallback candidate The fallback chain was reusing the primary model's provider instance (and therefore its API key and endpoint) for all fallback candidates. When the primary was zhipu and fallbacks were moonshot/anthropic/minimax, every fallback request was sent to the wrong endpoint with the wrong key, resulting in 401 errors across the board. Add a lazy provider cache (resolveProvider) on AgentLoop that looks up each candidate's ModelConfig from model_list and creates the correct provider instance with the right API key and base URL. Providers are cached per provider/model key so they are only created once. --- pkg/agent/loop.go | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 3d13071c0..8b350154e 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -48,6 +48,7 @@ type AgentLoop struct { mediaStore media.MediaStore transcriber voice.Transcriber cmdRegistry *commands.Registry + providerCache sync.Map // map[string]providers.LLMProvider — lazily created per provider/model } // processOptions configures how a message is processed @@ -235,6 +236,45 @@ func registerSharedTools( } } +// resolveProvider returns the correct LLM provider for a fallback candidate. +// It lazily creates and caches provider instances per provider/model key so that +// each candidate uses its own API key and endpoint from the model_list config. +func (al *AgentLoop) resolveProvider( + providerName, model string, + fallbackProvider providers.LLMProvider, +) providers.LLMProvider { + key := providers.ModelKey(providerName, model) + + if cached, ok := al.providerCache.Load(key); ok { + return cached.(providers.LLMProvider) + } + + // Search model_list for a config entry matching this provider/model. + target := providerName + "/" + model + for i := range al.cfg.ModelList { + entryModel := strings.TrimSpace(al.cfg.ModelList[i].Model) + if !strings.EqualFold(entryModel, target) { + continue + } + mc := al.cfg.ModelList[i] + if mc.Workspace == "" { + mc.Workspace = al.cfg.WorkspacePath() + } + p, _, err := providers.CreateProviderFromConfig(&mc) + if err != nil { + logger.WarnCF("agent", "Failed to create provider for fallback candidate, using default", + map[string]any{"candidate": target, "error": err.Error()}) + break + } + al.providerCache.Store(key, p) + return p + } + + // Not found or creation failed — fall back to agent's default provider. + al.providerCache.Store(key, fallbackProvider) + return fallbackProvider +} + func (al *AgentLoop) Run(ctx context.Context) error { al.running.Store(true) @@ -939,7 +979,8 @@ func (al *AgentLoop) runLLMIteration( ctx, activeCandidates, func(ctx context.Context, provider, model string) (*providers.LLMResponse, error) { - return agent.Provider.Chat(ctx, messages, providerToolDefs, model, llmOpts) + p := al.resolveProvider(provider, model, agent.Provider) + return p.Chat(ctx, messages, providerToolDefs, model, llmOpts) }, ) if fbErr != nil {