bahi-bh commited on
Commit
ae69671
·
verified ·
1 Parent(s): c160687

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +168 -456
app.py CHANGED
@@ -1,498 +1,210 @@
1
  import time
2
  import random
3
  import traceback
4
- import asyncio
5
- import hashlib
6
- from typing import Optional, Dict, List, Any
7
- from dataclasses import dataclass, field
8
- from datetime import datetime
9
- from contextlib import asynccontextmanager
10
-
11
- from fastapi import FastAPI, BackgroundTasks, Request, HTTPException
12
- from fastapi.responses import JSONResponse
13
- from fastapi.middleware.cors import CORSMiddleware
14
  from pydantic import BaseModel
15
  import uvicorn
16
 
17
  from g4f.client import Client
18
  from g4f.Provider import __providers__
19
 
20
- # إعدادات النظام العامة
21
  MODEL = "gpt-4o-mini"
22
- MAX_CONCURRENT_REQUESTS = 3 # الحد الأقصى للطلبات المتزامنة من الخادم
23
- RESPONSE_TIMEOUT = 35 # ثواني
24
- CACHE_MAX_SIZE = 100 # حجم الكاش
25
- CACHE_TTL = 300 # الكاش يعيش 5 دقائق
26
-
27
- # بلاك ليست للمزودين المعروفين بأنهم يحتاجون auth أو يعطون مشاكل دائمة
28
- BLOCKED_KEYWORDS = [
29
- "openai", "qwen", "copilot", "gemini", "claude",
30
- "google", "meta", "tecnai", "liaobots", "deepinfra",
31
- "sambanova", "moonshot", "theb", "huggingchat"
32
- ]
33
-
34
-
35
- # ==================== نظام الإحصائيات والتتبع ====================
36
- @dataclass
37
- class ProviderStats:
38
- """إحصائيات كل مزود لاتخاذ قرارات ذكية"""
39
- name: str
40
- success_count: int = 0
41
- fail_count: int = 0
42
- avg_response_time: float = 0.0
43
- last_used: float = 0.0
44
- consecutive_failures: int = 0
45
- circuit_status: str = "closed" # closed (عادي), open (مفصول), half-open (اختبار)
46
- cooldown_until: float = 0.0
47
-
48
- @property
49
- def total_requests(self) -> int:
50
- return self.success_count + self.fail_count
51
-
52
- @property
53
- def success_rate(self) -> float:
54
- if self.total_requests == 0:
55
- return 1.0
56
- return self.success_count / self.total_requests
57
-
58
- @property
59
- def weight(self) -> float:
60
- """وزن ديناميكي: يفضل المزود الناجح السريع"""
61
- if self.circuit_status == "open":
62
- return 0.0
63
- if self.circuit_status == "half-open":
64
- return 0.5
65
-
66
- # وزن يعتمد على معدل النجاح والسرعة
67
- speed_factor = max(0.1, 1.0 / (self.avg_response_time + 1))
68
- return self.success_rate * speed_factor
69
-
70
- def record_success(self, response_time: float):
71
- self.success_count += 1
72
- self.consecutive_failures = 0
73
- # حساب المتوسط المتحرك
74
- if self.avg_response_time == 0:
75
- self.avg_response_time = response_time
76
- else:
77
- self.avg_response_time = (self.avg_response_time * 0.7) + (response_time * 0.3)
78
- self.last_used = time.time()
79
-
80
- # إعادة Circuit Breaker للعمل
81
- if self.circuit_status == "half-open":
82
- self.circuit_status = "closed"
83
-
84
- def record_failure(self):
85
- self.fail_count += 1
86
- self.consecutive_failures += 1
87
- self.last_used = time.time()
88
-
89
- # Circuit Breaker Logic
90
- if self.consecutive_failures >= 3:
91
- self.circuit_status = "open"
92
- self.cooldown_until = time.time() + 120 # 2 دقيقة حظر
93
- elif self.consecutive_failures >= 1:
94
- # حظر خفيف عند أول فشل
95
- self.cooldown_until = time.time() + 30
96
-
97
-
98
- # ==================== الذاكرة المؤقتة (Cache) ====================
99
- class LRUCache:
100
- """كاش محلي ذكي لتجنب طلب نفس السؤال مراراً"""
101
- def __init__(self, maxsize: int = 100, ttl: int = 300):
102
- self.maxsize = maxsize
103
- self.ttl = ttl
104
- self.cache: Dict[str, Dict[str, Any]] = {}
105
- self.access_order: List[str] = []
106
- self.lock = asyncio.Lock()
107
-
108
- def _generate_key(self, prompt: str, model: str) -> str:
109
- return hashlib.md5(f"{model}:{prompt}".encode()).hexdigest()
110
-
111
- async def get(self, prompt: str, model: str) -> Optional[Dict]:
112
- key = self._generate_key(prompt, model)
113
- async with self.lock:
114
- if key not in self.cache:
115
- return None
116
-
117
- entry = self.cache[key]
118
- if time.time() - entry["timestamp"] > self.ttl:
119
- del self.cache[key]
120
- self.access_order.remove(key)
121
- return None
122
-
123
- # تحديث أمر الوصول (LRU)
124
- self.access_order.remove(key)
125
- self.access_order.append(key)
126
- return entry["data"]
127
-
128
- async def set(self, prompt: str, model: str, data: Dict):
129
- key = self._generate_key(prompt, model)
130
- async with self.lock:
131
- if key in self.cache:
132
- self.access_order.remove(key)
133
-
134
- while len(self.access_order) >= self.maxsize:
135
- oldest = self.access_order.pop(0)
136
- del self.cache[oldest]
137
-
138
- self.cache[key] = {
139
- "timestamp": time.time(),
140
- "data": data
141
- }
142
- self.access_order.append(key)
143
-
144
-
145
- # ==================== مدير المزودين الذكي ====================
146
- class ProviderManager:
147
- def __init__(self, providers: list):
148
- self.providers = providers
149
- self.stats: Dict[str, ProviderStats] = {
150
- p.__name__: ProviderStats(name=p.__name__)
151
- for p in providers
152
- }
153
- self.lock = asyncio.Lock()
154
- self.global_semaphore = asyncio.Semaphore(MAX_CONCURRENT_REQUESTS)
155
- self.rate_limiters: Dict[str, asyncio.Semaphore] = {
156
- p.__name__: asyncio.Semaphore(1) for p in providers
157
- }
158
-
159
- async def get_ready_providers(self) -> List[Any]:
160
- """جلب المزودين الجاهزين فقط"""
161
- ready = []
162
- now = time.time()
163
- async with self.lock:
164
- for provider in self.providers:
165
- stat = self.stats[provider.__name__]
166
-
167
- # فحص التبريد
168
- if stat.cooldown_until > now:
169
- if stat.circuit_status == "open" and now > stat.cooldown_until:
170
- stat.circuit_status = "half-open"
171
- stat.cooldown_until = 0
172
- else:
173
- continue
174
-
175
- if stat.weight > 0:
176
- ready.append((provider, stat.weight))
177
-
178
- # ترتيب حسب الوزن (الأفضل أولاً)
179
- ready.sort(key=lambda x: x[1], reverse=True)
180
- return [p for p, _ in ready]
181
-
182
- async def mark_success(self, provider, elapsed: float):
183
- async with self.lock:
184
- self.stats[provider.__name__].record_success(elapsed)
185
-
186
- async def mark_failure(self, provider, error: str):
187
- async with self.lock:
188
- self.stats[provider.__name__].record_failure()
189
-
190
- async def get_stats(self) -> Dict:
191
- async with self.lock:
192
- return {
193
- name: {
194
- "success_rate": round(s.success_rate * 100, 2),
195
- "avg_time": round(s.avg_response_time, 2),
196
- "circuit": s.circuit_status,
197
- "total": s.total_requests
198
- }
199
- for name, s in self.stats.items()
200
- }
201
 
