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:
parent
ffbe69c2d9
commit
ae696ade12
2 changed files with 9 additions and 60 deletions
|
|
@ -78,20 +78,6 @@ var (
|
|||
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{
|
||||
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.
|
||||
if status := extractHTTPStatus(msg); status > 0 {
|
||||
|
|
|
|||
|
|
@ -207,26 +207,20 @@ func TestClassifyError_FormatPatterns(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClassifyError_ModelInvalidPatterns(t *testing.T) {
|
||||
patterns := []string{
|
||||
"nemotron-3-nano-30b-a3b:free is not a valid model ID",
|
||||
"model not found",
|
||||
"model_not_found: the requested model does not exist",
|
||||
"model not available in this region",
|
||||
"the model does not exist or you do not have access",
|
||||
"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",
|
||||
func TestClassifyError_Status400_ModelInvalid(t *testing.T) {
|
||||
// All 400 errors should be classified as FailoverModelInvalid (retriable + warning).
|
||||
// The actual error message is shown in the warning to the user.
|
||||
tests := []string{
|
||||
"API request failed:\n Status: 400\n Body: nemotron-3-nano-30b-a3b:free is not a valid model ID",
|
||||
"API request failed:\n Status: 400\n Body: unknown error from provider",
|
||||
"API request failed:\n Status: 400\n Body: model not found",
|
||||
}
|
||||
|
||||
for _, msg := range patterns {
|
||||
for _, msg := range tests {
|
||||
err := errors.New(msg)
|
||||
result := ClassifyError(err, "nvidia", "test-model")
|
||||
if result == nil {
|
||||
t.Errorf("pattern %q: expected non-nil", msg)
|
||||
continue
|
||||
t.Fatalf("pattern %q: expected non-nil", msg)
|
||||
}
|
||||
if result.Reason != FailoverModelInvalid {
|
||||
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) {
|
||||
err := errors.New("image dimensions exceed max allowed 2048x2048")
|
||||
result := ClassifyError(err, "openai", "gpt-4o")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue