diff --git a/src/hooks/runtime-fallback/constants.ts b/src/hooks/runtime-fallback/constants.ts index cb968ef91..43df0f936 100644 --- a/src/hooks/runtime-fallback/constants.ts +++ b/src/hooks/runtime-fallback/constants.ts @@ -41,14 +41,12 @@ export const RETRYABLE_ERROR_PATTERNS = [ /(?:^|\s)429(?:\s|$)/, /(?:^|\s)503(?:\s|$)/, /(?:^|\s)529(?:\s|$)/, - - // Chinese rate-limit / quota patterns (Zhipu, etc.) - /使用上限/, // "usage limit" — Zhipu: "已达到 5 小时的使用上限" - /频率限制/, // "rate limit" — generic Chinese rate-limit - /请求过于频繁/, // "too many requests" — common Chinese 429 message - /暂时不可用/, // "temporarily unavailable" - /服务不可用/, // "service unavailable" - /请稍后重试/, // "please try again later" + /使用上限/, + /频率限制/, + /请求过于频繁/, + /暂时不可用/, + /服务不可用/, + /请稍后重试/, ] /** diff --git a/src/hooks/runtime-fallback/error-classifier.test.ts b/src/hooks/runtime-fallback/error-classifier.test.ts index c9eef6e06..a7f43210a 100644 --- a/src/hooks/runtime-fallback/error-classifier.test.ts +++ b/src/hooks/runtime-fallback/error-classifier.test.ts @@ -59,6 +59,44 @@ describe("runtime-fallback error classifier", () => { expect(retryable).toBe(true) }) + test("treats localized transient provider messages as retryable", () => { + //#given + const errors = [ + { message: "请求过于频繁,请稍后重试" }, + { message: "服务暂时不可用" }, + { message: "触发频率限制" }, + ] + + //#when + const retryable = errors.map((error) => isRetryableError(error, [429, 503, 529])) + + //#then + expect(retryable).toEqual([true, true, true]) + }) + + test("classifies localized quota exhaustion messages as quota_exceeded", () => { + //#given + const errors = [ + { message: "已达到 5 小时的使用上限" }, + { message: "已达到每日调用限制" }, + { message: "额度不足" }, + { message: "账户余额不足" }, + { message: "免费额度已耗尽" }, + ] + + //#when + const classifications = errors.map((error) => classifyErrorType(error)) + + //#then + expect(classifications).toEqual([ + "quota_exceeded", + "quota_exceeded", + "quota_exceeded", + "quota_exceeded", + "quota_exceeded", + ]) + }) + test("classifies ProviderModelNotFoundError as model_not_found", () => { //#given const error = { diff --git a/src/shared/model-error-classifier.test.ts b/src/shared/model-error-classifier.test.ts index 33fd3f4c1..0ab40e8eb 100644 --- a/src/shared/model-error-classifier.test.ts +++ b/src/shared/model-error-classifier.test.ts @@ -216,6 +216,21 @@ describe("model-error-classifier", () => { expect(result).toBe(true) }) + test("treats localized transient provider messages as retryable", () => { + //#given + const errors = [ + { message: "请求过于频繁,请稍后重试" }, + { message: "服务暂时不可用" }, + { message: "触发频率限制" }, + ] + + //#when + const results = errors.map((error) => shouldRetryError(error)) + + //#then + expect(results).toEqual([true, true, true]) + }) + test("treats subscription quota message as non-retryable", () => { //#given const error = { message: "Subscription quota exceeded. You can continue using free models." } @@ -227,6 +242,22 @@ describe("model-error-classifier", () => { expect(result).toBe(false) }) + test("treats localized quota exhaustion messages as non-retryable stop errors", () => { + //#given + const errors = [ + { message: "已达到 5 小时的使用上限" }, + { message: "额度不足" }, + { message: "账户余额不足" }, + { message: "免费额度已耗尽" }, + ] + + //#when + const results = errors.map((error) => shouldRetryError(error)) + + //#then + expect(results).toEqual([false, false, false, false]) + }) + test("treats HTTP 429 rate limit message as retryable", () => { //#given const error = { message: "429 Too Many Requests: rate limit reached" } diff --git a/src/shared/model-error-classifier.ts b/src/shared/model-error-classifier.ts index a5d70ac62..787f4825a 100644 --- a/src/shared/model-error-classifier.ts +++ b/src/shared/model-error-classifier.ts @@ -67,6 +67,7 @@ const RETRYABLE_MESSAGE_PATTERNS = [ "balance", "temporarily unavailable", "try again", + "请稍后重试", "503", "502", "504",