bahi-bh commited on
Commit
9bbcc0b
·
verified ·
1 Parent(s): 499feed

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +289 -460
app.py CHANGED
@@ -1,523 +1,352 @@
1
- import os
 
 
 
 
 
2
  import json
3
  import time
4
  import uuid
5
- import asyncio
6
  import logging
7
- import uvicorn
8
- import random
9
- from fastapi import FastAPI, HTTPException, Request, Response
10
- from fastapi.middleware.cors import CORSMiddleware
11
- from concurrent.futures import ThreadPoolExecutor
12
- from curl_cffi import requests as curl_requests
13
- from typing import Dict, List, Optional
14
-
15
- # =========================================================
16
- # 1. الإعدادات
17
- # =========================================================
18
- API_KEY = os.environ.get("API_KEY", "sk-your-secret-key")
19
- PORT = int(os.environ.get("PORT", 7860))
20
- MAX_WORKERS = 50
21
- REFRESH_INTERVAL = 300 # ثواني بين دورات الاستكشاف
22
- CACHE_FILE = "models_cache.json"
23
-
24
- # نماذج غير نصية — يُستبعد استيرادها
25
- MODEL_BLACKLIST = [
26
- "embed", "embedding",
27
- "image", "vision",
28
- "audio", "tts", "whisper",
29
- "moderation",
30
- "dall-e", "stable-diffusion", "midjourney",
31
- ]
32
-
33
- # ===================== الحالة العامة =====================
34
- WORKING_MODELS: Dict[str, dict] = {} # { model_id: {providers, latency, health} }
35
- PROVIDER_MODEL_MAP: Dict[str, List[str]] = {} # { chat_url: [model_id, ...] }
36
-
37
- STATE_LOCK = asyncio.Lock()
38
- REQUEST_LIMITER = asyncio.Semaphore(30)
39
- EXECUTOR = ThreadPoolExecutor(max_workers=MAX_WORKERS)
40
-
41
- app = FastAPI(title="Omega Orchestrator", version="3.0")
42
  app.add_middleware(
43
  CORSMiddleware,
44
  allow_origins=["*"],
 
45
  allow_methods=["*"],
46
  allow_headers=["*"],
47
  )
48
 
49
- logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
50
- logger = logging.getLogger("OMEGA")
 
51
 
 
 
 
52
 
53
- # =========================================================
54
- # 2. مساعدات
55
- # =========================================================
56
- def stealth_headers() -> dict:
57
- return {
58
- "User-Agent": (
59
- f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
60
- f"AppleWebKit/537.36 (KHTML, like Gecko) "
61
- f"Chrome/{random.randint(120, 126)}.0.0.0 Safari/537.36"
62
- ),
63
- "Origin": "https://g4f.space",
64
- "Referer": "https://g4f.space/",
65
- "X-Requested-With": "XMLHttpRequest",
66
- "Accept": "application/json",
67
- "Content-Type": "application/json",
68
- }
69
 
 
 
 
70
 
71
- def is_blacklisted(model_id: str) -> bool:
72
- low = model_id.lower()
73
- return any(b in low for b in MODEL_BLACKLIST)
74
 
 
75
 
76
- def verify_api_key(request: Request) -> bool:
77
- """
78
- يقبل المفتاح من ثلاث طرق:
79
- - Authorization: Bearer <key>
80
- - x-api-key: <key>
81
- - api-key: <key>
82
- وصول مفتوح إذا لم يُضبط API_KEY.
83
- """
84
- if not API_KEY or API_KEY == "sk-your-secret-key":
85
  return True
86
 
