139 lines
4.8 KiB
Go
139 lines
4.8 KiB
Go
// Filename: internal/errors/upstream_errors.go
|
|
package errors
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
// TODO: [Future Evolution] This file establishes the new, granular error classification framework.
|
|
// The next step is to refactor the handleKeyUsageEvent method in APIKeyService to utilize these new
|
|
// classifiers and implement the corresponding actions:
|
|
//
|
|
// 1. On IsPermanentUpstreamError:
|
|
// - Set mapping status to models.StatusBanned.
|
|
// - Set the master APIKey's status to models.MasterStatusRevoked.
|
|
// - This is a "one-strike, you're out" policy for definitively invalid keys.
|
|
//
|
|
// 2. On IsTemporaryUpstreamError:
|
|
// - Increment mapping.ConsecutiveErrorCount.
|
|
// - Check against the blacklist threshold to potentially set status to models.StatusDisabled.
|
|
// - This is for recoverable errors that are the key's fault (e.g., quota limits).
|
|
//
|
|
// 3. On ALL other upstream errors (that are not Permanent or Temporary):
|
|
// - These are treated as "Truly Ignorable" from the key's perspective (e.g., 503 Service Unavailable).
|
|
// - Do NOT increment the error count. Only update LastUsedAt.
|
|
// - This prevents good keys from being punished for upstream service instability.
|
|
|
|
// --- 1. Permanent Errors ---
|
|
// Errors that indicate the API Key itself is permanently invalid.
|
|
// Action: Ban mapping, Revoke Master Key.
|
|
var permanentErrorSubstrings = []string{
|
|
"invalid api key",
|
|
"api key not valid",
|
|
"api key suspended",
|
|
"API Key not found",
|
|
"api key expired",
|
|
"permission denied", // Often indicates the key lacks permissions for the target model/service.
|
|
"permission_denied", // Catches the 'status' field in Google's JSON error, e.g., "status": "PERMISSION_DENIED".
|
|
"service_disabled", // Catches the 'reason' field for disabled APIs, e.g., "reason": "SERVICE_DISABLED".
|
|
"api has not been used",
|
|
"reported as leaked", // Leaked
|
|
}
|
|
|
|
// --- 2. Temporary Errors ---
|
|
// Errors that are attributable to the key's state but are recoverable over time.
|
|
// Action: Increment consecutive error count, potentially disable the key.
|
|
var temporaryErrorSubstrings = []string{
|
|
"quota",
|
|
"Quota exceeded",
|
|
"limit reached",
|
|
"insufficient",
|
|
"request limit",
|
|
"billing",
|
|
"exceeded",
|
|
"too many requests",
|
|
}
|
|
|
|
// --- 3. Unretryable Request Errors ---
|
|
// Errors indicating a problem with the user's request, not the key. Retrying with a new key is pointless.
|
|
// Action: Abort the retry loop immediately in ProxyHandler.
|
|
var unretryableRequestErrorSubstrings = []string{
|
|
"invalid content",
|
|
"invalid argument",
|
|
"malformed",
|
|
"unsupported",
|
|
"invalid model",
|
|
}
|
|
|
|
// --- 4. Ignorable Client/Network Errors ---
|
|
// Network-level errors, typically caused by the client disconnecting.
|
|
// Action: Ignore for logging and metrics purposes.
|
|
var clientNetworkErrorSubstrings = []string{
|
|
"context canceled",
|
|
"connection reset by peer",
|
|
"broken pipe",
|
|
"use of closed network connection",
|
|
"request canceled",
|
|
"invalid query parameters", // 参数解析错误,归类为客户端错误
|
|
}
|
|
|
|
// --- 5. Retryable Network/Gateway Errors ---
|
|
// Errors that indicate temporary network or gateway issues, should retry with same or different key.
|
|
// Action: Retry the request.
|
|
var retryableNetworkErrorSubstrings = []string{
|
|
"bad gateway",
|
|
"service unavailable",
|
|
"gateway timeout",
|
|
"connection refused",
|
|
"connection reset",
|
|
"stream transmission interrupted", // ✅ 新增:流式传输中断
|
|
"failed to establish stream", // ✅ 新增:流式连接建立失败
|
|
"upstream connect error",
|
|
"no healthy upstream",
|
|
"502",
|
|
"503",
|
|
"504",
|
|
}
|
|
|
|
// IsPermanentUpstreamError checks if an upstream error indicates the key is permanently invalid.
|
|
func IsPermanentUpstreamError(msg string) bool {
|
|
return containsSubstring(msg, permanentErrorSubstrings)
|
|
}
|
|
|
|
// IsTemporaryUpstreamError checks if an upstream error is due to temporary, key-specific limits.
|
|
func IsTemporaryUpstreamError(msg string) bool {
|
|
return containsSubstring(msg, temporaryErrorSubstrings)
|
|
}
|
|
|
|
// IsUnretryableRequestError checks if an upstream error is due to a malformed user request.
|
|
func IsUnretryableRequestError(msg string) bool {
|
|
return containsSubstring(msg, unretryableRequestErrorSubstrings)
|
|
}
|
|
|
|
// IsClientNetworkError checks if an error is a common, ignorable client-side network issue.
|
|
func IsClientNetworkError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
return containsSubstring(err.Error(), clientNetworkErrorSubstrings)
|
|
}
|
|
|
|
// IsRetryableNetworkError checks if an error is a temporary network/gateway issue.
|
|
func IsRetryableNetworkError(msg string) bool {
|
|
return containsSubstring(msg, retryableNetworkErrorSubstrings)
|
|
}
|
|
|
|
// containsSubstring is a helper function to avoid code repetition.
|
|
func containsSubstring(s string, substrings []string) bool {
|
|
if s == "" {
|
|
return false
|
|
}
|
|
lowerS := strings.ToLower(s)
|
|
for _, sub := range substrings {
|
|
if strings.Contains(lowerS, sub) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|