-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy patherrors.py
More file actions
198 lines (168 loc) · 6.4 KB
/
errors.py
File metadata and controls
198 lines (168 loc) · 6.4 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
from email.utils import parsedate_to_datetime
from time import time
from typing import Any, Literal, Optional, Union
from httpx import Response
from pydantic import BaseModel, ValidationError
from typing_extensions import override
from workflowai.core.domain import tool_call
ProviderErrorCode = Literal[
# Max number of tokens were exceeded in the prompt
"max_tokens_exceeded",
# The model failed to generate a response
"failed_generation",
# The model generated a response but it was not valid
"invalid_generation",
# The model returned an error that we currently do not handle
# The returned status code will match the provider status code and the entire
# provider response will be provided the error details.
#
# This error is intended as a fallback since we do not control what the providers
# return. We track this error on our end and the error should eventually
# be assigned a different status code
"unknown_provider_error",
# The provider returned a rate limit error
"rate_limit",
# The provider returned a server overloaded error
"server_overloaded",
# The requested provider does not support the model
"invalid_provider_config",
# The provider returned a 500
"provider_internal_error",
# The provider returned a 502 or 503
"provider_unavailable",
# The request timed out
"read_timeout",
# The requested model does not support the requested generation mode
# (e-g a model that does not support images generation was sent an image)
"model_does_not_support_mode",
# Invalid file provided
"invalid_file",
# The maximum number of tool call iterations was reached
"max_tool_call_iteration",
# The current configuration does not support structured generation
"structured_generation_error",
# The content was moderated
"content_moderation",
# Task banned
"task_banned",
# The request timed out
"timeout",
# Agent run failed
"agent_run_failed",
]
ErrorCode = Union[
ProviderErrorCode,
Literal[
# The object was not found
"object_not_found",
# There are no configured providers supporting the requested model
# This error will never happen when using WorkflowAI keys
"no_provider_supporting_model",
# The requested provider does not support the model
"provider_does_not_support_model",
# Run properties are invalid, for example the model does not exist
"invalid_run_properties",
# An internal error occurred
"internal_error",
# The request was invalid
"bad_request",
"invalid_file",
"connection_error",
],
str, # Using as a fallback to avoid validation error if an error code is added to the API
]
class BaseError(BaseModel):
details: Optional[dict[str, Any]] = None
message: str = "Unknown error"
status_code: Optional[int] = None
code: Optional[ErrorCode] = None
class ErrorResponse(BaseModel):
error: BaseError
id: Optional[str] = None
task_output: Optional[dict[str, Any]] = None
def _retry_after_to_delay_seconds(retry_after: Any) -> Optional[float]:
if retry_after is None:
return None
try:
return float(retry_after)
except ValueError:
pass
try:
retry_after_date = parsedate_to_datetime(retry_after)
current_time = time()
return retry_after_date.timestamp() - current_time
except (TypeError, ValueError, OverflowError):
return None
class WorkflowAIError(Exception):
def __init__(
self,
response: Optional[Response],
error: BaseError,
run_id: Optional[str] = None,
retry_after_delay_seconds: Optional[float] = None,
partial_output: Optional[dict[str, Any]] = None,
tool_call_requests: Optional[list["tool_call.ToolCallRequest"]] = None,
):
self.error = error
self.run_id = run_id
self.response = response
self._retry_after_delay_seconds = retry_after_delay_seconds
self.partial_output = partial_output
self.tool_call_requests = tool_call_requests
def __str__(self):
return f"WorkflowAIError : [{self.error.code}] ({self.error.status_code}): [{self.error.message}]"
@classmethod
def error_cls(cls, status_code: int, code: Optional[str] = None):
if status_code == 401:
return InvalidAPIKeyError
if code == "invalid_generation" or code == "failed_generation" or code == "agent_run_failed":
return InvalidGenerationError
return cls
@classmethod
def from_response(cls, response: Response, data: Union[bytes, str, None] = None):
try:
res = ErrorResponse.model_validate_json(data or response.content)
error_cls = cls.error_cls(response.status_code, res.error.code)
return error_cls(error=res.error, run_id=res.id, response=response, partial_output=res.task_output)
except ValidationError:
return cls.error_cls(response.status_code)(
error=BaseError(
message="Unknown error",
details={
"raw": str(data),
},
),
response=response,
)
@property
def retry_after_delay_seconds(self) -> Optional[float]:
if self._retry_after_delay_seconds:
return self._retry_after_delay_seconds
if self.response:
return _retry_after_to_delay_seconds(self.response.headers.get("Retry-After"))
return None
@property
def code(self) -> Optional[ErrorCode]:
return self.error.code
@property
def status_code(self) -> Optional[int]:
return self.error.status_code
@property
def message(self) -> str:
return self.error.message
@property
def details(self) -> Optional[dict[str, Any]]:
return self.error.details
class InvalidGenerationError(WorkflowAIError): ...
class MaxTurnsReachedError(WorkflowAIError): ...
class InvalidAPIKeyError(WorkflowAIError):
@property
@override
def message(self) -> str:
return (
"❌ Your API key is invalid. Please double-check your API key, "
"or create a new one at https://workflowai.com/organization/settings/api-keys "
"or from your self-hosted WorkflowAI instance."
)
def __str__(self) -> str:
return self.message