bahi-bh commited on
Commit
906da7a
·
verified ·
1 Parent(s): 36aa72c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +338 -177
app.py CHANGED
@@ -2,21 +2,33 @@ 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
  # =====================================================
@@ -24,14 +36,34 @@ logger = logging.getLogger(__name__)
24
  # =====================================================
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
  # =====================================================
@@ -59,191 +91,269 @@ class ChatRequest(BaseModel):
59
  messages: List[Message]
60
  stream: bool = False
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()
83
-
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")
167
- 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
- # =================================================
187
- # STREAMING
188
- # =================================================
189
-
 
 
 
190
  if body.stream:
191
-
192
  async def generate_stream():
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
-
208
  try:
209
-
210
  content = ""
211
-
212
- if (
213
- chunk.choices
214
- and chunk.choices[0].delta
215
- and chunk.choices[0].delta.content
216
- ):
217
- content = chunk.choices[0].delta.content
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
233
- }
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",
@@ -257,24 +367,23 @@ async def chat_completions(
257
  }
258
  ]
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": {
271
  "message": str(e),
272
- "type": "server_error"
 
 
273
  }
274
  }
275
-
276
  yield f"data: {json.dumps(error_payload)}\n\n"
277
-
278
  return StreamingResponse(
279
  generate_stream(),
280
  media_type="text/event-stream",
@@ -284,28 +393,45 @@ async def chat_completions(
284
  "X-Accel-Buffering": "no"
285
  }
286
  )
287
-
288
- # =================================================
289
- # NORMAL RESPONSE
290
- # =================================================
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",
@@ -322,31 +448,66 @@ 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
  # =====================================================
341
  # RUN
342
  # =====================================================
343
 
344
  if __name__ == "__main__":
345
-
346
  import uvicorn
347
-
 
 
 
348
  uvicorn.run(
349
  app,
350
  host="0.0.0.0",
351
- port=7860
352
- )
 
 
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
+ from enum import Enum
12
 
13
  import g4f
14
  from g4f.client import Client
15
+ from g4f.Provider import (
16
+ BingCreateImage,
17
+ OpenaiChat,
18
+ Claude,
19
+ Blackbox,
20
+ DeepInfra,
21
+ PerplexityLabs
22
+ )
23
 
24
  # =====================================================
25
  # LOGGING
26
  # =====================================================
27
 
28
+ logging.basicConfig(
29
+ level=logging.INFO,
30
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
31
+ )
32
  logger = logging.getLogger(__name__)
33
 
34
  # =====================================================
 
36
  # =====================================================
37
 
38
  API_KEY = "sk-your-secret-key"
39
+ REQUEST_TIMEOUT = 120
40
+ MAX_RETRIES = 3
41
+
42
+ # =====================================================
43
+ # PROVIDERS MAPPING
44
+ # =====================================================
45
+
46
+ PROVIDERS_MAP: Dict[str, type] = {
47
+ "gpt-4o": Blackbox,
48
+ "gpt-4o-mini": DeepInfra,
49
+ "gpt-4": Claude,
50
+ "claude-3-haiku": Claude,
51
+ "claude-3-sonnet": Claude,
52
+ "llama-3.1-70b": DeepInfra,
53
+ "mixtral-8x7b": DeepInfra,
54
+ "deepseek-chat": Blackbox,
55
+ "perplexity-chat": PerplexityLabs,
56
+ }
57
 
58
  # =====================================================
59
  # FASTAPI
60
  # =====================================================
61
 
62
  app = FastAPI(
63
+ title="Universal AI Gateway Pro",
64
+ version="5.0.0",
65
+ docs_url="/docs",
66
+ openapi_url="/openapi.json"
67
  )
68
 
69
  # =====================================================
 
91
  messages: List[Message]
92
  stream: bool = False
93
  temperature: Optional[float] = 0.7
94
+ max_tokens: Optional[int] = 2048
95
+ top_p: Optional[float] = 1.0
96
+
97
+ class ErrorResponse(BaseModel):
98
+ error: Dict[str, str]
99
 
100
  # =====================================================
101
  # AUTH
102
  # =====================================================
103
 