202
 
203
- # ==================== المحرك الرئيسي (Engine) ====================
204
  class SmartG4F:
 
205
  def __init__(self):
206
- # جمع المزودين
207
- self.all_providers = self._collect_providers()
208
- random.shuffle(self.all_providers)
209
-
210
- self.manager = ProviderManager(self.all_providers)
211
- self.cache = LRUCache(maxsize=CACHE_MAX_SIZE, ttl=CACHE_TTL)
212
- self.client = Client()
213
- self.request_queue: asyncio.Queue = asyncio.Queue(maxsize=50)
214
- self.processing = set() # تتبع الطلبات النشطة
215
-
216
- def _collect_providers(self) -> list:
 
 
 
 
 
 
 
 
 
 
 
 
217
  providers = []
218
- for provider in __providers__:
219
- try:
220
- if not getattr(provider, "working", False):
221
- continue
222
- if getattr(provider, "needs_auth", False):
223
- continue
224
- if getattr(provider, "use_nodriver", False):
225
- continue
226
-
227
- name = provider.__name__.lower()
228
- if any(x in name for x in BLOCKED_KEYWORDS):
229
- continue
230
-
231
- providers.append(provider)
232
- except:
233
- continue
234
  return providers
235
-
236
- def _extract_error_code(self, error_str: str) -> str:
237
- """استخراج نوع الخطأ بطريقة ذكية"""
238
- err = error_str.lower()
239
- if "429" in err or "too many requests" in err or "rate limit" in err:
240
- return "RATE_LIMIT"
241
- elif "timeout" in err or "timed out" in err:
242
- return "TIMEOUT"
243
- elif "cloudflare" in err or "cf-ray" in err:
244
- return "CLOUDFLARE"
245
- elif "403" in err or "forbidden" in err:
246
- return "FORBIDDEN"
247
- elif "502" in err or "503" in err or "bad gateway" in err:
248
- return "SERVER_ERROR"
249
- elif "empty response" in err:
250
- return "EMPTY"
251
- else:
252
- return "UNKNOWN"
253
-
254
- async def ask(self, prompt: str, max_retries: int = 3) -> Dict:
255
- # 1. التحقق من الكاش أولاً
256
- cached = await self.cache.get(prompt, MODEL)
257
- if cached:
258
- return {**cached, "cached": True}
259
-
260
- # 2. إذا كان الخادم تحت الضغط، ارفض الطلبات الجديدة بأسلوب ناعم
261
- if len(self.processing) >= MAX_CONCURRENT_REQUESTS * 2:
262
- return {
263
- "success": False,
264
- "error": "Server is currently at high load. Please retry in 10 seconds.",
265
- "retry_after": 10
266
- }
267
-
268
- request_id = id(prompt) + time.time()
269
- self.processing.add(request_id)
270
-
271
- try:
272
- errors = []
273
- last_exception = None
274
-
275
- for attempt in range(max_retries):
276
- providers = await self.manager.get_ready_providers()
277
-
278
- if not providers:
279
- # انتظار قصير قبل إعادة المحاولة لعل بعض المزودين يتاحون
280
- await asyncio.sleep(min(2 ** attempt, 10))
281
- continue
282
-
283
- # اختيار المزود الأفضل للمحاولة الحالية (وليس عشوائياً)
284
- provider = providers[0] # الأعلى وزناً
285
-
286
- # Rate Limiting لكل مزود (لا نضرب نفس المزود بطلبين في نفس الثانية)
287
- async with self.manager.rate_limiters[provider.__name__]:
288
- print(f"[TRY {attempt+1}] Using {provider.__name__} (weight: {self.manager.stats[provider.__name__].weight:.2f})")
289
-
290
- start_time = time.time()
291
-
292
- try:
293
- # تشغيل الطلب في Thread منفصل
294
- response = await asyncio.wait_for(
295
- asyncio.to_thread(
296
- self.client.chat.completions.create,
297
- model=MODEL,
298
- provider=provider,
299
- messages=[{"role": "user", "content": prompt}],
300
- timeout=RESPONSE_TIMEOUT,
301
- ),
302
- timeout=RESPONSE_TIMEOUT + 5
303
- )
304
-
305
- text = response.choices[0].message.content
306
- if not text:
307
- raise Exception("Empty response")
308
-
309
- elapsed = time.time() - start_time
310
-
311
- # تسجيل النجاح
312
- await self.manager.mark_success(provider, elapsed)
313
-
314
- result = {
315
- "success": True,
316
- "provider": provider.__name__,
317
- "response": text,
318
- "response_time": round(elapsed, 2),
319
- "attempt": attempt + 1,
320
- "cached": False
321
  }
