bahi-bh commited on
Commit
d3968e6
·
verified ·
1 Parent(s): d589d39

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +332 -97
app.py CHANGED
@@ -12,7 +12,7 @@ import logging
12
 
13
  import g4f
14
  from g4f.client import Client
15
- from g4f import Provider
16
 
17
 
18
  # =====================================================
@@ -29,77 +29,216 @@ logger = logging.getLogger(__name__)
29
 
30
  API_KEY = "sk-your-secret-key"
31
  REQUEST_TIMEOUT = 45
32
- MAX_RETRIES = 2
33
 
34
 
35
  # =====================================================
36
  # MODEL → PROVIDER MAP
37
- # الإصلاح الوحيد: كل نموذج مربوط بـ provider يعمل
38
- # بدون API key بدل auto العشوائي
39
  # =====================================================
40
 
41
  MODEL_PROVIDERS = {
42
- # Cohere - تعمل عبر HuggingSpace
43
- "command-r": [Provider.HuggingSpace, Provider.Jmuz],
44
- "command-r-plus": [Provider.HuggingSpace, Provider.Jmuz],
45
- "command-r7b": [Provider.HuggingSpace],
46
- "command-a": [Provider.HuggingSpace],
47
- "command": [Provider.HuggingSpace, Provider.Jmuz],
48
- "command-light": [Provider.HuggingSpace, Provider.Jmuz],
49
- "command-nightly": [Provider.HuggingSpace],
50
- "c4ai-aya-expanse-8b": [Provider.HuggingSpace],
51
- "c4ai-aya-expanse-32b": [Provider.HuggingSpace],
52
-
53
- # Kimi
54
- "kimi": [Provider.Jmuz],
55
- "moonshot-v1-8k": [Provider.Jmuz],
56
- "moonshot-v1-32k": [Provider.Jmuz],
57
-
58
- # GPT-4
59
- "gpt-4": [Provider.Jmuz, Provider.Liaobots, Provider.PollinationsAI],
60
- "gpt-4-turbo": [Provider.Jmuz, Provider.Liaobots, Provider.PollinationsAI],
61
- "gpt-4o": [Provider.PollinationsAI, Provider.Jmuz, Provider.Liaobots],
62
- "gpt-4o-mini": [Provider.PollinationsAI, Provider.DDG, Provider.Jmuz],
63
- "gpt-3.5-turbo": [Provider.DDG, Provider.Jmuz, Provider.PollinationsAI],
64
-
65
- # DeepSeek
66
- "deepseek-chat": [Provider.PollinationsAI, Provider.Jmuz],
67
- "deepseek-r1": [Provider.PollinationsAI, Provider.Jmuz],
68
- "deepseek-v3": [Provider.PollinationsAI, Provider.Jmuz],
69
-
70
- # Llama
71
- "llama-3.1-8b": [Provider.PollinationsAI, Provider.DDG],
72
- "llama-3.1-70b": [Provider.PollinationsAI, Provider.Jmuz],
73
- "llama-3.3-70b": [Provider.PollinationsAI, Provider.Jmuz],
74
- "llama-4-scout": [Provider.PollinationsAI],
75
- "llama-4-maverick": [Provider.PollinationsAI],
76
-
77
- # Mistral
78
- "mistral-7b": [Provider.PollinationsAI, Provider.Jmuz],
79
- "mixtral-8x7b": [Provider.PollinationsAI, Provider.Jmuz],
80
- "mistral-large":[Provider.PollinationsAI, Provider.Jmuz],
81
-
82
- # Gemini
83
- "gemini-2.0-flash": [Provider.PollinationsAI, Provider.Jmuz],
84
- "gemini-1.5-flash": [Provider.PollinationsAI, Provider.Jmuz],
85
- "gemini-1.5-pro": [Provider.PollinationsAI, Provider.Jmuz],
86
- "gemini-pro": [Provider.PollinationsAI, Provider.Jmuz],
87
-
88
- # Qwen
89
- "qwen-2.5-72b": [Provider.PollinationsAI, Provider.Jmuz],
90
- "qwen-2.5-coder-32b": [Provider.PollinationsAI],
91
- "qwq-32b": [Provider.PollinationsAI, Provider.Jmuz],
92
-
93
- # Claude (عبر Jmuz proxy)
94
- "claude-3-haiku": [Provider.Jmuz, Provider.Liaobots],
95
- "claude-3-sonnet": [Provider.Jmuz, Provider.Liaobots],
96
- "claude-3-opus": [Provider.Jmuz, Provider.Liaobots],
97
- "claude-3.5-sonnet": [Provider.Jmuz, Provider.Liaobots],
98
-
99
- # Other
100
- "phi-4": [Provider.PollinationsAI],
101
- "sonar-pro": [Provider.PollinationsAI, Provider.Jmuz],
102
- "sonar": [Provider.PollinationsAI, Provider.Jmuz],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  }
104
 