104
+ def verify_api_key(req: Request) -> bool:
105
+ """التحقق من مفتاح API"""
106
+
107
  auth = req.headers.get("Authorization")
108
+
109
+ # السماح للاختبار بدون مفتاح
110
  if not auth:
111
+ logger.warning("Request without API key detected")
112
  return True
113
+
114
  if not auth.startswith("Bearer "):
115
  raise HTTPException(
116
  status_code=401,
117
+ detail="Invalid Authorization Format. Use: Bearer <token>"
118
  )
119
+
120
  token = auth.replace("Bearer ", "").strip()
121
+
122
  if token != API_KEY:
123
  raise HTTPException(
124
  status_code=403,
125
  detail="Invalid API Key"
126
  )
127
+
128
  return True
129
 
130
  # =====================================================
131
+ # HELPER FUNCTIONS
132
+ # =====================================================
133
+
134
+ def get_provider_for_model(model: str) -> type:
135
+ """الحصول على المزود المناسب للنموذج"""
136
+
137
+ provider = PROVIDERS_MAP.get(model, DeepInfra)
138
+ logger.info(f"Using provider {provider.__name__} for model {model}")
139
+ return provider
140
+
141
+ async def create_chat_client(model: str) -> Client:
142
+ """إنشاء عميل محسّن مع المزود المناسب"""
143
+
144
+ provider = get_provider_for_model(model)
145
+
146
+ try:
147
+ client = Client(
148
+ provider=provider,
149
+ timeout=REQUEST_TIMEOUT,
150
+ max_retries=MAX_RETRIES
151
+ )
152
+ return client
153
+ except Exception as e:
154
+ logger.error(f"Failed to create client for {model}: {e}")
155
+ # fallback to default client
156
+ return Client()
157
+
158
+ def format_messages(messages: List[Message]) -> List[Dict]:
159
+ """تنسيق الرسائل للإرسال"""
160
+
161
+ return [
162
+ {
163
+ "role": m.role,
164
+ "content": m.content
165
+ }
166
+ for m in messages
167
+ ]
168
+
169
+ async def create_completion_with_retry(
170
+ client: Client,
171
+ model: str,
172
+ messages: List[Dict],
173
+ stream: bool = False,
174
+ temperature: float = 0.7,
175
+ max_tokens: int = 2048
176
+ ) -> any:
177
+ """إنشاء استجابة مع إعادة محاولة"""
178
+
179
+ retries = 0
180
+ last_error = None
181
+
182
+ while retries < MAX_RETRIES:
183
+ try:
184
+ response = await asyncio.to_thread(
185
+ client.chat.completions.create,
186
+ model=model,
187
+ messages=messages,
188
+ stream=stream,
189
+ temperature=temperature,
190
+ max_tokens=max_tokens
191
+ )
192
+ return response
193
+
194
+ except Exception as e:
195
+ last_error = e
196
+ retries += 1
197
+ logger.warning(
198
+ f"Attempt {retries}/{MAX_RETRIES} failed: {e}"
199
+ )
200
+
201
+ if retries < MAX_RETRIES:
202
+ await asyncio.sleep(2 ** retries) # exponential backoff
203
+ else:
204
+ raise last_error
205
+
206
+ # =====================================================
207
+ # ENDPOINTS
208
  # =====================================================
209
 
210
  @app.get("/")
211
  async def root():
212
+ """جذر API - معلومات الخدمة"""
213
+
214
  return {
215
  "status": "online",
216
+ "service": "Universal AI Gateway Pro",
217
+ "version": "5.0.0",
218
+ "providers": list(PROVIDERS_MAP.keys()),
219
+ "documentation": "/docs"
220
  }
221
 
 
 
 
 
222
  @app.get("/v1/models")
223
  async def get_models():
224
+ """قائمة النماذج المتاحة"""
225
+
226
  models_data = []