322
-
323
- # تخزين النتيجة في الكاش
324
- await self.cache.set(prompt, MODEL, result)
325
-
326
- return result
327
-
328
- except asyncio.TimeoutError:
329
- await self.manager.mark_failure(provider, "timeout")
330
- errors.append({
331
- "provider": provider.__name__,
332
- "error": "TIMEOUT",
333
- "attempt": attempt + 1
334
- })
335
- # لا ننتظر كثيراً مع Timeout، ننتقل سريعاً
336
- continue
337
-
338
- except Exception as e:
339
- err_str = str(e)
340
- err_type = self._extract_error_code(err_str)
341
- await self.manager.mark_failure(provider, err_str)
342
-
343
- errors.append({
344
- "provider": provider.__name__,
345
- "error": err_type,
346
- "detail": err_str[:200],
347
- "attempt": attempt + 1
348
- })
349
-
350
- # Backoff ذكي: لا ننام إذا عندنا مزودين آخرين جاهزين
351
- remaining = await self.manager.get_ready_providers()
352
- if len(remaining) <= 1 and attempt < max_retries - 1:
353
- await asyncio.sleep(random.uniform(1.0, 3.0))
354
-
355
- return {
356
- "success": False,
357
- "error": "All providers failed after retries",
358
- "errors": errors
359
- }
360
-
361
- finally:
362
- self.processing.discard(request_id)
363
-
364
- async def health_check(self) -> Dict:
365
- """فحص صحة النظام"""
366
- providers = await self.manager.get_ready_providers()
367
- stats = await self.manager.get_stats()
368
-
369
- best_providers = sorted(
370
- stats.items(),
371
- key=lambda x: x[1].get("success_rate", 0),
372
- reverse=True
373
- )[:5]
374
-
375
  return {
376
- "status": "healthy" if len(providers) > 0 else "degraded",
377
- "available_providers": len(providers),
378
- "total_providers": len(self.all_providers),
379
- "active_requests": len(self.processing),
380
- "top_performers": [name for name, _ in best_providers],
381
- "provider_stats": stats
382
  }
