refactor(providers): simplify 400 error handling, remove modelInvalidPatterns

Remove modelInvalidPatterns since all 400 errors are now uniformly
handled by classifyByStatus(400) → FailoverModelInvalid. The actual
error message is already shown in the warning sent to the user, so
pattern-based detection is redundant.
This commit is contained in:
muava12 2026-02-25 00:24:16 +08:00
parent ffbe69c2d9
commit ae696ade12
2 changed files with 9 additions and 60 deletions

View file

@ -78,20 +78,6 @@ var (
substr("invalid request format"), substr("invalid request format"),
} }
// Model invalid/not found patterns: these are 400 errors that should be
// retriable (fallback to next model), NOT treated as format errors.
modelInvalidPatterns = []errorPattern{
substr("not a valid model"),
substr("model not found"),
substr("model_not_found"),
substr("model not available"),
substr("does not exist"),
substr("no such model"),
substr("invalid model"),
rxp(`model.*not.*supported`),
rxp(`model.*is.*unavailable`),
rxp(`model.*is.*deprecated`),
}
imageDimensionPatterns = []errorPattern{ imageDimensionPatterns = []errorPattern{
rxp(`image dimensions exceed max`), rxp(`image dimensions exceed max`),
@ -143,17 +129,6 @@ func ClassifyError(err error, provider, model string) *FailoverError {
} }
} }
// Model invalid/not found: retriable, should fallback to another model.
// This MUST run before HTTP status classification, because 400 + "not a valid model"
// would otherwise be classified as non-retriable FailoverFormat.
if matchesAny(msg, modelInvalidPatterns) {
return &FailoverError{
Reason: FailoverModelInvalid,
Provider: provider,
Model: model,
Wrapped: err,
}
}
// Try HTTP status code extraction. // Try HTTP status code extraction.
if status := extractHTTPStatus(msg); status > 0 { if status := extractHTTPStatus(msg); status > 0 {

View file

@ -207,26 +207,20 @@ func TestClassifyError_FormatPatterns(t *testing.T) {
} }
} }
func TestClassifyError_ModelInvalidPatterns(t *testing.T) { func TestClassifyError_Status400_ModelInvalid(t *testing.T) {
patterns := []string{ // All 400 errors should be classified as FailoverModelInvalid (retriable + warning).
"nemotron-3-nano-30b-a3b:free is not a valid model ID", // The actual error message is shown in the warning to the user.
"model not found", tests := []string{
"model_not_found: the requested model does not exist", "API request failed:\n Status: 400\n Body: nemotron-3-nano-30b-a3b:free is not a valid model ID",
"model not available in this region", "API request failed:\n Status: 400\n Body: unknown error from provider",
"the model does not exist or you do not have access", "API request failed:\n Status: 400\n Body: model not found",
"no such model: gpt-5-turbo",
"invalid model specified",
"model llama-3-8b is not supported",
"model gpt-4o-mini is unavailable",
"model codellama is deprecated",
} }
for _, msg := range patterns { for _, msg := range tests {
err := errors.New(msg) err := errors.New(msg)
result := ClassifyError(err, "nvidia", "test-model") result := ClassifyError(err, "nvidia", "test-model")
if result == nil { if result == nil {
t.Errorf("pattern %q: expected non-nil", msg) t.Fatalf("pattern %q: expected non-nil", msg)
continue
} }
if result.Reason != FailoverModelInvalid { if result.Reason != FailoverModelInvalid {
t.Errorf("pattern %q: reason = %q, want model_invalid", msg, result.Reason) t.Errorf("pattern %q: reason = %q, want model_invalid", msg, result.Reason)
@ -240,26 +234,6 @@ func TestClassifyError_ModelInvalidPatterns(t *testing.T) {
} }
} }
func TestClassifyError_ModelInvalid_OverridesStatus400(t *testing.T) {
// This is the exact production error: status 400 + "not a valid model".
// Before the fix, status 400 was classified as FailoverFormat (non-retriable),
// which prevented fallback to other models.
err := fmt.Errorf("API request failed:\n Status: 400\n Body: {\"error\":{\"message\":\"nemotron-3-nano-30b-a3b:free is not a valid model ID\",\"code\":400}}")
result := ClassifyError(err, "nvidia", "nemotron-3-nano-30b-a3b:free")
if result == nil {
t.Fatal("expected non-nil for model-invalid 400 error")
}
if result.Reason != FailoverModelInvalid {
t.Errorf("reason = %q, want model_invalid (should override status 400)", result.Reason)
}
if !result.IsRetriable() {
t.Error("model-invalid error should be retriable to allow fallback to next model")
}
if !result.IsModelInvalid() {
t.Error("should be classified as model invalid")
}
}
func TestClassifyError_ImageDimensionError(t *testing.T) { func TestClassifyError_ImageDimensionError(t *testing.T) {
err := errors.New("image dimensions exceed max allowed 2048x2048") err := errors.New("image dimensions exceed max allowed 2048x2048")
result := ClassifyError(err, "openai", "gpt-4o") result := ClassifyError(err, "openai", "gpt-4o")