@@ -41,10 +41,10 @@ def can_retry_initialization(self) -> bool:
4141 """Check if provider initialization can be retried."""
4242 if self .initialization_attempts >= self .max_attempts :
4343 return False
44-
44+
4545 if self .backoff_until and datetime .now () < self .backoff_until :
4646 return False
47-
47+
4848 return True
4949
5050 def record_initialization_failure (self , error : Exception ) -> None :
@@ -54,7 +54,7 @@ def record_initialization_failure(self, error: Exception) -> None:
5454 self .last_error_time = datetime .now ()
5555 self .is_initialized = False
5656 self .is_initializing = False
57-
57+
5858 # Exponential backoff: 2^attempts seconds
5959 backoff_seconds = min (2 ** self .initialization_attempts , 300 ) # Max 5 minutes
6060 self .backoff_until = datetime .now () + timedelta (seconds = backoff_seconds )
@@ -214,7 +214,7 @@ def __init__(self,
214214 self .provider_cleanup_tasks : Set [asyncio .Task ] = set ()
215215 self ._manager_lock = asyncio .Lock ()
216216 self ._shutdown_event = asyncio .Event ()
217-
217+
218218 # Existing configuration
219219 self .fallback_chain : List [str ] = []
220220 self .default_provider : Optional [str ] = None
@@ -231,7 +231,7 @@ def _register_cleanup(self) -> None:
231231 """Register cleanup callback for graceful shutdown."""
232232 import atexit
233233 import signal
234-
234+
235235 def cleanup_sync ():
236236 """Synchronous cleanup wrapper."""
237237 try :
@@ -252,7 +252,7 @@ def cleanup_sync():
252252 except Exception as e :
253253 # Silently skip cleanup on shutdown errors
254254 logger .debug (f"Cleanup error (safe to ignore at shutdown): { e } " )
255-
255+
256256 atexit .register (cleanup_sync )
257257
258258 def _get_provider_state (self , provider_name : str ) -> ProviderState :
@@ -263,16 +263,16 @@ def _get_provider_state(self, provider_name: str) -> ProviderState:
263263
264264 async def _ensure_provider_initialized (self , provider_name : str ) -> bool :
265265 """Ensure provider is properly initialized with thread safety.
266-
266+
267267 Returns:
268268 bool: True if provider is initialized, False if initialization failed
269269 """
270270 state = self ._get_provider_state (provider_name )
271-
271+
272272 # Fast path: already initialized
273273 if state .is_initialized :
274274 return True
275-
275+
276276 # Check if initialization is possible
277277 if not state .can_retry_initialization ():
278278 logger .error (f"Provider { provider_name } initialization blocked: "
@@ -285,14 +285,14 @@ async def _ensure_provider_initialized(self, provider_name: str) -> bool:
285285 # Double-check after acquiring lock (another thread might have initialized)
286286 if state .is_initialized :
287287 return True
288-
288+
289289 # Check if already initializing in another coroutine
290290 if state .is_initializing :
291291 logger .info (f"Provider { provider_name } is already being initialized, waiting..." )
292292 # Wait for initialization to complete (with timeout)
293293 try :
294294 await asyncio .wait_for (
295- self ._wait_for_initialization (state ),
295+ self ._wait_for_initialization (state ),
296296 timeout = 30.0
297297 )
298298 return state .is_initialized
@@ -302,34 +302,34 @@ async def _ensure_provider_initialized(self, provider_name: str) -> bool:
302302
303303 # Mark as initializing
304304 state .is_initializing = True
305-
305+
306306 try :
307307 # Get provider configuration
308308 config = self .config_manager .load_provider_config (provider_name )
309309 provider_instance = self .registry .get_provider (provider_name , config .model_dump ())
310310
311311 logger .info (f"Initializing provider: { provider_name } " )
312-
312+
313313 # Initialize the provider
314314 await provider_instance .initialize (config .model_dump ())
315-
315+
316316 # Mark as successfully initialized
317317 state .record_initialization_success ()
318-
318+
319319 logger .info (f"Successfully initialized provider: { provider_name } " )
320320 return True
321321
322322 except Exception as e :
323323 # Record the failure
324324 state .record_initialization_failure (e )
325-
325+
326326 logger .error (f"Failed to initialize provider { provider_name } "
327327 f"(attempt { state .initialization_attempts } ): { e } " )
328-
328+
329329 # If this was the last attempt, mark provider as unhealthy
330330 if not state .can_retry_initialization ():
331331 self .load_balancer .update_provider_health (provider_name , False )
332-
332+
333333 return False
334334
335335 async def _wait_for_initialization (self , state : ProviderState ) -> None :
@@ -364,7 +364,7 @@ async def _execute_provider_operation(self, provider_name: str, method: str, *ar
364364 operation = getattr (provider_instance , method )
365365 if not callable (operation ):
366366 raise ProviderError (provider_name , f"Method { method } not available on provider" )
367-
367+
368368 response = await operation (* args , ** kwargs )
369369
370370 # Calculate duration and add metadata
@@ -414,20 +414,20 @@ def _is_critical_error(self, error: Exception) -> bool:
414414 critical_error_patterns = [
415415 "connection" ,
416416 "authentication" ,
417- "unauthorized" ,
417+ "unauthorized" ,
418418 "invalid_api_key" ,
419419 "token" ,
420420 "timeout"
421421 ]
422-
422+
423423 error_str = str (error ).lower ()
424424 return any (pattern in error_str for pattern in critical_error_patterns )
425425
426426 async def cleanup (self ) -> None :
427427 """Enhanced cleanup with proper resource management."""
428428 if self ._shutdown_event .is_set ():
429429 return # Already cleaning up
430-
430+
431431 logger .info ("Starting LLM Manager cleanup..." )
432432 self ._shutdown_event .set ()
433433
@@ -447,15 +447,15 @@ async def cleanup(self) -> None:
447447 cleanup_errors = []
448448 # Only cleanup providers that have been initialized (have instances or states)
449449 providers_to_cleanup = set ()
450-
450+
451451 # Add providers that have instances (safely access private attribute)
452452 registry_instances = getattr (self .registry , '_instances' , {})
453453 if registry_instances :
454454 providers_to_cleanup .update (registry_instances .keys ())
455-
455+
456456 # Add providers that have states (initialized)
457457 providers_to_cleanup .update (self .provider_states .keys ())
458-
458+
459459 for provider_name in providers_to_cleanup :
460460 try :
461461 # Only try to cleanup if provider has an instance
@@ -466,8 +466,13 @@ async def cleanup(self) -> None:
466466 logger .debug (f"Cleaned up provider: { provider_name } " )
467467 except Exception as e :
468468 # Only track and log non-configuration errors
469- if "Configuration error" in str (e ) or "not configured" in str (e ).lower ():
469+ error_str = str (e ).lower ()
470+ if "configuration error" in error_str or "not configured" in error_str :
470471 logger .debug (f"Skipping cleanup for unconfigured provider { provider_name } " )
472+ elif "event loop is closed" in error_str :
473+ # During interpreter shutdown, the event loop may already be closed.
474+ # Cleanup is best-effort; don't spam warnings in this case.
475+ logger .debug (f"Skipping cleanup for provider { provider_name } : event loop is closed" )
471476 else :
472477 cleanup_errors .append (f"{ provider_name } : { e } " )
473478 logger .warning (f"Cleanup failed for { provider_name } : { e } " )
@@ -483,7 +488,7 @@ async def cleanup(self) -> None:
483488 def get_provider_status (self ) -> Dict [str , Dict [str , Any ]]:
484489 """Get detailed status of all providers."""
485490 status = {}
486-
491+
487492 for provider_name in self .registry .list_providers ():
488493 state = self ._get_provider_state (provider_name )
489494 status [provider_name ] = {
@@ -496,22 +501,22 @@ def get_provider_status(self) -> Dict[str, Dict[str, Any]]:
496501 "backoff_until" : state .backoff_until .isoformat () if state .backoff_until else None ,
497502 "health_status" : self .load_balancer .provider_health .get (provider_name , True )
498503 }
499-
504+
500505 return status
501506
502507 async def reset_provider (self , provider_name : str ) -> bool :
503508 """Reset a provider's state and force reinitialization.
504-
509+
505510 Args:
506511 provider_name: Name of provider to reset
507-
512+
508513 Returns:
509514 bool: True if reset successful
510515 """
511516 if provider_name not in self .provider_states :
512517 logger .warning (f"Provider { provider_name } not found in states" )
513518 return False
514-
519+
515520 async with self ._manager_lock :
516521 # Reset state
517522 state = self .provider_states [provider_name ]
@@ -522,9 +527,9 @@ async def reset_provider(self, provider_name: str) -> bool:
522527 state .last_error = None
523528 state .last_error_time = None
524529 state .backoff_until = None
525-
530+
526531 logger .info (f"Reset provider state: { provider_name } " )
527-
532+
528533 # Try to reinitialize
529534 return await self ._ensure_provider_initialized (provider_name )
530535 def _initialize_providers (self ) -> None :
@@ -603,7 +608,7 @@ async def chat_operation(provider_name: str) -> LLMResponse:
603608 return self .response_normalizer .normalize_response (response )
604609
605610 async def chat_stream (self ,messages : List [Message ],provider : Optional [str ] = None ,callbacks : Optional [List [BaseCallbackHandler ]] = None ,** kwargs ) -> AsyncGenerator [LLMResponseChunk , None ]:
606- """Send streaming chat request with callback support.
611+ """Send streaming chat request with callback support.
607612 Args:
608613 messages: List of conversation messages
609614 provider: Specific provider to use (optional)
@@ -617,7 +622,7 @@ async def chat_stream(self,messages: List[Message],provider: Optional[str] = Non
617622 providers = self ._get_providers_for_request (provider )
618623 provider_name = providers [0 ]
619624 await self ._ensure_provider_initialized (provider_name )
620-
625+
621626 # Get provider instance
622627 provider_instance = self .registry .get_provider (provider_name )
623628
@@ -649,7 +654,7 @@ async def chat_stream(self,messages: List[Message],provider: Optional[str] = Non
649654 provider_name , 'chat_stream' , duration , False , error = str (e )
650655 )
651656 raise
652-
657+
653658 def _get_internal_callbacks (self ) -> List [BaseCallbackHandler ]:
654659 """Get internal monitoring callbacks."""
655660 # For now, return empty list
0 commit comments