105
 
@@ -109,7 +248,7 @@ MODEL_PROVIDERS = {
109
 
110
  app = FastAPI(
111
  title="Universal AI Gateway",
112
- version="4.2.0"
113
  )
114
 
115
 
@@ -148,14 +287,26 @@ class ChatRequest(BaseModel):
148
  # =====================================================
149
 
150
  def verify_api_key(req: Request):
 
151
  auth = req.headers.get("Authorization")
 
152
  if not auth:
153
  return True
 
154
  if not auth.startswith("Bearer "):
155
- raise HTTPException(status_code=401, detail="Invalid Authorization Format")
 
 
 
 
156
  token = auth.replace("Bearer ", "").strip()
 
157
  if token != API_KEY:
158
- raise HTTPException(status_code=403, detail="Invalid API Key")
 
 
 
 
159
  return True
160
 
161
 
@@ -165,28 +316,32 @@ def verify_api_key(req: Request):
165
 
166
  @app.get("/")
167
  async def root():
 
168
  return {
169
  "status": "online",
170
  "service": "Universal AI Gateway",
171
- "version": "4.2.0"
172
  }
173
 
174
 
175
  # =====================================================
176
- # MODELS
177
  # =====================================================
178
 
179
  @app.get("/v1/models")
180
  async def get_models():
 
181
  models_data = []
182
- now = int(time.time())
183
  for model_name in MODEL_PROVIDERS.keys():
 
184
  models_data.append({
185
  "id": model_name,
186
  "object": "model",
187
- "created": now,
188
  "owned_by": "g4f"
189
  })
 
190
  return {
191
  "object": "list",
192
  "data": models_data
@@ -195,27 +350,42 @@ async def get_models():
195
 
196
  # =====================================================
197
  # SAFE COMPLETION
198
- # يجرب كل provider في القائمة واحداً تلو الآخر
199
  # =====================================================
200
 
201
  async def safe_completion(model, messages, stream=False):
202
 
203
- # جلب قائمة الـ providers لهذا النموذج
204
  providers = MODEL_PROVIDERS.get(model)
205
 
206
- # نموذج غير موجود في الجدول → fallback عام
207
  if not providers:
208
- logger.warning(f"Model '{model}' not in table, using fallback providers")
209
- providers = [Provider.PollinationsAI, Provider.Jmuz, Provider.DDG]
 
 
 
 
 
 
 
210
 
211
  last_error = None
212
 
213
  for provider_cls in providers:
214
- pname = getattr(provider_cls, "__name__", str(provider_cls))
 
 
 
 
 
 
215
  try:
216
- logger.info(f"Trying provider={pname} model={model}")
217
 
218
- client = Client(provider=provider_cls)
 
 
 
 
 
 
219
 
220
  response = await asyncio.wait_for(
221
  asyncio.to_thread(
@@ -227,16 +397,27 @@ async def safe_completion(model, messages, stream=False):
227
  timeout=REQUEST_TIMEOUT
228
  )
229
 
230
- logger.info(f"Success | provider={pname} model={model}")
 
 
 
231
  return response
232
 
233
  except asyncio.TimeoutError:
234
- last_error = f"{pname}: timeout"
235
- logger.warning(f"Timeout | provider={pname}")
 
 
 
 
236
 
237
  except Exception as e:
 
238
  last_error = e
239
- logger.warning(f"Failed | provider={pname} | {e}")
 
 
 
240
 
241
  raise Exception(last_error)
242
 
@@ -246,16 +427,24 @@ async def safe_completion(model, messages, stream=False):
246
  # =====================================================
247
 
248
  @app.post("/v1/chat/completions")
249
- async def chat_completions(req: Request, body: ChatRequest):
 
 
 
250
 
251
  verify_api_key(req)
252
 
253
  messages = [
254
- {"role": m.role, "content": m.content}
 
 
 
255
  for m in body.messages
256
  ]
257
 
258
- logger.info(f"Request model={body.model} stream={body.stream}")
 
 
259
 
260
  # =================================================
261
  # STREAMING
@@ -267,6 +456,8 @@ async def chat_completions(req: Request, body: ChatRequest):
267
 
268
  try:
269
 
 
 
270
  response = await safe_completion(
271
  model=body.model,
272
  messages=messages,
@@ -274,9 +465,18 @@ async def chat_completions(req: Request, body: ChatRequest):
274
  )
275
 
276
  chunk_id = f"chatcmpl-{uuid.uuid4().hex}"
 
277
  has_content = False
278
 
279
- for chunk in response:
 
 
 
 
 
 
 
 
280
 
281
  try:
282
 
@@ -302,7 +502,9 @@ async def chat_completions(req: Request, body: ChatRequest):
302
  "choices": [
303
  {
304
  "index": 0,
305
- "delta": {"content": content},
 
 
306
  "finish_reason": None
307
  }
308
  ]
@@ -316,16 +518,24 @@ async def chat_completions(req: Request, body: ChatRequest):
316
  await asyncio.sleep(0)
317
 
318
  except Exception as chunk_error:
319
- logger.error(f"Chunk error: {chunk_error}")
 
 
 
320
 
321
  if not has_content:
 
322
  error_payload = {
323
  "error": {
324
  "message": "Provider returned empty stream",
325
  "type": "empty_stream"
326
  }
327
  }
328
- yield f"data: {json.dumps(error_payload)}\n\n"
 
 
 
 
329
 
330
  final_payload = {
331
  "id": chunk_id,
@@ -341,12 +551,18 @@ async def chat_completions(req: Request, body: ChatRequest):
341
  ]
342
  }
343
 
344
- yield f"data: {json.dumps(final_payload)}\n\n"
 
 
 
 
345
  yield "data: [DONE]\n\n"
346
 
347
  except Exception as e:
348
 
349
- logger.error(f"Streaming error: {e}")
 
 
350
 
351
  error_payload = {
352
  "error": {
@@ -355,7 +571,12 @@ async def chat_completions(req: Request, body: ChatRequest):
355
  }
356
  }
357
 
358
- yield f"data: {json.dumps(error_payload)}\n\n"
 
 
 
 
 
359
 
360
  return StreamingResponse(
361
  generate_stream(),
@@ -382,8 +603,14 @@ async def chat_completions(req: Request, body: ChatRequest):
382
  assistant_message = ""
383
 
384
  try:
385
- assistant_message = response.choices[0].message.content
 
 
 
 
 
386
  except Exception:
 
387
  assistant_message = str(response)
388
 
389
  return JSONResponse({
@@ -410,7 +637,9 @@ async def chat_completions(req: Request, body: ChatRequest):
410
 
411
  except Exception as e:
412
 
413
- logger.error(f"Chat error: {e}")
 
 
414
 
415
  raise HTTPException(
416
  status_code=500,
@@ -423,5 +652,11 @@ async def chat_completions(req: Request, body: ChatRequest):
423
  # =====================================================
424
 
425
  if __name__ == "__main__":
 
426
  import uvicorn
427
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
 
 
 
 
12
 
13
  import g4f
14
  from g4f.client import Client
15
+ import g4f.Provider as Provider
16
 
17
 
18
  # =====================================================
 
29
 
30
  API_KEY = "sk-your-secret-key"
31
  REQUEST_TIMEOUT = 45
 
32
 
33
 
34
  # =====================================================
35
  # MODEL → PROVIDER MAP
36
+ # مزودون حقيقيون بدون API KEY
 
37
  # =====================================================
38
 
39
  MODEL_PROVIDERS = {
40
+
41
+ # =================================================
42
+ # COHERE
43
+ # =================================================
44
+
45
+ "command-r": [
46
+ Provider.PollinationsAI,
47
+ Provider.DDG
48
+ ],
49
+
50
+ "command-r-plus": [
51
+ Provider.PollinationsAI,
52
+ Provider.DDG
53
+ ],
54
+
55
+ "command-r7b": [
56
+ Provider.PollinationsAI
57
+ ],
58
+
59
+ "command-a": [
60
+ Provider.PollinationsAI
61
+ ],
62
+
63
+ "command": [
64
+ Provider.PollinationsAI
65
+ ],
66
+
67
+ "command-light": [
68
+ Provider.PollinationsAI
69
+ ],
70
+
71
+ "c4ai-aya-expanse-8b": [
72
+ Provider.PollinationsAI
73
+ ],
74
+
75
+ "c4ai-aya-expanse-32b": [
76
+ Provider.PollinationsAI
77
+ ],
78
+
79
+ # =================================================
80
+ # KIMI
81
+ # =================================================
82
+
83
+ "kimi-k2": [
84
+ Provider.PollinationsAI
85
+ ],
86
+
87
+ "kimi": [
88
+ Provider.PollinationsAI
89
+ ],
90
+
91
+ # =================================================
92
+ # GPT
93
+ # =================================================
94
+
95
+ "gpt-4": [
96
+ Provider.PollinationsAI,
97
+ Provider.DDG
98
+ ],
99
+
100
+ "gpt-4o": [
101
+ Provider.PollinationsAI,
102
+ Provider.DDG
103
+ ],
104
+
105
+ "gpt-4o-mini": [
106
+ Provider.PollinationsAI,
107
+ Provider.DDG
108
+ ],
109
+
110
+ "gpt-3.5-turbo": [
111
+ Provider.DDG,
112
+ Provider.PollinationsAI
113
+ ],
114
+
115
+ # =================================================
116
+ # DEEPSEEK
117
+ # =================================================
118
+
119
+ "deepseek-chat": [
120
+ Provider.PollinationsAI
121
+ ],
122
+
123
+ "deepseek-r1": [
124
+ Provider.PollinationsAI
125
+ ],
126
+
127
+ "deepseek-v3": [
128
+ Provider.PollinationsAI
129
+ ],
130
+
131
+ # =================================================
132
+ # LLAMA
133
+ # =================================================
134
+
135
+ "llama-3.1-8b": [
136
+ Provider.PollinationsAI
137
+ ],
138
+
139
+ "llama-3.1-70b": [
140
+ Provider.PollinationsAI
141
+ ],
142
+
143
+ "llama-3.3-70b": [
144
+ Provider.PollinationsAI
145
+ ],
146
+
147
+ "llama-4-scout": [
148
+ Provider.PollinationsAI
149
+ ],
150
+
151
+ "llama-4-maverick": [
152
+ Provider.PollinationsAI
153
+ ],
154
+
155
+ # =================================================
156
+ # QWEN
157
+ # =================================================
158
+
159
+ "qwen-2.5-72b": [
160
+ Provider.PollinationsAI
161
+ ],
162
+
163
+ "qwen-2.5-coder-32b": [
164
+ Provider.PollinationsAI
165
+ ],
166
+
167
+ "qwq-32b": [
168
+ Provider.PollinationsAI
169
+ ],
170
+
171
+ # =================================================
172
+ # GEMINI
173
+ # =================================================
174
+
175
+ "gemini-2.0-flash": [
176
+ Provider.PollinationsAI
177
+ ],
178
+
179
+ "gemini-1.5-flash": [
180
+ Provider.PollinationsAI
181
+ ],
182
+
183
+ "gemini-1.5-pro": [
184
+ Provider.PollinationsAI
185
+ ],
186
+
187
+ "gemini-pro": [
188
+ Provider.PollinationsAI
189
+ ],
190
+
191
+ # =================================================
192
+ # MISTRAL
193
+ # =================================================
194
+
195
+ "mistral-7b": [
196
+ Provider.PollinationsAI
197
+ ],
198
+
199
+ "mixtral-8x7b": [
200
+ Provider.PollinationsAI
201
+ ],
202
+
203
+ "mistral-large": [
204
+ Provider.PollinationsAI
205
+ ],
206
+
207
+ # =================================================
208
+ # CLAUDE
209
+ # =================================================
210
+
211
+ "claude-3-haiku": [
212
+ Provider.PollinationsAI
213
+ ],
214
+
215
+ "claude-3-sonnet": [
216
+ Provider.PollinationsAI
217
+ ],
218
+
219
+ "claude-3-opus": [
220
+ Provider.PollinationsAI
221
+ ],
222
+
223
+ "claude-3.5-sonnet": [
224
+ Provider.PollinationsAI
225
+ ],
226
+
227
+ # =================================================
228
+ # OTHER
229
+ # =================================================
230
+
231
+ "phi-4": [
232
+ Provider.PollinationsAI
233
+ ],
234
+
235
+ "sonar": [
236
+ Provider.PollinationsAI
237
+ ],
238
+
239
+ "sonar-pro": [
240
+ Provider.PollinationsAI
241
+ ],
242
  }
243
 
244
 
 
248
 
249
  app = FastAPI(
250
  title="Universal AI Gateway",
251
+ version="5.0.0"
252
  )
253
 
254
 
 
287
  # =====================================================
288
 
289
  def verify_api_key(req: Request):
290
+
291
  auth = req.headers.get("Authorization")
292
+
293
  if not auth:
294
  return True
295
+
296
  if not auth.startswith("Bearer "):
297
+ raise HTTPException(
298
+ status_code=401,
299
+ detail="Invalid Authorization Format"
300
+ )
301
+
302
  token = auth.replace("Bearer ", "").strip()
303
+
304
  if token != API_KEY:
305
+ raise HTTPException(
306
+ status_code=403,
307
+ detail="Invalid API Key"
308
+ )
309
+
310
  return True
311
 
312
 
 
316
 
317
  @app.get("/")
318
  async def root():
319
+
320
  return {
321
  "status": "online",
322
  "service": "Universal AI Gateway",
323
+ "version": "5.0.0"
324
  }
325
 
326
 
327
  # =====================================================
328
+ # MODELS LIST
329
  # =====================================================
330
 
331
  @app.get("/v1/models")
332
  async def get_models():
333
+
334
  models_data = []
335
+
336
  for model_name in MODEL_PROVIDERS.keys():
337
+
338
  models_data.append({
339
  "id": model_name,
340
  "object": "model",
341
+ "created": int(time.time()),
342
  "owned_by": "g4f"
343
  })
344
+
345
  return {
346
  "object": "list",
347
  "data": models_data
 
350
 
351
  # =====================================================
352
  # SAFE COMPLETION
 
353
  # =====================================================
354
 
355
  async def safe_completion(model, messages, stream=False):
356
 
 
357
  providers = MODEL_PROVIDERS.get(model)
358
 
 
359
  if not providers:
360
+
361
+ logger.warning(
362
+ f"Model '{model}' not found. Using fallback."
363
+ )
364
+
365
+ providers = [
366
+ Provider.PollinationsAI,
367
+ Provider.DDG
368
+ ]
369
 
370
  last_error = None
371
 
372
  for provider_cls in providers:
373
+
374
+ provider_name = getattr(
375
+ provider_cls,
376
+ "__name__",
377
+ str(provider_cls)
378
+ )
379
+
380
  try:
 
381
 
382
+ logger.info(
383
+ f"Trying provider={provider_name} model={model}"
384
+ )
385
+
386
+ client = Client(
387
+ provider=provider_cls
388
+ )
389
 
390
  response = await asyncio.wait_for(
391
  asyncio.to_thread(
 
397
  timeout=REQUEST_TIMEOUT
398
  )
399
 
400
+ logger.info(
401
+ f"Success provider={provider_name} model={model}"
402
+ )
403
+
404
  return response
405
 
406
  except asyncio.TimeoutError:
407
+
408
+ last_error = f"{provider_name}: timeout"
409
+
410
+ logger.warning(
411
+ f"Timeout provider={provider_name}"
412
+ )
413
 
414
  except Exception as e:
415
+
416
  last_error = e
417
+
418
+ logger.warning(
419
+ f"Failed provider={provider_name} error={e}"
420
+ )
421
 
422
  raise Exception(last_error)
423
 
 
427
  # =====================================================
428
 
429
  @app.post("/v1/chat/completions")
430
+ async def chat_completions(
431
+ req: Request,
432
+ body: ChatRequest
433
+ ):
434
 
435
  verify_api_key(req)
436
 
437
  messages = [
438
+ {
439
+ "role": m.role,
440
+ "content": m.content
441
+ }
442
  for m in body.messages
443
  ]
444
 
445
+ logger.info(
446
+ f"Request model={body.model} stream={body.stream}"
447
+ )
448
 
449
  # =================================================
450
  # STREAMING
 
456
 
457
  try:
458
 
459
+ yield ":\n\n"
460
+
461
  response = await safe_completion(
462
  model=body.model,
463
  messages=messages,
 
465
  )
466
 
467
  chunk_id = f"chatcmpl-{uuid.uuid4().hex}"
468
+
469
  has_content = False
470
 
471
+ response_iter = iter(response)
472
+
473
+ while True:
474
+
475
+ try:
476
+ chunk = next(response_iter)
477
+
478
+ except StopIteration:
479
+ break
480
 
481
  try:
482
 
 
502
  "choices": [
503
  {
504
  "index": 0,
505
+ "delta": {
506
+ "content": content
507
+ },
508
  "finish_reason": None
509
  }
510
  ]
 
518
  await asyncio.sleep(0)
519
 
520
  except Exception as chunk_error:
521
+
522
+ logger.error(
523
+ f"Chunk error: {chunk_error}"
524
+ )
525
 
526
  if not has_content:
527
+
528
  error_payload = {
529
  "error": {
530
  "message": "Provider returned empty stream",
531
  "type": "empty_stream"
532
  }
533
  }
534
+
535
+ yield (
536
+ f"data: "
537
+ f"{json.dumps(error_payload)}\n\n"
538
+ )
539
 
540
  final_payload = {
541
  "id": chunk_id,
 
551
  ]
552
  }
553
 
554
+ yield (
555
+ f"data: "
556
+ f"{json.dumps(final_payload)}\n\n"
557
+ )
558
+
559
  yield "data: [DONE]\n\n"
560
 
561
  except Exception as e:
562
 
563
+ logger.error(
564
+ f"Streaming error: {e}"
565
+ )
566
 
567
  error_payload = {
568
  "error": {
 
571
  }
572
  }
573
 
574
+ yield (
575
+ f"data: "
576
+ f"{json.dumps(error_payload)}\n\n"
577
+ )
578
+
579
+ yield "data: [DONE]\n\n"
580
 
581
  return StreamingResponse(
582
  generate_stream(),
 
603
  assistant_message = ""
604
 
605
  try:
606
+
607
+ assistant_message = (
608
+ response.choices[0]
609
+ .message.content
610
+ )
611
+
612
  except Exception:
613
+
614
  assistant_message = str(response)
615
 
616
  return JSONResponse({
 
637
 
638
  except Exception as e:
639
 
640
+ logger.error(
641
+ f"Chat error: {e}"
642
+ )
643
 
644
  raise HTTPException(
645
  status_code=500,
 
652
  # =====================================================
653
 
654
  if __name__ == "__main__":
655
+
656
  import uvicorn
657
+
658
+ uvicorn.run(
659
+ app,
660
+ host="0.0.0.0",
661
+ port=7860
662
+ )