bahi-bh commited on
Commit
b8579bc
·
verified ·
1 Parent(s): 465c178

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +165 -98
app.py CHANGED
@@ -1,160 +1,227 @@
1
  import time
2
  import traceback
3
  import asyncio
 
 
 
 
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
- # إعدادات محسنة
13
  MODEL = "gpt-4o-mini"
14
- MAX_RETRIES = 3 # تقليل المحاولات: 3 كافية جداً لتوفير وقت المستخدم
15
- TIMEOUT = 30 # تقليل وقت الانتظار: 60 ثانية كثيرة جداً وتعطل الخادم
16
- MAX_CONCURRENT = 5 # أقصى عدد طلبات متوازية (مهم جداً لمنع الرات ليميت الشامل)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
- # تجميع المزودين الآمنين
19
  SAFE_PROVIDERS = []
20
- BLOCKED_KEYWORDS = ["openai", "qwen", "copilot", "gemini", "claude", "google", "meta", "tecnai", "liaobots"]
21
-
22
  for provider in __providers__:
23
  try:
24
  if not getattr(provider, "working", False): continue
25
  if getattr(provider, "needs_auth", False): continue
26
  if getattr(provider, "use_nodriver", False): continue
27
-
28
  name = provider.__name__.lower()
29
  if any(x in name for x in BLOCKED_KEYWORDS): continue
30
-
31
  SAFE_PROVIDERS.append(provider)
32
- except:
33
  pass
34
 
35
- print(f"[+] SAFE PROVIDERS: {len(SAFE_PROVIDERS)}")
36
-
37
- client = Client()
38
 
39
 
40
  class SmartG4F:
41
  def __init__(self):
42
- self.good = []
43
- self.bad = {} # provider_name: expire_timestamp
44
- self.current_index = 0 # مؤشر الدوران المنتظم (Round Robin)
45
- self.lock = asyncio.Lock() # حماية البيانات من التزامن
46
- self.semaphore = asyncio.Semaphore(MAX_CONCURRENT) # حماية الخادم من الضغط
47
-
48
- def mark_bad(self, provider, cooldown):
 
 
49
  self.bad[provider.__name__] = time.time() + cooldown
50
 
51
- def is_bad(self, provider):
52
  expire = self.bad.get(provider.__name__)
53
- if not expire: return False
 
54
  if time.time() > expire:
55
- del self.bad[provider.__name__]
56
  return False
57
  return True
58
 
59
  async def get_next_provider(self):
60
  async with self.lock:
61
- # بناء حوض المزودين المتاحين في هذه اللحظة
62
  pool = [p for p in self.good if not self.is_bad(p)]
63
-
64
  for p in SAFE_PROVIDERS:
65
  if p not in pool and not self.is_bad(p):
66
  pool.append(p)
67
-
68
- if not pool: return None
69
-
70
- # 🧠 الذكاء هنا: Round Robin يضمن عدم ضرب نفس المزود مرتين متتاليتين
71
  provider = pool[self.current_index % len(pool)]
72
  self.current_index += 1
73
  return provider
74
 
75
- async def ask(self, prompt):
76
- errors = []
77
-
78
- # إجبار الخادم على عدم تجاوز الحد الأقصى للطلبات المتوازية
 
 
 
 
 
 
 
 
 
79
  async with self.semaphore:
80
- for attempt in range(MAX_RETRIES):
81
- provider = await self.get_next_provider()
82
-
83
- if not provider:
84
- return {"success": False, "error": "All providers are currently on cooldown. Try again later."}
85
-
86
- print(f"[TRY {attempt+1}] {provider.__name__}")
87
-
88
- try:
89
- # تشغيل الكود المتزامن (sync) في Thread منفصل لئلا يعلق الـ FastAPI
90
- response = await asyncio.to_thread(
91
- client.chat.completions.create,
92
- model=MODEL,
93
- provider=provider,
94
- messages=[{"role": "user", "content": prompt}],
95
- timeout=TIMEOUT,
96
- )
97
-
98
- text = response.choices[0].message.content
99
-
100
- if not text:
101
- raise Exception("Empty response")
102
-
103
- # إضافة المزود الناجح للقائمة المفضلة
104
- async with self.lock:
105
- if provider not in self.good:
106
- self.good.append(provider)
107
-
108
- return {
109
- "success": True,
110
- "provider": provider.__name__,
111
- "response": text
112
- }
113
-
114
- except Exception as e:
115
- err = str(e).lower()
116
- traceback.print_exc()
117
-
118
- errors.append({
119
- "provider": provider.__name__,
120
- "error": str(e)
121
- })
122
-
123
- async with self.lock:
124
- # 🧠 فترات تبريد ذكية: قصيرة لأن الرات ليميت المجاني يزول بسرعة
125
- if "429" in err:
126
- # 60 ثانية كافية لزوال الرات ليميت، وليس 10 دقائق!
127
- self.mark_bad(provider, 60)
128
- elif "cloudflare" in err:
129
- self.mark_bad(provider, 300) # 5 دقائق
130
- elif "timeout" in err:
131
- self.mark_bad(provider, 120) # دقيقتان
132
- else:
133
- self.mark_bad(provider, 60) # دقيقة واحدة فقط
134
-
135
- # ننتقل للمزود التالي فوراً بدون إيقاف الخادم (تم حذف time.sleep)
136
- continue
137
-
138
- return {"success": False, "errors": errors}
139
-
140
- # إعداد FastAPI
141
- app = FastAPI()
142
- ai = SmartG4F()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
  class Query(BaseModel):