383
 
384
 
385
- # ==================== إعداد FastAPI ====================
386
- @asynccontextmanager
387
- async def lifespan(app: FastAPI):
388
- # بدء التشغيل
389
- print(f"[+] Total providers collected: {len(ai.all_providers)}")
390
- print("[+] System ready and optimized for minimal rate-limiting")
391
-
392
- # بدء مهمة Health Check دورية في الخلفية (اختياري)
393
- # asyncio.create_task(periodic_health_reporter())
394
-
395
- yield
396
-
397
- # إيقاف التشغيل بأمان
398
- print("[+] Shutting down gracefully...")
399
-
400
- app = FastAPI(
401
- title="Smart G4F Gateway",
402
- description="AI Gateway optimized against Rate Limits",
403
- version="4.0.0",
404
- lifespan=lifespan
405
- )
406
-
407
- app.add_middleware(
408
- CORSMiddleware,
409
- allow_origins=["*"],
410
- allow_methods=["*"],
411
- allow_headers=["*"],
412
- )
413
 
414
  ai = SmartG4F()
415
 
416
 
417
- # ==================== نماذج البيانات ====================
418
  class Query(BaseModel):
419
  prompt: str
420
- max_retries: Optional[int] = 3
421
-
422
 
423
- class BatchQuery(BaseModel):
424
- prompts: List[str]
425
 
