aisbf 0.2.3__py3-none-any.whl → 0.2.4__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.
@@ -164,71 +164,195 @@ class RotationHandler:
164
164
  logger.error(f"Rotation {rotation_id} not found")
165
165
  raise HTTPException(status_code=400, detail=f"Rotation {rotation_id} not found")
166
166
 
167
- logger.info(f"Rotation config: {rotation_config}")
167
+ logger.info(f"Rotation config loaded successfully")
168
168
  providers = rotation_config.providers
169
169
  logger.info(f"Number of providers in rotation: {len(providers)}")
170
170
 
171
- weighted_models = []
171
+ # Collect all available models with their weights
172
+ available_models = []
173
+ skipped_providers = []
174
+ total_models_considered = 0
172
175
 
176
+ logger.info(f"=== MODEL SELECTION PROCESS START ===")
177
+ logger.info(f"Scanning providers for available models...")
178
+
173
179
  for provider in providers:
174
- logger.info(f"Processing provider: {provider['provider_id']}")
180
+ provider_id = provider['provider_id']
181
+ logger.info(f"")
182
+ logger.info(f"--- Processing provider: {provider_id} ---")
183
+
184
+ # Check if provider exists in configuration
185
+ provider_config = self.config.get_provider(provider_id)
186
+ if not provider_config:
187
+ logger.error(f" [ERROR] Provider {provider_id} not found in providers configuration")
188
+ logger.error(f" Available providers: {list(self.config.providers.keys())}")
189
+ logger.error(f" Skipping this provider")
190
+ skipped_providers.append(provider_id)
191
+ continue
192
+
193
+ # Check if provider is rate limited/deactivated
194
+ provider_handler = get_provider_handler(provider_id, provider.get('api_key'))
195
+ if provider_handler.is_rate_limited():
196
+ logger.warning(f" [SKIPPED] Provider {provider_id} is rate limited/deactivated")
197
+ logger.warning(f" Reason: Provider has exceeded failure threshold or is in cooldown period")
198
+ skipped_providers.append(provider_id)
199
+ continue
200
+
201
+ logger.info(f" [AVAILABLE] Provider {provider_id} is active and ready")
202
+
203
+ models_in_provider = len(provider['models'])
204
+ total_models_considered += models_in_provider
205
+ logger.info(f" Found {models_in_provider} model(s) in this provider")
206
+
175
207
  for model in provider['models']:
176
- logger.info(f" Model: {model['name']}, weight: {model['weight']}")
177
- weighted_models.extend([model] * model['weight'])
178
-
179
- logger.info(f"Total weighted models: {len(weighted_models)}")
180
- if not weighted_models:
181
- logger.error("No models available in rotation")
182
- raise HTTPException(status_code=400, detail="No models available in rotation")
208
+ model_name = model['name']
209
+ model_weight = model['weight']
210
+ model_rate_limit = model.get('rate_limit', 'N/A')
211
+
212
+ logger.info(f" - Model: {model_name}")
213
+ logger.info(f" Weight (Priority): {model_weight}")
214
+ logger.info(f" Rate Limit: {model_rate_limit}")
215
+
216
+ # Add provider_id and api_key to model for later use
217
+ model_with_provider = model.copy()
218
+ model_with_provider['provider_id'] = provider_id
219
+ model_with_provider['api_key'] = provider.get('api_key')
220
+ available_models.append(model_with_provider)
221
+
222
+ logger.info(f"")
223
+ logger.info(f"=== MODEL SELECTION SUMMARY ===")
224
+ logger.info(f"Total providers scanned: {len(providers)}")
225
+ logger.info(f"Providers skipped (rate limited): {len(skipped_providers)}")
226
+ if skipped_providers:
227
+ logger.info(f"Skipped providers: {', '.join(skipped_providers)}")
228
+ logger.info(f"Total models considered: {total_models_considered}")
229
+ logger.info(f"Total models available: {len(available_models)}")
230
+
231
+ if not available_models:
232
+ logger.error("No models available in rotation (all providers may be rate limited)")
233
+ logger.error("All providers in this rotation are currently deactivated")
234
+ raise HTTPException(status_code=503, detail="No models available in rotation (all providers may be rate limited)")
183
235
 
