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.
39 lines
1.5 KiB
Java
39 lines
1.5 KiB
Java
package vip.mate.llm.chatmodel;
|
|
|
|
import org.springframework.ai.chat.model.ChatModel;
|
|
import org.springframework.retry.support.RetryTemplate;
|
|
import org.springframework.stereotype.Component;
|
|
import vip.mate.llm.chatgpt.ChatGPTChatModel;
|
|
import vip.mate.llm.chatgpt.ChatGPTResponsesClient;
|
|
import vip.mate.llm.model.ModelConfigEntity;
|
|
import vip.mate.llm.model.ModelProtocol;
|
|
import vip.mate.llm.model.ModelProviderEntity;
|
|
|
|
/**
|
|
* ChatGPT Responses API (the "/codex/responses" endpoint reached via OAuth)
|
|
* doesn't go through Spring AI's standard ChatModel builders — it has its own
|
|
* {@link ChatGPTChatModel} wrapper around {@link ChatGPTResponsesClient}.
|
|
* The {@link RetryTemplate} parameter is ignored because retry happens inside
|
|
* the OAuth-aware client itself.
|
|
*/
|
|
@Component
|
|
public class ChatGPTResponsesChatModelBuilder implements ChatModelBuilder {
|
|
|
|
private final ChatGPTResponsesClient chatGPTResponsesClient;
|
|
|
|
public ChatGPTResponsesChatModelBuilder(ChatGPTResponsesClient chatGPTResponsesClient) {
|
|
this.chatGPTResponsesClient = chatGPTResponsesClient;
|
|
}
|
|
|
|
@Override
|
|
public ModelProtocol supportedProtocol() {
|
|
return ModelProtocol.OPENAI_CHATGPT;
|
|
}
|
|
|
|
@Override
|
|
public ChatModel build(ModelConfigEntity model, ModelProviderEntity provider, RetryTemplate retry) {
|
|
Double temp = model.getTemperature() != null ? model.getTemperature() : 0.7;
|
|
return new ChatGPTChatModel(chatGPTResponsesClient, model.getModelName(), temp);
|
|
}
|
|
}
|