Mirrowel commited on
Commit
aaab6f8
·
1 Parent(s): 97ae153

refactor(rotator): randomize key selection and streamline daily stats reset

Browse files

Randomize API key selection lists in `RotatingClient` to ensure a fair distribution of keys, especially when multiple keys have similar usage statistics. This prevents deterministic selection biases and promotes better load balancing.

Consolidate the daily usage statistics reset logic into a dedicated `_reset_daily_stats_if_needed` method within the `UsageManager`. This centralizes and clarifies the reset mechanism, removing inline and potentially inconsistent checks.

src/rotator_library/client.py CHANGED
@@ -291,7 +291,13 @@ class RotatingClient:
291
 
292
  # Establish a global deadline for the entire request lifecycle.
293
  deadline = time.time() + self.global_timeout
294
- keys_for_provider = self.api_keys[provider]
 
 
 
 
 
 
295
  tried_keys = set()
296
  last_exception = None
297
  kwargs = self._convert_model_params(**kwargs)
@@ -439,7 +445,11 @@ class RotatingClient:
439
  """A dedicated generator for retrying streaming completions with full request preparation and per-key retries."""
440
  model = kwargs.get("model")
441
  provider = model.split('/')[0]
442
- keys_for_provider = self.api_keys[provider]
 
 
 
 
443
  deadline = time.time() + self.global_timeout
444
  tried_keys = set()
445
  last_exception = None
@@ -465,7 +475,7 @@ class RotatingClient:
465
 
466
  lib_logger.info(f"Acquiring key for model {model}. Tried keys: {len(tried_keys)}/{len(keys_for_provider)}")
467
  current_key = await self.usage_manager.acquire_key(
468
- available_keys=keys_to_try,
469
  model=model,
470
  deadline=deadline
471
  )
 
291
 
292
  # Establish a global deadline for the entire request lifecycle.
293
  deadline = time.time() + self.global_timeout
294
+
295
+ # Create a mutable copy of the keys and shuffle it to ensure
296
+ # that the key selection is randomized, which is crucial when
297
+ # multiple keys have the same usage stats.
298
+ keys_for_provider = list(self.api_keys[provider])
299
+ random.shuffle(keys_for_provider)
300
+
301
  tried_keys = set()
302
  last_exception = None
303
  kwargs = self._convert_model_params(**kwargs)
 
445
  """A dedicated generator for retrying streaming completions with full request preparation and per-key retries."""
446
  model = kwargs.get("model")
447
  provider = model.split('/')[0]
448
+
449
+ # Create a mutable copy of the keys and shuffle it.
450
+ keys_for_provider = list(self.api_keys[provider])
451
+ random.shuffle(keys_for_provider)
452
+
453
  deadline = time.time() + self.global_timeout
454
  tried_keys = set()
455
  last_exception = None
 
475
 
476
  lib_logger.info(f"Acquiring key for model {model}. Tried keys: {len(tried_keys)}/{len(keys_for_provider)}")
477
  current_key = await self.usage_manager.acquire_key(
478
+ available_keys=keys_to_try,
479
  model=model,
480
  deadline=deadline
481
  )
src/rotator_library/usage_manager.py CHANGED
@@ -134,6 +134,7 @@ class UsageManager:
134
  respecting a global deadline.
135
  """
136
  await self._lazy_init()
 
137
  self._initialize_key_states(available_keys)
138
 
139
  # This loop continues as long as the global deadline has not been met.
@@ -238,10 +239,6 @@ class UsageManager:
238
  today_utc_str = datetime.now(timezone.utc).date().isoformat()
239
  key_data = self._usage_data.setdefault(key, {"daily": {"date": today_utc_str, "models": {}}, "global": {"models": {}}, "model_cooldowns": {}, "failures": {}})
240
 
241
- # Perform a just-in-time daily reset if the date has changed.
242
- if key_data["daily"].get("date") != today_utc_str:
243
- key_data["daily"] = {"date": today_utc_str, "models": {}}
244
-
245
  # Always record a success and reset failures
246
  model_failures = key_data.setdefault("failures", {}).setdefault(model, {})
247
  model_failures["consecutive_failures"] = 0
 
134
  respecting a global deadline.
135
  """
136
  await self._lazy_init()
137
+ await self._reset_daily_stats_if_needed()
138
  self._initialize_key_states(available_keys)
139
 
140
  # This loop continues as long as the global deadline has not been met.
 
239
  today_utc_str = datetime.now(timezone.utc).date().isoformat()
240
  key_data = self._usage_data.setdefault(key, {"daily": {"date": today_utc_str, "models": {}}, "global": {"models": {}}, "model_cooldowns": {}, "failures": {}})
241
 
 
 
 
 
242
  # Always record a success and reset failures
243
  model_failures = key_data.setdefault("failures", {}).setdefault(model, {})
244
  model_failures["consecutive_failures"] = 0