236
+ # Sort models by weight in descending order (higher weight = higher priority)
237
+ available_models.sort(key=lambda m: m['weight'], reverse=True)
238
+
239
+ logger.info(f"")
240
+ logger.info(f"=== PRIORITY-BASED SELECTION ===")
241
+ logger.info(f"Models sorted by weight (descending priority):")
242
+ for idx, model in enumerate(available_models, 1):
243
+ logger.info(f" {idx}. {model['name']} (provider: {model['provider_id']}, weight: {model['weight']})")
244
+
245
+ # Find the highest weight
246
+ highest_weight = available_models[0]['weight']
247
+ logger.info(f"")
248
+ logger.info(f"Highest priority weight: {highest_weight}")
249
+
250
+ # Filter models with the highest weight
251
+ highest_weight_models = [m for m in available_models if m['weight'] == highest_weight]
252
+ logger.info(f"Models with highest priority ({highest_weight}): {len(highest_weight_models)}")
253
+ for model in highest_weight_models:
254
+ logger.info(f" - {model['name']} (provider: {model['provider_id']})")
255
+
256
+ # If multiple models have the same highest weight, randomly select among them
184
257
  import random
185
- selected_model = random.choice(weighted_models)
186
- logger.info(f"Selected model: {selected_model}")
187
-
188
- provider_id = selected_model['provider_id']
189
- api_key = selected_model.get('api_key')
190
- model_name = selected_model['name']
258
+ if len(highest_weight_models) > 1:
259
+ logger.info(f"Multiple models with same highest priority - performing random selection")
260
+ selected_model = random.choice(highest_weight_models)
261
+ logger.info(f"Randomly selected from {len(highest_weight_models)} candidates")
262
+ else:
263
+ selected_model = highest_weight_models[0]
264
+ logger.info(f"Single model with highest priority - deterministic selection")
191
265
 
192
- logger.info(f"Selected provider_id: {provider_id}")
193
- logger.info(f"Selected model_name: {model_name}")
194
- logger.info(f"API key present: {bool(api_key)}")
195
-
196
- logger.info(f"Getting provider handler for {provider_id}")
197
- handler = get_provider_handler(provider_id, api_key)
198
- logger.info(f"Provider handler obtained: {handler.__class__.__name__}")
199
-
200
- if handler.is_rate_limited():
201
- raise HTTPException(status_code=503, detail="All providers temporarily unavailable")
202
-
203
- try:
204
- logger.info(f"Model requested: {model_name}")
205
- logger.info(f"Messages count: {len(request_data.get('messages', []))}")
206
- logger.info(f"Max tokens: {request_data.get('max_tokens')}")
207
- logger.info(f"Temperature: {request_data.get('temperature', 1.0)}")
208
- logger.info(f"Stream: {request_data.get('stream', False)}")
266
+ logger.info(f"")
267
+ logger.info(f"=== FINAL SELECTION ===")
268
+ logger.info(f"Selected model: {selected_model['name']}")
269
+ logger.info(f"Selected provider: {selected_model['provider_id']}")
270
+ logger.info(f"Model weight (priority): {selected_model['weight']}")
271
+ logger.info(f"Model rate limit: {selected_model.get('rate_limit', 'N/A')}")
272
+ logger.info(f"=== MODEL SELECTION PROCESS END ===")
273
+
274
+ # Retry logic: Try up to 2 times with different models
275
+ max_retries = 2
276
+ tried_models = []
277
+ last_error = None
278
+
279
+ for attempt in range(max_retries):
280
+ logger.info(f"")
281
+ logger.info(f"=== ATTEMPT {attempt + 1}/{max_retries} ===")
209
282
 
210
- # Apply rate limiting with model-specific rate limit if available
211
- rate_limit = selected_model.get('rate_limit')
212
- logger.info(f"Model-specific rate limit: {rate_limit}")
213
- logger.info("Applying rate limiting...")
214
- await handler.apply_rate_limit(rate_limit)
215
- logger.info("Rate limiting applied")
283
+ # Select a model that hasn't been tried yet
284
+ remaining_models = [m for m in available_models if m not in tried_models]
285
+
286
+ if not remaining_models:
287
+ logger.error(f"No more models available to try")
288
+ logger.error(f"All {len(available_models)} models have been attempted")
289
+ break
290
+
291
+ # Sort remaining models by weight and select the best one
292
+ remaining_models.sort(key=lambda m: m['weight'], reverse=True)
293
+ current_model = remaining_models[0]
294
+ tried_models.append(current_model)
295
+
296
+ logger.info(f"Trying model: {current_model['name']} (provider: {current_model['provider_id']})")
297
+ logger.info(f"Attempt {attempt + 1} of {max_retries}")
298
+
299
+ provider_id = current_model['provider_id']
300
+ api_key = current_model.get('api_key')
301
+ model_name = current_model['name']
302
+
303
+ logger.info(f"Getting provider handler for {provider_id}")
304
+ handler = get_provider_handler(provider_id, api_key)
305
+ logger.info(f"Provider handler obtained: {handler.__class__.__name__}")
216
306
 