87
- candidates: List[str] = []
88
-
89
- auth = request.headers.get("Authorization", "")
90
- if auth.startswith("Bearer "):
91
- candidates.append(auth[7:].strip())
92
-
93
- for h in ("x-api-key", "api-key"):
94
- v = request.headers.get(h, "").strip()
95
- if v:
96
- candidates.append(v)
97
-
98
- return any(c == API_KEY for c in candidates)
99
-
100
-
101
- # =========================================================
102
- # 3. استخراج المحتوى من أي رد JSON
103
- # =========================================================
104
- def extract_content(data) -> Optional[str]:
105
- if not isinstance(data, dict):
106
- return None
107
-
108
- for f in ("response", "content", "text", "output", "result", "generated_text", "completion"):
109
- v = data.get(f)
110
- if isinstance(v, str) and v.strip():
111
- return v.strip()
112
-
113
- choices = data.get("choices")
114
- if isinstance(choices, list) and choices:
115
- c = choices[0]
116
- if isinstance(c, dict):
117
- msg = c.get("message", {})
118
- if isinstance(msg, dict):
119
- t = msg.get("content")
120
- if isinstance(t, str) and t.strip():
121
- return t.strip()
122
- txt = c.get("text", "")
123
- if isinstance(txt, str) and txt.strip():
124
- return txt.strip()
125
- delta = c.get("delta", {})
126
- if isinstance(delta, dict):
127
- t = delta.get("content", "")
128
- if isinstance(t, str) and t.strip():
129
- return t.strip()
130
-
131
- msg = data.get("message")
132
- if isinstance(msg, dict):
133
- c = msg.get("content")
134
- if isinstance(c, list):
135
- parts = [b.get("text", "") for b in c if isinstance(b, dict) and b.get("type") == "text"]
136
- joined = "".join(parts).strip()
137
- if joined:
138
- return joined
139
- elif isinstance(c, str) and c.strip():
140
- return c.strip()
141
- elif isinstance(msg, str) and msg.strip():
142
- return msg.strip()
143
-
144
- inner = data.get("data")
145
- if isinstance(inner, dict):
146
- return extract_content(inner)
147
-
148
- return None
149
-
150
-
151
- # =========================================================
152
- # 4. كلاس المزوّد الأساسي
153
- # =========================================================
154
- class BaseProvider:
155
- def __init__(self, name: str, url_chat: str, url_models: str = ""):
156
- self.name = name
157
- self.url_chat = url_chat
158
- self.url_models = url_models or url_chat
159
- self.headers = stealth_headers()
160
- self.aliases: Dict[str, str] = {}
161
- self.fails = 0
162
- self.success = 0
163
- self.cooldown = 0.0
164
- self.latency = 0.0
165
- self.health = 100
166
-
167
- def _record_success(self, latency: float):
168
- self.success += 1
169
- self.latency = (self.latency + latency) / 2 if self.latency else latency
170
- self._calc_health()
171
-
172
- def _record_fail(self):
173
- self.fails += 1
174
- self._calc_health()
175
- if self.fails >= 3:
176
- self.cooldown = time.time() + 60
177
-
178
- def _calc_health(self):
179
- total = self.success + self.fails
180
- self.health = int(self.success / total * 100) if total else 100
181
-
182
- # ── جلب النماذج — بدون تحقق، مباشرة من المزوّد ──────
183
- async def fetch_models(self) -> List[str]:
184
- loop = asyncio.get_event_loop()
185
- try:
186
- async with REQUEST_LIMITER:
187
- raw = await loop.run_in_executor(EXECUTOR, self._fetch_models_sync)
188
- return [m for m in set(raw) if m and not is_blacklisted(m)]
189
- except Exception as e:
190
- logger.warning(f"[{self.name}] fetch_models error: {e}")
191
- return []
192
-
193
- def _fetch_models_sync(self) -> List[str]:
194
- # نقاط النهاية المحتملة بالترتيب
195
- base = self.url_models.rstrip("/")
196
- chat = self.url_chat.rstrip("/")
197
- endpoints = list(dict.fromkeys([
198
- base,
199
- base + "/v1/models",
200
- base + "/models",
201
- chat + "/v1/models",
202
- chat + "/models",
203
- ]))
204
-
205
- with curl_requests.Session() as session:
206
- for ep in endpoints:
207
- try:
208
- r = session.get(ep, headers=self.headers, impersonate="chrome124", timeout=12)
209
- if r.status_code != 200:
210
- continue
211
- ids = self._parse_models(r.json())
212
- if ids:
213
- logger.info(f"[{self.name}] {len(ids)} models ← {ep}")
214
- return ids
215
- except Exception:
216
- continue
217
- return []
218
-
219
- @staticmethod
220
- def _parse_models(data) -> List[str]:
221
- """يستخرج معرّفات النماذج من أي هيكل JSON ممكن."""
222
- ids: List[str] = []
223
-
224
- if isinstance(data, list):
225
- for item in data:
226
- if isinstance(item, str) and item.strip():
227
- ids.append(item.strip())
228
- elif isinstance(item, dict):
229
- for k in ("id", "name", "model", "model_id", "modelId"):
230
- v = item.get(k)
231
- if isinstance(v, str) and v.strip():
232
- ids.append(v.strip())
233
- break
234
-
235
- elif isinstance(data, dict):
236
- for key in ("data", "models", "result", "results", "items"):
237
- sub = data.get(key)
238
- if isinstance(sub, list):
239
- found = BaseProvider._parse_models(sub)
240
- if found:
241
- return found
242
- for k in ("id", "name", "model"):
243
- v = data.get(k)
244
- if isinstance(v, str) and v.strip():
245
- ids.append(v.strip())
246
-
247
- return ids
248
-
249
- # ── إرسال الطلب ──────────────────────────────────────
250
- async def attempt_request(self, payload: dict) -> Optional[str]:
251
- payload = payload.copy()
252
-
253
- if time.time() < self.cooldown:
254
- return None
255
-
256
- model_id = payload.get("model", "")
257
- payload["model"] = self.aliases.get(model_id, model_id)
258
 