426
-
427
- # ==================== Endpoints ====================
428
  @app.get("/")
429
  async def home():
430
- health = await ai.health_check()
431
- return health
432
-
433
-
434
- @app.get("/stats")
435
- async def stats():
436
- return await ai.health_check()
437
 
438
-
439
- @app.post("/ask")
440
- async def ask(query: Query, request: Request):
441
- # التحقق من حجم الطلب
442
- if len(query.prompt) > 8000:
443
- raise HTTPException(status_code=413, detail="Prompt too large")
444
-
445
- result = await ai.ask(query.prompt, max_retries=min(query.max_retries or 3, 5))
446
-
447
- if result.get("success"):
448
- return result
449
- else:
450
- return JSONResponse(
451
- status_code=503 if "retry_after" in result else 424,
452
- content=result,
453
- headers={"Retry-After": str(result.get("retry_after", 5))} if "retry_after" in result else {}
454
- )
455
-
456
-
457
- @app.post("/ask/batch")
458
- async def ask_batch(batch: BatchQuery):
459
- """معالجة دفعة من الأسئلة بأمان (لكن ليس في نفس اللحظة بالضبط)"""
460
- if len(batch.prompts) > 10:
461
- raise HTTPException(status_code=400, detail="Max 10 prompts per batch")
462
-
463
- results = []
464
- for prompt in batch.prompts:
465
- result = await ai.ask(prompt, max_retries=2)
466
- results.append(result)
467
- # تأخير 1 ثانية بين كل دفعة والثانية لتجنب Rate Limit الشامل
468
- await asyncio.sleep(1)
469
-
470
- successful = sum(1 for r in results if r.get("success"))
471
  return {
472
- "batch_size": len(batch.prompts),
473
- "successful": successful,
474
- "failed": len(batch.prompts) - successful,
475
- "results": results
476
  }
477
 
478
 
479
- @app.get("/providers")
480
- async def list_providers():
481
- """عرض حالة المزودين"""
482
- stats = await ai.manager.get_stats()
483
- ready = [p.__name__ for p in await ai.manager.get_ready_providers()]
484
- return {
485
- "total": len(ai.all_providers),
486
- "ready": ready,
487
- "stats": stats
488
- }
489
 
490
 
491
- # ==================== التشغيل ====================
492
  if __name__ == "__main__":
 
493
  uvicorn.run(
494
  app,
495
  host="0.0.0.0",
496
- port=7860,
497
- workers=1 # لا تزيد عن 1 إلا إذا فصلت الذاكرة (state)
498
- )
 
1
  import time
2
  import random
3
  import traceback
4
+
5
+ from fastapi import FastAPI
 
 
 
 
 
 
 
 
6
  from pydantic import BaseModel
7
  import uvicorn
8
 
9
  from g4f.client import Client
10
  from g4f.Provider import __providers__
11
 
 
12
  MODEL = "gpt-4o-mini"
13
+
14
+ MAX_RETRIES = 10
15
+ TIMEOUT = 60
16
+
17
+ SAFE_PROVIDERS = []
18
+
19
+ for provider in __providers__:
20
+
21
+ try:
22
+
23
+ name = provider.__name__.lower()
24
+
25
+ if not getattr(provider, "working", False):
26
+ continue
27
+
28
+ if getattr(provider, "needs_auth", False):
29
+ continue
30
+
31
+ if getattr(provider, "use_nodriver", False):
32
+ continue
33
+
34
+ blocked = [
35
+ "openai",
36
+ "qwen",
37
+ "copilot",
38
+ "gemini",
39
+ "claude",
40
+ ]
41
+
42
+ if any(x in name for x in blocked):
43
+ continue
44
+
45
+ SAFE_PROVIDERS.append(provider)
46
+
47
+ except:
48
+ pass
49
+
50
+ random.shuffle(SAFE_PROVIDERS)
51
+
52
+ print(f"[+] SAFE PROVIDERS: {len(SAFE_PROVIDERS)}")
53
+
54
+ client = Client()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
 
 
57
  class SmartG4F:
