bahi-bh commited on
Commit
a866d69
·
verified ·
1 Parent(s): fdbfb13

Update app.py

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