-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherror_classifier.py
More file actions
121 lines (104 loc) · 3.72 KB
/
Copy patherror_classifier.py
File metadata and controls
121 lines (104 loc) · 3.72 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
"""
Error classification for LLM API responses.
Classifies HTTP errors from providers into user-friendly categories
and generates actionable messages.
"""
from __future__ import annotations
import re
from enum import Enum
from typing import Optional
class LLMApiError(Exception):
"""Raised by API stream functions on non-200 responses."""
def __init__(self, status_code: int, response_text: str, provider: str):
self.status_code = status_code
self.response_text = response_text
self.provider = provider
super().__init__(f"{provider} API error {status_code}: {response_text[:200]}")
class ErrorClass(Enum):
RATE_LIMIT = "rate_limit"
CONTEXT_OVERFLOW = "context_overflow"
AUTH = "auth"
BILLING = "billing"
TIMEOUT = "timeout"
FORMAT = "format"
UNKNOWN = "unknown"
# Patterns checked against lowercased response text
_PATTERNS = [
(ErrorClass.CONTEXT_OVERFLOW, [
r"context.?length", r"token.?limit", r"too.?many.?tokens",
r"maximum.?context", r"input.?too.?long", r"exceeds.*max.*length",
r"content_length_exceeded", r"max_tokens",
]),
(ErrorClass.RATE_LIMIT, [
r"rate.?limit", r"too.?many.?requests", r"quota.?exceeded",
r"throttl", r"retry.?after",
]),
(ErrorClass.AUTH, [
r"auth", r"api.?key", r"invalid.?key", r"unauthorized",
r"forbidden", r"permission", r"access.?denied",
]),
(ErrorClass.BILLING, [
r"billing", r"payment", r"insufficient.?funds", r"credits",
r"subscription", r"plan.?limit",
]),
(ErrorClass.FORMAT, [
r"invalid.?request", r"malformed", r"bad.?request",
r"validation.?error", r"schema",
]),
]
def classify_error(status_code: int, response_text: str) -> ErrorClass:
"""Classify an API error by status code and response body."""
lower = response_text.lower()
# Status-code shortcuts
if status_code == 429:
return ErrorClass.RATE_LIMIT
if status_code in (401, 403):
return ErrorClass.AUTH
if status_code == 408:
return ErrorClass.TIMEOUT
# Pattern matching on response body
for error_class, patterns in _PATTERNS:
for pat in patterns:
if re.search(pat, lower):
return error_class
return ErrorClass.UNKNOWN
_FRIENDLY = {
ErrorClass.RATE_LIMIT: (
"Rate limited by {provider}. Please wait a moment and try again."
),
ErrorClass.CONTEXT_OVERFLOW: (
"The conversation is too long for {provider}'s context window. "
"Try clearing older messages or switching to a model with a larger context."
),
ErrorClass.AUTH: (
"Authentication failed for {provider}. "
"Please check your API key in Settings."
),
ErrorClass.BILLING: (
"{provider} rejected the request due to a billing or quota issue. "
"Check your account balance or plan limits."
),
ErrorClass.TIMEOUT: (
"The request to {provider} timed out. The server may be overloaded — "
"try again shortly."
),
ErrorClass.FORMAT: (
"{provider} rejected the request format. "
"This may be a bug — please report it."
),
ErrorClass.UNKNOWN: (
"{provider} returned an error (HTTP {status}). "
"Details: {detail}"
),
}
def friendly_message(error_class: ErrorClass, provider: str,
status_code: int = 0,
response_text: str = "") -> str:
"""Return a user-friendly error message."""
template = _FRIENDLY.get(error_class, _FRIENDLY[ErrorClass.UNKNOWN])
detail = response_text[:200] if response_text else "no details"
return template.format(
provider=provider.capitalize(),
status=status_code,
detail=detail,
)