litellm/litellm/proxy/hooks
Tufail Akram 84eeebd509
fix(proxy): track and drain management audit log writes on shutdown (#43594)
* fix(proxy): track and drain management audit log writes on shutdown

* fix(proxy): track hook spawns and defer registration in audit log drain

* fix: satisfy LIT006 on audit log task registry by documenting the required cast

* fix(proxy): satisfy LIT006 and LIT010 in audit task registry

* test(proxy): drive the audit drain tests with events instead of wall-clock waits

* fix(proxy): drain audit writes beside the shutdown flushes and track only premium writes

Run the audit drain concurrently with the gateway and request-error flushes so
the two waits overlap inside the teardown budget, register the running audit
task only after the premium check, drop the four duplicate task-failure
loggers, and cover the shutdown order and a real hook spawn with tests

* fix(proxy): start hook audit writes before their email and secret manager calls

Key and user hooks now spawn their tracked audit write first, so a slow
invite email or secret manager call no longer spends the drain budget
before the write starts. The endpoints stop tracking the hook tasks, so
shutdown never waits on hook I/O when audit logging is off.

Adds regression tests for the hook order, the failed block or unblock
update, and the shutdown drain without a database.

* fix(proxy): track only the audit insert, not the code that awaits it

The shutdown drain now waits on each audit row insert itself, so it never waits on a caller's later work and covers every caller without per-endpoint wrappers. The user/new hook still tracks its own task because it reads the user row before the insert

* test(proxy): drain the audit registry in the config audit tests

The audit insert now runs in its own tracked task, one hop after the caller's task, so a single yield no longer reaches it

* fix(proxy): name dropped audit rows in the drain timeout warning

Each tracked audit task is named after its action, table, object id and destination, so a shutdown that drops a row says which one. Adds tests that the drain waits for slow audit callbacks and that the warning names the dropped row

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-16 08:41:34 +00:00
..
litellm_skills refactor(types): replace Any with proven types in 16 files (#45029) 2026-10-07 04:31:51 -07:00
mcp_semantic_filter refactor(proxy): expose public names for private proxy helpers (#45170) 2026-10-07 18:59:27 -07:00
__init__.py refactor(proxy): expose public names for private proxy helpers (#45170) 2026-10-07 18:59:27 -07:00
autorouter_baseline_cache.py fix(router): preserve native baseline identity and accounting (#44960) 2026-10-07 11:00:31 -07:00
azure_content_safety.py refactor(proxy): expose public names for private proxy helpers (#45170) 2026-10-07 18:59:27 -07:00
batch_enqueued_tokens.py refactor(typing): clear 1143 basedpyright Any errors across 273 files (#46574) 2026-10-15 21:08:47 +00:00
batch_rate_limiter.py fix(managed_files): enforce input file ownership on batch and fine-tuning create (#45855) 2026-10-12 13:58:25 -04:00
batch_redis_get.py refactor(proxy): expose public names for private proxy helpers (#45170) 2026-10-07 18:59:27 -07:00
cache_control_check.py refactor(proxy): expose public names for private proxy helpers (#45170) 2026-10-07 18:59:27 -07:00
dynamic_rate_limiter.py refactor(proxy): expose public names for private proxy helpers (#45170) 2026-10-07 18:59:27 -07:00
dynamic_rate_limiter_v3.py refactor(proxy): expose public names for private proxy helpers (#45170) 2026-10-07 18:59:27 -07:00
example_presidio_ad_hoc_recognizer.json fix(presidio_pii_masking.py): enable user to pass ad hoc recognizer for pii masking 2024-02-20 16:01:15 -08:00
key_management_event_hooks.py fix(proxy): track and drain management audit log writes on shutdown (#43594) 2026-10-16 08:41:34 +00:00
max_budget_per_session_limiter.py refactor(proxy): expose public names for private proxy helpers (#45170) 2026-10-07 18:59:27 -07:00
max_iterations_limiter.py refactor(proxy): expose public names for private proxy helpers (#45170) 2026-10-07 18:59:27 -07:00
model_max_budget_limiter.py feat(proxy): report when each per-model budget window resets (#45797) 2026-10-15 10:56:45 -07:00
parallel_request_limiter.py fix(responses): write one spend-log row per native WebSocket turn (#46768) 2026-10-15 19:05:22 -07:00
parallel_request_limiter_v3.py feat(proxy): split Azure PTU deployments across teams with per-team PTU ceilings and PTU-hour reporting (#43043) 2026-10-15 18:44:30 +00:00
prompt_cache_prediction.py refactor(anthropic): move prompt cache observation lookup out of the proxy hook (#45748) 2026-10-10 19:06:16 +00:00
prompt_injection_detection.py refactor(proxy): expose public names for private proxy helpers (#45170) 2026-10-07 18:59:27 -07:00
proxy_track_cost_callback.py fix(proxy): honor disable_end_user_cost_tracking in spend logs and end-user tables (#40945) 2026-10-15 21:48:20 +00:00
rate_limiter_utils.py refactor(prometheus): source PROXY_LLM_PROVIDER_FALLBACK from litellm.constants 2026-09-14 20:29:35 +00:00
README.dynamic_rate_limiter_v3.md [Feat] Fixes to dynamic rate limiter v3 - add saturatation detection (#15119) 2025-10-01 18:35:34 -07:00
responses_id_security.py fix(responses): bill a background response once, on the first read or poll that sees it finished (#45703) 2026-10-15 11:00:14 -07:00
sensitive_data_routing.py refactor(typing): clear 1143 basedpyright Any errors across 273 files (#46574) 2026-10-15 21:08:47 +00:00
user_management_event_hooks.py fix(proxy): track and drain management audit log writes on shutdown (#43594) 2026-10-16 08:41:34 +00:00

Dynamic Rate Limiter v3 - Saturation-Aware Priority-Based Rate Limiting

Overview

The v3 dynamic rate limiter implements saturation-aware rate limiting with priority-based allocation. It balances resource efficiency (allowing unused capacity to be borrowed) with fairness guarantees (enforcing priorities during high load).

Key Behavior:

  • When system is under 80% capacity: Generous mode - allows priority borrowing
  • When system is at/above 80% capacity: Strict mode - enforces normalized priority limits

How It Works

Flow Diagram

┌─────────────────────────────────────────────────────────────┐
│                    Incoming Request                          │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│  1. Check Model Saturation                                   │
│     - Query v3 limiter's Redis counters                      │
│     - Calculate: current_usage / capacity                    │
│     - Returns: 0.0 (empty) to 1.0+ (saturated)              │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
                ┌────────┴────────┐
                │  Saturation?    │
                └────────┬────────┘
                         │
         ┌───────────────┴───────────────┐
         │                               │
         ▼                               ▼
   < 80% (Generous)                >= 80% (Strict)
         │                               │
         ▼                               ▼
┌─────────────────────┐         ┌─────────────────────┐
│  Generous Mode      │         │  Strict Mode        │
│                     │         │                     │
│  - Enforce model-   │         │  - Normalize        │
│    wide capacity    │         │    priority weights │
│  - No priority      │         │    (if over 1.0)    │
│    restrictions     │         │                     │
│  - Allows borrowing │         │  - Create priority- │
│                     │         │    specific         │
│  - First-come-      │         │    descriptors      │
│    first-served     │         │                     │
│    until capacity   │         │  - Enforce strict   │
│                     │         │    limits per       │
│                     │         │    priority         │
└──────────┬──────────┘         └──────────┬──────────┘
           │                               │
           │                               ▼
           │                    ┌──────────────────────┐
           │                    │  Track model usage   │
           │                    │  for future          │
           │                    │  saturation checks   │
           │                    └──────────┬───────────┘
           │                               │
           └───────────────┬───────────────┘
                           │
                           ▼
                    ┌──────────────┐
                    │  v3 Limiter  │
                    │  Check       │
                    └──────┬───────┘
                           │
           ┌───────────────┴───────────────┐
           │                               │
           ▼                               ▼
     OVER_LIMIT                        OK
           │                               │
           ▼                               ▼
   Return 429 Error              Allow Request

Configuration

Priority Reservation

Set priority weights in your proxy configuration:

litellm.priority_reservation = {
    "premium": 0.75,    # 75% of capacity
    "standard": 0.25    # 25% of capacity
}

Priority Reservation Settings

Configure saturation-aware behavior:

litellm.priority_reservation_settings = PriorityReservationSettings(
    default_priority=0.5,           # Default weight for users without explicit priority
    saturation_threshold=0.80,      # 80% - threshold for strict mode enforcement
    tracking_multiplier=10          # 10x - multiplier for non-blocking tracking in strict mode
)

Settings:

  • default_priority (default: 0.5) - Priority weight for users without explicit priority metadata
  • saturation_threshold (default: 0.80) - Saturation level (0.0-1.0) at which strict priority enforcement begins
  • tracking_multiplier (default: 10) - Multiplier for model-wide tracking limits in strict mode

User Priority Assignment

Set priority in user metadata:

user_api_key_dict.metadata = {"priority": "premium"}

Priority Weight Normalization

If priorities sum to > 1.0, they are automatically normalized:

Input:  {key_a: 0.60, key_b: 0.80} = 1.40 total
Output: {key_a: 0.43, key_b: 0.57} = 1.00 total

This ensures total allocation never exceeds model capacity.

Implementation Details

Saturation Detection

  • Queries v3 limiter's Redis counters for model-wide usage
  • Checks both RPM and TPM, returns higher saturation value
  • Non-blocking reads (doesn't increment counters)

Mode Selection

Generous Mode (< 80% saturation):

  • Creates single model-wide descriptor
  • Enforces total capacity only
  • Allows any priority to use available capacity
  • Prevents over-subscription via model-wide limit

Strict Mode (>= 80% saturation):

  • Creates priority-specific descriptors with normalized weights
  • Each priority gets its reserved allocation
  • Tracks model-wide usage separately (non-blocking, 10x multiplier)
  • Ensures fairness under load

Test scenarios covered:

  1. No rate limiting when under capacity
  2. Priority queue behavior during saturation
  3. Spillover capacity for default keys
  4. Over-allocated priorities with normalization
  5. Default priority value handling

_PROXY_DynamicRateLimitHandlerV3

Main handler class inheriting from CustomLogger.

Key Methods:

  • async_pre_call_hook() - Main entry point, routes to generous/strict mode
  • _check_model_saturation() - Queries Redis for current usage
  • _handle_generous_mode() - Enforces model-wide capacity only
  • _handle_strict_mode() - Enforces normalized priority limits
  • _normalize_priority_weights() - Handles over-allocation
  • _create_priority_based_descriptors() - Creates rate limit descriptors