227
+
228
+ # النماذج المدعومة مع مزودين حقيقيين
229
+ supported_models = {
230
+ "gpt-4o": "Blackbox",
231
+ "gpt-4o-mini": "DeepInfra",
232
+ "gpt-4": "Claude",
233
+ "claude-3-haiku": "Claude",
234
+ "claude-3-sonnet": "Claude",
235
+ "llama-3.1-70b": "DeepInfra",
236
+ "mixtral-8x7b": "DeepInfra",
237
+ "deepseek-chat": "Blackbox",
238
+ "perplexity-chat": "PerplexityLabs",
239
+ }
240
+
241
+ for model_id, provider_name in supported_models.items():
242
+ models_data.append({
243
+ "id": model_id,
244
+ "object": "model",
245
+ "created": int(time.time()),
246
+ "owned_by": provider_name,
247
+ "provider": provider_name,
248
+ "active": True
249
+ })
250
+
251
+ logger.info(f"Returning {len(models_data)} available models")
252
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  return {
254
  "object": "list",
255
  "data": models_data
256
  }
257
 
258
+ @app.get("/v1/models/{model_id}")
259
+ async def get_model_info(model_id: str):
260
+ """معلومات نموذج محدد"""
261
+
262
+ if model_id not in PROVIDERS_MAP:
263
+ raise HTTPException(
264
+ status_code=404,
265
+ detail=f"Model {model_id} not found"
266
+ )
267
+
268
+ provider = PROVIDERS_MAP[model_id]
269
+
270
+ return {
271
+ "id": model_id,
272
+ "object": "model",
273
+ "created": int(time.time()),
274
+ "owned_by": provider.__name__,
275
+ "provider": provider.__name__,
276
+ "active": True
277
+ }
278
 
279
+ @app.post("/v1/chat/completions", response_class=StreamingResponse)
280
+ async def chat_completions(req: Request, body: ChatRequest):
281
+ """استكمال المحادثات - متوافق مع OpenAI API"""
282
+
283
+ # التحقق من المفتاح
284
  verify_api_key(req)
285
+
286
+ # تنسيق الرسائل
287
+ messages = format_messages(body.messages)
288
+
 
 
 
 
 
289
  logger.info(
290
+ f"Chat request - Model: {body.model}, "
291
+ f"Stream: {body.stream}, "
292
+ f"Messages: {len(messages)}"
293
  )
294
+
295
+ # إنشاء العميل
296
+ client = await create_chat_client(body.model)
297
+
298
+ # =========================================================
299
+ # STREAMING RESPONSE
300
+ # =========================================================
301
+
302
  if body.stream:
303
+
304
  async def generate_stream():
305
+ """��ولّد الدفق المتدفق"""
306
+
307
+ chunk_id = f"chatcmpl-{uuid.uuid4().hex}"
308
+
309
  try:
310
+ # الحصول على الاستجابة المتدفقة
311
+ response = await create_completion_with_retry(
312
+ client=client,
 
313
  model=body.model,
314
  messages=messages,
315
+ stream=True,
316
+ temperature=body.temperature,
317
+ max_tokens=body.max_tokens
318
  )
319
+
320
+ # إرسال الأجزاء
 
321
  for chunk in response:
 
322
  try:
 
323
  content = ""
324
+ finish_reason = None
325
+
326
+ # استخراج المحتوى من الجزء
327
+ if hasattr(chunk, 'choices') and chunk.choices:
328
+ if hasattr(chunk.choices[0], 'delta'):
329
+ if chunk.choices[0].delta.content:
330
+ content = chunk.choices[0].delta.content
331
+ if hasattr(chunk.choices[0], 'finish_reason'):
332
+ finish_reason = chunk.choices[0].finish_reason
333
+
334
+ # إنشاء payload
335
+ payload = {
336
+ "id": chunk_id,
337
+ "object": "chat.completion.chunk",
338
+ "created": int(time.time()),
339
+ "model": body.model,
340
+ "choices": [
341
+ {
342
+ "index": 0,
343
+ "delta": {"content": content} if content else {},
344
+ "finish_reason": finish_reason
345
+ }
346
+ ]
347
+ }
348
+
349
+ yield f"data: {json.dumps(payload)}\n\n"
350
+ await asyncio.sleep(0.01) # تحسين الاستجابة
351
+
 
 
352
  except Exception as chunk_error:
353
+ logger.error(f"Chunk processing error: {chunk_error}")
354
+ continue
355
+
356
+ # إرسال الحزمة النهائية
 