145
  prompt: str
 
 
146
 
147
  @app.get("/")
148
  async def home():
 
149
  return {
150
  "status": "running",
151
  "safe_providers": len(SAFE_PROVIDERS),
152
- "healthy_providers": len(ai.good)
 
 
 
 
 
 
 
 
 
 
 
 
153
  }
154
 
 
155
  @app.post("/ask")
156
  async def ask(query: Query):
 
 
 
157
  return await ai.ask(query.prompt)
158
 
 
159
  if __name__ == "__main__":
160
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  import time
2
  import traceback
3
  import asyncio
4
+ import logging
5
+ from collections import deque
6
+ from contextlib import asynccontextmanager
7
+ from typing import Optional
8
 
9
+ from fastapi import FastAPI, HTTPException
10
+ from fastapi.middleware.cors import CORSMiddleware
11
  from pydantic import BaseModel
12
  import uvicorn
13
 
14
  from g4f.client import Client
15
  from g4f.Provider import __providers__
16
 
17
+ # ============ إعدادات محسنة ============
18
  MODEL = "gpt-4o-mini"
19
+ MAX_RETRIES = 3
20
+ TIMEOUT = 30 # timeout لكل طلب منفرد
21
+ GLOBAL_TIMEOUT = 60 # 🆕 timeout إجمالي لكل عملية (يمنع بقاء الطلب معلقاً)
22
+ MAX_CONCURRENT = 5
23
+ GOOD_POOL_SIZE = 10 # 🆕 حجم القائمة المفضلة (dequeue بدلاً من قائمة غير محدودة)
24
+
25
+ # ============ Logging منظم ============
26
+ logging.basicConfig(
27
+ level=logging.INFO,
28
+ format="%(asctime)s | %(levelname)s | %(message)s",
29
+ datefmt="%H:%M:%S",
30
+ )
31
+ log = logging.getLogger("g4f-smart")
32
+
33
+ # ============ تجميع المزودين ============
34
+ BLOCKED_KEYWORDS = ["openai", "qwen", "copilot", "gemini", "claude",
35
+ "google", "meta", "tecnai", "liaobots"]
36
 
 
37
  SAFE_PROVIDERS = []
 
 
38
  for provider in __providers__:
39
  try:
40
  if not getattr(provider, "working", False): continue
41
  if getattr(provider, "needs_auth", False): continue
42
  if getattr(provider, "use_nodriver", False): continue
 
43
  name = provider.__name__.lower()
44
  if any(x in name for x in BLOCKED_KEYWORDS): continue
 
45
  SAFE_PROVIDERS.append(provider)
46
+ except Exception:
47
  pass
48
 
49
+ log.info(f"[+] SAFE PROVIDERS: {len(SAFE_PROVIDERS)}")
 
 
50
 
51
 
52
  class SmartG4F:
53
  def __init__(self):
54
+ self.client = Client() # 🆕 داخل الكلاس
55
+ # 🆕 deque بحد أقصى: القائمة المفضلة لا تنمو إلى ما لا نهاية
56
+ self.good = deque(maxlen=GOOD_POOL_SIZE)
57
+ self.bad: dict[str, float] = {}
58
+ self.current_index = 0
59
+ self.lock = asyncio.Lock()
60
+ self.semaphore = asyncio.Semaphore(MAX_CONCURRENT)
61
+
62
+ def mark_bad(self, provider, cooldown: float):
63
  self.bad[provider.__name__] = time.time() + cooldown
64
 
65
+ def is_bad(self, provider) -> bool:
66
  expire = self.bad.get(provider.__name__)
67
+ if not expire:
68
+ return False
69
  if time.time() > expire:
70
+ self.bad.pop(provider.__name__, None)
71
  return False
72
  return True
73
 
74
  async def get_next_provider(self):
75
  async with self.lock:
76
+ # 🆕 pool واحد موحّد: good أولاً ثم SAFE_PROVIDERS
77
  pool = [p for p in self.good if not self.is_bad(p)]
 
78
  for p in SAFE_PROVIDERS:
79
  if p not in pool and not self.is_bad(p):
80
  pool.append(p)
81
+
82
+ if not pool:
83
+ return None
84
+
85
  provider = pool[self.current_index % len(pool)]
86
  self.current_index += 1
87
  return provider
88
 