217
- logger.info(f"Sending request to provider handler...")
218
- response = await handler.handle_request(
219
- model=model_name,
220
- messages=request_data['messages'],
221
- max_tokens=request_data.get('max_tokens'),
222
- temperature=request_data.get('temperature', 1.0),
223
- stream=request_data.get('stream', False)
224
- )
225
- logger.info(f"Response received from provider")
226
- handler.record_success()
227
- logger.info(f"=== RotationHandler.handle_rotation_request END ===")
228
- return response
229
- except Exception as e:
230
- handler.record_failure()
231
- raise HTTPException(status_code=500, detail=str(e))
307
+ if handler.is_rate_limited():
308
+ logger.warning(f"Provider {provider_id} is rate limited, skipping to next model")
309
+ continue
310
+
311
+ try:
312
+ logger.info(f"Model requested: {model_name}")
313
+ logger.info(f"Messages count: {len(request_data.get('messages', []))}")
314
+ logger.info(f"Max tokens: {request_data.get('max_tokens')}")
315
+ logger.info(f"Temperature: {request_data.get('temperature', 1.0)}")
316
+ logger.info(f"Stream: {request_data.get('stream', False)}")
317
+
318
+ # Apply rate limiting with model-specific rate limit if available
319
+ rate_limit = current_model.get('rate_limit')
320
+ logger.info(f"Model-specific rate limit: {rate_limit}")
321
+ logger.info("Applying rate limiting...")
322
+ await handler.apply_rate_limit(rate_limit)
323
+ logger.info("Rate limiting applied")
324
+
325
+ logger.info(f"Sending request to provider handler...")
326
+ response = await handler.handle_request(
327
+ model=model_name,
328
+ messages=request_data['messages'],
329
+ max_tokens=request_data.get('max_tokens'),
330
+ temperature=request_data.get('temperature', 1.0),
331
+ stream=request_data.get('stream', False)
332
+ )
333
+ logger.info(f"Response received from provider")
334
+ handler.record_success()
335
+ logger.info(f"=== RotationHandler.handle_rotation_request END ===")
336
+ logger.info(f"Request succeeded on attempt {attempt + 1}")
337
+ return response
338
+ except Exception as e:
339
+ last_error = str(e)
340
+ handler.record_failure()
341
+ logger.error(f"Attempt {attempt + 1} failed: {str(e)}")
342
+ logger.error(f"Error type: {type(e).__name__}")
343
+ logger.error(f"Will try next model...")
344
+ continue
345
+
346
+ # All retries exhausted
347
+ logger.error(f"")
348
+ logger.error(f"=== ALL RETRIES EXHAUSTED ===")
349
+ logger.error(f"Attempted {len(tried_models)} different model(s): {[m['name'] for m in tried_models]}")
350
+ logger.error(f"Last error: {last_error}")
351
+ logger.error(f"Max retries ({max_retries}) reached without success")
352
+ raise HTTPException(
353
+ status_code=503,
354
+ detail=f"All providers in rotation failed after {max_retries} attempts. Last error: {last_error}"
355
+ )
232
356
 
233
357
  async def handle_rotation_model_list(self, rotation_id: str) -> List[Dict]:
234
358
  rotation_config = self.config.get_rotation(rotation_id)
@@ -310,6 +434,11 @@ class AutoselectHandler:
310
434
 
311
435
  async def _get_model_selection(self, prompt: str) -> str:
312
436
  """Send the autoselect prompt to a model and get the selection"""
437
+ import logging
438
+ logger = logging.getLogger(__name__)
439
+ logger.info(f"=== AUTOSELECT MODEL SELECTION START ===")
440
+ logger.info(f"Using 'general' rotation for model selection")
441
+
313
442
  # Use the first available provider/model for the selection
314
443
  # This is a simple implementation - could be enhanced to use a specific selection model
315
444
  rotation_handler = RotationHandler()
@@ -322,27 +451,64 @@ class AutoselectHandler:
322
451
  "stream": False
323
452
  }
324
453
 
