mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-14 03:33:43 +08:00
Two related issues from the Kimi-401 user report:
1. Backend (NodeStreamingChatHelper): a primary AUTH_ERROR (e.g. Kimi 401
with an invalid API key) returned immediately without trying the
fallback chain — a fallback provider with a different, valid key
never got a chance. Even with DashScope correctly configured as the
fallback, the user chat dead-ended on a 401.
The original assumption ("auth never self-heals so do not retry")
holds for the primary same-model retry loop but is wrong for the
fallback chain — different providers have different keys. Apply the
same break-into-fallback policy that BILLING and MODEL_NOT_FOUND
already use. recordPrimary(false) is preserved so the cooldown
counter still accumulates.
2. Frontend (chatError.ts + i18n): the error-text matching for
/认证|auth|unauthorized|401/i was so broad it matched the substring
"auth" inside URLs like https://api.kimi.com/.../auth, classifying
any model 401 as user "session expired" and rendering the misleading
"页面将自动跳转到登录页" copy. (The redirect itself only fires from
/api/v1/auth/* axios paths and SSE-connection 401s, not from this
payload-text path — but the copy alone is the worst kind of false
alarm.)
Add a new ChatErrorCategory provider_auth_error and split the
pattern matching: narrow auth_expired (HTTP 401 / 登录已过期 /
session expired / 凭证失效) is matched FIRST, then the broad
401-ish pattern routes to provider_auth_error. BACKEND_ERROR_TYPE_MAP
for AUTH_ERROR is also remapped, since structured backend payloads
currently always come from LLM providers — never from our own
/api/v1/auth path.
Tests
- NodeStreamingChatHelperFailoverTest (5 cases): primary 401 →
fallback succeeds; chain skips auth-failing fallback to next healthy
one; whole-chain failure surfaces last AUTH_ERROR (no silent drop);
BILLING regression unchanged; primary-success path does not touch
chain
- Browser preview verified: new i18n keys resolve in en-US, classifier
correctly routes "[错误] 401 from kimi.com" → provider_auth_error
while "[错误] HTTP 401 from /api/v1/auth/ping" stays auth_expired
- 186 tests pass (was 181 + 5 new); vue-tsc clean
Do-not-touch list: handleAuthFailure() in useStream/api/index.ts (real
session-expiry path) is unmodified — only the misclassification
upstream is fixed. auth_expired i18n copy is unchanged.
46 lines
2.1 KiB
Java
46 lines
2.1 KiB
Java
package vip.mate.llm.chatmodel;
|
|
|
|
import org.springframework.ai.chat.model.ChatModel;
|
|
import org.springframework.retry.support.RetryTemplate;
|
|
import vip.mate.llm.model.ModelConfigEntity;
|
|
import vip.mate.llm.model.ModelProtocol;
|
|
import vip.mate.llm.model.ModelProviderEntity;
|
|
|
|
/**
|
|
* Strategy contract for assembling a Spring AI {@link ChatModel} from a
|
|
* persisted {@link ModelConfigEntity} + {@link ModelProviderEntity} pair.
|
|
*
|
|
* <p>One implementation per {@link ModelProtocol}. New provider protocols
|
|
* are added by registering a new {@code @Component} that implements this
|
|
* interface — no edits to {@link ProviderChatModelFactory} or
|
|
* {@code AgentGraphBuilder} required.</p>
|
|
*
|
|
* <p>This interface lives in {@code vip.mate.llm.chatmodel} (under the {@code llm}
|
|
* package) so that {@code llm.failover.ProviderInitProbe} can build a
|
|
* {@link ChatModel} for health-probing without depending on the {@code agent}
|
|
* package — which would create the circular dependency
|
|
* {@code agent → llm → agent}. Prior to this extraction, all model-building
|
|
* logic lived inside {@code AgentGraphBuilder}.</p>
|
|
*/
|
|
public interface ChatModelBuilder {
|
|
|
|
/** The protocol this builder handles; the factory routes by this key. */
|
|
ModelProtocol supportedProtocol();
|
|
|
|
/**
|
|
* Build a fresh {@link ChatModel} for the given runtime configuration.
|
|
* Implementations must be stateless — callers may invoke this many times
|
|
* for the same model id and expect equivalent (but not necessarily ==)
|
|
* results.
|
|
*
|
|
* @param model runtime model row with temperature / max tokens / etc.
|
|
* @param provider provider row supplying API key, base URL, and provider-level
|
|
* generate kwargs
|
|
* @param retry retry template to wire into the underlying Spring AI client
|
|
* where supported. Implementations whose protocols don't
|
|
* expose a Spring AI {@code RetryTemplate} hook (DashScope
|
|
* native, ChatGPT Responses) may ignore this parameter.
|
|
*/
|
|
ChatModel build(ModelConfigEntity model, ModelProviderEntity provider, RetryTemplate retry);
|
|
}
|