259
- try:
260
- async with REQUEST_LIMITER:
261
- loop = asyncio.get_event_loop()
262
- t0 = time.time()
263
- text = await loop.run_in_executor(EXECUTOR, self._post_sync, payload)
264
- lat = time.time() - t0
265
-
266
- if text:
267
- self._record_success(lat)
268
- return text
269
-
270
- self._record_fail()
271
- return None
272
- except Exception:
273
- self._record_fail()
274
- return None
275
-
276
- def _post_sync(self, payload: dict) -> Optional[str]:
277
- with curl_requests.Session() as session:
278
- try:
279
- r = session.post(
280
- self.url_chat,
281
- headers=self.headers,
282
- json=payload,
283
- impersonate="chrome124",
284
- timeout=30,
285
- )
286
- if r.status_code == 200:
287
- content = extract_content(r.json())
288
- if content:
289
- return content
290
- except Exception:
291
- pass
292
- return None
293
-
294
-
295
- # =========================================================
296
- # 5. تعريف المزوّدين
297
- # أضف أي مزوّد جديد هنا بنفس الطريقة
298
- # =========================================================
299
- PROVIDER_INSTANCES: List[BaseProvider] = [
300
- BaseProvider("G4F-Groq", "https://g4f.space/api/groq"),
301
- BaseProvider("G4F-Gemini", "https://g4f.space/api/gemini"),
302
- BaseProvider("G4F-Pollinations","https://g4f.space/api/pollinations"),
303
- BaseProvider("G4F-Ollama", "https://g4f.space/api/ollama"),
304
- BaseProvider("G4F-Perplexity", "https://g4f.space/api/perplexity"),
305
- BaseProvider("G4F-OpenAI", "https://g4f.space/api/openai"),
306
- BaseProvider("G4F-DeepSeek", "https://g4f.space/api/deepseek"),
307
- BaseProvider("G4F-Mistral", "https://g4f.space/api/mistral"),
308
- BaseProvider("G4F-Claude", "https://g4f.space/api/claude"),
309
- BaseProvider("G4F-Meta", "https://g4f.space/api/meta"),
310
- ]
311
-
312
-
313
- # =========================================================
314
- # 6. التخزين المؤقت
315
- # =========================================================
316
- async def load_cache():
317
- global WORKING_MODELS, PROVIDER_MODEL_MAP
318
- try:
319
- if os.path.exists(CACHE_FILE):
320
- with open(CACHE_FILE, "r", encoding="utf-8") as f:
321
- d = json.load(f)
322
- async with STATE_LOCK:
323
- WORKING_MODELS = d.get("WORKING_MODELS", {})
324
- PROVIDER_MODEL_MAP = d.get("PROVIDER_MODEL_MAP", {})
325
- logger.info(f"✅ Cache loaded — {len(WORKING_MODELS)} models.")
326
- except Exception as e:
327
- logger.error(f"Cache load error: {e}")
328
 
 
 
 
 
 
329
 