454
+ logger.info(f"Selection request parameters:")
455
+ logger.info(f" Temperature: 0.1 (low for deterministic selection)")
456
+ logger.info(f" Max tokens: 100 (short response expected)")
457
+ logger.info(f" Stream: False")
458
+
325
459
  # Use the fallback rotation for the selection
326
460
  try:
461
+ logger.info(f"Sending selection request to rotation handler...")
327
462
  response = await rotation_handler.handle_rotation_request("general", selection_request)
463
+ logger.info(f"Selection response received")
464
+
328
465
  content = response.get('choices', [{}])[0].get('message', {}).get('content', '')
466
+ logger.info(f"Raw response content: {content[:200]}..." if len(content) > 200 else f"Raw response content: {content}")
467
+
329
468
  model_id = self._extract_model_selection(content)
469
+
470
+ if model_id:
471
+ logger.info(f"=== AUTOSELECT MODEL SELECTION SUCCESS ===")
472
+ logger.info(f"Selected model ID: {model_id}")
473
+ else:
474
+ logger.warning(f"=== AUTOSELECT MODEL SELECTION FAILED ===")
475
+ logger.warning(f"Could not extract model ID from response")
476
+ logger.warning(f"Response content: {content}")
477
+
330
478
  return model_id
331
479
  except Exception as e:
480
+ logger.error(f"=== AUTOSELECT MODEL SELECTION ERROR ===")
481
+ logger.error(f"Error during model selection: {str(e)}")
482
+ logger.error(f"Will use fallback model")
332
483
  # If selection fails, we'll handle it in the main handler
333
484
  return None
334
485
 
335
486
  async def handle_autoselect_request(self, autoselect_id: str, request_data: Dict) -> Dict:
336
487
  """Handle an autoselect request"""
488
+ import logging
489
+ logger = logging.getLogger(__name__)
490
+ logger.info(f"=== AUTOSELECT REQUEST START ===")
491
+ logger.info(f"Autoselect ID: {autoselect_id}")
492
+
337
493
  autoselect_config = self.config.get_autoselect(autoselect_id)
338
494
  if not autoselect_config:
495
+ logger.error(f"Autoselect {autoselect_id} not found")
339
496
  raise HTTPException(status_code=400, detail=f"Autoselect {autoselect_id} not found")
340
497
 
498
+ logger.info(f"Autoselect config loaded")
499
+ logger.info(f"Available models for selection: {len(autoselect_config.available_models)}")
500
+ for model_info in autoselect_config.available_models:
501
+ logger.info(f" - {model_info.model_id}: {model_info.description}")
502
+ logger.info(f"Fallback model: {autoselect_config.fallback}")
503
+
341
504
  # Extract the user prompt from the request
342
505
  user_messages = request_data.get('messages', [])
343
506
  if not user_messages:
507
+ logger.error("No messages provided")
344
508
  raise HTTPException(status_code=400, detail="No messages provided")
345
509
 
510
+ logger.info(f"User messages count: {len(user_messages)}")
511
+
346
512
  # Build a string representation of the user prompt
347
513
  user_prompt = ""
348
514
  for msg in user_messages:
@@ -353,37 +519,73 @@ class AutoselectHandler:
353
519
  content = str(content)
354
520
  user_prompt += f"{role}: {content}\n"
355
521
 
522
+ logger.info(f"User prompt length: {len(user_prompt)} characters")
523
+ logger.info(f"User prompt preview: {user_prompt[:200]}..." if len(user_prompt) > 200 else f"User prompt: {user_prompt}")
524
+
356
525
  # Build the autoselect prompt
526
+ logger.info(f"Building autoselect prompt...")
357
527
  autoselect_prompt = self._build_autoselect_prompt(user_prompt, autoselect_config)
528
+ logger.info(f"Autoselect prompt built (length: {len(autoselect_prompt)} characters)")
358
529
 
359
530
  # Get the model selection
531
+ logger.info(f"Requesting model selection from AI...")
360
532
  selected_model_id = await self._get_model_selection(autoselect_prompt)
361
533
 
362
534
  # Validate the selected model
535
+ logger.info(f"=== MODEL VALIDATION ===")
363
536
  if not selected_model_id:
364
537
  # Fallback to the configured fallback model
538
+ logger.warning(f"No model ID returned from selection")
539
+ logger.warning(f"Using fallback model: {autoselect_config.fallback}")
365
540
  selected_model_id = autoselect_config.fallback
366
541
  else:
367
542
  # Check if the selected model is in the available models list
