bahi-bh commited on
Commit
1b0f789
·
verified ·
1 Parent(s): 8530fef

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +553 -84
app.py CHANGED
@@ -1,22 +1,28 @@
1
- from fastapi import FastAPI, Request, HTTPException
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.responses import StreamingResponse, JSONResponse
4
  from pydantic import BaseModel
5
- from typing import List, Optional
6
  import asyncio
7
  import json
8
  import time
9
  import uuid
10
  import logging
 
 
11
 
12
  import g4f
13
  from g4f.client import Client
 
14
 
15
  # =====================================================
16
  # LOGGING
17
  # =====================================================
18
 
19
- logging.basicConfig(level=logging.INFO)
 
 
 
20
  logger = logging.getLogger(__name__)
21
 
22
  # =====================================================
@@ -25,13 +31,128 @@ logger = logging.getLogger(__name__)
25
 
26
  API_KEY = "sk-your-secret-key"
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  # =====================================================
29
  # FASTAPI
30
  # =====================================================
31
 
32
  app = FastAPI(
33
  title="Universal AI Gateway",
34
- version="4.0.0"
 
35
  )
36
 
37
  # =====================================================
@@ -61,22 +182,67 @@ class ChatRequest(BaseModel):
61
  temperature: Optional[float] = 0.7
62
  max_tokens: Optional[int] = 4096
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  # =====================================================
65
  # AUTH
66
  # =====================================================
67
 
68
  def verify_api_key(req: Request):
69
-
70
  auth = req.headers.get("Authorization")
71
 
72
- # السماح للاختبار
73
  if not auth:
74
  return True
75
 
76
  if not auth.startswith("Bearer "):
77
  raise HTTPException(
78
  status_code=401,
79
- detail="Invalid Authorization Format"
80
  )
81
 
82
  token = auth.replace("Bearer ", "").strip()
@@ -84,83 +250,289 @@ def verify_api_key(req: Request):
84
  if token != API_KEY:
85
  raise HTTPException(
86
  status_code=403,
87
- detail="Invalid API Key"
88
  )
89
 
90
  return True
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  # =====================================================
93
  # ROOT
94
  # =====================================================
95
 
96
  @app.get("/")
97
  async def root():
98
-
99
  return {
100
  "status": "online",
101
  "service": "Universal AI Gateway",
102
- "version": "4.0.0"
 
 
 
 
 
 
 
103
  }
104
 
105
  # =====================================================
106
- # MODELS
107
  # =====================================================
108
 
109
- @app.get("/v1/models")
110
- async def get_models():
 
111
 
112
- models_data = []
113
 
114
- fallback_models = [
115
- "gpt-4o-mini",
116
- "gpt-4o",
117
- "gpt-4",
118
- "gpt-3.5-turbo",
119
- "claude-3-haiku",
120
- "llama-3.1-70b",
121
- "mixtral-8x7b",
122
- "deepseek-chat",
123
- "gemini-pro"
124
- ]
125
 
126
- try:
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- if hasattr(g4f.models, "_all_models"):
 
 
 
 
 
 
129
 
130
- all_models = list(g4f.models._all_models)
 
 
131
 
132
- for model in all_models[:50]:
 
 
133
 
134
- models_data.append({
135
- "id": str(model),
136
- "object": "model",
137
- "created": int(time.time()),
138
- "owned_by": "g4f"
139
- })
140
 
141
- except Exception as e:
 
 
 
142
 
143
- logger.error(f"Models error: {e}")
144
 
145
- # fallback
146
- if not models_data:
 
 
 
 
 
 
 
 
147
 
148
- for model in fallback_models:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
- models_data.append({
151
- "id": model,
152
- "object": "model",
153
- "created": int(time.time()),
154
- "owned_by": "g4f"
155
- })
156
 
157
  return {
158
  "object": "list",
159
- "data": models_data
 
160
  }
161
 
162
  # =====================================================
163
- # CHAT COMPLETIONS
164
  # =====================================================
165
 
166
  @app.post("/v1/chat/completions")