330
- async def save_cache():
331
- try:
332
- async with STATE_LOCK:
333
- snap = {
334
- "WORKING_MODELS": dict(WORKING_MODELS),
335
- "PROVIDER_MODEL_MAP": dict(PROVIDER_MODEL_MAP),
336
- }
337
- with open(CACHE_FILE, "w", encoding="utf-8") as f:
338
- json.dump(snap, f, indent=2, ensure_ascii=False)
339
- except Exception as e:
340
- logger.error(f"Cache save error: {e}")
341
 
 
 
 
342
 
343
- # =========================================================
344
- # 7. محرك الاستكشاف — استيراد مباشر بلا تحقق مسبق
345
- # =========================================================
346
- async def discovery_engine():
347
- """
348
- يجلب النماذج مباشرةً من كل مزوّد في نفس الوقت
349
- ويضيفها للسجل فوراً — بدون خطوة تحقق تُعيق الاستيراد.
350
- الفلترة الوحيدة: استبعاد القائمة السوداء (صور/صوت/تضمين).
351
- """
352
- await load_cache()
353
 
354
- while True:
355
- logger.info("🔍 Discovery cycle …")
 
 
 
356
 
357
- fresh_models: Dict[str, dict] = {}
358
- fresh_map: Dict[str, List[str]] = {}
 
359
 
360
- # جلب متوازٍ من جميع المزوّدين
361
- tasks = {p: asyncio.create_task(p.fetch_models()) for p in PROVIDER_INSTANCES}
362
 
363
- for provider, task in tasks.items():
364
- try:
365
- models = await task
366
- except Exception as e:
367
- logger.warning(f"[{provider.name}] task error: {e}")
368
- models = []
369
 
370
- fresh_map[provider.url_chat] = models
 
 
 
 
 
 
 
 
 
 
371
 
372
- for m in models:
373
- if m not in fresh_models:
374
- fresh_models[m] = {
375
- "providers": [],
376
- "latency": 0.0,
377
- "health": provider.health,
378
- }
379
- if provider.url_chat not in fresh_models[m]["providers"]:
380
- fresh_models[m]["providers"].append(provider.url_chat)
381
 
382
- logger.info(f"[{provider.name}] → {len(models)} models imported.")
383
 
384
- # نشر ذري
385
- async with STATE_LOCK:
386
- global WORKING_MODELS, PROVIDER_MODEL_MAP
387
- WORKING_MODELS = fresh_models
388
- PROVIDER_MODEL_MAP = fresh_map
389
 
390
- await save_cache()
391
- logger.info(f"🚀 Ready — {len(WORKING_MODELS)} total models.")
392
- await asyncio.sleep(REFRESH_INTERVAL)
393
 
 
 
 
 
 
 
394
 
395
- # =========================================================
396
- # 8. المعالج المركزي
397
- # =========================================================
398
- @app.on_event("startup")
399
- async def on_startup():
400
- asyncio.create_task(discovery_engine())
401
 
 
402
 
403
- @app.api_route("/{path:path}", methods=["GET", "HEAD", "POST", "OPTIONS"])
404
- async def omega_handler(request: Request, path: str):
405
 
406
- # CORS preflight
407
- if request.method == "OPTIONS":
408
- return Response(
409
- status_code=204,
410
- headers={
411
- "Access-Control-Allow-Origin": "*",
412
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
413
- "Access-Control-Allow-Headers": "*",
414
- },
415
- )
416
 
417
- p = path.lower().strip("/")
418
-
419
- # ── قائمة النماذج ─────────────────────────────────────
420
- if request.method in ("GET", "HEAD"):
421
- if "models" in p or p in ("", "v1", "v1/"):
422
- if "models" in p:
423
- async with STATE_LOCK:
424
- ids = sorted(WORKING_MODELS.keys())
425
- return {
426
- "object": "list",
427
- "data": [
428
- {"id": m, "object": "model", "created": int(time.time()), "owned_by": "omega"}
429
- for m in ids
430
- ],
431
- }
432
- return Response(status_code=200)
433
 
434
- # ── إرسال رسائل ───────────────────────────────────────
435
- if request.method == "POST" and any(x in p for x in ("messages", "completions", "chat")):
 
 
436
 
437
- if not verify_api_key(request):
438
- raise HTTPException(status_code=401, detail="Unauthorized.")
 
439
 
440
- try:
441
- body = await request.json()
442
- except Exception:
443
- raise HTTPException(status_code=400, detail="Invalid JSON.")
 
