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.
Files changed (69) hide show
  1. agentscope_runtime/adapters/agentscope/message.py +32 -7
  2. agentscope_runtime/adapters/agentscope/stream.py +121 -91
  3. agentscope_runtime/adapters/agno/__init__.py +0 -0
  4. agentscope_runtime/adapters/agno/message.py +30 -0
  5. agentscope_runtime/adapters/agno/stream.py +122 -0
  6. agentscope_runtime/adapters/langgraph/__init__.py +12 -0
  7. agentscope_runtime/adapters/langgraph/message.py +257 -0
  8. agentscope_runtime/adapters/langgraph/stream.py +205 -0
  9. agentscope_runtime/cli/__init__.py +7 -0
  10. agentscope_runtime/cli/cli.py +63 -0
  11. agentscope_runtime/cli/commands/__init__.py +2 -0
  12. agentscope_runtime/cli/commands/chat.py +815 -0
  13. agentscope_runtime/cli/commands/deploy.py +1074 -0
  14. agentscope_runtime/cli/commands/invoke.py +58 -0
  15. agentscope_runtime/cli/commands/list_cmd.py +103 -0
  16. agentscope_runtime/cli/commands/run.py +176 -0
  17. agentscope_runtime/cli/commands/sandbox.py +128 -0
  18. agentscope_runtime/cli/commands/status.py +60 -0
  19. agentscope_runtime/cli/commands/stop.py +185 -0
  20. agentscope_runtime/cli/commands/web.py +166 -0
  21. agentscope_runtime/cli/loaders/__init__.py +6 -0
  22. agentscope_runtime/cli/loaders/agent_loader.py +295 -0
  23. agentscope_runtime/cli/state/__init__.py +10 -0
  24. agentscope_runtime/cli/utils/__init__.py +18 -0
  25. agentscope_runtime/cli/utils/console.py +378 -0
  26. agentscope_runtime/cli/utils/validators.py +118 -0
  27. agentscope_runtime/common/collections/redis_mapping.py +4 -1
  28. agentscope_runtime/engine/app/agent_app.py +55 -9
  29. agentscope_runtime/engine/deployers/__init__.py +1 -0
  30. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +56 -1
  31. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +449 -41
  32. agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +273 -0
  33. agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +640 -0
  34. agentscope_runtime/engine/deployers/agentrun_deployer.py +152 -22
  35. agentscope_runtime/engine/deployers/base.py +27 -2
  36. agentscope_runtime/engine/deployers/kubernetes_deployer.py +161 -31
  37. agentscope_runtime/engine/deployers/local_deployer.py +188 -25
  38. agentscope_runtime/engine/deployers/modelstudio_deployer.py +109 -18
  39. agentscope_runtime/engine/deployers/state/__init__.py +9 -0
  40. agentscope_runtime/engine/deployers/state/manager.py +388 -0
  41. agentscope_runtime/engine/deployers/state/schema.py +96 -0
  42. agentscope_runtime/engine/deployers/utils/build_cache.py +736 -0
  43. agentscope_runtime/engine/deployers/utils/detached_app.py +105 -30
  44. agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +31 -10
  45. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +23 -10
  46. agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +35 -2
  47. agentscope_runtime/engine/deployers/utils/k8s_utils.py +241 -0
  48. agentscope_runtime/engine/deployers/utils/net_utils.py +65 -0
  49. agentscope_runtime/engine/deployers/utils/package.py +56 -6
  50. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +16 -2
  51. agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +155 -5
  52. agentscope_runtime/engine/deployers/utils/wheel_packager.py +107 -123
  53. agentscope_runtime/engine/runner.py +30 -9
  54. agentscope_runtime/engine/schemas/exception.py +604 -0
  55. agentscope_runtime/engine/services/agent_state/redis_state_service.py +61 -8
  56. agentscope_runtime/engine/services/agent_state/state_service_factory.py +2 -5
  57. agentscope_runtime/engine/services/memory/redis_memory_service.py +129 -25
  58. agentscope_runtime/engine/services/session_history/redis_session_history_service.py +160 -34
  59. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +113 -39
  60. agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +20 -4
  61. agentscope_runtime/sandbox/build.py +50 -57
  62. agentscope_runtime/sandbox/utils.py +2 -0
  63. agentscope_runtime/version.py +1 -1
  64. {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/METADATA +31 -8
  65. {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/RECORD +69 -36
  66. {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/entry_points.txt +1 -0
  67. {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/WHEEL +0 -0
  68. {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/licenses/LICENSE +0 -0
  69. {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._health = False
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
- """Initialize the Redis connection."""
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
- """Close the Redis connection."""
79
+ """Closes the Redis connection."""
40
80
  if self._redis:
41
- await self._redis.close()
81
+ await self._redis.aclose()
42
82
  self._redis = None
43
- self._health = False
44
83
 
45
84
  async def health(self) -> bool:
46
- """Service health check."""
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
- return json.loads(state_json)
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