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"),
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue