-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.py
More file actions
271 lines (218 loc) · 9.81 KB
/
Copy patherrors.py
File metadata and controls
271 lines (218 loc) · 9.81 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
# Spec: realizes llm-provider §7 (canonical error categories).
"""Errors raised by an llm-provider implementation.
A provider call (``ready()`` or ``complete()``) MAY raise one of the
canonical category errors documented below. Each error class carries
a ``category`` class attribute matching the canonical string
identifier so callers can dispatch on the category without matching
exception types directly.
This module is also the single source of truth for the canonical
category strings; :data:`TRANSIENT_CATEGORIES` lives here, and
``openarmature.graph.middleware.retry``'s default classifier imports
it.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from openarmature.llm.response import Usage
# ---------------------------------------------------------------------------
# Canonical category strings (llm-provider spec §7)
# ---------------------------------------------------------------------------
PROVIDER_AUTHENTICATION = "provider_authentication"
PROVIDER_UNAVAILABLE = "provider_unavailable"
PROVIDER_INVALID_MODEL = "provider_invalid_model"
PROVIDER_MODEL_NOT_LOADED = "provider_model_not_loaded"
PROVIDER_RATE_LIMIT = "provider_rate_limit"
PROVIDER_INVALID_RESPONSE = "provider_invalid_response"
PROVIDER_INVALID_REQUEST = "provider_invalid_request"
PROVIDER_UNSUPPORTED_CONTENT_BLOCK = "provider_unsupported_content_block"
STRUCTURED_OUTPUT_INVALID = "structured_output_invalid"
# Per spec §7 "Retry classification": these three categories are
# *transient* — a retry MAY succeed. The other categories
# (`provider_authentication`, `provider_invalid_model`,
# `provider_invalid_request`, `provider_invalid_response`,
# `structured_output_invalid`) are non-transient and MUST NOT be
# retried by the default classifier.
#
# ``structured_output_invalid`` is explicitly non-transient by default
# per §7: a model that fails schema compliance on a given prompt usually
# fails the same way on retry. Users wanting retry-on-validation-failure
# semantics MAY include it in a custom classifier's transient set.
#
# Note: ``finish_reason: "error"`` is also transient per spec §7, but
# that's a Response-level signal rather than an exception category, so
# it isn't part of this set — the default retry middleware operates on
# raised exceptions.
TRANSIENT_CATEGORIES: frozenset[str] = frozenset(
{
PROVIDER_RATE_LIMIT,
PROVIDER_UNAVAILABLE,
PROVIDER_MODEL_NOT_LOADED,
}
)
# ---------------------------------------------------------------------------
# Exception classes
# ---------------------------------------------------------------------------
class LlmProviderError(Exception):
"""Base for all llm-provider errors. Each subclass carries a
``category`` class attribute matching one of the canonical
category strings above.
Provider-originated errors SHOULD preserve the underlying provider
exception as ``__cause__`` so callers can reach the wire-level
detail when needed.
"""
category: str
class ProviderAuthentication(LlmProviderError):
"""Auth failed: invalid key, expired token, missing credentials."""
category = PROVIDER_AUTHENTICATION
class ProviderUnavailable(LlmProviderError):
"""Provider is unreachable: network failure, 5xx error, DNS, timeout."""
category = PROVIDER_UNAVAILABLE
class ProviderInvalidModel(LlmProviderError):
"""The bound model does not exist on this provider. Terminal:
retry will not succeed without changing the bound model."""
category = PROVIDER_INVALID_MODEL
class ProviderModelNotLoaded(LlmProviderError):
"""The bound model is known to the provider but is not currently
serving (e.g., a local vLLM/LM Studio/llama.cpp server has the
model configured but not loaded). Distinct from
``provider_invalid_model`` because retry MAY succeed once loading
completes."""
category = PROVIDER_MODEL_NOT_LOADED
class ProviderRateLimit(LlmProviderError):
"""Provider returned a rate-limit response (HTTP 429 or equivalent).
When the provider supplies a ``Retry-After`` header (or its
equivalent), the parsed seconds-to-wait surfaces on
:attr:`retry_after`. ``None`` if the provider didn't include one.
"""
category = PROVIDER_RATE_LIMIT
retry_after: float | None
def __init__(self, *args: Any, retry_after: float | None = None) -> None:
super().__init__(*args)
self.retry_after = retry_after
class ProviderInvalidResponse(LlmProviderError):
"""Provider returned a malformed response that cannot be parsed
into the expected :class:`Response` shape (missing required
fields, invalid tool_calls structure, invalid JSON)."""
category = PROVIDER_INVALID_RESPONSE
class ProviderInvalidRequest(LlmProviderError):
"""The request was malformed before sending (per-role message
constraints violated, ``tool_call_id`` does not match an earlier
assistant tool call, duplicate tool names, etc.). Raised by the
implementation's pre-send validation, not by the provider."""
category = PROVIDER_INVALID_REQUEST
# Non-transient by default — the bound model's capability set does
# not change between calls, so retrying without changing the request
# (the message list, the bound model, or the provider) will not
# succeed.
#
# Distinct from ProviderInvalidRequest. ProviderInvalidRequest covers
# spec-shape violations (the request is malformed at the wire layer);
# ProviderUnsupportedContentBlock covers capability mismatches (the
# request is well-formed but the bound model can't fulfill it).
# Splitting them lets callers route the unsupported-content case
# differently (e.g., fall back to a multimodal-capable provider)
# without overloading the malformed-request category.
class ProviderUnsupportedContentBlock(LlmProviderError):
"""Raised when the bound model does not support a content block
type used in the request.
Examples: a text-only model received an image block, or the model
supports images but not the requested ``media_type`` or ``source``
variant.
Attributes:
block_type: The block type that was rejected (e.g., ``"image"``),
when the provider's response makes this identifiable.
reason: The provider's human-readable description of the
rejection, when available.
"""
category = PROVIDER_UNSUPPORTED_CONTENT_BLOCK
block_type: str | None
reason: str | None
def __init__(
self,
*args: Any,
block_type: str | None = None,
reason: str | None = None,
) -> None:
super().__init__(*args)
self.block_type = block_type
self.reason = reason
# Non-transient by default — a model that fails schema compliance on a
# given prompt usually fails the same way on retry. The default
# RetryMiddleware classifier does NOT retry this category. Users wanting
# retry-on-validation-failure semantics MAY include the category in a
# custom classifier's transient set.
#
# Distinct from ProviderInvalidResponse, which covers wire-shape
# malformation. StructuredOutputInvalid is raised when the wire envelope
# is fine but the content does not validate against the caller's schema.
class StructuredOutputInvalid(LlmProviderError):
"""Raised when a ``complete()`` call requested a ``response_schema``
and the provider's content could not be parsed as JSON or did not
validate against the schema.
Attributes:
response_schema: The JSON Schema requested.
raw_content: The raw response content the model produced.
failure_description: A description of the parse or validation
failure.
finish_reason: The normalized finish reason of the response that
failed validation (``"length"`` signals a truncation, the key
retry signal; ``"stop"`` a clean-finish schema/parse failure).
usage: Token usage of the response that failed validation, for
cost attribution and truncation corroboration.
response_id: The provider's response identifier, when present.
response_model: The model identifier the provider reported, when
present.
The failure is a downstream parse/validation step on an intact wire
response, so the response-side context genuinely exists. It is
attached at the parse/validate call site (which holds the parsed
Response); the four fields default to ``None`` until enriched there.
"""
category = STRUCTURED_OUTPUT_INVALID
response_schema: dict[str, Any]
raw_content: str
failure_description: str
finish_reason: str | None
usage: Usage | None
response_id: str | None
response_model: str | None
def __init__(
self,
*args: Any,
response_schema: dict[str, Any],
raw_content: str,
failure_description: str,
finish_reason: str | None = None,
usage: Usage | None = None,
response_id: str | None = None,
response_model: str | None = None,
) -> None:
super().__init__(*args)
self.response_schema = response_schema
self.raw_content = raw_content
self.failure_description = failure_description
self.finish_reason = finish_reason
self.usage = usage
self.response_id = response_id
self.response_model = response_model
__all__ = [
"PROVIDER_AUTHENTICATION",
"PROVIDER_INVALID_MODEL",
"PROVIDER_INVALID_REQUEST",
"PROVIDER_INVALID_RESPONSE",
"PROVIDER_MODEL_NOT_LOADED",
"PROVIDER_RATE_LIMIT",
"PROVIDER_UNAVAILABLE",
"PROVIDER_UNSUPPORTED_CONTENT_BLOCK",
"STRUCTURED_OUTPUT_INVALID",
"TRANSIENT_CATEGORIES",
"LlmProviderError",
"ProviderAuthentication",
"ProviderInvalidModel",
"ProviderInvalidRequest",
"ProviderInvalidResponse",
"ProviderModelNotLoaded",
"ProviderRateLimit",
"ProviderUnavailable",
"ProviderUnsupportedContentBlock",
"StructuredOutputInvalid",
]