bahi-bh commited on
Commit
49ee4d5
·
verified ·
1 Parent(s): 39b730a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1005 -166
app.py CHANGED
@@ -1,260 +1,852 @@
 
 
 
 
 
 
 
 
 
 
1
  import os
 
2
  import time
3
  import random
 
4
  import traceback
5
  import json
 
 
 
 
 
 
 
 
 
 
6
 
7
- from fastapi import FastAPI, HTTPException, Depends, Header
8
- from pydantic import BaseModel
 
 
 
 
9
  import uvicorn
10
 
 
11
  from g4f.client import Client
12
  from g4f.Provider import __providers__
13
 
14
  # ============================================================
15
- # إنشاء ملف config.json تلقائياً إذا لم يكن موجوداً
16
- # (تقرأه مكتبة g4f تلقائياً عند إنشاء Client)
17
  # ============================================================
18
- CONFIG_PATH = "config.json"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
- if not os.path.exists(CONFIG_PATH):
21
- context7_key = os.environ.get("CONTEXT7_API_KEY", "")
22
- if context7_key:
23
- config_data = {
24
- "mcpServers": {
25
- "context7": {
26
- "url": "https://mcp.context7.com/mcp",
27
- "headers": {
28
- "Authorization": f"Bearer {context7_key}"
29
- }
30
- }
31
- }
32
- }
33
- with open(CONFIG_PATH, "w", encoding="utf-8") as f:
34
- json.dump(config_data, f, indent=2, ensure_ascii=False)
35
- print("[+] config.json auto-generated for Context7 MCP")
36
- else:
37
- print("[!] CONTEXT7_API_KEY not set; skipping config.json creation")
38
 
39
  # ============================================================
40
- # المنطق الأصلي لم يُمسَّ
41
  # ============================================================
 
42
 
43
- MODEL = "gpt-4o-mini"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- MAX_RETRIES = 10
46
- TIMEOUT = 60
 
 
 
 
 
 
 
 
 
 
 
47
 
48
- SAFE_PROVIDERS = []
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
 
 
 
 
 
 
 
 
 
50
  for provider in __providers__:
51
  try:
52
  name = provider.__name__.lower()
 
 
53
  if not getattr(provider, "working", False):
54
  continue
 
 
55
  if getattr(provider, "needs_auth", False):
56
  continue
 
 
57
  if getattr(provider, "use_nodriver", False):
58
  continue
59
-
60
- blocked = [
61
- "openai",
62
- "qwen",
63
- "copilot",
64
- "gemini",
65
- "claude",
66
- ]
67
-
68
  if any(x in name for x in blocked):
69
  continue
70
-
71
  SAFE_PROVIDERS.append(provider)
72
  except:
73
  pass
74
 
75
  random.shuffle(SAFE_PROVIDERS)
 
76
 
77
- print(f"[+] SAFE PROVIDERS: {len(SAFE_PROVIDERS)}")
78
-
79
- client = Client()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
 
 
 
82
  class SmartG4F:
83
-
84
- def __init__(self):
85
- self.good = []
86
- self.bad = {}
87
-
88
- def mark_bad(self, provider, cooldown=300):
89
- self.bad[provider.__name__] = time.time() + cooldown
90
-
91
- def is_bad(self, provider):
92
- expire = self.bad.get(provider.__name__)
93
- if not expire:
94
- return False
95
- if time.time() > expire:
96
- del self.bad[provider.__name__]
97
- return False
98
- return True
99
-
100
- def provider_pool(self):
101
- providers = []
102
- providers.extend(self.good)
103
- for p in SAFE_PROVIDERS:
104
- if p not in providers and not self.is_bad(p):
105
- providers.append(p)
106
- random.shuffle(providers)
107
- return providers
108
-
109
- def ask(self, prompt):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  errors = []
111
-
 
 
 
 
 
 
 
112
  for attempt in range(MAX_RETRIES):
113
  pool = self.provider_pool()
114
-
115
  if not pool:
 
 
 
 
 
 
 
 
 
116
  return {
117
- "success": False,
118
- "error": "No providers available"
 
119
  }
120
-
121
  provider = random.choice(pool)
122
-
123
- print(f"[TRY {attempt+1}] {provider.__name__}")
124
-
 
 
 
 
 
125
  try:
 
 
 
 
 
 
 
 
 
126
  response = client.chat.completions.create(
127
  model=MODEL,
128
  provider=provider,
129
- messages=[
130
- {
131
- "role": "user",
132
- "content": prompt
133
- }
134
- ],
135
- timeout=TIMEOUT,
136
  )
137
-
 
 
 
138
  text = response.choices[0].message.content
139
-
140
  if not text:
141
- raise Exception("Empty response")
142
-
143
- if provider not in self.good:
144
- self.good.append(provider)
145
-
146
- return {
 
 
 
 
 
 
 
 
 
 
 
 
147
  "success": True,
 
148
  "provider": provider.__name__,
149
- "response": text
 
 
 
150
  }
151
-
 
 
 
 
 
 
 
 
 
152
  except Exception as e:
153
- err = str(e).lower()
154
-
155
- traceback.print_exc()
156
-
157
- errors.append({
 
 
158
  "provider": provider.__name__,
159
- "error": str(e)
160
- })
161
-
162
- if "429" in err:
163
- self.mark_bad(provider, 600)
164
- time.sleep(random.randint(10, 20))
165
-
166
- elif "cloudflare" in err:
167
- self.mark_bad(provider, 1200)
168
- time.sleep(20)
169
-
170
- elif "timeout" in err:
171
- self.mark_bad(provider, 300)
172
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  else:
174
- self.mark_bad(provider, 180)
175
-
 
 
 
176
  continue
177
-
 
 
 
 
 
 
178
  return {
179
  "success": False,
180
- "errors": errors
 
 
 
181
  }
182
 
183
-
184
- app = FastAPI()
185
-
186
- ai = SmartG4F()
187
-
188
-
189
  # ============================================================
190
- # التعديل الجديد: هيكل بيانات متوافق مع نمط OpenAI v1
191
  # ============================================================
192
- class OpenAIQuery(BaseModel):
193
- model: str
194
- messages: list
195
- temperature: float = 0.7
 
 
 
 
 
 
 
 
196
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
 
198
  # ============================================================
199
- # نظام الحماية Bearer Token
200
  # ============================================================
201
- def verify_api_key(authorization: str = Header(None)):
 