368
543
  available_ids = [m.model_id for m in autoselect_config.available_models]
369
544
  if selected_model_id not in available_ids:
545
+ logger.warning(f"Selected model '{selected_model_id}' not in available models list")
546
+ logger.warning(f"Available models: {available_ids}")
547
+ logger.warning(f"Using fallback model: {autoselect_config.fallback}")
370
548
  selected_model_id = autoselect_config.fallback
549
+ else:
550
+ logger.info(f"Selected model '{selected_model_id}' is valid and available")
551
+
552
+ logger.info(f"=== FINAL MODEL CHOICE ===")
553
+ logger.info(f"Selected model ID: {selected_model_id}")
554
+ logger.info(f"Selection method: {'AI-selected' if selected_model_id != autoselect_config.fallback else 'Fallback'}")
371
555
 
372
556
  # Now proxy the actual request to the selected rotation
557
+ logger.info(f"Proxying request to rotation: {selected_model_id}")
373
558
  rotation_handler = RotationHandler()
374
- return await rotation_handler.handle_rotation_request(selected_model_id, request_data)
559
+ response = await rotation_handler.handle_rotation_request(selected_model_id, request_data)
560
+ logger.info(f"=== AUTOSELECT REQUEST END ===")
561
+ return response
375
562
 
376
563
  async def handle_autoselect_streaming_request(self, autoselect_id: str, request_data: Dict):
377
564
  """Handle an autoselect streaming request"""
565
+ import logging
566
+ logger = logging.getLogger(__name__)
567
+ logger.info(f"=== AUTOSELECT STREAMING REQUEST START ===")
568
+ logger.info(f"Autoselect ID: {autoselect_id}")
569
+
378
570
  autoselect_config = self.config.get_autoselect(autoselect_id)
379
571
  if not autoselect_config:
572
+ logger.error(f"Autoselect {autoselect_id} not found")
380
573
  raise HTTPException(status_code=400, detail=f"Autoselect {autoselect_id} not found")
381
574
 
575
+ logger.info(f"Autoselect config loaded")
576
+ logger.info(f"Available models for selection: {len(autoselect_config.available_models)}")
577
+ for model_info in autoselect_config.available_models:
578
+ logger.info(f" - {model_info.model_id}: {model_info.description}")
579
+ logger.info(f"Fallback model: {autoselect_config.fallback}")
580
+
382
581
  # Extract the user prompt from the request
383
582
  user_messages = request_data.get('messages', [])
384
583
  if not user_messages:
584
+ logger.error("No messages provided")
385
585
  raise HTTPException(status_code=400, detail="No messages provided")
386
586
 
587
+ logger.info(f"User messages count: {len(user_messages)}")
588
+
387
589
  # Build a string representation of the user prompt
388
590
  user_prompt = ""
389
591
  for msg in user_messages:
@@ -393,21 +595,41 @@ class AutoselectHandler:
393
595
  content = str(content)
394
596
  user_prompt += f"{role}: {content}\n"
395
597
 
598
+ logger.info(f"User prompt length: {len(user_prompt)} characters")
599
+ logger.info(f"User prompt preview: {user_prompt[:200]}..." if len(user_prompt) > 200 else f"User prompt: {user_prompt}")
600
+
396
601
  # Build the autoselect prompt
602
+ logger.info(f"Building autoselect prompt...")
397
603
  autoselect_prompt = self._build_autoselect_prompt(user_prompt, autoselect_config)
604
+ logger.info(f"Autoselect prompt built (length: {len(autoselect_prompt)} characters)")
398
605
 
399
606
  # Get the model selection
607
+ logger.info(f"Requesting model selection from AI...")
400
608
  selected_model_id = await self._get_model_selection(autoselect_prompt)
401
609
 
402
610
  # Validate the selected model
611
+ logger.info(f"=== MODEL VALIDATION ===")
403
612
  if not selected_model_id:
613
+ logger.warning(f"No model ID returned from selection")
614
+ logger.warning(f"Using fallback model: {autoselect_config.fallback}")
404
615
  selected_model_id = autoselect_config.fallback
405
616
  else:
406
617
  available_ids = [m.model_id for m in autoselect_config.available_models]
407
618
  if selected_model_id not in available_ids:
619
+ logger.warning(f"Selected model '{selected_model_id}' not in available models list")
620
+ logger.warning(f"Available models: {available_ids}")
621
+ logger.warning(f"Using fallback model: {autoselect_config.fallback}")
408
622
  selected_model_id = autoselect_config.fallback
