@@ -27,6 +27,14 @@ def __init__(self, config: Dict):
2727 self .last_error = None
2828 self .latencies = [] # Track recent latencies
2929
30+ # Per-provider concurrency control
31+ self .max_concurrent = config .get ('max_concurrent' , None ) # None means no limit
32+ if self .max_concurrent is not None :
33+ self ._semaphore = threading .Semaphore (self .max_concurrent )
34+ logger .info (f"Provider { self .name } limited to { self .max_concurrent } concurrent requests" )
35+ else :
36+ self ._semaphore = None
37+
3038 @property
3139 def client (self ):
3240 """Lazy initialization of OpenAI client"""
@@ -63,6 +71,28 @@ def avg_latency(self) -> float:
6371 if not self .latencies :
6472 return 0
6573 return sum (self .latencies ) / len (self .latencies )
74+
75+ def acquire_slot (self , timeout : Optional [float ] = None ) -> bool :
76+ """
77+ Try to acquire a slot for this provider.
78+ Returns True if acquired, False if timeout or no limit.
79+ """
80+ if self ._semaphore is None :
81+ return True # No limit, always available
82+
83+ return self ._semaphore .acquire (blocking = True , timeout = timeout )
84+
85+ def release_slot (self ):
86+ """Release a slot for this provider."""
87+ if self ._semaphore is not None :
88+ self ._semaphore .release ()
89+
90+ def available_slots (self ) -> Optional [int ]:
91+ """Get number of available slots, None if unlimited."""
92+ if self ._semaphore is None :
93+ return None
94+ # Note: _value is internal but there's no public method to check availability
95+ return self ._semaphore ._value
6696
6797class ProxyClient :
6898 """OpenAI-compatible client that proxies to multiple providers"""
0 commit comments