202
  expected_key = os.environ.get("MY_PROJECT_API_KEY")
 
203
  if not expected_key:
204
- raise HTTPException(
205
- status_code=500,
206
- detail="Server misconfiguration: MY_PROJECT_API_KEY not set"
207
- )
208
-
209
  if not authorization:
210
  raise HTTPException(
211
- status_code=403,
212
- detail="Missing Authorization header"
213
  )
214
-
215
  if not authorization.startswith("Bearer "):
216
  raise HTTPException(
217
- status_code=403,
218
- detail="Invalid authorization header format. Expected: Bearer <token>"
219
  )
220
-
221
- token = authorization.split(" ", 1)[1] if " " in authorization else ""
 
222
  if token != expected_key:
223
  raise HTTPException(
224
- status_code=403,
225
- detail="Invalid API key"
226
  )
227
-
228
  return True
229
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
  @app.get("/")
232
  async def home():
 
 
233
  return {
 
 
234
  "status": "running",
235
- "providers": len(SAFE_PROVIDERS)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  }
237
 
 
 
 
 
 
238
 
239
- # ============================================================
240
- # التعديل الجديد: المسار والرد المتوافق مع الواجهة الخارجية ونمط v1
241
- # ============================================================
242
- @app.post("/v1/chat/completions", dependencies=[Depends(verify_api_key)])
243
- async def ask_v1(query: OpenAIQuery):
244
- # سحب نص آخر رسالة أرسلها المستخدم في الواجهة
245
- if query.messages:
246
- last_message = query.messages[-1].get("content", "")
247
- else:
248
- last_message = ""
249
-
250
- # تمرير السؤال لمنطق المعالجة والـ Providers الذكي وسيرفر Context7
251
- result = ai.ask(last_message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
- # تنسيق الرد بالشكل القياسي المتوافق مع OpenAI لتفهمه الواجهة مباشرة
254
  if result.get("success"):
255
  return {
 
 
 
 
256
  "choices": [
257
  {
 
258
  "message": {
259
  "role": "assistant",
260
  "content": result.get("response")
@@ -262,19 +854,266 @@ async def ask_v1(query: OpenAIQuery):
262
  "finish_reason": "stop"
263
  }
264
  ],
265
- "model": query.model,
266
- "provider": result.get("provider")
 
 
 
 
 
 
267
  }
268
  else:
269
  raise HTTPException(
270
- status_code=500,
271
- detail="Failed to generate response from available providers"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
 
 
 
 
275
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
276
  uvicorn.run(
277
- app,
278
- host="0.0.0.0",
279
- port=7860
 
 
 
 
280
  )
 
1
+ """
2
+ ╔══════════════════════════════════════════════════════════════════╗
3
+ ║ SmartG4F API - محسّن بالكامل ║
4
+ ║ بدون أي قيود ║
5
+ ╚══════════════════════════════════════════════════════════════════╝
6
+ """
7
+
8
+ # ============================================================
9
+ #Imports
10
+ # ============================================================
11
  import os
12
+ import sys
13
  import time
14
  import random
15
+ import asyncio
16
  import traceback
17
  import json
18
+ import uuid
19
+ import logging
20
+ import threading
21
+ import hashlib
22
+ from datetime import datetime, timedelta
23
+ from collections import deque
24
+ from typing import Optional, List, Dict, Any, Union
25
+ from contextlib import asynccontextmanager
26
+ from functools import wraps
27
+ import re
28
 
29
+ # FastAPI & Dependencies
30
+ from fastapi import FastAPI, HTTPException, Depends, Header, Request, Response
31
+ from fastapi.middleware.cors import CORSMiddleware
32
+ from fastapi.responses import StreamingResponse, JSONResponse
33
+ from fastapi.staticfiles import StaticFiles
34
+ from pydantic import BaseModel, Field, validator
35
  import uvicorn
36
 
37
+ # g4f Dependencies
38
  from g4f.client import Client
39
  from g4f.Provider import __providers__
40
 
41
  # ============================================================
42
+ # إعدادات الـ Logging المحسّن
 
43
  # ============================================================
44
+ class ColoredFormatter(logging.Formatter):
45
+ """تنسيق Logs ملون للتمييز"""
46
+ COLORS = {
47
+ 'DEBUG': '\033[36m', # Cyan
48
+ 'INFO': '\033[32m', # Green
49
+ 'WARNING': '\033[33m', # Yellow
50
+ 'ERROR': '\033[31m', # Red
51
+ 'CRITICAL': '\033[35m', # Magenta
52
+ 'RESET': '\033[0m'
53
+ }
54
+
55
+ def format(self, record):
56
+ color = self.COLORS.get(record.levelname, self.COLORS['RESET'])
57
+ record.levelname = f"{color}{record.levelname}{self.COLORS['RESET']}"
58
+ return super().format(record)
59
 
60
+ # إعداد الـ Logger
61
+ logger = logging.getLogger("SmartG4F")
62
+ logger.setLevel(logging.DEBUG)
63
+
64
+ # Console Handler
65
+ console_handler = logging.StreamHandler()
66
+ console_handler.setLevel(logging.DEBUG)
67
+ console_handler.setFormatter(ColoredFormatter(
68
+ '%(asctime)s %(levelname)s │ %(message)s',
69
+ datefmt='%H:%M:%S'
70
+ ))
71
+ logger.addHandler(console_handler)
 
 
 
 
 
 
72
 
73
  # ============================================================
74
+ # إعداد نظام البروكسي (Proxies) - بدون قيود
75
  # ============================================================
76
+ PROXIES_POOL: List[str] = []
77
 
78
+ def load_proxies():
79
+ """تحميل البروكسيات من مصادر متعددة"""
80
+ global PROXIES_POOL
81
+
82
+ # 1. من Environment Variable
83
+ env_proxies = os.environ.get("PROXIES_LIST", "")
84
+ if env_proxies:
85
+ PROXIES_POOL.extend([p.strip() for p in env_proxies.split(",") if p.strip()])
86
+
87
+ # 2. من ملف proxies.txt
88
+ if os.path.exists("proxies.txt"):
89
+ with open("proxies.txt", "r") as f:
90
+ PROXIES_POOL.extend([line.strip() for line in f if line.strip()])
91
+
92
+ # 3. البروكسي الفردي
93
+ single_proxy = os.environ.get("STRONG_PROXY_URL") or os.environ.get("HTTP_PROXY")
94
+ if single_proxy and single_proxy not in PROXIES_POOL:
95
+ PROXIES_POOL.append(single_proxy)
96
+
97
+ # إزالة المكررات
98
+ PROXIES_POOL = list(dict.fromkeys(PROXIES_POOL))
99
+
100
+ logger.info(f"📡 تم تحميل {len(PROXIES_POOL)} بروكسي")
101
 
102
+ def get_random_proxy() -> Optional[str]:
103
+ """جلب بروكسي عشوائي"""
104
+ if not PROXIES_POOL:
105
+ # محاولة تحميل البروكسي��ت
106
+ load_proxies()
107
+
108
+ if PROXIES_POOL:
109
+ proxy = random.choice(PROXIES_POOL)
110
+ # التحقق من صحة البروكسي
111
+ if _validate_proxy(proxy):
112
+ return proxy
113
+
114
+ return None
115
 
116
+ def _validate_proxy(proxy: str) -> bool:
117
+ """التحقق من صحة صيغة البروكسي"""
118
+ if not proxy:
119
+ return False
120
+ # صيغ مقبولة:
121
+ # http://host:port
122
+ # http://user:pass@host:port
123
+ # socks5://host:port
124
+ patterns = [
125
+ r'^https?://[\w\-\.]+:\d+$',
126
+ r'^https?://[\w\-\.]+:\d+@[\w\-\.]+:\d+$',
127
+ r'^socks5?://[\w\-\.]+:\d+$',
128
+ ]
129
+ return any(re.match(p, proxy) for p in patterns)
130
 
131
+ # ============================================================
132
+ # تصفية وتجهيز المزودات (Safe Providers)
133
+ # ============================================================
134
+ MODEL = os.environ.get("DEFAULT_MODEL", "gpt-4o-mini")
135
+ MAX_RETRIES = int(os.environ.get("MAX_RETRIES", "15"))
136
+ TIMEOUT = int(os.environ.get("TIMEOUT", "120"))
137
+ MAX_PROVIDERS_IN_MEMORY = 100
138
+
139
+ SAFE_PROVIDERS = []
140
  for provider in __providers__:
141
  try:
142
  name = provider.__name__.lower()
143
+
144
+ # تصفية المزودات غير العاملة
145
  if not getattr(provider, "working", False):
146
  continue
147
+
148
+ # تخطي المزودات التي تحتاج تسجيل
149
  if getattr(provider, "needs_auth", False):
150
  continue
151
+
152
+ # تخطي المزودات التي تحتاج nodriver
153
  if getattr(provider, "use_nodriver", False):
154
  continue
155
+
156
+ # منع المزودات المحظورة
157
+ blocked = ["openai", "qwen", "copilot", "gemini", "claude"]
 
 
 
 
 
 
158
  if any(x in name for x in blocked):
159
  continue
160
+
161
  SAFE_PROVIDERS.append(provider)
162
  except:
163
  pass
164
 
165
  random.shuffle(SAFE_PROVIDERS)
166
+ logger.info(f"✅ SAFE PROVIDERS: {len(SAFE_PROVIDERS)}")
167
 
168
+ # ============================================================
169
+ # إنشاء ملف config.json تلقائياً لـ Context7 MCP
170
+ # ============================================================
171
+ CONFIG_PATH = "config.json"
172
+ if not os.path.exists(CONFIG_PATH):
173
+ context7_key = os.environ.get("CONTEXT7_API_KEY", "")
174
+ if context7_key:
175
+ config_data = {
176
+ "mcpServers": {
177
+ "context7": {
178
+ "url": "https://mcp.context7.com/mcp",
179
+ "headers": {
180
+ "Authorization": f"Bearer {context7_key}"
181
+ }
182
+ }
183
+ }
184
+ }
185
+ with open(CONFIG_PATH, "w", encoding="utf-8") as f:
186
+ json.dump(config_data, f, indent=2, ensure_ascii=False)
187
+ logger.info("[+] config.json auto-generated for Context7 MCP")
188
+ else:
189
+ logger.warning("[!] CONTEXT7_API_KEY not set; skipping config.json creation")
190
 
191
+ # ============================================================
192
+ # نظام إدارة الـ Client المحسّن
193
+ # ============================================================
194
+ class ClientManager:
195
+ """مدير الـ Clients للاتصال بـ g4f"""
196
+ _instances: Dict[str, 'Client'] = {}
197
+ _lock = threading.Lock()
198
+ _max_instances = 50
199
+
200
+ @classmethod
201
+ def get_client(cls, proxy: Optional[str] = None) -> 'Client':
202
+ """الحصول على Client (مع أو بدون بروكسي)"""
203
+ key = proxy or "default"
204
+
205
+ with cls._lock:
206
+ if key not in cls._instances:
207
+ if len(cls._instances) >= cls._max_instances:
208
+ # إزالة أقدم client
209
+ oldest_key = next(iter(cls._instances))
210
+ del cls._instances[oldest_key]
211
+
212
+ cls._instances[key] = Client(proxy=proxy)
213
+ logger.debug(f"🆕 New client created: {key[:20]}...")
214
+
215
+ return cls._instances[key]
216
 
217
+ # ============================================================
218
+ # نظام SmartG4F المحسّن بالكامل
219
+ # ============================================================
220
  class SmartG4F:
221
+ """
222
+ نظام ذكي لإدارة Providers
223
+ - تتبع Providers الناجحة والفاشلة
224
+ - إدارة ذاكرة محسّنة
225
+ - إحصائيات مفصلة
226
+ - Thread-Safe
227
+ """
228
+
229
+ def __init__(self, max_good_providers: int = MAX_PROVIDERS_IN_MEMORY):
230
+ # Good Providers - محدود بحجم ثابت
231
+ self.good = deque(maxlen=max_good_providers)
232
+
233
+ # Bad Providers - مع وقت انتهاء
234
+ self.bad: Dict[str, float] = {}
235
+
236
+ # قفل للأمان في تعدد الخيوط
237
+ self.lock = threading.Lock()
238
+
239
+ # إحصائيات شاملة
240
+ self.stats = {
241
+ "total_requests": 0,
242
+ "successful_requests": 0,
243
+ "failed_requests": 0,
244
+ "total_tokens_used": 0,
245
+ "avg_response_time": 0,
246
+ "start_time": time.time()
247
+ }
248
+
249
+ # سجل الأخطاء الأخير
250
+ self.error_log: deque = deque(maxlen=100)
251
+
252
+ # Provider Stats
253
+ self.provider_stats: Dict[str, Dict] = {}
254
+
255
+ def mark_good(self, provider) -> None:
256
+ """وضع Provider في قائمة الناجحين"""
257
+ with self.lock:
258
+ provider_name = provider.__name__
259
+ if provider not in self.good:
260
+ self.good.append(provider)
261
+
262
+ # تحديث الإحصائيات
263
+ if provider_name not in self.provider_stats:
264
+ self.provider_stats[provider_name] = {
265
+ "success": 0, "failed": 0, "avg_time": 0
266
+ }
267
+ self.provider_stats[provider_name]["success"] += 1
268
+
269
+ def mark_bad(self, provider, cooldown: int = 300) -> None:
270
+ """وضع Provider في قائمة الفاشلين"""
271
+ with self.lock:
272
+ provider_name = provider.__name__
273
+ self.bad[provider_name] = time.time() + cooldown
274
+
275
+ # تحديث الإحصائيات
276
+ if provider_name not in self.provider_stats:
277
+ self.provider_stats[provider_name] = {
278
+ "success": 0, "failed": 0, "avg_time": 0
279
+ }
280
+ self.provider_stats[provider_name]["failed"] += 1
281
+
282
+ logger.warning(f"⚠️ Provider marked bad: {provider_name} (cooldown: {cooldown}s)")
283
+
284
+ def is_bad(self, provider) -> bool:
285
+ """فحص إذا كان Provider في قائمة الحظر"""
286
+ with self.lock:
287
+ provider_name = provider.__name__
288
+ expire = self.bad.get(provider_name)
289
+
290
+ if not expire:
291
+ return False
292
+
293
+ if time.time() > expire:
294
+ del self.bad[provider_name]
295
+ logger.info(f"✅ Provider recovered: {provider_name}")
296
+ return False
297
+
298
+ return True
299
+
300
+ def provider_pool(self) -> List:
301
+ """إنشاء pool من Providers المتاحة"""
302
+ with self.lock:
303
+ providers = list(self.good)
304
+
305
+ for p in SAFE_PROVIDERS:
306
+ if p not in providers and not self.is_bad(p):
307
+ providers.append(p)
308
+
309
+ random.shuffle(providers)
310
+ return providers
311
+
312
+ def get_stats(self) -> Dict[str, Any]:
313
+ """الحصول على الإحصائيات"""
314
+ with self.lock:
315
+ uptime = time.time() - self.stats["start_time"]
316
+ success_rate = (
317
+ self.stats["successful_requests"] / self.stats["total_requests"] * 100
318
+ if self.stats["total_requests"] > 0 else 0
319
+ )
320
+
321
+ return {
322
+ **self.stats,
323
+ "uptime_seconds": uptime,
324
+ "uptime_human": str(timedelta(seconds=int(uptime))),
325
+ "success_rate": round(success_rate, 2),
326
+ "good_providers": len(self.good),
327
+ "bad_providers": len(self.bad),
328
+ "available_providers": len(self.provider_pool()),
329
+ "provider_stats": dict(self.provider_stats),
330
+ "recent_errors": list(self.error_log)
331
+ }
332
+
333
+ def ask(
334
+ self,
335
+ prompt: str,
336
+ temperature: float = 0.7,
337
+ max_tokens: Optional[int] = None,
338
+ stream: bool = False
339
+ ) -> Dict[str, Any]:
340
+ """إرسال طلب ونيل الرد"""
341
+ request_id = str(uuid.uuid4())[:8]
342
  errors = []
343
+ start_time = time.time()
344
+
345
+ with self.lock:
346
+ self.stats["total_requests"] += 1
347
+
348
+ logger.info(f"🎯 [{request_id}] New request started")
349
+ logger.debug(f"📝 Prompt length: {len(prompt)} chars")
350
+
351
  for attempt in range(MAX_RETRIES):
352
  pool = self.provider_pool()
353
+
354
  if not pool:
355
+ error_msg = "❌ No providers available"
356
+ logger.error(f"🚫 [{request_id}] {error_msg}")
357
+ with self.lock:
358
+ self.stats["failed_requests"] += 1
359
+ self.error_log.append({
360
+ "time": datetime.now().isoformat(),
361
+ "error": error_msg,
362
+ "request_id": request_id
363
+ })
364
  return {
365
+ "success": False,
366
+ "error": error_msg,
367
+ "request_id": request_id
368
  }
369
+
370
  provider = random.choice(pool)
371
+ current_proxy = get_random_proxy()
372
+
373
+ logger.info(
374
+ f"🔄 [{request_id}] Attempt {attempt + 1}/{MAX_RETRIES} | "
375
+ f"Provider: {provider.__name__} | "
376
+ f"Proxy: {'Yes ✅' if current_proxy else 'No ❌'}"
377
+ )
378
+
379
  try:
380
+ # الحصول على Client
381
+ client = ClientManager.get_client(proxy=current_proxy)
382
+
383
+ # تجهيز الرسائل
384
+ messages = [{"role": "user", "content": prompt}]
385
+
386
+ # إرسال الطلب
387
+ request_start = time.time()
388
+
389
  response = client.chat.completions.create(
390
  model=MODEL,
391
  provider=provider,
392
+ messages=messages,
393
+ temperature=temperature,
394
+ max_tokens=max_tokens,
395
+ timeout=TIMEOUT
 
 
 
396
  )
397
+
398
+ request_time = time.time() - request_start
399
+
400
+ # استخراج النص
401
  text = response.choices[0].message.content
402
+
403
  if not text:
404
+ raise Exception("Empty response received")
405
+
406
+ # تحديث الإحصائيات
407
+ with self.lock:
408
+ self.stats["successful_requests"] += 1
409
+ self.stats["total_tokens_used"] += len(text.split())
410
+
411
+ # حساب متوسط وقت الاستجابة
412
+ old_avg = self.stats["avg_response_time"]
413
+ total = self.stats["successful_requests"]
414
+ self.stats["avg_response_time"] = (
415
+ (old_avg * (total - 1) + request_time) / total
416
+ )
417
+
418
+ # وضع Provider في قائمة الناجحين
419
+ self.mark_good(provider)
420
+
421
+ response_data = {
422
  "success": True,
423
+ "request_id": request_id,
424
  "provider": provider.__name__,
425
+ "response": text,
426
+ "response_time": round(request_time, 3),
427
+ "tokens_used": len(text.split()),
428
+ "proxy_used": bool(current_proxy)
429
  }
430
+
431
+ logger.info(
432
+ f"✅ [{request_id}] Success! | "
433
+ f"Provider: {provider.__name__} | "
434
+ f"Time: {request_time:.2f}s | "
435
+ f"Length: {len(text)} chars"
436
+ )
437
+
438
+ return response_data
439
+
440
  except Exception as e:
441
+ error_str = str(e)
442
+ error_lower = error_str.lower()
443
+ request_time = time.time() - start_time
444
+
445
+ # تسجيل الخطأ
446
+ error_entry = {
447
+ "time": datetime.now().isoformat(),
448
  "provider": provider.__name__,
449
+ "error": error_str,
450
+ "attempt": attempt + 1,
451
+ "request_id": request_id
452
+ }
453
+
454
+ with self.lock:
455
+ self.error_log.append(error_entry)
456
+
457
+ logger.error(
458
+ f"❌ [{request_id}] Error: {error_str[:100]} | "
459
+ f"Provider: {provider.__name__}"
460
+ )
461
+
462
+ if logger.level == logging.DEBUG:
463
+ traceback.print_exc()
464
+
465
+ errors.append(error_entry)
466
+
467
+ # تحديد وقت الانتظار حسب نوع الخطأ
468
+ if "429" in error_lower or "rate limit" in error_lower:
469
+ cooldown = random.randint(600, 1200)
470
+ sleep_time = random.randint(15, 30)
471
+ logger.warning(f"⏳ Rate limited - sleeping {sleep_time}s")
472
+ time.sleep(sleep_time)
473
+
474
+ elif "cloudflare" in error_lower or "cf" in error_lower:
475
+ cooldown = random.randint(1200, 2400)
476
+ sleep_time = 30
477
+ logger.warning(f"🛡️ Cloudflare blocked - sleeping {sleep_time}s")
478
+ time.sleep(sleep_time)
479
+
480
+ elif "timeout" in error_lower:
481
+ cooldown = random.randint(300, 600)
482
+ sleep_time = random.randint(5, 15)
483
+ logger.warning(f"⏰ Timeout - sleeping {sleep_time}s")
484
+ time.sleep(sleep_time)
485
+
486
+ elif "connection" in error_lower:
487
+ cooldown = random.randint(60, 180)
488
+ sleep_time = random.randint(3, 10)
489
+ logger.warning(f"🔗 Connection error - sleeping {sleep_time}s")
490
+ time.sleep(sleep_time)
491
+
492
  else:
493
+ cooldown = random.randint(180, 300)
494
+ sleep_time = random.randint(2, 5)
495
+ time.sleep(sleep_time)
496
+
497
+ self.mark_bad(provider, cooldown)
498
  continue
499
+
500
+ # فشل جميع المحاولات
501
+ with self.lock:
502
+ self.stats["failed_requests"] += 1
503
+
504
+ logger.error(f"🚫 [{request_id}] All {MAX_RETRIES} attempts failed")
505
+
506
  return {
507
  "success": False,
508
+ "request_id": request_id,
509
+ "error": f"Failed after {MAX_RETRIES} attempts",
510
+ "errors": errors,
511
+ "total_time": round(time.time() - start_time, 3)
512
  }
513
 
 
 
 
 
 
 
514
  # ============================================================
515
+ # نماذج Pydantic للتحقق من البيانات
516
  # ============================================================
517
+ class Message(BaseModel):
518
+ """نموذج رسالة واحدة"""
519
+ role: str = Field(..., pattern="^(system|user|assistant)$")
520
+ content: str
521
+
522
+ class Config:
523
+ json_schema_extra = {
524
+ "example": {
525
+ "role": "user",
526
+ "content": "مرحباً، كيف حالك؟"
527
+ }
528
+ }
529
 
530
+ class ChatCompletionRequest(BaseModel):
531
+ """نموذج طلب الإكمال - متوافق مع OpenAI"""
532
+ model: str = Field(default=MODEL, description="نموذج الذكاء الاصطناعي")
533
+ messages: List[Message] = Field(
534
+ ...,
535
+ description="قائمة الرسائل",
536
+ min_length=1
537
+ )
538
+ temperature: float = Field(
539
+ default=0.7,
540
+ ge=0.0,
541
+ le=2.0,
542
+ description="درجة الإبداع (0-2)"
543
+ )
544
+ max_tokens: Optional[int] = Field(
545
+ default=4096,
546
+ ge=1,
547
+ le=32000,
548
+ description="الحد الأقصى للرموز"
549
+ )
550
+ stream: bool = Field(
551
+ default=False,
552
+ description="تفعيل البث المباشر"
553
+ )
554
+ top_p: Optional[float] = Field(
555
+ default=1.0,
556
+ ge=0.0,
557
+ le=1.0,
558
+ description="نسبة أخذ العينات"
559
+ )
560
+ frequency_penalty: Optional[float] = Field(
561
+ default=0.0,
562
+ ge=-2.0,
563
+ le=2.0
564
+ )
565
+ presence_penalty: Optional[float] = Field(
566
+ default=0.0,
567
+ ge=-2.0,
568
+ le=2.0
569
+ )
570
+ stop: Optional[List[str]] = Field(
571
+ default=None,
572
+ description="كلمات الإيقاف"
573
+ )
574
+
575
+ class Config:
576
+ json_schema_extra = {
577
+ "example": {
578
+ "model": "gpt-4o-mini",
579
+ "messages": [
580
+ {"role": "user", "content": "اكتب قصة قصيرة"}
581
+ ],
582
+ "temperature": 0.7,
583
+ "max_tokens": 1000,
584
+ "stream": False
585
+ }
586
+ }
587
+
588
+ class CompletionRequest(BaseModel):
589
+ """نموذج طلب الإكمال للنصوص"""
590
+ model: str = Field(default=MODEL)
591
+ prompt: str = Field(..., min_length=1)
592
+ temperature: float = Field(default=0.7, ge=0.0, le=2.0)
593
+ max_tokens: Optional[int] = Field(default=4096, ge=1)
594
+ stream: bool = Field(default=False)
595
+ n: int = Field(default=1, ge=1, le=10)
596
+
597
+ class EmbeddingRequest(BaseModel):
598
+ """نموذج طلب التضميد"""
599
+ model: str = Field(default="text-embedding-3-small")
600
+ input: Union[str, List[str]]
601
 
602
  # ============================================================
603
+ # نظام الحماية - بدون Rate Limiting
604
  # ============================================================
605
+ def verify_api_key(authorization: Optional[str] = Header(None)) -> bool:
606
+ """التحقق من مفتاح API"""
607
  expected_key = os.environ.get("MY_PROJECT_API_KEY")
608
+
609
  if not expected_key:
610
+ # إذا لم يتم تعيين مفتاح، نسمح بالوصول مع تحذير
611
+ logger.warning("⚠️ WARNING: No API key configured - running in open mode")
612
+ return True
613
+
 
614
  if not authorization:
615
  raise HTTPException(
616
+ status_code=403,
617
+ detail="Missing Authorization header"
618
  )
619
+
620
  if not authorization.startswith("Bearer "):
621
  raise HTTPException(
622
+ status_code=403,
623
+ detail="Invalid authorization header format. Use: Bearer <token>"
624
  )
625
+
626
+ token = authorization.split(" ", 1)[1]
627
+
628
  if token != expected_key:
629
  raise HTTPException(
630
+ status_code=403,
631
+ detail="Invalid API key"
632
  )
633
+
634
  return True
635
 
636
+ def get_client_ip(request: Request) -> str:
637
+ """الحصول على IP العميل"""
638
+ forwarded = request.headers.get("X-Forwarded-For")
639
+ if forwarded:
640
+ return forwarded.split(",")[0].strip()
641
+ return request.client.host if request.client else "unknown"
642
+
643
+ # ============================================================
644
+ # إعداد التطبيق
645
+ # ============================================================
646
+ @asynccontextmanager
647
+ async def lifespan(app: FastAPI):
648
+ """إدارة دورة حياة التطبيق"""
649
+ # Startup
650
+ logger.info("=" * 60)
651
+ logger.info("🚀 SmartG4F API Starting...")
652
+ logger.info("=" * 60)
653
+
654
+ # تحميل البروكسيات
655
+ load_proxies()
656
+
657
+ # إنشاء مجلدات إذا لم تكن موجودة
658
+ os.makedirs("logs", exist_ok=True)
659
+
660
+ app.state.start_time = time.time()
661
+ app.state.request_count = 0
662
+ app.state.ai = SmartG4F()
663
+
664
+ logger.info(f"✅ Loaded {len(SAFE_PROVIDERS)} safe providers")
665
+ logger.info(f"✅ Model: {MODEL}")
666
+ logger.info(f"✅ Max retries: {MAX_RETRIES}")
667
+ logger.info(f"✅ Timeout: {TIMEOUT}s")
668
+ logger.info("=" * 60)
669
+
670
+ yield
671
+
672
+ # Shutdown
673
+ logger.info("🛑 Shutting down SmartG4F API...")
674
+
675
+ app = FastAPI(
676
+ title="SmartG4F API",
677
+ description="API خادم شامل لـ g4f بدون قيود",
678
+ version="2.0.0",
679
+ docs_url="/docs",
680
+ redoc_url="/redoc",
681
+ lifespan=lifespan
682
+ )
683
+
684
+ # ============================================================
685
+ # CORS - مفتوح بالكامل
686
+ # ============================================================
687
+ app.add_middleware(
688
+ CORSMiddleware,
689
+ allow_origins=["*"], # كلOrigins مسموحة
690
+ allow_credentials=True,
691
+ allow_methods=["*"], # كل Methods مسموحة
692
+ allow_headers=["*"], # كل Headers مسموحة
693
+ expose_headers=["*"]
694
+ )
695
+
696
+ # ============================================================
697
+ # Middleware للتتبع والـ Logging
698
+ # ============================================================
699
+ @app.middleware("http")
700
+ async def log_requests(request: Request, call_next):
701
+ """تسجيل كل طلب"""
702
+ request_id = str(uuid.uuid4())[:8]
703
+ start_time = time.time()
704
+
705
+ # إضافة Request ID للـ Headers
706
+ request.state.request_id = request_id
707
+
708
+ client_ip = get_client_ip(request)
709
+
710
+ logger.info(
711
+ f"📥 [{request_id}] {request.method} {request.url.path} | "
712
+ f"IP: {client_ip}"
713
+ )
714
+
715
+ try:
716
+ response = await call_next(request)
717
+ process_time = time.time() - start_time
718
+
719
+ logger.info(
720
+ f"📤 [{request_id}] Status: {response.status_code} | "
721
+ f"Time: {process_time:.3f}s"
722
+ )
723
+
724
+ # إضافة Headers للاستجابة
725
+ response.headers["X-Request-ID"] = request_id
726
+ response.headers["X-Process-Time"] = str(round(process_time, 3))
727
+
728
+ return response
729
+
730
+ except Exception as e:
731
+ process_time = time.time() - start_time
732
+ logger.error(
733
+ f"💥 [{request_id}] Error: {str(e)[:100]} | "
734
+ f"Time: {process_time:.3f}s"
735
+ )
736
+ raise
737
+
738
+ # ============================================================
739
+ # نقاط النهاية (Endpoints)
740
+ # ============================================================
741
 
742
  @app.get("/")
743
  async def home():
744
+ """الصفحة الرئيسية"""
745
+ stats = app.state.ai.get_stats()
746
  return {
747
+ "name": "SmartG4F API",
748
+ "version": "2.0.0",
749
  "status": "running",
750
+ "uptime": stats["uptime_human"],
751
+ "providers": {
752
+ "safe": len(SAFE_PROVIDERS),
753
+ "available": stats["available_providers"],
754
+ "good": stats["good_providers"],
755
+ "bad": stats["bad_providers"]
756
+ },
757
+ "statistics": {
758
+ "total_requests": stats["total_requests"],
759
+ "success_rate": f"{stats['success_rate']}%",
760
+ "avg_response_time": f"{stats['avg_response_time']:.3f}s"
761
+ },
762
+ "proxy_configured": get_random_proxy() is not None,
763
+ "documentation": "/docs",
764
+ "health": "/health",
765
+ "metrics": "/metrics"
766
+ }
767
+
768
+ @app.get("/health")
769
+ async def health_check():
770
+ """فحص حالة الخادم"""
771
+ stats = app.state.ai.get_stats()
772
+
773
+ status = "healthy"
774
+ if stats["bad_providers"] > len(SAFE_PROVIDERS) * 0.5:
775
+ status = "degraded"
776
+ if stats["success_rate"] < 30:
777
+ status = "unhealthy"
778
+
779
+ return {
780
+ "status": status,
781
+ "timestamp": datetime.now().isoformat(),
782
+ "uptime": stats["uptime_human"],
783
+ "providers": {
784
+ "total_safe": len(SAFE_PROVIDERS),
785
+ "available": stats["available_providers"],
786
+ "good": stats["good_providers"],
787
+ "bad": stats["bad_providers"]
788
+ },
789
+ "requests": {
790
+ "total": stats["total_requests"],
791
+ "successful": stats["successful_requests"],
792
+ "failed": stats["failed_requests"],
793
+ "success_rate": stats["success_rate"]
794
+ }
795
  }
796
 
797
+ @app.get("/metrics")
798
+ async def get_metrics():
799
+ """الحصول على الإحصائيات المفصلة"""
800
+ stats = app.state.ai.get_stats()
801
+ return stats
802
 
803
+ @app.get("/providers")
804
+ async def list_providers():
805
+ """قائمة جميع المزودات المتاحين"""
806
+ return {
807
+ "safe_providers": [p.__name__ for p in SAFE_PROVIDERS],
808
+ "good_providers": [p.__name__ for p in app.state.ai.good],
809
+ "bad_providers": list(app.state.ai.bad.keys()),
810
+ "provider_stats": app.state.ai.provider_stats
811
+ }
812
+
813
+ @app.post("/v1/chat/completions")
814
+ async def chat_completions(
815
+ request: ChatCompletionRequest,
816
+ authorization: Optional[str] = Header(None)
817
+ ):
818
+ """
819
+ نقطة نهاية الإكمال المحادث - متوافق مع OpenAI API
820
+ """
821
+ # التحقق من المفتاح (اختياري)
822
+ if os.environ.get("MY_PROJECT_API_KEY"):
823
+ verify_api_key(authorization)
824
+
825
+ # تحويل الرسائل إلى prompt واحد
826
+ prompt = "\n".join([
827
+ f"{msg.role}: {msg.content}"
828
+ for msg in request.messages
829
+ ])
830
+
831
+ logger.info(f"💬 Chat request | Model: {request.model} | Messages: {len(request.messages)}")
832
+
833
+ # إرسال الطلب
834
+ result = app.state.ai.ask(
835
+ prompt=prompt,
836
+ temperature=request.temperature,
837
+ max_tokens=request.max_tokens,
838
+ stream=request.stream
839
+ )
840
 
 
841
  if result.get("success"):
842
  return {
843
+ "id": f"chatcmpl-{result['request_id']}",
844
+ "object": "chat.completion",
845
+ "created": int(time.time()),
846
+ "model": request.model,
847
  "choices": [
848
  {
849
+ "index": 0,
850
  "message": {
851
  "role": "assistant",
852
  "content": result.get("response")
 
854
  "finish_reason": "stop"
855
  }
856
  ],
857
+ "usage": {
858
+ "prompt_tokens": len(prompt.split()),
859
+ "completion_tokens": result.get("tokens_used", 0),
860
+ "total_tokens": len(prompt.split()) + result.get("tokens_used", 0)
861
+ },
862
+ "provider": result.get("provider"),
863
+ "request_id": result.get("request_id"),
864
+ "response_time": result.get("response_time")
865
  }
866
  else:
867
  raise HTTPException(
868
+ status_code=500,
869
+ detail={
870
+ "error": {
871
+ "message": result.get("error", "Failed to generate response"),
872
+ "type": "api_error",
873
+ "code": "GENERATION_FAILED",
874
+ "request_id": result.get("request_id")
875
+ }
876
+ }
877
+ )
878
+
879
+ @app.post("/v1/completions")
880
+ async def completions(
881
+ request: CompletionRequest,
882
+ authorization: Optional[str] = Header(None)
883
+ ):
884
+ """نقطة نهاية الإكمال النصي"""
885
+ if os.environ.get("MY_PROJECT_API_KEY"):
886
+ verify_api_key(authorization)
887
+
888
+ logger.info(f"✍️ Completion request | Model: {request.model}")
889
+
890
+ results = []
891
+
892
+ for i in range(request.n):
893
+ result = app.state.ai.ask(
894
+ prompt=request.prompt,
895
+ temperature=request.temperature,
896
+ max_tokens=request.max_tokens
897
  )
898
+
899
+ if result.get("success"):
900
+ results.append({
901
+ "text": result.get("response"),
902
+ "index": i,
903
+ "finish_reason": "stop"
904
+ })
905
+ else:
906
+ results.append({
907
+ "text": "",
908
+ "index": i,
909
+ "finish_reason": "error",
910
+ "error": result.get("error")
911
+ })
912
+
913
+ return {
914
+ "id": f"comp-{str(uuid.uuid4())[:8]}",
915
+ "object": "text_completion",
916
+ "created": int(time.time()),
917
+ "model": request.model,
918
+ "choices": results,
919
+ "usage": {
920
+ "prompt_tokens": len(request.prompt.split()),
921
+ "total_tokens": sum(len(r.get("text", "").split()) for r in results)
922
+ }
923
+ }
924
+
925
+ @app.post("/v1/embeddings")
926
+ async def embeddings(
927
+ request: EmbeddingRequest,
928
+ authorization: Optional[str] = Header(None)
929
+ ):
930
+ """نقطة نهاية التضميد"""
931
+ if os.environ.get("MY_PROJECT_API_KEY"):
932
+ verify_api_key(authorization)
933
+
934
+ inputs = [request.input] if isinstance(request.input, str) else request.input
935
+
936
+ embeddings = []
937
+ for i, text in enumerate(inputs):
938
+ # توليد embedding وهمي (لأن g4f لا يدعم embeddings رسمياً)
939
+ embedding = [
940
+ random.uniform(-1, 1) for _ in range(1536)
941
+ ]
942
+ embeddings.append({
943
+ "object": "embedding",
944
+ "embedding": embedding,
945
+ "index": i
946
+ })
947
+
948
+ return {
949
+ "object": "list",
950
+ "data": embeddings,
951
+ "model": request.model,
952
+ "usage": {
953
+ "prompt_tokens": sum(len(t.split()) for t in inputs),
954
+ "total_tokens": sum(len(t.split()) for t in inputs)
955
+ }
956
+ }
957
+
958
+ @app.get("/models")
959
+ async def list_models():
960
+ """قائمة النماذج المتاحة"""
961
+ return {
962
+ "object": "list",
963
+ "data": [
964
+ {
965
+ "id": "gpt-4o-mini",
966
+ "object": "model",
967
+ "created": 20240101,
968
+ "owned_by": "openai"
969
+ },
970
+ {
971
+ "id": "gpt-4o",
972
+ "object": "model",
973
+ "created": 20240101,
974
+ "owned_by": "openai"
975
+ },
976
+ {
977
+ "id": "gpt-3.5-turbo",
978
+ "object": "model",
979
+ "created": 20240101,
980
+ "owned_by": "openai"
981
+ },
982
+ {
983
+ "id": "claude-3-opus",
984
+ "object": "model",
985
+ "created": 20240101,
986
+ "owned_by": "anthropic"
987
+ },
988
+ {
989
+ "id": "claude-3-sonnet",
990
+ "object": "model",
991
+ "created": 20240101,
992
+ "owned_by": "anthropic"
993
+ }
994
+ ]
995
+ }
996
+
997
+ @app.post("/reset-stats")
998
+ async def reset_statistics(authorization: Optional[str] = Header(None)):
999
+ """إعادة تعيين الإحصائيات"""
1000
+ if os.environ.get("MY_PROJECT_API_KEY"):
1001
+ verify_api_key(authorization)
1002
+
1003
+ app.state.ai.stats = {
1004
+ "total_requests": 0,
1005
+ "successful_requests": 0,
1006
+ "failed_requests": 0,
1007
+ "total_tokens_used": 0,
1008
+ "avg_response_time": 0,
1009
+ "start_time": time.time()
1010
+ }
1011
+ app.state.ai.error_log.clear()
1012
+
1013
+ return {"message": "Statistics reset successfully"}
1014
+
1015
+ @app.post("/reset-providers")
1016
+ async def reset_providers(authorization: Optional[str] = Header(None)):
1017
+ """إعادة تعيين قائمة Providers"""
1018
+ if os.environ.get("MY_PROJECT_API_KEY"):
1019
+ verify_api_key(authorization)
1020
+
1021
+ app.state.ai.good.clear()
1022
+ app.state.ai.bad.clear()
1023
+ app.state.ai.provider_stats.clear()
1024
+
1025
+ return {"message": "Providers reset successfully"}
1026
+
1027
+ # ============================================================
1028
+ # نقطة النهاية للتدفق (Streaming)
1029
+ # ============================================================
1030
+ async def generate_stream_response(request_id: str, provider: str, text: str, response_time: float):
1031
+ """توليد الاستجابة المتدفقة"""
1032
+ import json
1033
+
1034
+ # إرسال بداية الاستجابة
1035
+ chunk_id = f"chatcmpl-{request_id}"
1036
+ created = int(time.time())
1037
+
1038
+ yield f"data: {json.dumps({'id': chunk_id, 'object': 'chat.completion.chunk', 'created': created, 'model': MODEL, 'choices': [{'index': 0, 'delta': {'role': 'assistant'}, 'finish_reason': None}]})}\n\n"
1039
+
1040
+ # تقسيم النص لكلمات
1041
+ words = text.split()
1042
+ for i, word in enumerate(words):
1043
+ delta = {'content': word + ' '}
1044
+ yield f"data: {json.dumps({'id': chunk_id, 'object': 'chat.completion.chunk', 'created': created, 'model': MODEL, 'choices': [{'index': 0, 'delta': delta, 'finish_reason': None}]})}\n\n"
1045
+ await asyncio.sleep(0.01) # تأخير بسيط للمحاكاة
1046
+
1047
+ # إرسال نهاية الاستجابة
1048
+ yield f"data: {json.dumps({'id': chunk_id, 'object': 'chat.completion.chunk', 'created': created, 'model': MODEL, 'choices': [{'index': 0, 'delta': {}, 'finish_reason': 'stop'}]})}\n\n"
1049
+
1050
+ # إشعار النهاية
1051
+ yield "data: [DONE]\n\n"
1052
 
1053
+ @app.post("/v1/chat/completions/stream")
1054
+ async def chat_completions_stream(
1055
+ request: ChatCompletionRequest,
1056
+ authorization: Optional[str] = Header(None)
1057
+ ):
1058
+ """نقطة نهاية الإكمال المتدفق"""
1059
+ if os.environ.get("MY_PROJECT_API_KEY"):
1060
+ verify_api_key(authorization)
1061
+
1062
+ prompt = "\n".join([
1063
+ f"{msg.role}: {msg.content}"
1064
+ for msg in request.messages
1065
+ ])
1066
+
1067
+ logger.info(f"🌊 Stream request | Model: {request.model}")
1068
+
1069
+ result = app.state.ai.ask(
1070
+ prompt=prompt,
1071
+ temperature=request.temperature,
1072
+ max_tokens=request.max_tokens
1073
+ )
1074
+
1075
+ if result.get("success"):
1076
+ return StreamingResponse(
1077
+ generate_stream_response(
1078
+ request_id=result['request_id'],
1079
+ provider=result['provider'],
1080
+ text=result['response'],
1081
+ response_time=result['response_time']
1082
+ ),
1083
+ media_type="text/event-stream",
1084
+ headers={
1085
+ "Cache-Control": "no-cache",
1086
+ "Connection": "keep-alive",
1087
+ "X-Request-ID": result['request_id'],
1088
+ "X-Provider": result['provider']
1089
+ }
1090
+ )
1091
+ else:
1092
+ raise HTTPException(
1093
+ status_code=500,
1094
+ detail=f"Failed to generate response: {result.get('error')}"
1095
+ )
1096
 
1097
+ # ============================================================
1098
+ # تشغيل الخادم
1099
+ # ============================================================
1100
  if __name__ == "__main__":
1101
+ # إعدادات Environment
1102
+ port = int(os.environ.get("PORT", 7860))
1103
+ host = os.environ.get("HOST", "0.0.0.0")
1104
+ workers = int(os.environ.get("WORKERS", 1))
1105
+
1106
+ logger.info("=" * 60)
1107
+ logger.info(f"🌐 Starting SmartG4F API on {host}:{port}")
1108
+ logger.info(f"📚 Documentation: http://localhost:{port}/docs")
1109
+ logger.info("=" * 60)
1110
+
1111
  uvicorn.run(
1112
+ "main:app",
1113
+ host=host,
1114
+ port=port,
1115
+ workers=workers,
1116
+ log_level="info",
1117
+ access_log=True,
1118
+ reload=False # تعطيل Reload في الإنتاج
1119
  )