623
+ else:
624
+ logger.info(f"Selected model '{selected_model_id}' is valid and available")
625
+
626
+ logger.info(f"=== FINAL MODEL CHOICE ===")
627
+ logger.info(f"Selected model ID: {selected_model_id}")
628
+ logger.info(f"Selection method: {'AI-selected' if selected_model_id != autoselect_config.fallback else 'Fallback'}")
629
+ logger.info(f"Request mode: Streaming")
409
630
 
410
631
  # Now proxy the actual streaming request to the selected rotation
632
+ logger.info(f"Proxying streaming request to rotation: {selected_model_id}")
411
633
  rotation_handler = RotationHandler()
412
634
 
413
635
  async def stream_generator():
@@ -419,8 +641,10 @@ class AutoselectHandler:
419
641
  for chunk in response:
420
642
  yield f"data: {chunk}\n\n".encode('utf-8')
421
643
  except Exception as e:
644
+ logger.error(f"Error in streaming response: {str(e)}")
422
645
  yield f"data: {str(e)}\n\n".encode('utf-8')
423
646
 
647
+ logger.info(f"=== AUTOSELECT STREAMING REQUEST END ===")
424
648
  return StreamingResponse(stream_generator(), media_type="text/event-stream")
425
649
 
426
650
  async def handle_autoselect_model_list(self, autoselect_id: str) -> List[Dict]:
@@ -62,14 +62,56 @@ class BaseProviderHandler:
62
62
  self.last_request_time = time.time()
63
63
 
64
64
  def record_failure(self):
65
+ import logging
66
+ logger = logging.getLogger(__name__)
67
+
65
68
  self.error_tracking['failures'] += 1
66
69
  self.error_tracking['last_failure'] = time.time()
70
+
71
+ failure_count = self.error_tracking['failures']
72
+ logger.warning(f"=== PROVIDER FAILURE RECORDED ===")
73
+ logger.warning(f"Provider: {self.provider_id}")
74
+ logger.warning(f"Failure count: {failure_count}/3")
75
+ logger.warning(f"Last failure time: {self.error_tracking['last_failure']}")
76
+
67
77
  if self.error_tracking['failures'] >= 3:
68
78
  self.error_tracking['disabled_until'] = time.time() + 300 # 5 minutes
79
+ disabled_until_time = self.error_tracking['disabled_until']
80
+ cooldown_remaining = int(disabled_until_time - time.time())
81
+ logger.error(f"!!! PROVIDER DISABLED !!!")
82
+ logger.error(f"Provider: {self.provider_id}")
83
+ logger.error(f"Reason: 3 consecutive failures reached")
84
+ logger.error(f"Disabled until: {disabled_until_time}")
85
+ logger.error(f"Cooldown period: {cooldown_remaining} seconds (5 minutes)")
86
+ logger.error(f"Provider will be automatically re-enabled after cooldown")
87
+ else:
88
+ remaining_failures = 3 - failure_count
89
+ logger.warning(f"Provider still active. {remaining_failures} more failure(s) will disable it")
90
+ logger.warning(f"=== END FAILURE RECORDING ===")
69
91
 
70
92
  def record_success(self):
93
+ import logging
94
+ logger = logging.getLogger(__name__)
95
+
96
+ was_disabled = self.error_tracking['disabled_until'] is not None
97
+ previous_failures = self.error_tracking['failures']
98
+
71
99
  self.error_tracking['failures'] = 0
72
100
  self.error_tracking['disabled_until'] = None
101
+
102
+ logger.info(f"=== PROVIDER SUCCESS RECORDED ===")
103
+ logger.info(f"Provider: {self.provider_id}")
104
+ logger.info(f"Previous failure count: {previous_failures}")
105
+ logger.info(f"Failure count reset to: 0")
106
+
107
+ if was_disabled:
108
+ logger.info(f"!!! PROVIDER RE-ENABLED !!!")
109
+ logger.info(f"Provider: {self.provider_id}")
110
+ logger.info(f"Reason: Successful request after cooldown period")
111
+ logger.info(f"Provider is now active and available for requests")
112
+ else:
113
+ logger.info(f"Provider remains active")
114
+ logger.info(f"=== END SUCCESS RECORDING ===")
73
115
 
74
116
  class GoogleProviderHandler(BaseProviderHandler):
75
117
  def __init__(self, provider_id: str, api_key: str):