@@ -168,19 +540,18 @@ async def chat_completions(
168
  req: Request,
169
  body: ChatRequest
170
  ):
 
171
 
172
  verify_api_key(req)
173
 
 
174
  messages = [
175
- {
176
- "role": m.role,
177
- "content": m.content
178
- }
179
  for m in body.messages
180
  ]
181
 
182
  logger.info(
183
- f"Request model={body.model} stream={body.stream}"
184
  )
185
 
186
  # =================================================
@@ -193,15 +564,85 @@ async def chat_completions(
193
 
194
  try:
195
 
196
- client = Client()
 
 
 
197
 
198
- response = client.chat.completions.create(
199
- model=body.model,
200
- messages=messages,
201
- stream=True
202
  )
203
 
204
- chunk_id = f"chatcmpl-{uuid.uuid4().hex}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
 
206
  for chunk in response:
207
 
@@ -211,6 +652,7 @@ async def chat_completions(
211
 
212
  if (
213
  chunk.choices
 
214
  and chunk.choices[0].delta
215
  and chunk.choices[0].delta.content
216
  ):
@@ -218,15 +660,18 @@ async def chat_completions(
218
 
219
  if content:
220
 
 
 
221
  payload = {
222
  "id": chunk_id,
223
  "object": "chat.completion.chunk",
224
  "created": int(time.time()),
225
- "model": body.model,
226
  "choices": [
227
  {
228
  "index": 0,
229
  "delta": {
 
230
  "content": content
231
  },
232
  "finish_reason": None
@@ -234,21 +679,23 @@ async def chat_completions(
234
  ]
235
  }
236
 
237
- yield f"data: {json.dumps(payload)}\n\n"
238
-
239
  await asyncio.sleep(0)
240
 
241
  except Exception as chunk_error:
 
 
242
 
243
- logger.error(
244
- f"Chunk error: {chunk_error}"
245
- )
246
 
 
247
  final_payload = {
248
  "id": chunk_id,
249
  "object": "chat.completion.chunk",
250
  "created": int(time.time()),
251
- "model": body.model,
252
  "choices": [
253
  {
254
  "index": 0,
@@ -259,12 +706,11 @@ async def chat_completions(
259
  }
260
 
261
  yield f"data: {json.dumps(final_payload)}\n\n"
262
-
263
  yield "data: [DONE]\n\n"
264
 
265
  except Exception as e:
266
 
267
- logger.error(f"Streaming error: {e}")
268
 
269
  error_payload = {
270
  "error": {
@@ -281,7 +727,8 @@ async def chat_completions(
281
  headers={
282
  "Cache-Control": "no-cache",
283
  "Connection": "keep-alive",
284
- "X-Accel-Buffering": "no"
 
285
  }
286
  )
287
 
@@ -291,26 +738,36 @@ async def chat_completions(
291
 
292
  try:
293
 
294
- client = Client()
295
 
296
- response = await asyncio.to_thread(
297
- client.chat.completions.create,
298
  model=body.model,
299
- messages=messages
 
 
 
300
  )
301
 
 
302
  assistant_message = ""
303
 
304
  try:
305
- assistant_message = response.choices[0].message.content
306
- except:
307
- assistant_message = str(response)
 
 
 
 
 
 
 
308
 
309
  return JSONResponse({
310
  "id": f"chatcmpl-{uuid.uuid4().hex}",
311
  "object": "chat.completion",
312
  "created": int(time.time()),
313
- "model": body.model,
314
  "choices": [
315
  {
316
  "index": 0,
@@ -322,19 +779,26 @@ async def chat_completions(
322
  }
323
  ],
324
  "usage": {
325
- "prompt_tokens": 0,
326
- "completion_tokens": 0,
327
- "total_tokens": 0
328
  }
329
  })
330
 
 
 
 
331
  except Exception as e:
332
 
333
- logger.error(f"Chat error: {e}")
334
 
335
  raise HTTPException(
336
  status_code=500,
337
- detail=str(e)
 
 
 
 
338
  )
339
 
340
  # =====================================================
@@ -345,8 +809,13 @@ if __name__ == "__main__":
345
 
346
  import uvicorn
347
 
 
 
 
 
348
  uvicorn.run(
349
  app,
350
  host="0.0.0.0",
351
- port=7860
352
- )
 
 
1
+
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.responses import StreamingResponse, JSONResponse
4
  from pydantic import BaseModel
5
+ from typing import List, Optional, Dict
6
  import asyncio
7
  import json
8
  import time
9
  import uuid
10
  import logging
11
+ import random
12
+ import httpx
13
 
14
  import g4f
15
  from g4f.client import Client
16
+ from g4f import Provider
17
 
18
  # =====================================================
19
  # LOGGING
20
  # =====================================================
21
 
22
+ logging.basicConfig(
23
+ level=logging.INFO,
24
+ format="%(asctime)s [%(levelname)s] %(message)s"
25
+ )
26
  logger = logging.getLogger(__name__)
27
 
28
  # =====================================================
 
31
 
32
  API_KEY = "sk-your-secret-key"
33
 
34
+ # =====================================================
35
+ # PROXY CONFIG
36
+ # =====================================================
37
+
38
+ # قائمة البروكسيات - أضف بروكسياتك هنا
39
+ PROXY_LIST = [
40
+ # "http://user:pass@proxy1:port",
41
+ # "http://user:pass@proxy2:port",
42
+ # "socks5://user:pass@proxy3:port",
43
+ ]
44
+
45
+ # بروكسي واحد إذا كان لديك
46
+ SINGLE_PROXY = None # مثال: "http://user:pass@host:port"
47
+
48
+ # =====================================================
49
+ # PROVIDER MAP - ربط النماذج بمزوديها
50
+ # =====================================================
51
+
52
+ MODEL_PROVIDER_MAP: Dict[str, list] = {
53
+ # GPT Models
54
+ "gpt-4o": [
55
+ Provider.Blackbox,
56
+ Provider.Liaobots,
57
+ Provider.OpenaiChat,
58
+ ],
59
+ "gpt-4o-mini": [
60
+ Provider.Blackbox,
61
+ Provider.DDG,
62
+ Provider.Liaobots,
63
+ Provider.OpenaiChat,
64
+ ],
65
+ "gpt-4": [
66
+ Provider.Blackbox,
67
+ Provider.Liaobots,
68
+ Provider.OpenaiChat,
69
+ ],
70
+ "gpt-3.5-turbo": [
71
+ Provider.DDG,
72
+ Provider.Blackbox,
73
+ Provider.OpenaiChat,
74
+ ],
75
+ # Claude Models
76
+ "claude-3-haiku": [
77
+ Provider.DDG,
78
+ Provider.Blackbox,
79
+ ],
80
+ "claude-3-5-sonnet": [
81
+ Provider.Blackbox,
82
+ Provider.Liaobots,
83
+ ],
84
+ "claude-3-opus": [
85
+ Provider.Liaobots,
86
+ Provider.Blackbox,
87
+ ],
88
+ # Llama Models
89
+ "llama-3.1-70b": [
90
+ Provider.Blackbox,
91
+ Provider.DeepInfra,
92
+ Provider.Cloudflare,
93
+ ],
94
+ "llama-3.1-8b": [
95
+ Provider.Cloudflare,
96
+ Provider.DeepInfra,
97
+ ],
98
+ "llama-3.2-90b": [
99
+ Provider.Blackbox,
100
+ Provider.DeepInfra,
101
+ ],
102
+ # Mixtral Models
103
+ "mixtral-8x7b": [
104
+ Provider.DeepInfra,
105
+ Provider.Blackbox,
106
+ ],
107
+ # Deepseek Models
108
+ "deepseek-chat": [
109
+ Provider.DeepInfra,
110
+ Provider.Blackbox,
111
+ ],
112
+ "deepseek-v3": [
113
+ Provider.Blackbox,
114
+ Provider.DeepInfra,
115
+ ],
116
+ # Gemini Models
117
+ "gemini-pro": [
118
+ Provider.Blackbox,
119
+ Provider.Liaobots,
120
+ ],
121
+ "gemini-1.5-pro": [
122
+ Provider.Blackbox,
123
+ Provider.Liaobots,
124
+ ],
125
+ # Qwen Models
126
+ "qwen-2.5-72b": [
127
+ Provider.Blackbox,
128
+ Provider.DeepInfra,
129
+ ],
130
+ # Default fallback
131
+ "default": [
132
+ Provider.Blackbox,
133
+ Provider.DDG,
134
+ Provider.DeepInfra,
135
+ ],
136
+ }
137
+
138
+ # =====================================================
139
+ # FALLBACK MODELS - إذا فشل النموذج المطلوب
140
+ # =====================================================
141
+
142
+ FALLBACK_MODELS = [
143
+ "gpt-4o-mini",
144
+ "gpt-3.5-turbo",
145
+ "llama-3.1-70b",
146
+ ]
147
+
148
  # =====================================================
149
  # FASTAPI
150
  # =====================================================
151
 
152
  app = FastAPI(
153
  title="Universal AI Gateway",
154
+ description="بوابة ذكاء اصطناعي شاملة مع دعم البروكسي",
155
+ version="5.0.0"
156
  )
157
 
158
  # =====================================================
 
182
  temperature: Optional[float] = 0.7
183
  max_tokens: Optional[int] = 4096
184
 
185
+ # =====================================================
186
+ # PROXY HELPER
187
+ # =====================================================
188
+
189
+ def get_proxy() -> Optional[str]:
190
+ """الحصول على بروكسي من القائمة"""
191
+
192
+ # إذا كان هناك بروكسي واحد محدد
193
+ if SINGLE_PROXY:
194
+ logger.info(f"Using single proxy: {SINGLE_PROXY[:20]}...")
195
+ return SINGLE_PROXY
196
+
197
+ # اختيار عشوائي من القائمة
198
+ if PROXY_LIST:
199
+ proxy = random.choice(PROXY_LIST)
200
+ logger.info(f"Using proxy from list: {proxy[:20]}...")
201
+ return proxy
202
+
203
+ return None
204
+
205
+ def get_g4f_proxy_config() -> dict:
206
+ """إعداد البروكسي لـ g4f"""
207
+ proxy = get_proxy()
208
+ if proxy:
209
+ return {"proxy": proxy}
210
+ return {}
211
+
212
+ # =====================================================
213
+ # CLIENT BUILDER
214
+ # =====================================================
215
+
216
+ def build_client(proxy: Optional[str] = None) -> Client:
217
+ """بناء عميل g4f مع البروكسي"""
218
+ proxy_url = proxy or get_proxy()
219
+
220
+ if proxy_url:
221
+ try:
222
+ client = Client(proxies=proxy_url)
223
+ logger.info("Client created with proxy")
224
+ return client
225
+ except Exception as e:
226
+ logger.warning(f"Failed to create client with proxy: {e}")
227
+
228
+ return Client()
229
+
230
  # =====================================================
231
  # AUTH
232
  # =====================================================
233
 
234
  def verify_api_key(req: Request):
235
+ """التحقق من مفتاح API"""
236
  auth = req.headers.get("Authorization")
237
 
238
+ # السماح بدون مفتاح للاختبار
239
  if not auth:
240
  return True
241
 
242
  if not auth.startswith("Bearer "):
243
  raise HTTPException(
244
  status_code=401,
245
+ detail="Invalid Authorization Format - يجب أن يبدأ بـ Bearer"
246
  )
247
 
248
  token = auth.replace("Bearer ", "").strip()
 
250
  if token != API_KEY:
251
  raise HTTPException(
252
  status_code=403,
253
+ detail="Invalid API Key - مفتاح API غير صحيح"
254
  )
255
 
256
  return True
257
 
258
+ # =====================================================
259
+ # CORE: SMART COMPLETION WITH RETRY + FALLBACK
260
+ # =====================================================
261
+
262
+ async def smart_completion(
263
+ model: str,
264
+ messages: list,
265
+ stream: bool = False,
266
+ max_retries: int = 3,
267
+ proxy: Optional[str] = None
268
+ ):
269
+ """
270
+ محاولة ذكية مع:
271
+ - تجربة عدة مزودين
272
+ - إعادة المحاولة عند الفشل
273
+ - التحويل لنموذج بديل
274
+ """
275
+
276
+ # الحصول على قائمة المزودين للنموذج
277
+ providers = MODEL_PROVIDER_MAP.get(
278
+ model,
279
+ MODEL_PROVIDER_MAP["default"]
280
+ )
281
+
282
+ # خلط المزودين عشوائياً لتوزيع الحمل
283
+ providers_shuffled = providers.copy()
284
+ random.shuffle(providers_shuffled)
285
+
286
+ last_error = None
287
+
288
+ # -----------------------------------------------
289
+ # المحاولة مع كل مزود
290
+ # -----------------------------------------------
291
+
292
+ for attempt in range(max_retries):
293
+
294
+ for provider in providers_shuffled:
295
+
296
+ try:
297
+
298
+ logger.info(
299
+ f"Attempt {attempt+1} | Model: {model} | Provider: {provider.__name__}"
300
+ )
301
+
302
+ client = build_client(proxy)
303
+
304
+ proxy_config = get_g4f_proxy_config()
305
+
306
+ response = await asyncio.to_thread(
307
+ client.chat.completions.create,
308
+ model=model,
309
+ messages=messages,
310
+ provider=provider,
311
+ stream=stream,
312
+ **proxy_config
313
+ )
314
+
315
+ logger.info(
316
+ f"✅ Success | Model: {model} | Provider: {provider.__name__}"
317
+ )
318
+
319
+ return response, model
320
+
321
+ except Exception as e:
322
+
323
+ last_error = e
324
+ logger.warning(
325
+ f"❌ Failed | Model: {model} | Provider: {provider.__name__} | Error: {str(e)[:100]}"
326
+ )
327
+
328
+ # انتظار قصير قبل المحاولة التالية
329
+ await asyncio.sleep(0.5)
330
+
331
+ # انتظار قبل دورة المحاولات الجديدة
332
+ if attempt < max_retries - 1:
333
+ wait_time = (attempt + 1) * 1.5
334
+ logger.info(f"Waiting {wait_time}s before retry {attempt+2}...")
335
+ await asyncio.sleep(wait_time)
336
+
337
+ # -----------------------------------------------
338
+ # Fallback: تجربة نماذج بديلة
339
+ # -----------------------------------------------
340
+
341
+ logger.warning(
342
+ f"All providers failed for {model}. Trying fallback models..."
343
+ )
344
+
345
+ for fallback_model in FALLBACK_MODELS:
346
+
347
+ if fallback_model == model:
348
+ continue
349
+
350
+ fallback_providers = MODEL_PROVIDER_MAP.get(
351
+ fallback_model,
352
+ MODEL_PROVIDER_MAP["default"]
353
+ )
354
+
355
+ for fb_provider in fallback_providers[:2]: # أول مزودين فقط
356
+
357
+ try:
358
+
359
+ logger.info(
360
+ f"🔄 Fallback | Model: {fallback_model} | Provider: {fb_provider.__name__}"
361
+ )
362
+
363
+ client = build_client(proxy)
364
+ proxy_config = get_g4f_proxy_config()
365
+
366
+ response = await asyncio.to_thread(
367
+ client.chat.completions.create,
368
+ model=fallback_model,
369
+ messages=messages,
370
+ provider=fb_provider,
371
+ stream=stream,
372
+ **proxy_config
373
+ )
374
+
375
+ logger.info(
376
+ f"✅ Fallback Success | Model: {fallback_model} | Provider: {fb_provider.__name__}"
377
+ )
378
+
379
+ return response, fallback_model
380
+
381
+ except Exception as e:
382
+
383
+ logger.warning(
384
+ f"❌ Fallback Failed | Model: {fallback_model} | Error: {str(e)[:100]}"
385
+ )
386
+ await asyncio.sleep(0.3)
387
+
388
+ # -----------------------------------------------
389
+ # آخر محاولة: بدون تحديد مزود
390
+ # -----------------------------------------------
391
+
392
+ try:
393
+
394
+ logger.info(f"🆘 Last resort attempt for model: {model}")
395
+
396
+ client = build_client(proxy)
397
+
398
+ response = await asyncio.to_thread(
399
+ client.chat.completions.create,
400
+ model=model,
401
+ messages=messages,
402
+ stream=stream
403
+ )
404
+
405
+ return response, model
406
+
407
+ except Exception as e:
408
+
409
+ last_error = e
410
+ logger.error(f"💀 All attempts failed: {str(e)}")
411
+
412
+ raise HTTPException(
413
+ status_code=503,
414
+ detail={
415
+ "error": "All providers failed",
416
+ "message": str(last_error),
417
+ "model_requested": model,
418
+ "suggestion": "Try again later or use a different model"
419
+ }
420
+ )
421
+
422
  # =====================================================
423
  # ROOT
424
  # =====================================================
425
 
426
  @app.get("/")
427
  async def root():
 
428
  return {
429
  "status": "online",
430
  "service": "Universal AI Gateway",
431
+ "version": "5.0.0",
432
+ "proxy_enabled": bool(PROXY_LIST or SINGLE_PROXY),
433
+ "supported_models": list(MODEL_PROVIDER_MAP.keys()),
434
+ "endpoints": {
435
+ "chat": "/v1/chat/completions",
436
+ "models": "/v1/models",
437
+ "health": "/health"
438
+ }
439
  }
440
 
441
  # =====================================================
442
+ # HEALTH CHECK
443
  # =====================================================
444
 
445
+ @app.get("/health")
446
+ async def health_check():
447
+ """فحص صحة الخدمة"""
448
 
449
+ proxy_status = "disabled"
450
 
451
+ if SINGLE_PROXY or PROXY_LIST:
452
+ proxy_status = "enabled"
 
 
 
 
 
 
 
 
 
453
 
454
+ # اختبار البروكسي
455
+ proxy = get_proxy()
456
+ if proxy:
457
+ try:
458
+ async with httpx.AsyncClient(
459
+ proxies=proxy,
460
+ timeout=10
461
+ ) as client:
462
+ resp = await client.get("https://api.ipify.org?format=json")
463
+ ip_data = resp.json()
464
+ proxy_status = f"working - IP: {ip_data.get('ip', 'unknown')}"
465
+ except Exception as e:
466
+ proxy_status = f"error - {str(e)[:50]}"
467
 
468
+ return {
469
+ "status": "healthy",
470
+ "timestamp": int(time.time()),
471
+ "proxy": proxy_status,
472
+ "providers_count": len(MODEL_PROVIDER_MAP),
473
+ "fallback_models": FALLBACK_MODELS
474
+ }
475
 
476
+ # =====================================================
477
+ # MODELS LIST
478
+ # =====================================================
479
 
480
+ @app.get("/v1/models")
481
+ async def get_models():
482
+ """قائمة النماذج المتاحة"""
483
 
484
+ models_data = []
 
 
 
 
 
485
 
486
+ # النماذج من الخريطة
487
+ for model_id in MODEL_PROVIDER_MAP.keys():
488
+ if model_id == "default":
489
+ continue
490
 
491
+ providers_count = len(MODEL_PROVIDER_MAP[model_id])
492
 
493
+ models_data.append({
494
+ "id": model_id,
495
+ "object": "model",
496
+ "created": int(time.time()),
497
+ "owned_by": "g4f",
498
+ "providers_available": providers_count,
499
+ "permission": [],
500
+ "root": model_id,
501
+ "parent": None
502
+ })
503
 
504
+ # إضافة نماذج إضافية من g4f
505
+ try:
506
+ if hasattr(g4f.models, "_all_models"):
507
+ all_models = list(g4f.models._all_models)
508
+ existing_ids = {m["id"] for m in models_data}
509
+
510
+ for model in all_models[:30]:
511
+ model_str = str(model)
512
+ if model_str not in existing_ids:
513
+ models_data.append({
514
+ "id": model_str,
515
+ "object": "model",
516
+ "created": int(time.time()),
517
+ "owned_by": "g4f",
518
+ "providers_available": 1,
519
+ "permission": [],
520
+ "root": model_str,
521
+ "parent": None
522
+ })
523
+ existing_ids.add(model_str)
524
 
525
+ except Exception as e:
526
+ logger.warning(f"Could not fetch g4f models: {e}")
 
 
 
 
527
 
528
  return {
529
  "object": "list",
530
+ "data": models_data,
531
+ "total": len(models_data)
532
  }
533
 
534
  # =====================================================
535
+ # CHAT COMPLETIONS - MAIN ENDPOINT
536
  # =====================================================
537
 
538
  @app.post("/v1/chat/completions")
 
540
  req: Request,
541
  body: ChatRequest
542
  ):
543
+ """نقطة نهاية المحادثة الرئيسية"""
544
 
545
  verify_api_key(req)
546
 
547
+ # تحويل الرسائل
548
  messages = [
549
+ {"role": m.role, "content": m.content}
 
 
 
550
  for m in body.messages
551
  ]
552
 
553
  logger.info(
554
+ f"📨 Request | model={body.model} | stream={body.stream} | messages={len(messages)}"
555
  )
556
 
557
  # =================================================
 
564
 
565
  try:
566
 
567
+ # الحصول على البروكسي
568
+ proxy = get_proxy()
569
+ chunk_id = f"chatcmpl-{uuid.uuid4().hex}"
570
+ actual_model = body.model
571
 
572
+ # محاولة مع كل مزود
573
+ providers = MODEL_PROVIDER_MAP.get(
574
+ body.model,
575
+ MODEL_PROVIDER_MAP["default"]
576
  )
577
 
578
+ providers_shuffled = providers.copy()
579
+ random.shuffle(providers_shuffled)
580
+
581
+ response = None
582
+ last_err = None
583
+
584
+ # تجربة كل مزود
585
+ for provider in providers_shuffled:
586
+
587
+ try:
588
+
589
+ logger.info(
590
+ f"Stream attempt | Provider: {provider.__name__}"
591
+ )
592
+
593
+ client = build_client(proxy)
594
+ proxy_config = get_g4f_proxy_config()
595
+
596
+ response = await asyncio.to_thread(
597
+ client.chat.completions.create,
598
+ model=body.model,
599
+ messages=messages,
600
+ provider=provider,
601
+ stream=True,
602
+ **proxy_config
603
+ )
604
+
605
+ logger.info(
606
+ f"✅ Stream connected | Provider: {provider.__name__}"
607
+ )
608
+ break
609
+
610
+ except Exception as e:
611
+ last_err = e
612
+ logger.warning(
613
+ f"❌ Stream provider failed: {provider.__name__} | {str(e)[:80]}"
614
+ )
615
+ await asyncio.sleep(0.3)
616
+
617
+ # إذا فشل كل شيء، جرب بدون مزود محدد
618
+ if response is None:
619
+
620
+ try:
621
+ client = build_client(proxy)
622
+ response = await asyncio.to_thread(
623
+ client.chat.completions.create,
624
+ model=body.model,
625
+ messages=messages,
626
+ stream=True
627
+ )
628
+ logger.info("✅ Stream connected without specific provider")
629
+
630
+ except Exception as e:
631
+ last_err = e
632
+ logger.error(f"💀 Stream completely failed: {e}")
633
+
634
+ error_payload = {
635
+ "error": {
636
+ "message": str(e),
637
+ "type": "server_error",
638
+ "code": 503
639
+ }
640
+ }
641
+ yield f"data: {json.dumps(error_payload)}\n\n"
642
+ return
643
+
644
+ # إرسال البيانات
645
+ has_content = False
646
 
647
  for chunk in response:
648
 
 
652
 
653
  if (
654
  chunk.choices
655
+ and len(chunk.choices) > 0
656
  and chunk.choices[0].delta
657
  and chunk.choices[0].delta.content
658
  ):
 
660
 
661
  if content:
662
 
663
+ has_content = True
664
+
665
  payload = {
666
  "id": chunk_id,
667
  "object": "chat.completion.chunk",
668
  "created": int(time.time()),
669
+ "model": actual_model,
670
  "choices": [
671
  {
672
  "index": 0,
673
  "delta": {
674
+ "role": "assistant",
675
  "content": content
676
  },
677
  "finish_reason": None
 
679
  ]
680
  }
681
 
682
+ yield f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
 
683
  await asyncio.sleep(0)
684
 
685
  except Exception as chunk_error:
686
+ logger.error(f"Chunk processing error: {chunk_error}")
687
+ continue
688
 
689
+ # إذا لم يكن هناك محتوى
690
+ if not has_content:
691
+ logger.warning("Stream produced no content!")
692
 
693
+ # إشارة الانتهاء
694
  final_payload = {
695
  "id": chunk_id,
696
  "object": "chat.completion.chunk",
697
  "created": int(time.time()),
698
+ "model": actual_model,
699
  "choices": [
700
  {
701
  "index": 0,
 
706
  }
707
 
708
  yield f"data: {json.dumps(final_payload)}\n\n"
 
709
  yield "data: [DONE]\n\n"
710
 
711
  except Exception as e:
712
 
713
+ logger.error(f"Stream generation error: {e}")
714
 
715
  error_payload = {
716
  "error": {
 
727
  headers={
728
  "Cache-Control": "no-cache",
729
  "Connection": "keep-alive",
730
+ "X-Accel-Buffering": "no",
731
+ "Transfer-Encoding": "chunked"
732
  }
733
  )
734
 
 
738
 
739
  try:
740
 
741
+ proxy = get_proxy()
742
 
743
+ response, actual_model = await smart_completion(
 
744
  model=body.model,
745
+ messages=messages,
746
+ stream=False,
747
+ max_retries=3,
748
+ proxy=proxy
749
  )
750
 
751
+ # استخراج المحتوى
752
  assistant_message = ""
753
 
754
  try:
755
+ assistant_message = response.choices[0].message.content or ""
756
+ except AttributeError:
757
+ try:
758
+ assistant_message = str(response)
759
+ except Exception:
760
+ assistant_message = "Error extracting response"
761
+
762
+ # حساب الرموز تقريبياً
763
+ prompt_tokens = sum(len(m["content"].split()) for m in messages)
764
+ completion_tokens = len(assistant_message.split())
765
 
766
  return JSONResponse({
767
  "id": f"chatcmpl-{uuid.uuid4().hex}",
768
  "object": "chat.completion",
769
  "created": int(time.time()),
770
+ "model": actual_model,
771
  "choices": [
772
  {
773
  "index": 0,
 
779
  }
780
  ],
781
  "usage": {
782
+ "prompt_tokens": prompt_tokens,
783
+ "completion_tokens": completion_tokens,
784
+ "total_tokens": prompt_tokens + completion_tokens
785
  }
786
  })
787
 
788
+ except HTTPException:
789
+ raise
790
+
791
  except Exception as e:
792
 
793
+ logger.error(f"Chat completion error: {e}")
794
 
795
  raise HTTPException(
796
  status_code=500,
797
+ detail={
798
+ "error": str(e),
799
+ "model": body.model,
800
+ "message": "Internal server error"
801
+ }
802
  )
803
 
804
  # =====================================================
 
809
 
810
  import uvicorn
811
 
812
+ logger.info("🚀 Starting Universal AI Gateway v5.0.0")
813
+ logger.info(f"🔌 Proxy enabled: {bool(PROXY_LIST or SINGLE_PROXY)}")
814
+ logger.info(f"📦 Models configured: {len(MODEL_PROVIDER_MAP)}")
815
+
816
  uvicorn.run(
817
  app,
818
  host="0.0.0.0",
819
+ port=7860,
820
+ log_level="info"
821
+ )