58
+
59
  def __init__(self):
60
+
61
+ self.good = []
62
+ self.bad = {}
63
+
64
+ def mark_bad(self, provider, cooldown=300):
65
+
66
+ self.bad[provider.__name__] = time.time() + cooldown
67
+
68
+ def is_bad(self, provider):
69
+
70
+ expire = self.bad.get(provider.__name__)
71
+
72
+ if not expire:
73
+ return False
74
+
75
+ if time.time() > expire:
76
+ del self.bad[provider.__name__]
77
+ return False
78
+
79
+ return True
80
+
81
+ def provider_pool(self):
82
+
83
  providers = []
84
+
85
+ providers.extend(self.good)
86
+
87
+ for p in SAFE_PROVIDERS:
88
+
89
+ if p not in providers and not self.is_bad(p):
90
+ providers.append(p)
91
+
92
+ random.shuffle(providers)
93
+
 
 
 
 
 
 
94
  return providers
95
+
96
+ def ask(self, prompt):
97
+
98
+ errors = []
99
+
100
+ for attempt in range(MAX_RETRIES):
101
+
102
+ pool = self.provider_pool()
103
+
104
+ if not pool:
105
+
106
+ return {
107
+ "success": False,
108
+ "error": "No providers available"
109
+ }
110
+
111
+ provider = random.choice(pool)
112
+
113
+ print(f"[TRY {attempt+1}] {provider.__name__}")
114
+
115
+ try:
116
+
117
+ response = client.chat.completions.create(
118
+ model=MODEL,
119
+ provider=provider,
120
+ messages=[
121
+ {
122
+ "role": "user",
123
+ "content": prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  }
125
+ ],
126
+ timeout=TIMEOUT,
127
+ )
128
+
129
+ text = response.choices[0].message.content
130
+
131
+ if not text:
132
+ raise Exception("Empty response")
133
+
134
+ if provider not in self.good:
135
+ self.good.append(provider)
136
+
137
+ return {
138
+ "success": True,
139
+ "provider": provider.__name__,
140
+ "response": text
141
+ }
142
+
143
+ except Exception as e:
144
+
145
+ err = str(e).lower()
146
+
147
+ traceback.print_exc()
148
+
149
+ errors.append({
150
+ "provider": provider.__name__,
151
+ "error": str(e)
152
+ })
153
+
154
+ if "429" in err:
155
+
156
+ self.mark_bad(provider, 600)
157
+ time.sleep(random.randint(10, 20))
158
+
159
+ elif "cloudflare" in err:
160
+
161
+ self.mark_bad(provider, 1200)
162
+ time.sleep(20)
163
+
164
+ elif "timeout" in err:
165
+
166
+ self.mark_bad(provider, 300)
167
+
168
+ else:
169
+
170
+ self.mark_bad(provider, 180)
171
+
172
+ continue
173
+
 
 
 
 
174
  return {
175
+ "success": False,
176
+ "errors": errors
 
 
 
 
177
  }
178
 
179
 
180
+ app = FastAPI()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
  ai = SmartG4F()
183
 
184
 
 
185
  class Query(BaseModel):
186
  prompt: str
 
 
187
 
 
 
188
 
 
 
189
  @app.get("/")
190
  async def home():
 
 
 
 
 
 
 
191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  return {
193
+ "status": "running",
194
+ "providers": len(SAFE_PROVIDERS)
 
 
195
  }
196
 
197
 
198
+ @app.post("/ask")
199
+ async def ask(query: Query):
200
+
201
+ return ai.ask(query.prompt)
 
 
 
 
 
 
202
 
203
 
 
204
  if __name__ == "__main__":
205
+
206
  uvicorn.run(
207
  app,
208
  host="0.0.0.0",
209
+ port=7860
210
+ )