89
+ async def _call_provider(self, provider, prompt: str):
90
+ """دالة مساعدة قابلة للإلغاء مع timeout."""
91
+ return await asyncio.to_thread(
92
+ self.client.chat.completions.create,
93
+ model=MODEL,
94
+ provider=provider,
95
+ messages=[{"role": "user", "content": prompt}],
96
+ timeout=TIMEOUT,
97
+ )
98
+
99
+ async def ask(self, prompt: str) -> dict:
100
+ errors: list[dict] = []
101
+
102
  async with self.semaphore:
103
+ # 🆕 حماية من التعليق الكلي
104
+ try:
105
+ async with asyncio.timeout(GLOBAL_TIMEOUT):
106
+ for attempt in range(MAX_RETRIES):
107
+ provider = await self.get_next_provider()
108
+ if not provider:
109
+ return {
110
+ "success": False,
111
+ "error": "All providers are currently on cooldown."
112
+ }
113
+
114
+ log.info(f"[TRY {attempt+1}/{MAX_RETRIES}] {provider.__name__}")
115
+
116
+ try:
117
+ response = await self._call_provider(provider, prompt)
118
+ text = response.choices[0].message.content
119
+
120
+ if not text:
121
+ raise ValueError("Empty response")
122
+
123
+ async with self.lock:
124
+ # 🆕 deque تحافظ على الحجم ثابتاً تلقائياً
125
+ if provider in self.good:
126
+ self.good.remove(provider)
127
+ self.good.appendleft(provider) # الأحدث في الأول
128
+
129
+ return {
130
+ "success": True,
131
+ "provider": provider.__name__,
132
+ "response": text,
133
+ }
134
+
135
+ except asyncio.CancelledError:
136
+ raise
137
+ except Exception as e:
138
+ err = str(e).lower()
139
+ log.warning(f"{provider.__name__} failed: {e}")
140
+
141
+ errors.append({
142
+ "provider": provider.__name__,
143
+ "error": str(e),
144
+ })
145
+
146
+ # 🆕 تصنيف ذكي للأخطاء
147
+ if "429" in err or "rate" in err:
148
+ self.mark_bad(provider, 60)
149
+ elif "cloudflare" in err:
150
+ self.mark_bad(provider, 300)
151
+ elif "timeout" in err:
152
+ self.mark_bad(provider, 120)
153
+ elif "403" in err or "forbidden" in err:
154
+ self.mark_bad(provider, 600)
155
+ else:
156
+ self.mark_bad(provider, 60)
157
+ except TimeoutError:
158
+ log.error("Global timeout reached")
159
+ errors.append({"stage": "global", "error": "Global timeout"})
160
+
161
+ # 🆕 إرجاع آخر خطأ فقط عند الفشل لتقليل حجم الرد
162
+ last_error = errors[-1] if errors else {"error": "Unknown"}
163
+ return {
164
+ "success": False,
165
+ "attempts": len(errors),
166
+ "last_error": last_error,
167
+ "errors": errors, # اختياري: يمكن حذفها
168
+ }
169
+
170
+
171
+ # ============ إعداد FastAPI ============
172
+ @asynccontextmanager
173
+ async def lifespan(_: FastAPI):
174
+ ai = SmartG4F()
175
+ app.state.ai = ai
176
+ log.info("Server started")
177
+ yield
178
+ log.info("Server shutting down")
179
+
180
+
181
+ app = FastAPI(title="Smart G4F", lifespan=lifespan)
182
+
183
+ app.add_middleware(
184
+ CORSMiddleware,
185
+ allow_origins=["*"],
186
+ allow_methods=["*"],
187
+ allow_headers=["*"],
188
+ )
189
+
190
 
191
  class Query(BaseModel):
192
  prompt: str
193
+ model: Optional[str] = None
194
+
195
 
196
  @app.get("/")
197
  async def home():
198
+ ai: SmartG4F = app.state.ai
199
  return {
200
  "status": "running",
201
  "safe_providers": len(SAFE_PROVIDERS),
202
+ "healthy_providers": len(ai.good),
203
+ "cooldown_providers": len(ai.bad),
204
+ }
205
+
206
+
207
+ @app.get("/health")
208
+ async def health():
209
+ ai: SmartG4F = app.state.ai
210
+ return {
211
+ "providers_available": sum(
212
+ 1 for p in SAFE_PROVIDERS if not ai.is_bad(p)
213
+ ),
214
+ "total": len(SAFE_PROVIDERS),
215
  }
216
 
217
+
218
  @app.post("/ask")
219
  async def ask(query: Query):
220
+ if not query.prompt.strip():
221
+ raise HTTPException(status_code=400, detail="Prompt cannot be empty")
222
+ ai: SmartG4F = app.state.ai
223
  return await ai.ask(query.prompt)
224
 
225
+
226
  if __name__ == "__main__":
227
  uvicorn.run(app, host="0.0.0.0", port=7860)