|
10 | 10 | import time |
11 | 11 | from typing import TYPE_CHECKING, Any |
12 | 12 |
|
13 | | -if TYPE_CHECKING: |
14 | | - from src.core.domain.client_termination import ClientTerminationReason |
15 | | - from src.core.domain.session_key import SessionKey |
16 | | - |
17 | | - |
18 | | -class LLMProxyError(Exception): |
| 13 | +if TYPE_CHECKING: |
| 14 | + from src.core.domain.client_termination import ClientTerminationReason |
| 15 | + from src.core.domain.session_key import SessionKey |
| 16 | + |
| 17 | + |
| 18 | +def _is_json_scalar(value: Any) -> bool: |
| 19 | + return isinstance(value, str | int | float | bool) or value is None |
| 20 | + |
| 21 | + |
| 22 | +def _to_json_safe_error_value(value: Any) -> Any: |
| 23 | + if _is_json_scalar(value): |
| 24 | + return value |
| 25 | + if isinstance(value, dict): |
| 26 | + return { |
| 27 | + str(key): _to_json_safe_error_value(nested_value) |
| 28 | + for key, nested_value in value.items() |
| 29 | + if isinstance(key, str | int | float | bool) |
| 30 | + } |
| 31 | + if isinstance(value, list | tuple | set): |
| 32 | + return [_to_json_safe_error_value(item) for item in value] |
| 33 | + |
| 34 | + model_dump = getattr(value, "model_dump", None) |
| 35 | + if callable(model_dump): |
| 36 | + return _to_json_safe_error_value(model_dump(mode="json")) |
| 37 | + |
| 38 | + return str(value) |
| 39 | + |
| 40 | + |
| 41 | +class LLMProxyError(Exception): |
19 | 42 | """Base exception class for all LLM proxy errors.""" |
20 | 43 |
|
21 | 44 | __resilience_context__: dict[str, Any] | None |
@@ -44,23 +67,25 @@ def __init__( |
44 | 67 | for key, value in (kwargs or {}).items(): |
45 | 68 | setattr(self, key, value) |
46 | 69 |
|
47 | | - def to_dict(self) -> dict[str, Any]: |
48 | | - error_dict: dict[str, Any] = { |
49 | | - "message": self.message, |
50 | | - "type": self.__class__.__name__, |
51 | | - "details": self.details, |
52 | | - } |
| 70 | + def to_dict(self) -> dict[str, Any]: |
| 71 | + error_dict: dict[str, Any] = { |
| 72 | + "message": self.message, |
| 73 | + "type": self.__class__.__name__, |
| 74 | + "details": _to_json_safe_error_value(self.details), |
| 75 | + } |
53 | 76 |
|
54 | 77 | # Include any additional attributes that were set via kwargs |
55 | 78 | for attr_name in dir(self): |
56 | 79 | if ( |
57 | | - not attr_name.startswith("_") |
58 | | - and attr_name not in ["message", "details", "status_code", "args"] |
59 | | - and not callable(getattr(self, attr_name)) |
60 | | - ): |
61 | | - error_dict[attr_name] = getattr(self, attr_name) |
62 | | - |
63 | | - return {"error": error_dict} |
| 80 | + not attr_name.startswith("_") |
| 81 | + and attr_name not in ["message", "details", "status_code", "args"] |
| 82 | + and not callable(getattr(self, attr_name)) |
| 83 | + ): |
| 84 | + error_dict[attr_name] = _to_json_safe_error_value( |
| 85 | + getattr(self, attr_name) |
| 86 | + ) |
| 87 | + |
| 88 | + return {"error": error_dict} |
64 | 89 |
|
65 | 90 |
|
66 | 91 | class AuthenticationError(LLMProxyError): |
|
0 commit comments