isa-model 0.3.91__py3-none-any.whl → 0.4.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 (228) hide show
  1. isa_model/client.py +1166 -584
  2. isa_model/core/cache/redis_cache.py +410 -0
  3. isa_model/core/config/config_manager.py +282 -12
  4. isa_model/core/config.py +91 -1
  5. isa_model/core/database/__init__.py +1 -0
  6. isa_model/core/database/direct_db_client.py +114 -0
  7. isa_model/core/database/migration_manager.py +563 -0
  8. isa_model/core/database/migrations.py +297 -0
  9. isa_model/core/database/supabase_client.py +258 -0
  10. isa_model/core/dependencies.py +316 -0
  11. isa_model/core/discovery/__init__.py +19 -0
  12. isa_model/core/discovery/consul_discovery.py +190 -0
  13. isa_model/core/logging/__init__.py +54 -0
  14. isa_model/core/logging/influx_logger.py +523 -0
  15. isa_model/core/logging/loki_logger.py +160 -0
  16. isa_model/core/models/__init__.py +46 -0
  17. isa_model/core/models/config_models.py +625 -0
  18. isa_model/core/models/deployment_billing_tracker.py +430 -0
  19. isa_model/core/models/model_billing_tracker.py +60 -88
  20. isa_model/core/models/model_manager.py +66 -25
  21. isa_model/core/models/model_metadata.py +690 -0
  22. isa_model/core/models/model_repo.py +217 -55
  23. isa_model/core/models/model_statistics_tracker.py +234 -0
  24. isa_model/core/models/model_storage.py +0 -1
  25. isa_model/core/models/model_version_manager.py +959 -0
  26. isa_model/core/models/system_models.py +857 -0
  27. isa_model/core/pricing_manager.py +2 -249
  28. isa_model/core/repositories/__init__.py +9 -0
  29. isa_model/core/repositories/config_repository.py +912 -0
  30. isa_model/core/resilience/circuit_breaker.py +366 -0
  31. isa_model/core/security/secrets.py +358 -0
  32. isa_model/core/services/__init__.py +2 -4
  33. isa_model/core/services/intelligent_model_selector.py +479 -370
  34. isa_model/core/storage/hf_storage.py +2 -2
  35. isa_model/core/types.py +8 -0
  36. isa_model/deployment/__init__.py +5 -48
  37. isa_model/deployment/core/__init__.py +2 -31
  38. isa_model/deployment/core/deployment_manager.py +1278 -368
  39. isa_model/deployment/local/__init__.py +31 -0
  40. isa_model/deployment/local/config.py +248 -0
  41. isa_model/deployment/local/gpu_gateway.py +607 -0
  42. isa_model/deployment/local/health_checker.py +428 -0
  43. isa_model/deployment/local/provider.py +586 -0
  44. isa_model/deployment/local/tensorrt_service.py +621 -0
  45. isa_model/deployment/local/transformers_service.py +644 -0
  46. isa_model/deployment/local/vllm_service.py +527 -0
  47. isa_model/deployment/modal/__init__.py +8 -0
  48. isa_model/deployment/modal/config.py +136 -0
  49. isa_model/deployment/modal/deployer.py +894 -0
  50. isa_model/deployment/modal/services/__init__.py +3 -0
  51. isa_model/deployment/modal/services/audio/__init__.py +1 -0
  52. isa_model/deployment/modal/services/audio/isa_audio_chatTTS_service.py +520 -0
  53. isa_model/deployment/modal/services/audio/isa_audio_openvoice_service.py +758 -0
  54. isa_model/deployment/modal/services/audio/isa_audio_service_v2.py +1044 -0
  55. isa_model/deployment/modal/services/embedding/__init__.py +1 -0
  56. isa_model/deployment/modal/services/embedding/isa_embed_rerank_service.py +296 -0
  57. isa_model/deployment/modal/services/llm/__init__.py +1 -0
  58. isa_model/deployment/modal/services/llm/isa_llm_service.py +424 -0
  59. isa_model/deployment/modal/services/video/__init__.py +1 -0
  60. isa_model/deployment/modal/services/video/isa_video_hunyuan_service.py +423 -0
  61. isa_model/deployment/modal/services/vision/__init__.py +1 -0
  62. isa_model/deployment/modal/services/vision/isa_vision_ocr_service.py +519 -0
  63. isa_model/deployment/modal/services/vision/isa_vision_qwen25_service.py +709 -0
  64. isa_model/deployment/modal/services/vision/isa_vision_table_service.py +676 -0
  65. isa_model/deployment/modal/services/vision/isa_vision_ui_service.py +833 -0
  66. isa_model/deployment/modal/services/vision/isa_vision_ui_service_optimized.py +660 -0
  67. isa_model/deployment/models/org-org-acme-corp-tenant-a-service-llm-20250825-225822/tenant-a-service_modal_service.py +48 -0
  68. isa_model/deployment/models/org-test-org-123-prefix-test-service-llm-20250825-225822/prefix-test-service_modal_service.py +48 -0
  69. isa_model/deployment/models/test-llm-service-llm-20250825-204442/test-llm-service_modal_service.py +48 -0
  70. isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-212906/test-monitoring-gpt2_modal_service.py +48 -0
  71. isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-213009/test-monitoring-gpt2_modal_service.py +48 -0
  72. isa_model/deployment/storage/__init__.py +5 -0
  73. isa_model/deployment/storage/deployment_repository.py +824 -0
  74. isa_model/deployment/triton/__init__.py +10 -0
  75. isa_model/deployment/triton/config.py +196 -0
  76. isa_model/deployment/triton/configs/__init__.py +1 -0
  77. isa_model/deployment/triton/provider.py +512 -0
  78. isa_model/deployment/triton/scripts/__init__.py +1 -0
  79. isa_model/deployment/triton/templates/__init__.py +1 -0
  80. isa_model/inference/__init__.py +47 -1
  81. isa_model/inference/ai_factory.py +179 -16
  82. isa_model/inference/legacy_services/__init__.py +21 -0
  83. isa_model/inference/legacy_services/model_evaluation.py +637 -0
  84. isa_model/inference/legacy_services/model_service.py +573 -0
  85. isa_model/inference/legacy_services/model_serving.py +717 -0
  86. isa_model/inference/legacy_services/model_training.py +561 -0
  87. isa_model/inference/models/__init__.py +21 -0
  88. isa_model/inference/models/inference_config.py +551 -0
  89. isa_model/inference/models/inference_record.py +675 -0
  90. isa_model/inference/models/performance_models.py +714 -0
  91. isa_model/inference/repositories/__init__.py +9 -0
  92. isa_model/inference/repositories/inference_repository.py +828 -0
  93. isa_model/inference/services/audio/__init__.py +21 -0
  94. isa_model/inference/services/audio/base_realtime_service.py +225 -0
  95. isa_model/inference/services/audio/base_stt_service.py +184 -11
  96. isa_model/inference/services/audio/isa_tts_service.py +0 -0
  97. isa_model/inference/services/audio/openai_realtime_service.py +320 -124
  98. isa_model/inference/services/audio/openai_stt_service.py +53 -11
  99. isa_model/inference/services/base_service.py +17 -1
  100. isa_model/inference/services/custom_model_manager.py +277 -0
  101. isa_model/inference/services/embedding/__init__.py +13 -0
  102. isa_model/inference/services/embedding/base_embed_service.py +111 -8
  103. isa_model/inference/services/embedding/isa_embed_service.py +305 -0
  104. isa_model/inference/services/embedding/ollama_embed_service.py +15 -3
  105. isa_model/inference/services/embedding/openai_embed_service.py +2 -4
  106. isa_model/inference/services/embedding/resilient_embed_service.py +285 -0
  107. isa_model/inference/services/embedding/tests/test_embedding.py +222 -0
  108. isa_model/inference/services/img/__init__.py +2 -2
  109. isa_model/inference/services/img/base_image_gen_service.py +24 -7
  110. isa_model/inference/services/img/replicate_image_gen_service.py +84 -422
  111. isa_model/inference/services/img/services/replicate_face_swap.py +193 -0
  112. isa_model/inference/services/img/services/replicate_flux.py +226 -0
  113. isa_model/inference/services/img/services/replicate_flux_kontext.py +219 -0
  114. isa_model/inference/services/img/services/replicate_sticker_maker.py +249 -0
  115. isa_model/inference/services/img/tests/test_img_client.py +297 -0
  116. isa_model/inference/services/llm/__init__.py +10 -2
  117. isa_model/inference/services/llm/base_llm_service.py +361 -26
  118. isa_model/inference/services/llm/cerebras_llm_service.py +628 -0
  119. isa_model/inference/services/llm/helpers/llm_adapter.py +71 -12
  120. isa_model/inference/services/llm/helpers/llm_prompts.py +342 -0
  121. isa_model/inference/services/llm/helpers/llm_utils.py +321 -23
  122. isa_model/inference/services/llm/huggingface_llm_service.py +581 -0
  123. isa_model/inference/services/llm/local_llm_service.py +747 -0
  124. isa_model/inference/services/llm/ollama_llm_service.py +11 -3
  125. isa_model/inference/services/llm/openai_llm_service.py +670 -56
  126. isa_model/inference/services/llm/yyds_llm_service.py +10 -3
  127. isa_model/inference/services/vision/__init__.py +27 -6
  128. isa_model/inference/services/vision/base_vision_service.py +118 -185
  129. isa_model/inference/services/vision/blip_vision_service.py +359 -0
  130. isa_model/inference/services/vision/helpers/image_utils.py +19 -10
  131. isa_model/inference/services/vision/isa_vision_service.py +634 -0
  132. isa_model/inference/services/vision/openai_vision_service.py +19 -10
  133. isa_model/inference/services/vision/tests/test_ocr_client.py +284 -0
  134. isa_model/inference/services/vision/vgg16_vision_service.py +257 -0
  135. isa_model/serving/api/cache_manager.py +245 -0
  136. isa_model/serving/api/dependencies/__init__.py +1 -0
  137. isa_model/serving/api/dependencies/auth.py +194 -0
  138. isa_model/serving/api/dependencies/database.py +139 -0
  139. isa_model/serving/api/error_handlers.py +284 -0
  140. isa_model/serving/api/fastapi_server.py +240 -18
  141. isa_model/serving/api/middleware/auth.py +317 -0
  142. isa_model/serving/api/middleware/security.py +268 -0
  143. isa_model/serving/api/middleware/tenant_context.py +414 -0
  144. isa_model/serving/api/routes/analytics.py +489 -0
  145. isa_model/serving/api/routes/config.py +645 -0
  146. isa_model/serving/api/routes/deployment_billing.py +315 -0
  147. isa_model/serving/api/routes/deployments.py +475 -0
  148. isa_model/serving/api/routes/gpu_gateway.py +440 -0
  149. isa_model/serving/api/routes/health.py +32 -12
  150. isa_model/serving/api/routes/inference_monitoring.py +486 -0
  151. isa_model/serving/api/routes/local_deployments.py +448 -0
  152. isa_model/serving/api/routes/logs.py +430 -0
  153. isa_model/serving/api/routes/settings.py +582 -0
  154. isa_model/serving/api/routes/tenants.py +575 -0
  155. isa_model/serving/api/routes/unified.py +992 -171
  156. isa_model/serving/api/routes/webhooks.py +479 -0
  157. isa_model/serving/api/startup.py +318 -0
  158. isa_model/serving/modal_proxy_server.py +249 -0
  159. isa_model/utils/gpu_utils.py +311 -0
  160. {isa_model-0.3.91.dist-info → isa_model-0.4.3.dist-info}/METADATA +76 -22
  161. isa_model-0.4.3.dist-info/RECORD +193 -0
  162. isa_model/deployment/cloud/__init__.py +0 -9
  163. isa_model/deployment/cloud/modal/__init__.py +0 -10
  164. isa_model/deployment/cloud/modal/isa_vision_doc_service.py +0 -766
  165. isa_model/deployment/cloud/modal/isa_vision_table_service.py +0 -532
  166. isa_model/deployment/cloud/modal/isa_vision_ui_service.py +0 -406
  167. isa_model/deployment/cloud/modal/register_models.py +0 -321
  168. isa_model/deployment/core/deployment_config.py +0 -356
  169. isa_model/deployment/core/isa_deployment_service.py +0 -401
  170. isa_model/deployment/gpu_int8_ds8/app/server.py +0 -66
  171. isa_model/deployment/gpu_int8_ds8/scripts/test_client.py +0 -43
  172. isa_model/deployment/gpu_int8_ds8/scripts/test_client_os.py +0 -35
  173. isa_model/deployment/runtime/deployed_service.py +0 -338
  174. isa_model/deployment/services/__init__.py +0 -9
  175. isa_model/deployment/services/auto_deploy_vision_service.py +0 -538
  176. isa_model/deployment/services/model_service.py +0 -332
  177. isa_model/deployment/services/service_monitor.py +0 -356
  178. isa_model/deployment/services/service_registry.py +0 -527
  179. isa_model/eval/__init__.py +0 -92
  180. isa_model/eval/benchmarks.py +0 -469
  181. isa_model/eval/config/__init__.py +0 -10
  182. isa_model/eval/config/evaluation_config.py +0 -108
  183. isa_model/eval/evaluators/__init__.py +0 -18
  184. isa_model/eval/evaluators/base_evaluator.py +0 -503
  185. isa_model/eval/evaluators/llm_evaluator.py +0 -472
  186. isa_model/eval/factory.py +0 -531
  187. isa_model/eval/infrastructure/__init__.py +0 -24
  188. isa_model/eval/infrastructure/experiment_tracker.py +0 -466
  189. isa_model/eval/metrics.py +0 -798
  190. isa_model/inference/adapter/unified_api.py +0 -248
  191. isa_model/inference/services/helpers/stacked_config.py +0 -148
  192. isa_model/inference/services/img/flux_professional_service.py +0 -603
  193. isa_model/inference/services/img/helpers/base_stacked_service.py +0 -274
  194. isa_model/inference/services/others/table_transformer_service.py +0 -61
  195. isa_model/inference/services/vision/doc_analysis_service.py +0 -640
  196. isa_model/inference/services/vision/helpers/base_stacked_service.py +0 -274
  197. isa_model/inference/services/vision/ui_analysis_service.py +0 -823
  198. isa_model/scripts/inference_tracker.py +0 -283
  199. isa_model/scripts/mlflow_manager.py +0 -379
  200. isa_model/scripts/model_registry.py +0 -465
  201. isa_model/scripts/register_models.py +0 -370
  202. isa_model/scripts/register_models_with_embeddings.py +0 -510
  203. isa_model/scripts/start_mlflow.py +0 -95
  204. isa_model/scripts/training_tracker.py +0 -257
  205. isa_model/training/__init__.py +0 -74
  206. isa_model/training/annotation/annotation_schema.py +0 -47
  207. isa_model/training/annotation/processors/annotation_processor.py +0 -126
  208. isa_model/training/annotation/storage/dataset_manager.py +0 -131
  209. isa_model/training/annotation/storage/dataset_schema.py +0 -44
  210. isa_model/training/annotation/tests/test_annotation_flow.py +0 -109
  211. isa_model/training/annotation/tests/test_minio copy.py +0 -113
  212. isa_model/training/annotation/tests/test_minio_upload.py +0 -43
  213. isa_model/training/annotation/views/annotation_controller.py +0 -158
  214. isa_model/training/cloud/__init__.py +0 -22
  215. isa_model/training/cloud/job_orchestrator.py +0 -402
  216. isa_model/training/cloud/runpod_trainer.py +0 -454
  217. isa_model/training/cloud/storage_manager.py +0 -482
  218. isa_model/training/core/__init__.py +0 -23
  219. isa_model/training/core/config.py +0 -181
  220. isa_model/training/core/dataset.py +0 -222
  221. isa_model/training/core/trainer.py +0 -720
  222. isa_model/training/core/utils.py +0 -213
  223. isa_model/training/factory.py +0 -424
  224. isa_model-0.3.91.dist-info/RECORD +0 -138
  225. /isa_model/{core/storage/minio_storage.py → deployment/modal/services/audio/isa_audio_fish_service.py} +0 -0
  226. /isa_model/deployment/{services → modal/services/vision}/simple_auto_deploy_vision_service.py +0 -0
  227. {isa_model-0.3.91.dist-info → isa_model-0.4.3.dist-info}/WHEEL +0 -0
  228. {isa_model-0.3.91.dist-info → isa_model-0.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,366 @@
1
+ """
2
+ Circuit Breaker Implementation for External Service Calls
3
+
4
+ Provides resilience patterns including:
5
+ - Circuit breaker for external API calls
6
+ - Retry with exponential backoff
7
+ - Timeout handling
8
+ - Health monitoring
9
+ """
10
+
11
+ import asyncio
12
+ import time
13
+ import logging
14
+ import functools
15
+ from typing import Dict, Any, Optional, Callable, Union, List
16
+ from enum import Enum
17
+ from dataclasses import dataclass, field
18
+ from circuitbreaker import circuit
19
+ import structlog
20
+
21
+ logger = structlog.get_logger(__name__)
22
+
23
+ class CircuitState(Enum):
24
+ CLOSED = "closed" # Normal operation
25
+ OPEN = "open" # Circuit breaker triggered, blocking calls
26
+ HALF_OPEN = "half_open" # Testing if service is recovered
27
+
28
+ @dataclass
29
+ class CircuitBreakerConfig:
30
+ """Configuration for circuit breaker"""
31
+ failure_threshold: int = 5 # Number of failures before opening
32
+ recovery_timeout: int = 30 # Seconds before trying to recover
33
+ expected_exception: type = Exception # Exception type that triggers circuit breaker
34
+ success_threshold: int = 3 # Successful calls needed to close circuit
35
+ timeout: float = 30.0 # Request timeout in seconds
36
+
37
+ @dataclass
38
+ class CircuitBreakerStats:
39
+ """Circuit breaker statistics"""
40
+ total_calls: int = 0
41
+ successful_calls: int = 0
42
+ failed_calls: int = 0
43
+ consecutive_failures: int = 0
44
+ consecutive_successes: int = 0
45
+ last_failure_time: Optional[float] = None
46
+ last_success_time: Optional[float] = None
47
+ state_changes: List[Dict[str, Any]] = field(default_factory=list)
48
+
49
+ class EnhancedCircuitBreaker:
50
+ """Enhanced circuit breaker with monitoring and configuration"""
51
+
52
+ def __init__(self, name: str, config: CircuitBreakerConfig):
53
+ self.name = name
54
+ self.config = config
55
+ self.stats = CircuitBreakerStats()
56
+ self.state = CircuitState.CLOSED
57
+ self.last_state_change = time.time()
58
+
59
+ # Create underlying circuit breaker
60
+ self._circuit = circuit(
61
+ failure_threshold=config.failure_threshold,
62
+ recovery_timeout=config.recovery_timeout,
63
+ expected_exception=config.expected_exception
64
+ )
65
+
66
+ logger.info("Circuit breaker initialized", name=name, config=config)
67
+
68
+ def __call__(self, func: Callable):
69
+ """Decorator to wrap functions with circuit breaker"""
70
+
71
+ @functools.wraps(func)
72
+ async def wrapper(*args, **kwargs):
73
+ return await self.call(func, *args, **kwargs)
74
+
75
+ # Apply the circuit breaker decorator
76
+ wrapped_func = self._circuit(wrapper)
77
+ return wrapped_func
78
+
79
+ async def call(self, func: Callable, *args, **kwargs):
80
+ """Execute function with circuit breaker protection"""
81
+ start_time = time.time()
82
+
83
+ try:
84
+ # Record call attempt
85
+ self.stats.total_calls += 1
86
+
87
+ # Check if circuit is open
88
+ if self.state == CircuitState.OPEN:
89
+ if time.time() - self.last_state_change < self.config.recovery_timeout:
90
+ raise CircuitBreakerOpenException(
91
+ f"Circuit breaker '{self.name}' is open"
92
+ )
93
+ else:
94
+ # Try to move to half-open state
95
+ self._change_state(CircuitState.HALF_OPEN)
96
+
97
+ # Execute the function with timeout
98
+ try:
99
+ if asyncio.iscoroutinefunction(func):
100
+ result = await asyncio.wait_for(
101
+ func(*args, **kwargs),
102
+ timeout=self.config.timeout
103
+ )
104
+ else:
105
+ result = func(*args, **kwargs)
106
+
107
+ # Record success
108
+ self._record_success()
109
+
110
+ return result
111
+
112
+ except asyncio.TimeoutError:
113
+ self._record_failure()
114
+ raise CircuitBreakerTimeoutException(
115
+ f"Timeout after {self.config.timeout}s for '{self.name}'"
116
+ )
117
+ except self.config.expected_exception as e:
118
+ self._record_failure()
119
+ raise
120
+
121
+ except Exception as e:
122
+ duration = time.time() - start_time
123
+ logger.error(
124
+ "Circuit breaker call failed",
125
+ name=self.name,
126
+ error=str(e),
127
+ duration=duration,
128
+ state=self.state.value
129
+ )
130
+ raise
131
+
132
+ def _record_success(self):
133
+ """Record successful call"""
134
+ self.stats.successful_calls += 1
135
+ self.stats.consecutive_successes += 1
136
+ self.stats.consecutive_failures = 0
137
+ self.stats.last_success_time = time.time()
138
+
139
+ # If we're in half-open state and have enough successes, close the circuit
140
+ if (self.state == CircuitState.HALF_OPEN and
141
+ self.stats.consecutive_successes >= self.config.success_threshold):
142
+ self._change_state(CircuitState.CLOSED)
143
+
144
+ logger.debug(
145
+ "Circuit breaker success",
146
+ name=self.name,
147
+ consecutive_successes=self.stats.consecutive_successes,
148
+ state=self.state.value
149
+ )
150
+
151
+ def _record_failure(self):
152
+ """Record failed call"""
153
+ self.stats.failed_calls += 1
154
+ self.stats.consecutive_failures += 1
155
+ self.stats.consecutive_successes = 0
156
+ self.stats.last_failure_time = time.time()
157
+
158
+ # Check if we should open the circuit
159
+ if (self.state == CircuitState.CLOSED and
160
+ self.stats.consecutive_failures >= self.config.failure_threshold):
161
+ self._change_state(CircuitState.OPEN)
162
+ elif self.state == CircuitState.HALF_OPEN:
163
+ # Any failure in half-open state reopens the circuit
164
+ self._change_state(CircuitState.OPEN)
165
+
166
+ logger.warning(
167
+ "Circuit breaker failure",
168
+ name=self.name,
169
+ consecutive_failures=self.stats.consecutive_failures,
170
+ state=self.state.value
171
+ )
172
+
173
+ def _change_state(self, new_state: CircuitState):
174
+ """Change circuit breaker state"""
175
+ old_state = self.state
176
+ self.state = new_state
177
+ self.last_state_change = time.time()
178
+
179
+ # Record state change
180
+ state_change = {
181
+ "from_state": old_state.value,
182
+ "to_state": new_state.value,
183
+ "timestamp": self.last_state_change,
184
+ "total_calls": self.stats.total_calls,
185
+ "consecutive_failures": self.stats.consecutive_failures
186
+ }
187
+ self.stats.state_changes.append(state_change)
188
+
189
+ logger.warning(
190
+ "Circuit breaker state changed",
191
+ name=self.name,
192
+ from_state=old_state.value,
193
+ to_state=new_state.value,
194
+ consecutive_failures=self.stats.consecutive_failures
195
+ )
196
+
197
+ def get_stats(self) -> Dict[str, Any]:
198
+ """Get circuit breaker statistics"""
199
+ return {
200
+ "name": self.name,
201
+ "state": self.state.value,
202
+ "total_calls": self.stats.total_calls,
203
+ "successful_calls": self.stats.successful_calls,
204
+ "failed_calls": self.stats.failed_calls,
205
+ "success_rate": (
206
+ self.stats.successful_calls / self.stats.total_calls
207
+ if self.stats.total_calls > 0 else 0
208
+ ),
209
+ "consecutive_failures": self.stats.consecutive_failures,
210
+ "consecutive_successes": self.stats.consecutive_successes,
211
+ "last_failure_time": self.stats.last_failure_time,
212
+ "last_success_time": self.stats.last_success_time,
213
+ "last_state_change": self.last_state_change,
214
+ "config": {
215
+ "failure_threshold": self.config.failure_threshold,
216
+ "recovery_timeout": self.config.recovery_timeout,
217
+ "timeout": self.config.timeout
218
+ }
219
+ }
220
+
221
+ def reset(self):
222
+ """Reset circuit breaker to initial state"""
223
+ self.state = CircuitState.CLOSED
224
+ self.stats = CircuitBreakerStats()
225
+ self.last_state_change = time.time()
226
+
227
+ logger.info("Circuit breaker reset", name=self.name)
228
+
229
+ class CircuitBreakerOpenException(Exception):
230
+ """Exception raised when circuit breaker is open"""
231
+ pass
232
+
233
+ class CircuitBreakerTimeoutException(Exception):
234
+ """Exception raised when call times out"""
235
+ pass
236
+
237
+ # Global circuit breaker registry
238
+ _circuit_breakers: Dict[str, EnhancedCircuitBreaker] = {}
239
+
240
+ def get_circuit_breaker(
241
+ name: str,
242
+ config: Optional[CircuitBreakerConfig] = None
243
+ ) -> EnhancedCircuitBreaker:
244
+ """Get or create a circuit breaker"""
245
+ if name not in _circuit_breakers:
246
+ if config is None:
247
+ config = CircuitBreakerConfig()
248
+ _circuit_breakers[name] = EnhancedCircuitBreaker(name, config)
249
+
250
+ return _circuit_breakers[name]
251
+
252
+ def circuit_breaker(
253
+ name: str,
254
+ failure_threshold: int = 5,
255
+ recovery_timeout: int = 30,
256
+ timeout: float = 30.0,
257
+ expected_exception: type = Exception
258
+ ):
259
+ """Decorator for applying circuit breaker to functions"""
260
+ config = CircuitBreakerConfig(
261
+ failure_threshold=failure_threshold,
262
+ recovery_timeout=recovery_timeout,
263
+ timeout=timeout,
264
+ expected_exception=expected_exception
265
+ )
266
+
267
+ breaker = get_circuit_breaker(name, config)
268
+ return breaker
269
+
270
+ # Predefined circuit breakers for common services
271
+ def openai_circuit_breaker():
272
+ """Circuit breaker for OpenAI API calls"""
273
+ return circuit_breaker(
274
+ name="openai",
275
+ failure_threshold=3,
276
+ recovery_timeout=60,
277
+ timeout=120.0 # OpenAI can be slow
278
+ )
279
+
280
+ def replicate_circuit_breaker():
281
+ """Circuit breaker for Replicate API calls"""
282
+ return circuit_breaker(
283
+ name="replicate",
284
+ failure_threshold=3,
285
+ recovery_timeout=45,
286
+ timeout=300.0 # Replicate can be very slow for image generation
287
+ )
288
+
289
+ def database_circuit_breaker():
290
+ """Circuit breaker for database calls"""
291
+ return circuit_breaker(
292
+ name="database",
293
+ failure_threshold=5,
294
+ recovery_timeout=20,
295
+ timeout=10.0
296
+ )
297
+
298
+ def redis_circuit_breaker():
299
+ """Circuit breaker for Redis calls"""
300
+ return circuit_breaker(
301
+ name="redis",
302
+ failure_threshold=3,
303
+ recovery_timeout=15,
304
+ timeout=5.0
305
+ )
306
+
307
+ # Health check for all circuit breakers
308
+ async def check_circuit_breakers_health() -> Dict[str, Any]:
309
+ """Check health of all circuit breakers"""
310
+ health_status = {
311
+ "circuit_breakers": {},
312
+ "total_breakers": len(_circuit_breakers),
313
+ "open_breakers": 0,
314
+ "status": "healthy"
315
+ }
316
+
317
+ for name, breaker in _circuit_breakers.items():
318
+ stats = breaker.get_stats()
319
+ health_status["circuit_breakers"][name] = stats
320
+
321
+ if stats["state"] == "open":
322
+ health_status["open_breakers"] += 1
323
+
324
+ # Overall health status
325
+ if health_status["open_breakers"] > 0:
326
+ health_status["status"] = "degraded"
327
+
328
+ return health_status
329
+
330
+ # Utility functions for retry with exponential backoff
331
+ async def retry_with_backoff(
332
+ func: Callable,
333
+ max_retries: int = 3,
334
+ base_delay: float = 1.0,
335
+ max_delay: float = 60.0,
336
+ exponential_base: float = 2.0,
337
+ exceptions: tuple = (Exception,)
338
+ ):
339
+ """Retry function with exponential backoff"""
340
+
341
+ for attempt in range(max_retries + 1):
342
+ try:
343
+ if asyncio.iscoroutinefunction(func):
344
+ return await func()
345
+ else:
346
+ return func()
347
+ except exceptions as e:
348
+ if attempt == max_retries:
349
+ logger.error(
350
+ "All retry attempts failed",
351
+ attempts=attempt + 1,
352
+ error=str(e)
353
+ )
354
+ raise
355
+
356
+ delay = min(base_delay * (exponential_base ** attempt), max_delay)
357
+
358
+ logger.warning(
359
+ "Retry attempt failed, backing off",
360
+ attempt=attempt + 1,
361
+ max_retries=max_retries,
362
+ delay=delay,
363
+ error=str(e)
364
+ )
365
+
366
+ await asyncio.sleep(delay)