-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy patherrors.go
More file actions
308 lines (276 loc) · 9.11 KB
/
Copy patherrors.go
File metadata and controls
308 lines (276 loc) · 9.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
package litellm
import (
"context"
"encoding/json"
"errors"
"net/http"
"strings"
)
type ErrorType string
const (
ErrorTypeAuth ErrorType = "auth"
ErrorTypeRateLimit ErrorType = "rate_limit"
ErrorTypeNetwork ErrorType = "network"
ErrorTypeValidation ErrorType = "validation"
ErrorTypeProvider ErrorType = "provider"
ErrorTypeTimeout ErrorType = "timeout"
ErrorTypeQuota ErrorType = "quota"
ErrorTypeModel ErrorType = "model"
ErrorTypeInternal ErrorType = "internal"
ErrorTypeContextOverflow ErrorType = "context_overflow"
ErrorTypeOverloaded ErrorType = "overloaded"
ErrorTypeContentFilter ErrorType = "content_filter"
)
type LiteLLMError struct {
Type ErrorType
Code string
Message string
Provider string
Model string
StatusCode int
Retryable bool
RetryAfter int
Cause error
}
func (e *LiteLLMError) Error() string {
msg := strings.TrimSpace(e.Message)
if e.Code != "" && msg != "" {
return e.Code + ": " + msg
}
if msg != "" && shouldShowCause(e) {
if cause := strings.TrimSpace(e.Cause.Error()); cause != "" && cause != msg {
return msg + ": " + cause
}
}
if msg != "" {
return msg
}
if e.Code != "" {
return e.Code
}
if e.Type != "" {
return string(e.Type)
}
return "litellm error"
}
func shouldShowCause(e *LiteLLMError) bool {
if e == nil || e.Cause == nil {
return false
}
return e.Type == ErrorTypeNetwork || e.Type == ErrorTypeTimeout
}
func (e *LiteLLMError) Unwrap() error {
return e.Cause
}
func NewError(errorType ErrorType, message string) *LiteLLMError {
return &LiteLLMError{Type: errorType, Message: message, Retryable: isRetryableByType(errorType)}
}
func NewErrorWithCause(errorType ErrorType, message string, cause error) *LiteLLMError {
return &LiteLLMError{Type: errorType, Message: message, Cause: cause, Retryable: isRetryableByType(errorType)}
}
func NewProviderError(provider string, errorType ErrorType, message string) *LiteLLMError {
return &LiteLLMError{Type: errorType, Provider: provider, Message: message, Retryable: isRetryableByType(errorType)}
}
func NewProviderErrorWithCause(provider string, errorType ErrorType, message string, cause error) *LiteLLMError {
return &LiteLLMError{Type: errorType, Provider: provider, Message: message, Cause: cause, Retryable: isRetryableByType(errorType)}
}
func NewHTTPError(provider string, statusCode int, message string) *LiteLLMError {
code, message := parseHTTPErrorMessage(message)
errorType := classifyHTTPError(statusCode)
// Content moderation rejections are deterministic: the same payload will be
// rejected again, so retrying is futile. Providers signal them with vendor
// error codes rather than a common status (proxies often rewrite it to a
// retryable 429/5xx), hence the detection is by code, not by status.
if isContentFilterError(code, message) {
errorType = ErrorTypeContentFilter
}
return &LiteLLMError{
Type: errorType,
Code: code,
Provider: provider,
Message: message,
StatusCode: statusCode,
Retryable: isRetryableByType(errorType),
}
}
func parseHTTPErrorMessage(body string) (string, string) {
body = strings.TrimSpace(body)
if body == "" {
return "", ""
}
var payload struct {
Error any `json:"error"`
}
if err := json.Unmarshal([]byte(body), &payload); err != nil || payload.Error == nil {
return "", body
}
switch e := payload.Error.(type) {
case string:
return "", strings.TrimSpace(e)
case map[string]any:
code := stringField(e, "code")
msg := stringField(e, "message")
if msg == "" {
msg = body
}
return code, msg
default:
return "", body
}
}
func stringField(m map[string]any, key string) string {
v, ok := m[key].(string)
if !ok {
return ""
}
return strings.TrimSpace(v)
}
func NewAuthError(provider, message string) *LiteLLMError {
return NewProviderError(provider, ErrorTypeAuth, message)
}
func NewValidationError(provider, message string) *LiteLLMError {
return NewProviderError(provider, ErrorTypeValidation, message)
}
func WrapValidationError(provider string, err error) error {
if err == nil {
return nil
}
var e *LiteLLMError
if errors.As(err, &e) {
if e.Provider == "" {
copy := *e
copy.Provider = provider
return ©
}
return err
}
return NewProviderErrorWithCause(provider, ErrorTypeValidation, err.Error(), err)
}
func NewRateLimitError(provider, message string, retryAfter int) *LiteLLMError {
return &LiteLLMError{
Type: ErrorTypeRateLimit,
Provider: provider,
Message: message,
Retryable: true,
RetryAfter: retryAfter,
}
}
func NewModelError(provider, model, message string) *LiteLLMError {
return &LiteLLMError{Type: ErrorTypeModel, Provider: provider, Model: model, Message: message}
}
func NewNetworkError(provider, message string, cause error) *LiteLLMError {
if errors.Is(cause, context.Canceled) {
return &LiteLLMError{Type: ErrorTypeNetwork, Provider: provider, Message: message, Cause: cause, Retryable: false}
}
if errors.Is(cause, context.DeadlineExceeded) {
return &LiteLLMError{Type: ErrorTypeTimeout, Provider: provider, Message: message, Cause: cause, Retryable: false}
}
return &LiteLLMError{Type: ErrorTypeNetwork, Provider: provider, Message: message, Cause: cause, Retryable: true}
}
func NewTimeoutError(provider, message string) *LiteLLMError {
return &LiteLLMError{Type: ErrorTypeTimeout, Provider: provider, Message: message, Retryable: true}
}
func IsAuthError(err error) bool { return isErrorType(err, ErrorTypeAuth) }
func IsRateLimitError(err error) bool { return isErrorType(err, ErrorTypeRateLimit) }
func IsNetworkError(err error) bool { return isErrorType(err, ErrorTypeNetwork) }
func IsValidationError(err error) bool { return isErrorType(err, ErrorTypeValidation) }
func IsProviderError(err error) bool { return isErrorType(err, ErrorTypeProvider) }
func IsTimeoutError(err error) bool { return isErrorType(err, ErrorTypeTimeout) }
func IsModelError(err error) bool { return isErrorType(err, ErrorTypeModel) }
func IsContextOverflowError(err error) bool { return isErrorType(err, ErrorTypeContextOverflow) }
func IsOverloadedError(err error) bool { return isErrorType(err, ErrorTypeOverloaded) }
func IsContentFilterError(err error) bool { return isErrorType(err, ErrorTypeContentFilter) }
func IsRetryableError(err error) bool {
var e *LiteLLMError
return errors.As(err, &e) && e.Retryable
}
func GetRetryAfter(err error) int {
var e *LiteLLMError
if errors.As(err, &e) {
return e.RetryAfter
}
return 0
}
func WrapError(err error, provider string) error {
if err == nil {
return nil
}
if errors.Is(err, context.Canceled) {
return NewNetworkError(provider, err.Error(), err)
}
if errors.Is(err, context.DeadlineExceeded) {
return NewNetworkError(provider, err.Error(), err)
}
var e *LiteLLMError
if errors.As(err, &e) {
if e.Provider == "" {
copy := *e
copy.Provider = provider
return ©
}
return err
}
return NewProviderErrorWithCause(provider, ErrorTypeProvider, err.Error(), err)
}
func isErrorType(err error, errorType ErrorType) bool {
var e *LiteLLMError
return errors.As(err, &e) && e.Type == errorType
}
// contentFilterTokens are stable vendor error codes for content moderation
// rejections: Azure (content_filter), OpenAI (content_policy_violation;
// invalid_prompt on reasoning models), Zhipu-style gateways
// (sensitive_words_detected), DashScope/Qwen (data_inspection_failed in
// OpenAI-compat mode, InternalError.Algo.DataInspectionFailed natively).
// Anthropic has no dedicated code — its block is a generic
// invalid_request_error whose only marker is the fixed message
// "Output blocked by content filtering policy", hence a message token.
// Matched case-insensitively as substrings of the parsed error code and
// message; misses just fall back to status-based classification.
var contentFilterTokens = []string{
"content_filter",
"content_policy",
"sensitive_words",
"data_inspection_failed",
"datainspectionfailed",
"invalid_prompt",
"content filtering policy",
}
func isContentFilterError(code, message string) bool {
haystack := strings.ToLower(code + " " + message)
for _, token := range contentFilterTokens {
if strings.Contains(haystack, token) {
return true
}
}
return false
}
func classifyHTTPError(statusCode int) ErrorType {
switch {
case statusCode == http.StatusUnauthorized, statusCode == http.StatusForbidden:
return ErrorTypeAuth
case statusCode == http.StatusTooManyRequests:
return ErrorTypeRateLimit
case statusCode == http.StatusPaymentRequired:
return ErrorTypeQuota
case statusCode == http.StatusNotFound:
return ErrorTypeModel
case statusCode == http.StatusRequestTimeout:
return ErrorTypeTimeout
case statusCode == http.StatusBadRequest:
return ErrorTypeValidation
case statusCode == 529:
return ErrorTypeOverloaded
case statusCode >= 500:
return ErrorTypeProvider
default:
return ErrorTypeProvider
}
}
func isRetryableByType(errorType ErrorType) bool {
switch errorType {
case ErrorTypeNetwork, ErrorTypeTimeout, ErrorTypeRateLimit, ErrorTypeOverloaded, ErrorTypeProvider:
return true
default:
return false
}
}