返回提交历史
Modified
g4f/Provider/__init__.py
+2
-1
Modified
g4f/providers/retry_provider.py
+52
-1
XFEstudio/gpt4free
perf(provider): add circuit breaker and health cooldown to IterListProvider
c5b0f461
代码差异
2 个文件
+54
-2
@@ -1,7 +1,7 @@
1
1
from __future__ import annotations
2
2
3
3
from ..providers.types import BaseProvider, ProviderType
4
from ..providers.retry_provider import RetryProvider, IterListProvider, RotatedProvider
4
from ..providers.retry_provider import RetryProvider, IterListProvider, RotatedProvider, ProviderCircuitBreaker
5
5
from ..providers.base_provider import AsyncProvider, AsyncGeneratorProvider
6
6
from ..providers.create_images import CreateImagesProvider
7
7
@@ -511,6 +511,7 @@ __all__ = [
511
511
"RetryProvider",
512
512
"IterListProvider",
513
513
"RotatedProvider",
514
"ProviderCircuitBreaker",
514
515
"AsyncProvider",
515
516
"AsyncGeneratorProvider",
516
517
"CreateImagesProvider",
@@ -1,5 +1,7 @@
1
1
from __future__ import annotations
2
2
3
import time
4
import os
3
5
import random
4
6
5
7
from ..typing import Dict, Type, List, Messages, AsyncResult
@@ -140,6 +142,43 @@ class RotatedProvider(BaseRetryProvider):
140
142
raise_exceptions(exceptions)
141
143
142
144
145
class ProviderCircuitBreaker:
146
"""Tracks provider failure states to avoid repeatedly querying unavailable providers."""
147
_failures: Dict[str, int] = {}
148
_cooldowns: Dict[str, float] = {}
149
COOLDOWN_SECONDS: float = 60.0
150
MAX_CONSECUTIVE_FAILURES: int = 3
151
152
@classmethod
153
def is_available(cls, provider_name: str) -> bool:
154
if os.environ.get("G4F_DISABLE_CIRCUIT_BREAKER", "").lower() in ("1", "true"):
155
return True
156
if provider_name in cls._cooldowns:
157
if time.time() < cls._cooldowns[provider_name]:
158
return False
159
cls._cooldowns.pop(provider_name, None)
160
cls._failures[provider_name] = cls.MAX_CONSECUTIVE_FAILURES - 1
161
return True
162
163
@classmethod
164
def record_failure(cls, provider_name: str, cooldown: Optional[float] = None):
165
cls._failures[provider_name] = cls._failures.get(provider_name, 0) + 1
166
if cls._failures[provider_name] >= cls.MAX_CONSECUTIVE_FAILURES:
167
cooldown_time = cooldown or cls.COOLDOWN_SECONDS
168
cls._cooldowns[provider_name] = time.time() + cooldown_time
169
debug.log(f"Provider {provider_name} entered cooldown for {cooldown_time}s")
170
171
@classmethod
172
def record_success(cls, provider_name: str):
173
cls._failures.pop(provider_name, None)
174
cls._cooldowns.pop(provider_name, None)
175
176
@classmethod
177
def reset(cls):
178
cls._failures.clear()
179
cls._cooldowns.clear()
180
181
143
182
class IterListProvider(BaseRetryProvider):
144
183
def __init__(
145
184
self, providers: List[Type[BaseProvider]] = [], shuffle: bool = True
@@ -191,8 +230,10 @@ class IterListProvider(BaseRetryProvider):
191
230
if is_content(chunk):
192
231
started = True
193
232
if started:
233
ProviderCircuitBreaker.record_success(provider.__name__)
194
234
return
195
235
except Exception as e:
236
ProviderCircuitBreaker.record_failure(provider.__name__)
196
237
exceptions[provider.__name__] = e
197
238
debug.error(f"{provider.__name__}:", e)
198
239
if started:
@@ -215,7 +256,17 @@ class IterListProvider(BaseRetryProvider):
215
256
216
257
if self.shuffle:
217
258
random.shuffle(resolved_providers)
218
return resolved_providers
259
260
available_providers = []
261
cooling_down_providers = []
262
for p in resolved_providers:
263
p_name = getattr(p, "__name__", str(p))
264
if ProviderCircuitBreaker.is_available(p_name):
265
available_providers.append(p)
266
else:
267
cooling_down_providers.append(p)
268
269
return available_providers + cooling_down_providers
219
270
220
271
221
272
class RetryProvider(IterListProvider):