agentscope-runtime 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentscope_runtime/adapters/agentscope/message.py +32 -7
- agentscope_runtime/adapters/agentscope/stream.py +121 -91
- agentscope_runtime/adapters/agno/__init__.py +0 -0
- agentscope_runtime/adapters/agno/message.py +30 -0
- agentscope_runtime/adapters/agno/stream.py +122 -0
- agentscope_runtime/adapters/langgraph/__init__.py +12 -0
- agentscope_runtime/adapters/langgraph/message.py +257 -0
- agentscope_runtime/adapters/langgraph/stream.py +205 -0
- agentscope_runtime/cli/__init__.py +7 -0
- agentscope_runtime/cli/cli.py +63 -0
- agentscope_runtime/cli/commands/__init__.py +2 -0
- agentscope_runtime/cli/commands/chat.py +815 -0
- agentscope_runtime/cli/commands/deploy.py +1074 -0
- agentscope_runtime/cli/commands/invoke.py +58 -0
- agentscope_runtime/cli/commands/list_cmd.py +103 -0
- agentscope_runtime/cli/commands/run.py +176 -0
- agentscope_runtime/cli/commands/sandbox.py +128 -0
- agentscope_runtime/cli/commands/status.py +60 -0
- agentscope_runtime/cli/commands/stop.py +185 -0
- agentscope_runtime/cli/commands/web.py +166 -0
- agentscope_runtime/cli/loaders/__init__.py +6 -0
- agentscope_runtime/cli/loaders/agent_loader.py +295 -0
- agentscope_runtime/cli/state/__init__.py +10 -0
- agentscope_runtime/cli/utils/__init__.py +18 -0
- agentscope_runtime/cli/utils/console.py +378 -0
- agentscope_runtime/cli/utils/validators.py +118 -0
- agentscope_runtime/common/collections/redis_mapping.py +4 -1
- agentscope_runtime/engine/app/agent_app.py +55 -9
- agentscope_runtime/engine/deployers/__init__.py +1 -0
- agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +56 -1
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +449 -41
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +273 -0
- agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +640 -0
- agentscope_runtime/engine/deployers/agentrun_deployer.py +152 -22
- agentscope_runtime/engine/deployers/base.py +27 -2
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +161 -31
- agentscope_runtime/engine/deployers/local_deployer.py +188 -25
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +109 -18
- agentscope_runtime/engine/deployers/state/__init__.py +9 -0
- agentscope_runtime/engine/deployers/state/manager.py +388 -0
- agentscope_runtime/engine/deployers/state/schema.py +96 -0
- agentscope_runtime/engine/deployers/utils/build_cache.py +736 -0
- agentscope_runtime/engine/deployers/utils/detached_app.py +105 -30
- agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +31 -10
- agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +23 -10
- agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +35 -2
- agentscope_runtime/engine/deployers/utils/k8s_utils.py +241 -0
- agentscope_runtime/engine/deployers/utils/net_utils.py +65 -0
- agentscope_runtime/engine/deployers/utils/package.py +56 -6
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +16 -2
- agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +155 -5
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +107 -123
- agentscope_runtime/engine/runner.py +30 -9
- agentscope_runtime/engine/schemas/exception.py +604 -0
- agentscope_runtime/engine/services/agent_state/redis_state_service.py +61 -8
- agentscope_runtime/engine/services/agent_state/state_service_factory.py +2 -5
- agentscope_runtime/engine/services/memory/redis_memory_service.py +129 -25
- agentscope_runtime/engine/services/session_history/redis_session_history_service.py +160 -34
- agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +113 -39
- agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +20 -4
- agentscope_runtime/sandbox/build.py +50 -57
- agentscope_runtime/sandbox/utils.py +2 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/METADATA +31 -8
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/RECORD +69 -36
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/entry_points.txt +1 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/WHEEL +0 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Business System Exception Definitions
|
|
4
|
+
Provides a three-level exception structure:
|
|
5
|
+
Base Class -> HTTP Status Exceptions -> Business Exceptions
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AppBaseException(Exception):
|
|
12
|
+
"""
|
|
13
|
+
Business exception base class
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
status: HTTP status code, aligned with standard HTTP status codes
|
|
17
|
+
code: Business error code, used for business logic distinction
|
|
18
|
+
message: Error message
|
|
19
|
+
details: Additional error details
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
status: int,
|
|
25
|
+
code: str,
|
|
26
|
+
message: str,
|
|
27
|
+
details: Optional[Dict[str, Any]] = None,
|
|
28
|
+
):
|
|
29
|
+
"""
|
|
30
|
+
Initialize the exception
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
status: HTTP status code
|
|
34
|
+
code: Business error code
|
|
35
|
+
message: Error message
|
|
36
|
+
details: Additional details
|
|
37
|
+
"""
|
|
38
|
+
super().__init__(message)
|
|
39
|
+
self.status = status
|
|
40
|
+
self.code = code
|
|
41
|
+
self.message = message
|
|
42
|
+
self.details = details or {}
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
"""String representation"""
|
|
46
|
+
return f"[{self.status}] {self.code}: {self.message}"
|
|
47
|
+
|
|
48
|
+
def __repr__(self) -> str:
|
|
49
|
+
"""Detailed representation"""
|
|
50
|
+
return (
|
|
51
|
+
f"{self.__class__.__name__}(status={self.status}, code="
|
|
52
|
+
f"'{self.code}', message='{self.message}')"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
56
|
+
"""Convert to a dictionary"""
|
|
57
|
+
return {
|
|
58
|
+
"status": self.status,
|
|
59
|
+
"code": self.code,
|
|
60
|
+
"message": self.message,
|
|
61
|
+
"details": self.details,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ==================== HTTP Status Exceptions ====================
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class BadRequestException(AppBaseException):
|
|
69
|
+
"""400 Bad Request - Client request error"""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
code: str,
|
|
74
|
+
message: str,
|
|
75
|
+
details: Optional[Dict[str, Any]] = None,
|
|
76
|
+
):
|
|
77
|
+
super().__init__(400, code, message, details)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class UnauthorizedException(AppBaseException):
|
|
81
|
+
"""401 Unauthorized"""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
code: str,
|
|
86
|
+
message: str,
|
|
87
|
+
details: Optional[Dict[str, Any]] = None,
|
|
88
|
+
):
|
|
89
|
+
super().__init__(401, code, message, details)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ForbiddenException(AppBaseException):
|
|
93
|
+
"""403 Forbidden - Access denied"""
|
|
94
|
+
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
code: str,
|
|
98
|
+
message: str,
|
|
99
|
+
details: Optional[Dict[str, Any]] = None,
|
|
100
|
+
):
|
|
101
|
+
super().__init__(403, code, message, details)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class NotFoundException(AppBaseException):
|
|
105
|
+
"""404 Not Found - Resource does not exist"""
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
code: str,
|
|
110
|
+
message: str,
|
|
111
|
+
details: Optional[Dict[str, Any]] = None,
|
|
112
|
+
):
|
|
113
|
+
super().__init__(404, code, message, details)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class MethodNotAllowedException(AppBaseException):
|
|
117
|
+
"""405 Method Not Allowed"""
|
|
118
|
+
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
code: str,
|
|
122
|
+
message: str,
|
|
123
|
+
details: Optional[Dict[str, Any]] = None,
|
|
124
|
+
):
|
|
125
|
+
super().__init__(405, code, message, details)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class ConflictException(AppBaseException):
|
|
129
|
+
"""409 Conflict"""
|
|
130
|
+
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
code: str,
|
|
134
|
+
message: str,
|
|
135
|
+
details: Optional[Dict[str, Any]] = None,
|
|
136
|
+
):
|
|
137
|
+
super().__init__(409, code, message, details)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class UnprocessableEntityException(AppBaseException):
|
|
141
|
+
"""422 Unprocessable Entity"""
|
|
142
|
+
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
code: str,
|
|
146
|
+
message: str,
|
|
147
|
+
details: Optional[Dict[str, Any]] = None,
|
|
148
|
+
):
|
|
149
|
+
super().__init__(422, code, message, details)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class TooManyRequestsException(AppBaseException):
|
|
153
|
+
"""429 Too Many Requests"""
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
code: str,
|
|
158
|
+
message: str,
|
|
159
|
+
details: Optional[Dict[str, Any]] = None,
|
|
160
|
+
):
|
|
161
|
+
super().__init__(429, code, message, details)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class InternalServerErrorException(AppBaseException):
|
|
165
|
+
"""500 Internal Server Error"""
|
|
166
|
+
|
|
167
|
+
def __init__(
|
|
168
|
+
self,
|
|
169
|
+
code: str,
|
|
170
|
+
message: str,
|
|
171
|
+
details: Optional[Dict[str, Any]] = None,
|
|
172
|
+
):
|
|
173
|
+
super().__init__(500, code, message, details)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class BadGatewayException(AppBaseException):
|
|
177
|
+
"""502 Bad Gateway"""
|
|
178
|
+
|
|
179
|
+
def __init__(
|
|
180
|
+
self,
|
|
181
|
+
code: str,
|
|
182
|
+
message: str,
|
|
183
|
+
details: Optional[Dict[str, Any]] = None,
|
|
184
|
+
):
|
|
185
|
+
super().__init__(502, code, message, details)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class ServiceUnavailableException(AppBaseException):
|
|
189
|
+
"""503 Service Unavailable"""
|
|
190
|
+
|
|
191
|
+
def __init__(
|
|
192
|
+
self,
|
|
193
|
+
code: str,
|
|
194
|
+
message: str,
|
|
195
|
+
details: Optional[Dict[str, Any]] = None,
|
|
196
|
+
):
|
|
197
|
+
super().__init__(503, code, message, details)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class GatewayTimeoutException(AppBaseException):
|
|
201
|
+
"""504 Gateway Timeout"""
|
|
202
|
+
|
|
203
|
+
def __init__(
|
|
204
|
+
self,
|
|
205
|
+
code: str,
|
|
206
|
+
message: str,
|
|
207
|
+
details: Optional[Dict[str, Any]] = None,
|
|
208
|
+
):
|
|
209
|
+
super().__init__(504, code, message, details)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# ==================== Business Exceptions ====================
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# Authentication-related exceptions
|
|
216
|
+
class AuthenticationException(UnauthorizedException):
|
|
217
|
+
"""Authentication failed"""
|
|
218
|
+
|
|
219
|
+
def __init__(
|
|
220
|
+
self,
|
|
221
|
+
message: str = "Authentication failed",
|
|
222
|
+
details: Optional[Dict[str, Any]] = None,
|
|
223
|
+
):
|
|
224
|
+
super().__init__("AUTH_FAILED", message, details)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class TokenExpiredException(UnauthorizedException):
|
|
228
|
+
"""Token expired"""
|
|
229
|
+
|
|
230
|
+
def __init__(
|
|
231
|
+
self,
|
|
232
|
+
message: str = "Token has expired",
|
|
233
|
+
details: Optional[Dict[str, Any]] = None,
|
|
234
|
+
):
|
|
235
|
+
super().__init__("TOKEN_EXPIRED", message, details)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class InvalidTokenException(UnauthorizedException):
|
|
239
|
+
"""Invalid token"""
|
|
240
|
+
|
|
241
|
+
def __init__(
|
|
242
|
+
self,
|
|
243
|
+
message: str = "Invalid token",
|
|
244
|
+
details: Optional[Dict[str, Any]] = None,
|
|
245
|
+
):
|
|
246
|
+
super().__init__("INVALID_TOKEN", message, details)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# Permission-related exceptions
|
|
250
|
+
class PermissionDeniedException(ForbiddenException):
|
|
251
|
+
"""Permission denied"""
|
|
252
|
+
|
|
253
|
+
def __init__(
|
|
254
|
+
self,
|
|
255
|
+
message: str = "Permission denied",
|
|
256
|
+
details: Optional[Dict[str, Any]] = None,
|
|
257
|
+
):
|
|
258
|
+
super().__init__("PERMISSION_DENIED", message, details)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class AccessDeniedException(ForbiddenException):
|
|
262
|
+
"""Access denied"""
|
|
263
|
+
|
|
264
|
+
def __init__(
|
|
265
|
+
self,
|
|
266
|
+
message: str = "Access denied",
|
|
267
|
+
details: Optional[Dict[str, Any]] = None,
|
|
268
|
+
):
|
|
269
|
+
super().__init__("ACCESS_DENIED", message, details)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# Resource-related exceptions
|
|
273
|
+
class ResourceNotFoundException(NotFoundException):
|
|
274
|
+
"""Resource not found"""
|
|
275
|
+
|
|
276
|
+
def __init__(
|
|
277
|
+
self,
|
|
278
|
+
resource_type: str,
|
|
279
|
+
resource_id: str,
|
|
280
|
+
details: Optional[Dict[str, Any]] = None,
|
|
281
|
+
):
|
|
282
|
+
message = f"{resource_type} not found: {resource_id}"
|
|
283
|
+
super().__init__("RESOURCE_NOT_FOUND", message, details)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class UserNotFoundException(NotFoundException):
|
|
287
|
+
"""User not found"""
|
|
288
|
+
|
|
289
|
+
def __init__(self, user_id: str, details: Optional[Dict[str, Any]] = None):
|
|
290
|
+
message = f"User not found: {user_id}"
|
|
291
|
+
super().__init__("USER_NOT_FOUND", message, details)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class TaskNotFoundException(NotFoundException):
|
|
295
|
+
"""Task not found"""
|
|
296
|
+
|
|
297
|
+
def __init__(self, task_id: str, details: Optional[Dict[str, Any]] = None):
|
|
298
|
+
message = f"Task not found: {task_id}"
|
|
299
|
+
super().__init__("TASK_NOT_FOUND", message, details)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
# Parameter-related exceptions
|
|
303
|
+
class InvalidParameterException(BadRequestException):
|
|
304
|
+
"""Invalid parameter"""
|
|
305
|
+
|
|
306
|
+
def __init__(
|
|
307
|
+
self,
|
|
308
|
+
parameter: str,
|
|
309
|
+
message: str = None,
|
|
310
|
+
details: Optional[Dict[str, Any]] = None,
|
|
311
|
+
):
|
|
312
|
+
if message is None:
|
|
313
|
+
message = f"Invalid parameter: {parameter}"
|
|
314
|
+
super().__init__("INVALID_PARAMETER", message, details)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class MissingParameterException(BadRequestException):
|
|
318
|
+
"""Missing parameter"""
|
|
319
|
+
|
|
320
|
+
def __init__(
|
|
321
|
+
self,
|
|
322
|
+
parameter: str,
|
|
323
|
+
details: Optional[Dict[str, Any]] = None,
|
|
324
|
+
):
|
|
325
|
+
message = f"Missing required parameter: {parameter}"
|
|
326
|
+
super().__init__("MISSING_PARAMETER", message, details)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class ParameterValidationException(BadRequestException):
|
|
330
|
+
"""Parameter validation error"""
|
|
331
|
+
|
|
332
|
+
def __init__(
|
|
333
|
+
self,
|
|
334
|
+
parameter: str,
|
|
335
|
+
validation_error: str,
|
|
336
|
+
details: Optional[Dict[str, Any]] = None,
|
|
337
|
+
):
|
|
338
|
+
message = (
|
|
339
|
+
f"Parameter validation failed: {parameter} - {validation_error}"
|
|
340
|
+
)
|
|
341
|
+
super().__init__("PARAMETER_VALIDATION", message, details)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# Rate limit-related exceptions
|
|
345
|
+
class RateLimitExceededException(TooManyRequestsException):
|
|
346
|
+
"""Rate limit exceeded"""
|
|
347
|
+
|
|
348
|
+
def __init__(
|
|
349
|
+
self,
|
|
350
|
+
operation: str,
|
|
351
|
+
retry_after: int = 60,
|
|
352
|
+
details: Optional[Dict[str, Any]] = None,
|
|
353
|
+
):
|
|
354
|
+
message = (
|
|
355
|
+
f"Operation {operation} is too frequent, please retry "
|
|
356
|
+
f"after {retry_after} seconds"
|
|
357
|
+
)
|
|
358
|
+
super().__init__("RATE_LIMIT_EXCEEDED", message, details)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
# Business logic exceptions
|
|
362
|
+
class BusinessLogicException(UnprocessableEntityException):
|
|
363
|
+
"""Business logic exception"""
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class WorkflowException(BusinessLogicException):
|
|
367
|
+
"""Workflow error"""
|
|
368
|
+
|
|
369
|
+
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
370
|
+
super().__init__("WORKFLOW_ERROR", message, details)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class AgentException(BusinessLogicException):
|
|
374
|
+
"""Agent error"""
|
|
375
|
+
|
|
376
|
+
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
377
|
+
super().__init__("AGENT_ERROR", message, details)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class ResponseException(BusinessLogicException):
|
|
381
|
+
"""Response error"""
|
|
382
|
+
|
|
383
|
+
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
384
|
+
super().__init__("RESPONSE_ERROR", message, details)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# System-related exceptions
|
|
388
|
+
class SystemException(InternalServerErrorException):
|
|
389
|
+
"""System exception"""
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class DatabaseException(SystemException):
|
|
393
|
+
"""Database error"""
|
|
394
|
+
|
|
395
|
+
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
396
|
+
super().__init__("DATABASE_ERROR", message, details)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class RedisException(SystemException):
|
|
400
|
+
"""Redis error"""
|
|
401
|
+
|
|
402
|
+
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
403
|
+
super().__init__("REDIS_ERROR", message, details)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class ExternalServiceException(SystemException):
|
|
407
|
+
"""External service error"""
|
|
408
|
+
|
|
409
|
+
def __init__(
|
|
410
|
+
self,
|
|
411
|
+
service_name: str,
|
|
412
|
+
message: str,
|
|
413
|
+
details: Optional[Dict[str, Any]] = None,
|
|
414
|
+
):
|
|
415
|
+
full_message = f"External service {service_name} error: {message}"
|
|
416
|
+
super().__init__("EXTERNAL_SERVICE_ERROR", full_message, details)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# Configuration-related exceptions
|
|
420
|
+
class ConfigurationException(InternalServerErrorException):
|
|
421
|
+
"""Configuration error"""
|
|
422
|
+
|
|
423
|
+
def __init__(
|
|
424
|
+
self,
|
|
425
|
+
config_key: str,
|
|
426
|
+
message: str = None,
|
|
427
|
+
details: Optional[Dict[str, Any]] = None,
|
|
428
|
+
):
|
|
429
|
+
if message is None:
|
|
430
|
+
message = f"Configuration error: {config_key}"
|
|
431
|
+
super().__init__("CONFIGURATION_ERROR", message, details)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
# Network-related exceptions
|
|
435
|
+
class NetworkException(ServiceUnavailableException):
|
|
436
|
+
"""Network error"""
|
|
437
|
+
|
|
438
|
+
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
439
|
+
super().__init__("NETWORK_ERROR", message, details)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class TimeoutException(GatewayTimeoutException):
|
|
443
|
+
"""Timeout error"""
|
|
444
|
+
|
|
445
|
+
def __init__(
|
|
446
|
+
self,
|
|
447
|
+
operation: str,
|
|
448
|
+
timeout: int,
|
|
449
|
+
details: Optional[Dict[str, Any]] = None,
|
|
450
|
+
):
|
|
451
|
+
message = (
|
|
452
|
+
f"Operation {operation} timed out, timeout limit:"
|
|
453
|
+
f" {timeout} seconds"
|
|
454
|
+
)
|
|
455
|
+
super().__init__("TIMEOUT", message, details)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
# ==================== Agent runtime related Exceptions ====================
|
|
459
|
+
class AgentRuntimeErrorException(BusinessLogicException):
|
|
460
|
+
"""
|
|
461
|
+
Base class for agent runtime error exceptions (HTTP 422 - Unprocessable
|
|
462
|
+
Entity)
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
class ToolExecutionException(AgentRuntimeErrorException):
|
|
467
|
+
"""Error occurred during tool execution"""
|
|
468
|
+
|
|
469
|
+
def __init__(self, details: Optional[Dict[str, Any]] = None):
|
|
470
|
+
super().__init__(
|
|
471
|
+
"TOOL_EXECUTION_FAILED",
|
|
472
|
+
"Error occurred during tool execution",
|
|
473
|
+
details,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
class ToolNotFoundException(AgentRuntimeErrorException):
|
|
478
|
+
"""Specified tool not found"""
|
|
479
|
+
|
|
480
|
+
def __init__(
|
|
481
|
+
self,
|
|
482
|
+
tool_name: str,
|
|
483
|
+
details: Optional[Dict[str, Any]] = None,
|
|
484
|
+
):
|
|
485
|
+
message = f"Tool not found: {tool_name}"
|
|
486
|
+
super().__init__("TOOL_NOT_FOUND", message, details)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
class MCPConnectionException(AgentRuntimeErrorException):
|
|
490
|
+
"""Failed to connect to MCP service"""
|
|
491
|
+
|
|
492
|
+
def __init__(self, details: Optional[Dict[str, Any]] = None):
|
|
493
|
+
super().__init__(
|
|
494
|
+
"MCP_CONNECTION_FAILED",
|
|
495
|
+
"Failed to connect to MCP service",
|
|
496
|
+
details,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
class MCPProtocolException(AgentRuntimeErrorException):
|
|
501
|
+
"""Invalid MCP protocol message format"""
|
|
502
|
+
|
|
503
|
+
def __init__(self, details: Optional[Dict[str, Any]] = None):
|
|
504
|
+
super().__init__(
|
|
505
|
+
"MCP_PROTOCOL_ERROR",
|
|
506
|
+
"Invalid MCP protocol message format",
|
|
507
|
+
details,
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
class ModelExecutionException(AgentRuntimeErrorException):
|
|
512
|
+
"""Error occurred during model execution"""
|
|
513
|
+
|
|
514
|
+
def __init__(
|
|
515
|
+
self,
|
|
516
|
+
model_name: str,
|
|
517
|
+
details: Optional[Dict[str, Any]] = None,
|
|
518
|
+
):
|
|
519
|
+
message = f"Error occurred during execution of model: {model_name}"
|
|
520
|
+
super().__init__("MODEL_EXECUTION_FAILED", message, details)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
class ModelTimeoutException(AgentRuntimeErrorException):
|
|
524
|
+
"""Model inference timed out"""
|
|
525
|
+
|
|
526
|
+
def __init__(
|
|
527
|
+
self,
|
|
528
|
+
model_name: str,
|
|
529
|
+
timeout: int,
|
|
530
|
+
details: Optional[Dict[str, Any]] = None,
|
|
531
|
+
):
|
|
532
|
+
message = (
|
|
533
|
+
f"Model inference timed out: {model_name}, timeout limit:"
|
|
534
|
+
f" {timeout} seconds"
|
|
535
|
+
)
|
|
536
|
+
super().__init__("MODEL_TIMEOUT", message, details)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
class ModelNotFoundException(AgentRuntimeErrorException):
|
|
540
|
+
"""Specified model not found"""
|
|
541
|
+
|
|
542
|
+
def __init__(
|
|
543
|
+
self,
|
|
544
|
+
model_name: str,
|
|
545
|
+
details: Optional[Dict[str, Any]] = None,
|
|
546
|
+
):
|
|
547
|
+
message = f"Model not found: {model_name}"
|
|
548
|
+
super().__init__("MODEL_NOT_FOUND", message, details)
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
class UnauthorizedModelAccessException(AgentRuntimeErrorException):
|
|
552
|
+
"""Unauthorized access to model"""
|
|
553
|
+
|
|
554
|
+
def __init__(
|
|
555
|
+
self,
|
|
556
|
+
model_name: str,
|
|
557
|
+
details: Optional[Dict[str, Any]] = None,
|
|
558
|
+
):
|
|
559
|
+
message = f"Unauthorized access to model: {model_name}"
|
|
560
|
+
super().__init__("MODEL_UNAUTHORIZED_ACCESS", message, details)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
class UnknownAgentException(AgentRuntimeErrorException):
|
|
564
|
+
"""Generic agent error with no specific classification"""
|
|
565
|
+
|
|
566
|
+
def __init__(
|
|
567
|
+
self,
|
|
568
|
+
original_exception: Optional[Exception] = None,
|
|
569
|
+
details: Optional[Dict[str, Any]] = None,
|
|
570
|
+
):
|
|
571
|
+
message = "Unknown agent error" + (
|
|
572
|
+
f": {type(original_exception).__name__}: {str(original_exception)}"
|
|
573
|
+
if original_exception is not None
|
|
574
|
+
else ""
|
|
575
|
+
)
|
|
576
|
+
super().__init__(
|
|
577
|
+
"AGENT_UNKNOWN_ERROR",
|
|
578
|
+
message,
|
|
579
|
+
details,
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
class ModelQuotaExceededException(AgentRuntimeErrorException):
|
|
584
|
+
"""Model quota exceeded"""
|
|
585
|
+
|
|
586
|
+
def __init__(
|
|
587
|
+
self,
|
|
588
|
+
model_name: str,
|
|
589
|
+
details: Optional[Dict[str, Any]] = None,
|
|
590
|
+
):
|
|
591
|
+
message = f"Model quota exceeded: {model_name}"
|
|
592
|
+
super().__init__("MODEL_QUOTA_EXCEEDED", message, details)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
class ModelContextLengthExceededException(AgentRuntimeErrorException):
|
|
596
|
+
"""Model context length exceeded"""
|
|
597
|
+
|
|
598
|
+
def __init__(
|
|
599
|
+
self,
|
|
600
|
+
model_name: str,
|
|
601
|
+
details: Optional[Dict[str, Any]] = None,
|
|
602
|
+
):
|
|
603
|
+
message = f"Model context length exceeded: {model_name}"
|
|
604
|
+
super().__init__("MODEL_CONTEXT_LENGTH_EXCEEDED", message, details)
|
|
@@ -21,29 +21,68 @@ class RedisStateService(StateService):
|
|
|
21
21
|
self,
|
|
22
22
|
redis_url: str = "redis://localhost:6379/0",
|
|
23
23
|
redis_client: Optional[aioredis.Redis] = None,
|
|
24
|
+
socket_timeout: Optional[float] = 5.0,
|
|
25
|
+
socket_connect_timeout: Optional[float] = 5.0,
|
|
26
|
+
max_connections: Optional[int] = 50,
|
|
27
|
+
retry_on_timeout: bool = True,
|
|
28
|
+
ttl_seconds: Optional[int] = 3600, # 1 hour in seconds
|
|
29
|
+
health_check_interval: Optional[float] = 30.0,
|
|
30
|
+
socket_keepalive: bool = True,
|
|
24
31
|
):
|
|
32
|
+
"""
|
|
33
|
+
Initialize RedisStateService.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
redis_url: Redis connection URL
|
|
37
|
+
redis_client: Optional pre-configured Redis client
|
|
38
|
+
socket_timeout: Socket timeout in seconds (default: 5.0)
|
|
39
|
+
socket_connect_timeout: Socket connect timeout in seconds
|
|
40
|
+
(default: 5.0)
|
|
41
|
+
max_connections: Maximum number of connections in the pool
|
|
42
|
+
(default: 50)
|
|
43
|
+
retry_on_timeout: Whether to retry on timeout (default: True)
|
|
44
|
+
ttl_seconds: Time-to-live in seconds for state data. If None,
|
|
45
|
+
data never expires (default: 3600, i.e., 1 hour)
|
|
46
|
+
health_check_interval: Interval in seconds for health checks on
|
|
47
|
+
idle connections (default: 30.0).
|
|
48
|
+
Connections idle longer than this will be checked before reuse.
|
|
49
|
+
Set to 0 to disable.
|
|
50
|
+
socket_keepalive: Enable TCP keepalive to prevent
|
|
51
|
+
silent disconnections (default: True)
|
|
52
|
+
"""
|
|
25
53
|
self._redis_url = redis_url
|
|
26
54
|
self._redis = redis_client
|
|
27
|
-
self.
|
|
55
|
+
self._socket_timeout = socket_timeout
|
|
56
|
+
self._socket_connect_timeout = socket_connect_timeout
|
|
57
|
+
self._max_connections = max_connections
|
|
58
|
+
self._retry_on_timeout = retry_on_timeout
|
|
59
|
+
self._ttl_seconds = ttl_seconds
|
|
60
|
+
self._health_check_interval = health_check_interval
|
|
61
|
+
self._socket_keepalive = socket_keepalive
|
|
28
62
|
|
|
29
63
|
async def start(self) -> None:
|
|
30
|
-
"""
|
|
64
|
+
"""Starts the Redis connection with proper timeout and connection
|
|
65
|
+
pool settings."""
|
|
31
66
|
if self._redis is None:
|
|
32
67
|
self._redis = aioredis.from_url(
|
|
33
68
|
self._redis_url,
|
|
34
69
|
decode_responses=True,
|
|
70
|
+
socket_timeout=self._socket_timeout,
|
|
71
|
+
socket_connect_timeout=self._socket_connect_timeout,
|
|
72
|
+
max_connections=self._max_connections,
|
|
73
|
+
retry_on_timeout=self._retry_on_timeout,
|
|
74
|
+
health_check_interval=self._health_check_interval,
|
|
75
|
+
socket_keepalive=self._socket_keepalive,
|
|
35
76
|
)
|
|
36
|
-
self._health = True
|
|
37
77
|
|
|
38
78
|
async def stop(self) -> None:
|
|
39
|
-
"""
|
|
79
|
+
"""Closes the Redis connection."""
|
|
40
80
|
if self._redis:
|
|
41
|
-
await self._redis.
|
|
81
|
+
await self._redis.aclose()
|
|
42
82
|
self._redis = None
|
|
43
|
-
self._health = False
|
|
44
83
|
|
|
45
84
|
async def health(self) -> bool:
|
|
46
|
-
"""
|
|
85
|
+
"""Checks the health of the service."""
|
|
47
86
|
if not self._redis:
|
|
48
87
|
return False
|
|
49
88
|
try:
|
|
@@ -81,6 +120,11 @@ class RedisStateService(StateService):
|
|
|
81
120
|
round_id = 1
|
|
82
121
|
|
|
83
122
|
await self._redis.hset(key, round_id, json.dumps(state))
|
|
123
|
+
|
|
124
|
+
# Set TTL for the state key if configured
|
|
125
|
+
if self._ttl_seconds is not None:
|
|
126
|
+
await self._redis.expire(key, self._ttl_seconds)
|
|
127
|
+
|
|
84
128
|
return round_id
|
|
85
129
|
|
|
86
130
|
async def export_state(
|
|
@@ -110,4 +154,13 @@ class RedisStateService(StateService):
|
|
|
110
154
|
|
|
111
155
|
if state_json is None:
|
|
112
156
|
return None
|
|
113
|
-
|
|
157
|
+
|
|
158
|
+
# Refresh TTL when accessing the state
|
|
159
|
+
if self._ttl_seconds is not None:
|
|
160
|
+
await self._redis.expire(key, self._ttl_seconds)
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
return json.loads(state_json)
|
|
164
|
+
except json.JSONDecodeError:
|
|
165
|
+
# Return None for corrupted state data instead of raising exception
|
|
166
|
+
return None
|