Mirrowel commited on
Commit
48b6b15
·
1 Parent(s): 8b4ff52

feat(usage): ✨ use request count for credential selection when failed requests consume quota

Browse files

For providers like antigravity where failed requests also consume quota,
credential selection now uses `request_count` instead of `success_count`.
This ensures proper credential rotation when quota is consumed by failed
requests.

- Add `_REQUEST_COUNT_PROVIDERS` set to identify affected providers
- Update `_get_grouped_usage_count` to dynamically select usage field based on provider
- Extend `_get_usage_count` signature to support configurable usage fields

Files changed (1) hide show
  1. src/rotator_library/usage_manager.py +30 -11
src/rotator_library/usage_manager.py CHANGED
@@ -329,6 +329,10 @@ class UsageManager:
329
 
330
  return 1
331
 
 
 
 
 
332
  def _get_grouped_usage_count(self, key: str, model: str) -> int:
333
  """
334
  Get usage count for credential selection, considering quota groups.
@@ -339,6 +343,10 @@ class UsageManager:
339
  Weights are applied per-model to account for models that consume more quota
340
  per request (e.g., Opus might count 2x compared to Sonnet).
341
 
 
 
 
 
342
  Args:
343
  key: Credential identifier
344
  model: Model name (with provider prefix, e.g., "antigravity/claude-sonnet-4-5")
@@ -346,6 +354,15 @@ class UsageManager:
346
  Returns:
347
  Weighted combined usage if grouped, otherwise individual model usage
348
  """
 
 
 
 
 
 
 
 
 
349
  # Check if model is in a quota group
350
  group = self._get_model_quota_group(key, model)
351
 
@@ -356,13 +373,13 @@ class UsageManager:
356
  # Sum weighted usage across all models in the group
357
  total_weighted_usage = 0
358
  for grouped_model in grouped_models:
359
- usage = self._get_usage_count(key, grouped_model)
360
  weight = self._get_model_usage_weight(key, grouped_model)
361
  total_weighted_usage += usage * weight
362
  return total_weighted_usage
363
 
364
  # Not grouped - return individual model usage (no weight applied)
365
- return self._get_usage_count(key, model)
366
 
367
  def _get_usage_field_name(self, credential: str) -> str:
368
  """
@@ -390,7 +407,9 @@ class UsageManager:
390
 
391
  return "daily"
392
 
393
- def _get_usage_count(self, key: str, model: str) -> int:
 
 
394
  """
395
  Get the current usage count for a model from the appropriate usage structure.
396
 
@@ -401,9 +420,12 @@ class UsageManager:
401
  Args:
402
  key: Credential identifier
403
  model: Model name
 
 
 
404
 
405
  Returns:
406
- Usage count (success_count) for the model in the current window/period
407
  """
408
  if self._usage_data is None:
409
  return 0
@@ -412,15 +434,12 @@ class UsageManager:
412
  reset_mode = self._get_reset_mode(key)
413
 
414
  if reset_mode == "per_model":
415
- # New per-model structure: key_data["models"][model]["success_count"]
416
- return key_data.get("models", {}).get(model, {}).get("success_count", 0)
417
  else:
418
- # Legacy structure: key_data["daily"]["models"][model]["success_count"]
419
  return (
420
- key_data.get("daily", {})
421
- .get("models", {})
422
- .get(model, {})
423
- .get("success_count", 0)
424
  )
425
 
426
  # =========================================================================
 
329
 
330
  return 1
331
 
332
+ # Providers where request_count should be used for credential selection
333
+ # instead of success_count (because failed requests also consume quota)
334
+ _REQUEST_COUNT_PROVIDERS = {"antigravity"}
335
+
336
  def _get_grouped_usage_count(self, key: str, model: str) -> int:
337
  """
338
  Get usage count for credential selection, considering quota groups.
 
343
  Weights are applied per-model to account for models that consume more quota
344
  per request (e.g., Opus might count 2x compared to Sonnet).
345
 
346
+ For providers in _REQUEST_COUNT_PROVIDERS (e.g., antigravity), uses
347
+ request_count instead of success_count since failed requests also
348
+ consume quota.
349
+
350
  Args:
351
  key: Credential identifier
352
  model: Model name (with provider prefix, e.g., "antigravity/claude-sonnet-4-5")
 
354
  Returns:
355
  Weighted combined usage if grouped, otherwise individual model usage
356
  """
357
+ # Determine usage field based on provider
358
+ # Some providers (antigravity) count failed requests against quota
359
+ provider = self._get_provider_from_credential(key)
360
+ usage_field = (
361
+ "request_count"
362
+ if provider in self._REQUEST_COUNT_PROVIDERS
363
+ else "success_count"
364
+ )
365
+
366
  # Check if model is in a quota group
367
  group = self._get_model_quota_group(key, model)
368
 
 
373
  # Sum weighted usage across all models in the group
374
  total_weighted_usage = 0
375
  for grouped_model in grouped_models:
376
+ usage = self._get_usage_count(key, grouped_model, usage_field)
377
  weight = self._get_model_usage_weight(key, grouped_model)
378
  total_weighted_usage += usage * weight
379
  return total_weighted_usage
380
 
381
  # Not grouped - return individual model usage (no weight applied)
382
+ return self._get_usage_count(key, model, usage_field)
383
 
384
  def _get_usage_field_name(self, credential: str) -> str:
385
  """
 
407
 
408
  return "daily"
409
 
410
+ def _get_usage_count(
411
+ self, key: str, model: str, field: str = "success_count"
412
+ ) -> int:
413
  """
414
  Get the current usage count for a model from the appropriate usage structure.
415
 
 
420
  Args:
421
  key: Credential identifier
422
  model: Model name
423
+ field: The field to read for usage count (default: "success_count").
424
+ Use "request_count" for providers where failed requests also
425
+ consume quota (e.g., antigravity).
426
 
427
  Returns:
428
+ Usage count for the model in the current window/period
429
  """
430
  if self._usage_data is None:
431
  return 0
 
434
  reset_mode = self._get_reset_mode(key)
435
 
436
  if reset_mode == "per_model":
437
+ # New per-model structure: key_data["models"][model][field]
438
+ return key_data.get("models", {}).get(model, {}).get(field, 0)
439
  else:
440
+ # Legacy structure: key_data["daily"]["models"][model][field]
441
  return (
442
+ key_data.get("daily", {}).get("models", {}).get(model, {}).get(field, 0)
 
 
 
443
  )
444
 
445
  # =========================================================================