From d7378273b29d41e0ced25643d41a0c0501510962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=80=AA=E7=A8=8B=E4=BC=9F?= Date: Wed, 20 May 2026 16:49:54 +0800 Subject: [PATCH] fix(llm): classify "network connection error" as retryable SERVER_ERROR (#179) Some providers (notably SiliconFlow) return "network connection error" in the response body when their backend is overloaded or the upstream model connection is disrupted. classifyError() had no pattern for this string, so it fell through to UNKNOWN (non-retryable), surfacing the raw error to the user on the first failure instead of running the exponential-backoff recovery. Adds the pattern to the SERVER_ERROR classifier and a friendly message mapping in extractUserFriendlyError(); bumps MAX_RETRIES from 5 to 10 so sustained wiki batch load can ride out provider flaps without surfacing an error to the channel user. Closes #178 --- .../mate/agent/graph/NodeStreamingChatHelper.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/agent/graph/NodeStreamingChatHelper.java b/mateclaw-server/src/main/java/vip/mate/agent/graph/NodeStreamingChatHelper.java index 1122073a..1d817599 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/graph/NodeStreamingChatHelper.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/graph/NodeStreamingChatHelper.java @@ -333,7 +333,7 @@ public class NodeStreamingChatHelper { */ private static final int CONTENT_REPEAT_CHECK_INTERVAL = 200; - private static final int MAX_RETRIES = 5; + private static final int MAX_RETRIES = 10; // RATE_LIMIT: fail fast to failover chain — staying on the same // provider during a rate-limit window wastes time without recovery. // SERVER_ERROR keeps MAX_RETRIES (upstream flaps often self-heal). @@ -449,7 +449,12 @@ public class NodeStreamingChatHelper { // Reactor Netty wraps the raw socket cause in WebClientRequestException; // surface that wrapper too so retries fire even when the cause chain // string is "WebClientRequestException ...; nested ... SSLException". - || msg.contains("WebClientRequestException")) { + || msg.contains("WebClientRequestException") + // SiliconFlow and some other providers return "network connection error" + // in the response body when their backend is under high load or the + // upstream connection to the model server is disrupted. This is a + // transient server-side failure — classify as retryable. + || msg.contains("network connection error")) { return ErrorType.SERVER_ERROR; } return ErrorType.UNKNOWN; @@ -1502,6 +1507,11 @@ public class NodeStreamingChatHelper { if (msg.contains("rate_limit") || msg.contains("429")) return "Rate limit exceeded, please retry later"; if (msg.contains("timeout") || msg.contains("Timeout")) return "Request timeout, please retry"; if (msg.contains("502") || msg.contains("503") || msg.contains("504")) return "Model service temporarily unavailable"; + // SiliconFlow and similar providers surface "network connection error" when their + // backend is under high load or the upstream model connection is disrupted. + // Treat this as a transient failure so the user gets a retry-oriented message. + if (combined.contains("network connection error")) + return "Model service network error, please retry in a moment"; // 截断过长的原始消息 return msg.length() > 100 ? msg.substring(0, 100) + "..." : msg; }