444
 
445
- model = body.get("model", "")
446
- messages = body.get("messages", [])
447
 
448
- if not messages:
449
- raise HTTPException(status_code=400, detail="messages is required.")
 
 
 
 
 
450
 
451
- # اختيار المزوّدين القادرين على تقديم هذا النموذج
452
- async with STATE_LOCK:
453
- info = WORKING_MODELS.get(model)
454
- target_urls = list(info["providers"]) if info and info["providers"] else [p.url_chat for p in PROVIDER_INSTANCES]
455
 
456
- providers = [p for p in PROVIDER_INSTANCES if p.url_chat in target_urls]
457
- providers.sort(key=lambda p: (p.fails, -p.health, p.latency))
 
458
 
459
- reply: Optional[str] = None
460
 
461
- for provider in providers:
462
- if time.time() < provider.cooldown:
463
- continue
464
- reply = await provider.attempt_request(body)
465
- if reply:
466
- logger.info(f"✅ [{provider.name}] served model={model}")
467
- break
468
 
469
- # Fallback عبر g4f
470
- if not reply:
471
  try:
472
- from g4f.client import Client
473
- loop = asyncio.get_event_loop()
474
- reply = await loop.run_in_executor(
475
- EXECUTOR,
476
- lambda: Client()
477
- .chat.completions.create(model=model, messages=messages)
478
- .choices[0].message.content,
479
  )
480
- logger.info("🔄 g4f fallback used.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  except Exception as e:
482
- logger.warning(f"g4f fallback failed: {e}")
483
-
484
- if not reply:
485
- raise HTTPException(status_code=502, detail="All routes exhausted.")
486
-
487
- # ─ تنسيق الرد ─
488
- if "messages" in p and "chat" not in p:
489
- # Anthropic format
490
- return {
491
- "id": f"msg_{uuid.uuid4().hex}",
492
- "type": "message",
493
- "role": "assistant",
494
- "model": model,
495
- "content": [{"type": "text", "text": reply}],
496
- "stop_reason": "end_turn",
497
- "usage": {"input_tokens": 0, "output_tokens": 0},
 
 
 
498
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
 
500
- # OpenAI format
501
- return {
502
- "id": f"chatcmpl-{uuid.uuid4().hex}",
503
- "object": "chat.completion",
504
  "created": int(time.time()),
505
- "model": model,
506
  "choices": [
507
  {
508
- "index": 0,
509
- "message": {"role": "assistant", "content": reply},
510
- "finish_reason": "stop",
 
 
 
511
  }
512
  ],
513
- "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0},
514
- }
 
 
 
 
515
 
516
- return Response(status_code=404)
517
 
 
 
 
 
 
 
 
 
 
 
518
 
519
- # =========================================================
520
- # 9. نقطة الدخول
521
- # =========================================================
522
  if __name__ == "__main__":
523
- uvicorn.run(app, host="0.0.0.0", port=PORT, log_level="info")
 
 
 
 
 
 
 
 
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
+ # =====================================================
23
+ # CONFIG
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
+ # =====================================================
38
+ # CORS
39
+ # =====================================================
40
+
 
 
 
 
 
41
  app.add_middleware(
42
  CORSMiddleware,
43
  allow_origins=["*"],
44
+ allow_credentials=True,
45
  allow_methods=["*"],
46
  allow_headers=["*"],
47
  )
48
 
49
+ # =====================================================
50
+ # MODELS
51
+ # =====================================================
52
 
53
+ class Message(BaseModel):
54
+ role: str
55
+ content: str
56
 
57
+ class ChatRequest(BaseModel):
58
+ model: str
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",
250
+ "created": int(time.time()),
251
+ "model": body.model,
252
+ "choices": [
253
+ {
254
+ "index": 0,
255
+ "delta": {},
256
+ "finish_reason": "stop"
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",
281
+ headers={
282
+ "Cache-Control": "no-cache",
283
+ "Connection": "keep-alive",
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",
 
312
  "created": int(time.time()),
313
+ "model": body.model,
314
  "choices": [
315
  {
316
+ "index": 0,
317
+ "message": {
318
+ "role": "assistant",
319
+ "content": assistant_message
320
+ },
321
+ "finish_reason": "stop"
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
+ )