357
  final_payload = {
358
  "id": chunk_id,
359
  "object": "chat.completion.chunk",
 
367
  }
368
  ]
369
  }
370
+
371
+ yield f for {body.model}")
372
+
 
 
373
  except Exception as e:
374
+ logger.error(f"Streaming error: {e}", exc_info=True)
375
+
 
376
  error_payload = {
377
  "error": {
378
  "message": str(e),
379
+ "type": "server_error",
380
+ "param": None,
381
+ "code": "server_error"
382
  }
383
  }
384
+
385
  yield f"data: {json.dumps(error_payload)}\n\n"
386
+
387
  return StreamingResponse(
388
  generate_stream(),
389
  media_type="text/event-stream",
 
393
  "X-Accel-Buffering": "no"
394
  }
395
  )
396
+
397
+ # =========================================================
398
+ # NORMAL (NON-STREAMING) RESPONSE
399
+ # =========================================================
400
+
401
  try:
402
+ response = await create_completion_with_retry(
403
+ client=client,
 
 
 
404
  model=body.model,
405
+ messages=messages,
406
+ stream=False,
407
+ temperature=body.temperature,
408
+ max_tokens=body.max_tokens
409
  )
410
+
411
  assistant_message = ""
412
+ completion_tokens = 0
413
+
414
  try:
415
+ if hasattr(response, 'choices') and response.choices:
416
+ if hasattr(response.choices[0], 'message'):
417
+ assistant_message = response.choices[0].message.content
418
+ else:
419
+ assistant_message = str(response)
420
+ else:
421
+ assistant_message = str(response)
422
+ except Exception as parse_error:
423
+ logger.error(f"Response parsing error: {parse_error}")
424
  assistant_message = str(response)
425
+
426
+ # حساب التقريبي للـ tokens
427
+ completion_tokens = len(assistant_message.split()) * 1.3
428
+ prompt_tokens = sum(len(m["content"].split()) * 1.3 for m in messages)
429
+
430
+ logger.info(
431
+ f"Completion successful - Model: {body.model}, "
432
+ f"Tokens: {prompt_tokens + completion_tokens}"
433
+ )
434
+
435
  return JSONResponse({
436
  "id": f"chatcmpl-{uuid.uuid4().hex}",
437
  "object": "chat.completion",
 
448
  }
449
  ],
450
  "usage": {
451
+ "prompt_tokens": int(prompt_tokens),
452
+ "completion_tokens": int(completion_tokens),
453
+ "total_tokens": int(prompt_tokens + completion_tokens)
454
  }
455
  })
456
+
457
  except Exception as e:
458
+ logger.error(f"Chat completion error: {e}", exc_info=True)
459
+
 
460
  raise HTTPException(
461
  status_code=500,
462
+ detail={
463
+ "message": str(e),
464
+ "type": "server_error",
465
+ "model": body.model
466
+ }
467
  )
468
 
469
+ @app.get("/v1/health")
470
+ async def health_check():
471
+ """فحص صحة الخدمة"""
472
+
473
+ return {
474
+ "status": "healthy",
475
+ "timestamp": int(time.time()),
476
+ "version": "5.0.0"
477
+ }
478
+
479
+ # =====================================================
480
+ # ERROR HANDLERS
481
+ # =====================================================
482
+
483
+ @app.exception_handler(HTTPException)
484
+ async def http_exception_handler(request: Request, exc: HTTPException):
485
+ """معالج استثناءات HTTP"""
486
+
487
+ return JSONResponse(
488
+ status_code=exc.status_code,
489
+ content={
490
+ "error": {
491
+ "message": exc.detail,
492
+ "type": "http_error",
493
+ "status_code": exc.status_code
494
+ }
495
+ }
496
+ )
497
+
498
  # =====================================================
499
  # RUN
500
  # =====================================================
501
 
502
  if __name__ == "__main__":
 
503
  import uvicorn
504
+
505
+ logger.info("Starting Universal AI Gateway Pro v5.0.0")
506
+ logger.info(f"Available providers: {list(PROVIDERS_MAP.keys())}")
507
+
508
  uvicorn.run(
509
  app,
510
  host="0.0.0.0",
511
+ port=7860,
512
+ log_level="info"
513
+ )