bahi-bh commited on
Commit
f07bfda
ยท
verified ยท
1 Parent(s): 024f4e3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -296
app.py CHANGED
@@ -21,7 +21,7 @@ logging.basicConfig(
21
  logger = logging.getLogger("g4f-smart-router")
22
 
23
  # =====================================================
24
- # COOKIES
25
  # =====================================================
26
  def _load_cookies_raw() -> Dict[str, Any]:
27
  raw_env = (os.getenv("COOKIES_JSON") or "").strip()
@@ -60,7 +60,7 @@ def load_cookies() -> str:
60
  COOKIE_STATUS = load_cookies()
61
 
62
  # =====================================================
63
- # CACHE
64
  # =====================================================
65
  class TTLCache:
66
  def __init__(self, max_size: int = 100, ttl_seconds: int = 300):
@@ -98,7 +98,7 @@ class TTLCache:
98
  CACHE = TTLCache(max_size=100, ttl_seconds=300)
99
 
100
  # =====================================================
101
- # PROVIDERS - ูู‚ุท Qwen
102
  # =====================================================
103
  def get_provider(name: str):
104
  try:
@@ -106,199 +106,29 @@ def get_provider(name: str):
106
  except:
107
  return None
108
 
 
109
  REAL_PROVIDERS = {
 
 
 
 
 
 
110
  "Qwen": get_provider("Qwen"),
111
  }
112
  REAL_PROVIDERS = {k: v for k, v in REAL_PROVIDERS.items() if v}
113
 
114
  # =====================================================
115
- # MODELS - ุฌู…ูŠุน ู†ู…ุงุฐุฌ Qwen (ุจุนุฏ ุงู„ุจุญุซ)
116
  # =====================================================
117
  PROVIDER_MODELS_FALLBACK = {
118
- "Qwen": [
119
- # =====================================
120
- # Qwen 3.6 SERIES (ุฃุญุฏุซ ุงู„ุฅุตุฏุงุฑุงุช ุญุณุจ Alibaba Cloud 2026-04-02)
121
- # ุงู„ู…ุตุฏุฑ: https://www.alibabacloud.com/help/en/model-studio/newly-released-models
122
- # =====================================
123
- "qwen3.6-plus", # Qwen3.6-Plus ุงู„ุฑุฆูŠุณูŠ
124
- "qwen3.6-plus-2026-04-02", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
125
- "qwen3.6-35b-a3b", # ุงู„ู†ู…ูˆุฐุฌ ุงู„ู…ูุชูˆุญ ุงู„ู…ุตุฏุฑ 35B MoE
126
-
127
- # =====================================
128
- # Qwen 3.5 SERIES
129
- # ุงู„ู…ุตุฏุฑ: Alibaba Cloud Model Catalog
130
- # =====================================
131
- "qwen3.5-plus", # Qwen3.5-Plus ุงู„ุฑุฆูŠุณูŠ
132
- "qwen3.5-plus-2026-02-15", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
133
- "qwen3.5-flash", # ู„ู„ุงุณุชุฌุงุจุฉ ุงู„ุณุฑูŠุนุฉ
134
- "qwen3.5-flash-2026-02-23", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
135
- "qwen3.5-122b-a10b", # 122B ู…ุน 10B ู†ุดุท
136
- "qwen3.5-27b", # 27B ูƒุซูŠู
137
- "qwen3.5-35b-a3b", # 35B MoE ูƒุซูŠู
138
-
139
- # =====================================
140
- # Qwen 3 MAX & FLAGSHIP
141
- # ุงู„ู…ุตุฏุฑ: Promptfoo Documentation
142
- # =====================================
143
- "qwen3-max", # ุงู„ุฌูŠู„ ุงู„ุชุงู„ูŠ ุงู„ุฑุงุฆุฏ
144
- "qwen3-max-preview", # ู†ุณุฎุฉ ุชุฌุฑูŠุจูŠุฉ
145
- "qwen3-max-2025-09-23", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
146
- "qwen-max", # ุงู„ุฑุงุฆุฏ ุงู„ุฃุตู„ูŠ
147
- "qwen-max-latest", # ุฏุงุฆู…ุงู‹ ู…ุญุฏุซ
148
- "qwen-max-2025-01-25", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
149
-
150
- # =====================================
151
- # Qwen PLUS & TURBO (ู…ุชุนุฏุฏุฉ ุงู„ุงุณุชุฎุฏุงู…ุงุช)
152
- # =====================================
153
- "qwen-plus", # ู…ุชูˆุงุฒู† ู„ู„ุบุงูŠุฉ
154
- "qwen-plus-latest", # ุฏุงุฆู…ุงู‹ ู…ุญุฏุซ
155
- "qwen-plus-2025-09-11", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
156
- "qwen-plus-2025-07-28", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
157
- "qwen-plus-2025-07-14", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
158
- "qwen-plus-2025-04-28", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
159
- "qwen-plus-2025-01-25", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
160
- "qwen-turbo", # ุณุฑูŠุน ูˆูุนุงู„ ู…ู† ุญูŠุซ ุงู„ุชูƒู„ูุฉ
161
- "qwen-turbo-latest", # ุฏุงุฆู…ุงู‹ ู…ุญุฏุซ
162
- "qwen-turbo-2025-04-28", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
163
- "qwen-turbo-2024-11-01", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
164
- "qwen-flash", # ู…ุญุณู‘ู† ู„ู„ูˆู‚ุช (ูŠุณุชุจุฏู„ ุงู„ู†ูุงุซ)
165
- "qwen-flash-2025-07-28", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
166
-
167
- # =====================================
168
- # Qwen LONG (ุณูŠุงู‚ ูุงุฆู‚ ุงู„ุทูˆู„)
169
- # =====================================
170
- "qwen-long-latest", # ู…ู„ูŠูˆู† ุณูŠุงู‚
171
- "qwen-long-2025-01-25", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
172
-
173
- # =====================================
174
- # Qwen 3 CODER (ู†ู…ุงุฐุฌ ุงู„ุจุฑู…ุฌุฉ ุงู„ู…ุชุฎุตุตุฉ)
175
- # ุงู„ู…ุตุฏุฑ: Alibaba Cloud + Promptfoo
176
- # =====================================
177
- "qwen3-coder-plus", # ูƒูˆุฏ ุงุญุชุฑุงููŠ ู…ุน ุฃุฏุงุฉ ุงู„ุงุชุตุงู„
178
- "qwen3-coder-plus-2025-09-23", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
179
- "qwen3-coder-plus-2025-07-22", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
180
- "qwen3-coder-flash", # ุชูˆู„ูŠุฏ ูƒูˆุฏ ุณุฑูŠุน
181
- "qwen3-coder-flash-2025-07-28", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
182
- "qwen3-coder-next", # ุงู„ุฌูŠู„ ุงู„ุชุงู„ูŠ ู…ู† ุงู„ูƒูˆุฏ
183
- "qwen3-coder-480b-a35b-instruct", # 480B ู†ู…ูˆุฐุฌ ูƒูˆุฏ ู…ูุชูˆุญ
184
- "qwen3-coder-30b-a3b-instruct", # 30B ู†ู…ูˆุฐุฌ ูƒูˆุฏ ู…ูุชูˆุญ
185
-
186
- # =====================================
187
- # Qwen 3 VL (ุงู„ุฑุคูŠุฉ ูˆุงู„ููŠุฏูŠูˆ)
188
- # ุงู„ู…ุตุฏุฑ: Promptfoo + DashScope
189
- # =====================================
190
- "qwen3-vl-plus", # ุฑุคูŠุฉ ุฑุงุฆุฏุฉ
191
- "qwen3-vl-plus-2025-09-23", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
192
- "qwen3-vl-flash", # ู†ู…ูˆุฐุฌ ุฑุคูŠุฉ ุณุฑูŠุน
193
- "qwen3-vl-flash-2025-10-15", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
194
- "qwen3-vl-235b-a22b-thinking", # 235B ู…ุน ุงู„ุชููƒูŠุฑ
195
- "qwen3-vl-235b-a22b-instruct", # 235B ุชุนู„ูŠู…ุงุช
196
- "qwen3-vl-32b-thinking", # 32B ู…ุน ุงู„ุชููƒูŠุฑ
197
- "qwen3-vl-32b-instruct", # 32B ุชุนู„ูŠู…ุงุช
198
- "qwen3-vl-30b-a3b-thinking", # 30B ู…ุน ุงู„ุชููƒูŠุฑ
199
- "qwen3-vl-30b-a3b-instruct", # 30B ุชุนู„ูŠู…ุงุช
200
- "qwen3-vl-8b-thinking", # 8B ู…ุน ุงู„ุชููƒูŠุฑ
201
- "qwen3-vl-8b-instruct", # 8B ุชุนู„ูŠู…ุงุช
202
- "qwen-vl-max", # VL ู…ุงูƒุณ
203
- "qwen-vl-plus", # VL ุจู„ุต
204
- "qwen-vl-ocr", # ู…ุญุณู‘ู† ู„ู„ุชุนุฑู ุงู„ุถูˆุฆูŠ ุนู„ู‰ ุงู„ุญุฑูˆู
205
-
206
- # =====================================
207
- # Qwen 3 OMN (ุฑุคูŠุฉ + ูƒู„ุงู…)
208
- # =====================================
209
- "qwen3-omni-flash", # ู…ุชุนุฏุฏ ุงู„ูˆุณุงุฆุท
210
- "qwen3-omni-flash-2025-09-15", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
211
- "qwen3-omni-flash-realtime", # ุจุซ ู…ุจุงุดุฑ ููŠ ุงู„ูˆู‚ุช ุงู„ูุนู„ูŠ
212
- "qwen3-omni-flash-realtime-2025-09-15", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
213
- "qwen3-omni-30b-a3b-captioner", # ุชุฐูŠูŠู„ ุงู„ุตูˆุช
214
-
215
- # =====================================
216
- # QwQ REASONING (ุงู„ุงุณุชุฏู„ุงู„ ูˆุงู„ุจุญุซ)
217
- # ุงู„ู…ุตุฏุฑ: Promptfoo
218
- # =====================================
219
- "qwq-plus", # ู†ู…ูˆุฐุฌ ุงุณุชุฏู„ุงู„ ุชุฌุงุฑูŠ
220
- "qwq-32b", # QwQ 32B ู…ูุชูˆุญ
221
- "qwen-deep-research", # ู…ุณุงุนุฏ ุจุญุซ ู…ุน ุงุณุชุนู„ุงู… ูˆูŠุจ
222
- "qvq-max", # ุงุณุชุฏู„ุงู„ ุจุตุฑูŠ
223
- "qvq-max-latest", # ุฏุงุฆู…ุงู‹ ู…ุญุฏุซ
224
- "qvq-max-2025-03-25", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
225
- "qvq-72b-preview", # ุงุณุชุฏู„ุงู„ ุจุตุฑูŠ 72B
226
-
227
- # =====================================
228
- # Qwen 3 OPEN-SOURCE (ู…ูุชูˆุญุฉ ุงู„ู…ุตุฏุฑ)
229
- # ุงู„ู…ุตุฏุฑ: Alibaba Cloud
230
- # =====================================
231
- "qwen3-next-80b-a3b-thinking", # ุงู„ุฌูŠู„ ุงู„ุชุงู„ูŠ 80B ู…ุน ุงู„ุชููƒูŠุฑ
232
- "qwen3-next-80b-a3b-instruct", # ุงู„ุฌูŠู„ ุงู„ุชุงู„ูŠ 80B ุชุนู„ูŠู…ุงุช
233
- "qwen3-235b-a22b-thinking-2507", # 235B ูŠูˆู„ูŠูˆ 2025 ู…ุน ุงู„ุชููƒูŠุฑ
234
- "qwen3-235b-a22b-instruct-2507", # 235B ูŠูˆู„ูŠูˆ 2025 ุชุนู„ูŠู…ุงุช
235
- "qwen3-30b-a3b-thinking-2507", # 30B ูŠูˆู„ูŠูˆ 2025 ู…ุน ุงู„ุชููƒูŠุฑ
236
- "qwen3-30b-a3b-instruct-2507", # 30B ูŠูˆู„ูŠูˆ 2025 ุชุนู„ูŠู…ุงุช
237
- "qwen3-235b-a22b", # 235B ูˆุถุน ู…ุฒุฏูˆุฌ
238
- "qwen3-32b", # 32B ูˆุถุน ู…ุฒุฏูˆุฌ
239
- "qwen3-30b-a3b", # 30B ูˆุถุน ู…ุฒุฏูˆุฌ
240
- "qwen3-14b", # 14B ูˆุถุน ู…ุฒุฏูˆุฌ
241
- "qwen3-8b", # 8B ูˆุถุน ู…ุฒุฏูˆุฌ
242
- "qwen3-4b", # 4B ูˆุถุน ู…ุฒุฏูˆุฌ
243
- "qwen3-1.7b", # 1.7B ู„ู„ุฃุฌู‡ุฒุฉ ุงู„ุทุฑููŠุฉ
244
- "qwen3-0.6b", # 0.6B ู„ู„ุฃุฌู‡ุฒุฉ ุงู„ุทุฑููŠุฉ
245
-
246
- # =====================================
247
- # Qwen 2.5 SERIES (ู…ุณุชู‚ุฑุฉ ูˆู…ุฌุฑุจุฉ)
248
- # =====================================
249
- "qwen2.5-72b-instruct", # 72B ู†ู…ูˆุฐุฌ ูƒุซูŠู
250
- "qwen2.5-32b-instruct", # 32B ู†ู…ูˆุฐุฌ ูƒุซูŠู
251
- "qwen2.5-14b-instruct", # 14B ู†ู…ูˆุฐุฌ ูƒุซูŠู
252
- "qwen2.5-7b-instruct", # 7B ู†ู…ูˆุฐุฌ ูƒุซูŠู
253
- "qwen2.5-1.5b-instruct", # 1.5B ู†ู…ูˆุฐุฌ ุตุบูŠุฑ
254
- "qwen2.5-0.5b-instruct", # 0.5B ู†ู…ูˆุฐุฌ ุตุบูŠุฑ ุฌุฏุงู‹
255
- "qwen2.5-7b-instruct-1m", # 7B ู…ุน ุณูŠุงู‚ ู…ู„ูŠูˆู†ูŠ
256
- "qwen2.5-14b-instruct-1m", # 14B ู…ุน ุณูŠุงู‚ ู…ู„ูŠูˆู†ูŠ
257
- "qwen2.5-coder-32b-instruct", # 32B ูƒูˆุฏ
258
- "qwen2.5-coder-14b-instruct", # 14B ูƒูˆุฏ
259
- "qwen2.5-coder-7b-instruct", # 7B ูƒูˆุฏ
260
- "qwen2.5-coder-1.5b-instruct", # 1.5B ูƒูˆุฏ
261
- "qwen2.5-coder-0.5b-instruct", # 0.5B ูƒูˆุฏ
262
-
263
- # =====================================
264
- # QWEN MATH (ุงู„ู†ู…ุงุฐุฌ ุงู„ุฑูŠุงุถูŠุฉ)
265
- # =====================================
266
- "qwen-math-plus", # ุนุฏุฏูŠ ุจู„ุต
267
- "qwen-math-plus-latest", # ุฏุงุฆู…ุงู‹ ู…ุญุฏุซ
268
- "qwen-math-plus-2024-09-19", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
269
- "qwen-math-plus-2024-08-16", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
270
- "qwen-math-turbo", # ุนุฏุฏูŠ ุชูˆุฑุจูˆ
271
- "qwen-math-turbo-latest", # ุฏุงุฆู…ุงู‹ ู…ุญุฏุซ
272
- "qwen-math-turbo-2024-09-19", # ุฅุตุฏุงุฑ ู…ุคุฑุฎ
273
- "qwen2.5-math-72b-instruct", # 72B ู„ู„ู†ู…ุงุฐุฌ ุงู„ุฑูŠุงุถูŠุฉ
274
- "qwen2.5-math-7b-instruct", # 7B ู„ู„ู†ู…ุงุฐุฌ ุงู„ุฑูŠุงุถูŠุฉ
275
-
276
- # =====================================
277
- # QWEN TRANSLATION (ุงู„ุชุฑุฌู…ุฉ)
278
- # =====================================
279
- "qwen-mt-plus", # ู…ุชุฑุฌู…ุฉ ุจู„ุต
280
- "qwen-mt-turbo", # ู…ุชุฑุฌู…ุฉ ุชูˆุฑุจูˆ
281
-
282
- # =====================================
283
- # QWEN DOCUMENT (ุงุณุชุฎุฑุงุฌ ุงู„ู…ุณุชู†ุฏุงุช)
284
- # =====================================
285
- "qwen-doc-turbo", # ุงุณุชุฎุฑุงุฌ ุงู„ู…ุณุชู†ุฏุงุช ุงู„ู…ู‡ูŠูƒู„ุฉ
286
-
287
- # =====================================
288
- # QWEN IMAGE GENERATION (ุชูˆู„ูŠุฏ ุงู„ุตูˆุฑ)
289
- # =====================================
290
- "qwen-image-plus", # ู†ุต ุฅู„ู‰ ุตูˆุฑุฉ ู…ุน ุนุฑุถ ู†ุตูŠ ู…ุนู‚ุฏ
291
-
292
- # =====================================
293
- # ALIASES (ุงุณุชุนู„ุงู…ุงุช ู…ุฎุชุตุฑุฉ)
294
- # =====================================
295
- "qwen",
296
- "qwen2.5",
297
- "qwen-coder",
298
- "qwen-vl",
299
- "qwq",
300
- "qvq",
301
- ],
302
  }
303
 
304
  # =====================================================
@@ -307,7 +137,12 @@ PROVIDER_MODELS_FALLBACK = {
307
  _PROVIDER_MODEL_CACHE = {}
308
 
309
  def discover_provider_models(provider_obj: Any, provider_name: str) -> List[str]:
 
 
 
 
310
  candidates = []
 
311
  for attr in ("models", "model", "default_model", "available_models", "supported_models"):
312
  try:
313
  if hasattr(provider_obj, attr):
@@ -320,13 +155,18 @@ def discover_provider_models(provider_obj: Any, provider_name: str) -> List[str]
320
  candidates.append(str(val))
321
  except:
322
  pass
 
323
  if not candidates:
324
- candidates = PROVIDER_MODELS_FALLBACK.get(provider_name, ["qwen-max"])
 
 
325
  seen = set()
326
- return [m for m in candidates if not (m in seen or seen.add(m))]
 
 
327
 
328
  # =====================================================
329
- # STREAM CLEANER
330
  # =====================================================
331
  def clean_stream(chunk):
332
  try:
@@ -338,7 +178,6 @@ def clean_stream(chunk):
338
  if 'text' in delta:
339
  return delta['text']
340
  return chunk.get('content') or chunk.get('text') or ""
341
-
342
  if isinstance(chunk, str):
343
  if chunk and chunk[0] == '{' and chunk[-1] == '}':
344
  try:
@@ -358,11 +197,12 @@ def clean_stream(chunk):
358
  chunk = chunk.replace('\\t', ' ')
359
  return chunk
360
  return str(chunk)
361
- except Exception:
 
362
  return ""
363
 
364
  # =====================================================
365
- # CHAT LOGIC - ูู‚ุท ู…ุน Qwen ูˆ fallback ู…ุญุฏุฏ
366
  # =====================================================
367
  def ask(message: str, history, provider_name: str, model_name: str, stop_flag=None):
368
  message = (message or "").strip()
@@ -370,12 +210,14 @@ def ask(message: str, history, provider_name: str, model_name: str, stop_flag=No
370
  yield ""
371
  return
372
 
 
373
  key = f"{provider_name}|{model_name}|{message}"
374
  cached = CACHE.get(key)
375
  if cached:
376
  yield cached
377
  return
378
 
 
379
  msgs = []
380
  try:
381
  if history:
@@ -396,9 +238,16 @@ def ask(message: str, history, provider_name: str, model_name: str, stop_flag=No
396
 
397
  msgs.append({"role": "user", "content": message})
398
 
399
- # ุงุณุชุฑุงุชูŠุฌูŠุฉ ุงู„ุชุฑุงุฌุน: ูู‚ุท Qwen
 
400
  fallback_providers = [
401
  provider_name,
 
 
 
 
 
 
402
  "Qwen"
403
  ]
404
  used = []
@@ -409,25 +258,32 @@ def ask(message: str, history, provider_name: str, model_name: str, stop_flag=No
409
  used.append(pname)
410
  pobj = REAL_PROVIDERS.get(pname)
411
  if not pobj:
 
412
  continue
413
 
414
- if pname not in _PROVIDER_MODEL_CACHE:
415
- _PROVIDER_MODEL_CACHE[pname] = discover_provider_models(pobj, pname)
 
 
 
416
 
417
- model_candidates = [model_name] + [x for x in _PROVIDER_MODEL_CACHE[pname] if x != model_name]
 
 
 
 
418
 
419
- for m in model_candidates[:12]:
420
  try:
 
421
  stream = g4f.ChatCompletion.create(
422
  model=m,
423
  provider=pobj,
424
  messages=msgs,
425
  stream=True,
426
- timeout=30
427
  )
428
-
429
  buffer = []
430
-
431
  for chunk in stream:
432
  if stop_flag and stop_flag.is_set():
433
  return
@@ -436,35 +292,32 @@ def ask(message: str, history, provider_name: str, model_name: str, stop_flag=No
436
  continue
437
  buffer.append(c)
438
  yield c
439
-
440
  full = "".join(buffer)
441
  if full.strip():
442
  CACHE.set(key, full)
443
  return
444
-
445
  except Exception as e:
446
- logger.warning(f"Provider {pname} model {m} failed: {e}")
447
  continue
448
 
449
- yield "โŒ Failed with all providers."
450
 
451
  # =====================================================
452
  # FASTAPI
453
  # =====================================================
454
- app = FastAPI(title="G4F Smart Router", description="AI Gateway - Qwen Only")
455
 
456
  API_KEY = os.getenv("API_KEY", "mysecretkey123")
457
 
458
  class ChatRequest(BaseModel):
459
  message: str
460
- provider: str = "Qwen"
461
- model: str = "qwen-max"
462
  history: List[Any] = []
463
 
464
  # =====================================================
465
- # VERIFY API KEY
466
  # =====================================================
467
-
468
  def verify_api_key(request: Request):
469
  auth = request.headers.get("Authorization", "").strip()
470
  x_key = request.headers.get("X-API-Key", "").strip()
@@ -482,9 +335,8 @@ def verify_api_key(request: Request):
482
  raise HTTPException(status_code=401, detail="Invalid API key. Use 'Authorization: Bearer KEY' or 'X-API-Key: KEY'")
483
 
484
  # =====================================================
485
- # SUPPORT HEAD METHOD
486
  # =====================================================
487
-
488
  @app.head("/")
489
  async def head_root():
490
  return Response(status_code=200)
@@ -498,64 +350,46 @@ async def head_models():
498
  return Response(status_code=200)
499
 
500
  # =====================================================
501
- # CLAUDE-COMPATIBLE ENDPOINTS
502
  # =====================================================
503
-
504
  @app.get("/v1/models")
505
  async def v1_models(request: Request):
506
- """ู†ู…ุงุฐุฌ Qwen ุงู„ู…ุชุงุญุฉ"""
507
-
508
  models = []
509
  for pname, pobj in REAL_PROVIDERS.items():
510
- if pname not in _PROVIDER_MODEL_CACHE:
511
- _PROVIDER_MODEL_CACHE[pname] = discover_provider_models(pobj, pname)
512
- for model in _PROVIDER_MODEL_CACHE[pname][:30]:
513
  models.append({
514
  "id": model,
515
  "type": "model",
516
  "display_name": f"{pname} - {model}"
517
  })
518
-
519
  if not models:
520
- models = [
521
- {"id": "qwen3.6-plus", "type": "model", "display_name": "Qwen 3.6 - Plus"},
522
- {"id": "qwen3-max", "type": "model", "display_name": "Qwen 3 - Max"},
523
- {"id": "qwen-plus", "type": "model", "display_name": "Qwen - Plus"},
524
- {"id": "qwen3-coder-plus", "type": "model", "display_name": "Qwen 3 - Coder Plus"},
525
- {"id": "qwen3-vl-plus", "type": "model", "display_name": "Qwen 3 - Vision Plus"},
526
- ]
527
-
528
  return {"data": models}
529
 
530
  @app.post("/v1/messages")
531
  async def v1_messages(request: Request):
532
- """ู†ู‚ุทุฉ ู†ู‡ุงูŠุฉ ู…ุชูˆุงูู‚ุฉ ู…ุน Claude Desktop"""
533
  verify_api_key(request)
534
-
535
  body = await request.json()
536
-
537
  messages = body.get("messages", [])
538
  if not messages:
539
  raise HTTPException(status_code=400, detail="No messages provided")
540
-
541
  last_message = messages[-1]
542
  user_message = last_message.get("content", "")
543
-
544
- model = body.get("model", "qwen-max")
545
  system_prompt = body.get("system", "")
546
-
547
  history = []
548
  for msg in messages[:-1]:
549
  role = msg.get("role", "user")
550
  content = msg.get("content", "")
551
  history.append({"role": role, "content": content})
552
-
553
  full_message = user_message
554
  if system_prompt:
555
  full_message = f"[System: {system_prompt}]\n\n{user_message}"
556
 
557
  full_response = ""
558
- for chunk in ask(full_message, history, "Qwen", model):
559
  full_response = chunk
560
 
561
  return {
@@ -566,75 +400,52 @@ async def v1_messages(request: Request):
566
  "model": model,
567
  "stop_reason": "end_turn",
568
  "stop_sequence": None,
569
- "usage": {
570
- "input_tokens": len(user_message) // 4,
571
- "output_tokens": len(full_response) // 4
572
- }
573
  }
574
 
575
  @app.post("/v1/messages/stream")
576
  async def v1_messages_stream(request: Request):
577
- """ู†ู‚ุทุฉ ู†ู‡ุงูŠุฉ ู…ุชุฏูู‚ุฉ ู…ุชูˆุงูู‚ุฉ ู…ุน Claude Desktop"""
578
  verify_api_key(request)
579
-
580
  body = await request.json()
581
  messages = body.get("messages", [])
582
  if not messages:
583
  raise HTTPException(status_code=400, detail="No messages provided")
584
-
585
  last_message = messages[-1]
586
  user_message = last_message.get("content", "")
587
- model = body.get("model", "qwen-max")
588
  system_prompt = body.get("system", "")
589
-
590
  full_message = user_message
591
  if system_prompt:
592
  full_message = f"[System: {system_prompt}]\n\n{user_message}"
593
 
594
  async def generate_stream():
595
  message_id = f"msg_{int(time.time())}_{os.urandom(4).hex()}"
596
-
597
  yield f"event: message_start\ndata: {{\"message\": {{\"id\": \"{message_id}\", \"type\": \"message\", \"role\": \"assistant\", \"content\": [], \"model\": \"{model}\", \"stop_reason\": null, \"stop_sequence\": null, \"usage\": {{\"input_tokens\": 0, \"output_tokens\": 0}}}}}}\n\n"
598
-
599
  yield f"event: content_block_start\ndata: {{\"type\": \"content_block_start\", \"index\": 0, \"content_block\": {{\"type\": \"text\", \"text\": \"\"}}}}\n\n"
600
-
601
- for chunk in ask(full_message, [], "Qwen", model):
602
  yield f"event: content_block_delta\ndata: {{\"type\": \"content_block_delta\", \"index\": 0, \"delta\": {{\"type\": \"text_delta\", \"text\": {json.dumps(chunk, ensure_ascii=False)}}}}}\n\n"
603
-
604
  yield f"event: message_delta\ndata: {{\"type\": \"message_delta\", \"delta\": {{\"stop_reason\": \"end_turn\", \"stop_sequence\": null}}, \"usage\": {{\"output_tokens\": 100}}}}\n\n"
605
  yield f"event: message_stop\ndata: {{}}\n\n"
606
 
607
- return StreamingResponse(
608
- generate_stream(),
609
- media_type="text/event-stream",
610
- headers={
611
- "Cache-Control": "no-cache",
612
- "Connection": "keep-alive"
613
- }
614
- )
615
 
616
  # =====================================================
617
- # ORIGINAL ENDPOINTS
618
  # =====================================================
619
-
620
  @app.get("/")
621
  async def root():
622
  return {
623
- "message": "G4F Smart Router is running (Qwen Only - Full Models)",
624
- "provider": "Qwen",
625
  "endpoints": {
626
- "GET /": "Home page",
627
- "GET /health": "Health check",
628
  "GET /v1/models": "List models (NO AUTH)",
629
- "POST /v1/messages": "Send message (REQUIRES AUTH)",
630
- "POST /v1/messages/stream": "Stream message (REQUIRES AUTH)",
631
- "GET /providers": "List providers (REQUIRES AUTH)",
632
- "POST /chat": "Legacy chat (REQUIRES AUTH)",
633
- "POST /chat/stream": "Legacy stream (REQUIRES AUTH)"
634
  },
635
- "authentication": "Bearer YOUR_API_KEY or X-API-Key: YOUR_API_KEY",
636
  "cookies": COOKIE_STATUS,
637
- "models_count": len(PROVIDER_MODELS_FALLBACK.get("Qwen", [])),
638
  "status": "โœ… Server is working"
639
  }
640
 
@@ -642,48 +453,32 @@ async def root():
642
  async def health():
643
  return {"status": "ok", "cookies": COOKIE_STATUS, "providers": list(REAL_PROVIDERS.keys())}
644
 
 
 
 
 
 
645
  @app.post("/chat")
646
  async def chat(request: Request, chat_req: ChatRequest):
647
  verify_api_key(request)
648
-
649
  result = ""
650
  for chunk in ask(chat_req.message, chat_req.history, chat_req.provider, chat_req.model):
651
  result = chunk
652
-
653
  return JSONResponse({"response": result})
654
 
655
  @app.post("/chat/stream")
656
  async def chat_stream(request: Request, chat_req: ChatRequest):
657
  verify_api_key(request)
658
-
659
  async def generate():
660
  for chunk in ask(chat_req.message, chat_req.history, chat_req.provider, chat_req.model):
661
  yield f"data: {json.dumps({'delta': chunk}, ensure_ascii=False)}\n\n"
662
  yield "data: [DONE]\n\n"
663
-
664
  return StreamingResponse(generate(), media_type="text/event-stream")
665
 
666
- @app.get("/providers")
667
- async def get_providers(request: Request):
668
- verify_api_key(request)
669
-
670
- providers_info = {}
671
- for pname, pobj in REAL_PROVIDERS.items():
672
- if pname not in _PROVIDER_MODEL_CACHE:
673
- _PROVIDER_MODEL_CACHE[pname] = discover_provider_models(pobj, pname)
674
- providers_info[pname] = _PROVIDER_MODEL_CACHE[pname]
675
-
676
- return JSONResponse({"providers": providers_info})
677
-
678
  # =====================================================
679
- # RUN
680
  # =====================================================
681
  if __name__ == "__main__":
682
  import uvicorn
683
  port = int(os.getenv("PORT", 7860))
684
- uvicorn.run(
685
- "app:app",
686
- host="0.0.0.0",
687
- port=port,
688
- reload=False
689
- )
 
21
  logger = logging.getLogger("g4f-smart-router")
22
 
23
  # =====================================================
24
+ # COOKIES (ู„ู„ู…ุฒูˆุฏุงุช ุงู„ุชูŠ ุชุญุชุงุฌู‡ุง ู…ุซู„ Perplexity)
25
  # =====================================================
26
  def _load_cookies_raw() -> Dict[str, Any]:
27
  raw_env = (os.getenv("COOKIES_JSON") or "").strip()
 
60
  COOKIE_STATUS = load_cookies()
61
 
62
  # =====================================================
63
+ # CACHE ู…ุญุณู†
64
  # =====================================================
65
  class TTLCache:
66
  def __init__(self, max_size: int = 100, ttl_seconds: int = 300):
 
98
  CACHE = TTLCache(max_size=100, ttl_seconds=300)
99
 
100
  # =====================================================
101
+ # PROVIDERS - ู‚ุงุฆู…ุฉ ู…ุฒูˆุฏุงุช ู‚ุฏ ุชุนู…ู„
102
  # =====================================================
103
  def get_provider(name: str):
104
  try:
 
106
  except:
107
  return None
108
 
109
+ # ุชุฑุชูŠุจ ุงู„ู…ุฒูˆุฏุงุช ุญุณุจ ุงุญุชู…ุงู„ ุงู„ู†ุฌุงุญ (ุฌุฑุจู†ุงู‡ุง)
110
  REAL_PROVIDERS = {
111
+ "Blackbox": get_provider("Blackbox"),
112
+ "DeepSeek": get_provider("DeepSeek"),
113
+ "Perplexity": get_provider("Perplexity") or get_provider("PerplexityAi"),
114
+ "Copilot": get_provider("Copilot"),
115
+ "You": get_provider("You"),
116
+ "Bing": get_provider("Bing"),
117
  "Qwen": get_provider("Qwen"),
118
  }
119
  REAL_PROVIDERS = {k: v for k, v in REAL_PROVIDERS.items() if v}
120
 
121
  # =====================================================
122
+ # MODELS - ู‚ุงุฆู…ุฉ ู†ู…ุงุฐุฌ ู„ูƒู„ ู…ุฒูˆุฏ (ู…ุตุงุฏุฑ ู…ูˆุซูˆู‚ุฉ)
123
  # =====================================================
124
  PROVIDER_MODELS_FALLBACK = {
125
+ "Blackbox": ["gpt-4o", "claude-3.5-sonnet", "llama-3.1-70b", "gemini-pro"],
126
+ "DeepSeek": ["deepseek-chat", "deepseek-coder"],
127
+ "Perplexity": ["sonar", "sonar-pro", "gpt-4o", "llama-3"],
128
+ "Copilot": ["gpt-4o", "claude-3.5-sonnet"],
129
+ "You": ["gpt-4o", "claude-3.5-sonnet"],
130
+ "Bing": ["gpt-4o"],
131
+ "Qwen": ["qwen-max", "qwen-plus", "qwen-turbo"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  }
133
 
134
  # =====================================================
 
137
  _PROVIDER_MODEL_CACHE = {}
138
 
139
  def discover_provider_models(provider_obj: Any, provider_name: str) -> List[str]:
140
+ # ุฅุฐุง ูƒุงู† ู‡ู†ุงูƒ cache ุงุณุชุฎุฏู…ู‡
141
+ if provider_name in _PROVIDER_MODEL_CACHE:
142
+ return _PROVIDER_MODEL_CACHE[provider_name]
143
+
144
  candidates = []
145
+ # ุญุงูˆู„ ุงูƒุชุดุงู ุงู„ู†ู…ุงุฐุฌ ู…ู† ุงู„ูƒุงุฆู†
146
  for attr in ("models", "model", "default_model", "available_models", "supported_models"):
147
  try:
148
  if hasattr(provider_obj, attr):
 
155
  candidates.append(str(val))
156
  except:
157
  pass
158
+ # ุฅุฐุง ู„ู… ูŠูƒุชุดู ุดูŠุฆุงู‹ ุงุณุชุฎุฏู… fallback
159
  if not candidates:
160
+ candidates = PROVIDER_MODELS_FALLBACK.get(provider_name, ["gpt-4o"])
161
+
162
+ # ุฅุฒุงู„ุฉ ุงู„ุชูƒุฑุงุฑ
163
  seen = set()
164
+ unique = [m for m in candidates if not (m in seen or seen.add(m))]
165
+ _PROVIDER_MODEL_CACHE[provider_name] = unique
166
+ return unique
167
 
168
  # =====================================================
169
+ # STREAM CLEANER - ู„ุชู†ุธูŠู ุดุฐุฑุงุช ุงู„ุฑุฏ
170
  # =====================================================
171
  def clean_stream(chunk):
172
  try:
 
178
  if 'text' in delta:
179
  return delta['text']
180
  return chunk.get('content') or chunk.get('text') or ""
 
181
  if isinstance(chunk, str):
182
  if chunk and chunk[0] == '{' and chunk[-1] == '}':
183
  try:
 
197
  chunk = chunk.replace('\\t', ' ')
198
  return chunk
199
  return str(chunk)
200
+ except Exception as e:
201
+ logger.warning(f"clean_stream error: {e}")
202
  return ""
203
 
204
  # =====================================================
205
+ # CHAT LOGIC - ู…ุน fallback ุฐูƒูŠ
206
  # =====================================================
207
  def ask(message: str, history, provider_name: str, model_name: str, stop_flag=None):
208
  message = (message or "").strip()
 
210
  yield ""
211
  return
212
 
213
+ # ู…ูุชุงุญ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช
214
  key = f"{provider_name}|{model_name}|{message}"
215
  cached = CACHE.get(key)
216
  if cached:
217
  yield cached
218
  return
219
 
220
+ # ุจู†ุงุก ุณุฌู„ ุงู„ู…ุญุงุฏุซุฉ
221
  msgs = []
222
  try:
223
  if history:
 
238
 
239
  msgs.append({"role": "user", "content": message})
240
 
241
+ # ู‚ุงุฆู…ุฉ ุงู„ู…ุฒูˆุฏุงุช ุงู„ุชูŠ ุณู†ุญุงูˆู„ู‡ุง (ู…ุฑุชุจุฉ ุญุณุจ ุงุญุชู…ุงู„ ุงู„ู†ุฌุงุญ)
242
+ # ุงู„ู…ุฒูˆุฏ ุงู„ู…ุทู„ูˆุจ ุฃูˆู„ุงู‹ ุซู… Blackbox ุซู… DeepSeek ุซู… ุงู„ุจู‚ูŠุฉ
243
  fallback_providers = [
244
  provider_name,
245
+ "Blackbox",
246
+ "DeepSeek",
247
+ "Perplexity",
248
+ "Copilot",
249
+ "You",
250
+ "Bing",
251
  "Qwen"
252
  ]
253
  used = []
 
258
  used.append(pname)
259
  pobj = REAL_PROVIDERS.get(pname)
260
  if not pobj:
261
+ logger.info(f"Provider {pname} not available, skipping")
262
  continue
263
 
264
+ # ุงู„ุญุตูˆู„ ุนู„ู‰ ู‚ุงุฆู…ุฉ ุงู„ู†ู…ุงุฐุฌ ู„ู‡ุฐุง ุงู„ู…ุฒูˆุฏ
265
+ models_list = discover_provider_models(pobj, pname)
266
+ if not models_list:
267
+ logger.warning(f"No models for provider {pname}")
268
+ continue
269
 
270
+ # ุชุฑุชูŠุจ ุงู„ู†ู…ุงุฐุฌ: ุงู„ู†ู…ูˆุฐุฌ ุงู„ู…ุทู„ูˆุจ ุฃูˆู„ุงู‹ ุซู… ุจู‚ูŠุฉ ุงู„ู†ู…ุงุฐุฌ
271
+ if model_name in models_list:
272
+ model_candidates = [model_name] + [m for m in models_list if m != model_name]
273
+ else:
274
+ model_candidates = models_list
275
 
276
+ for m in model_candidates[:10]: # ุฌุฑุจ ุฃูˆู„ 10 ู†ู…ุงุฐุฌ ูƒุญุฏ ุฃู‚ุตู‰
277
  try:
278
+ logger.info(f"Trying provider {pname} with model {m}")
279
  stream = g4f.ChatCompletion.create(
280
  model=m,
281
  provider=pobj,
282
  messages=msgs,
283
  stream=True,
284
+ timeout=45
285
  )
 
286
  buffer = []
 
287
  for chunk in stream:
288
  if stop_flag and stop_flag.is_set():
289
  return
 
292
  continue
293
  buffer.append(c)
294
  yield c
 
295
  full = "".join(buffer)
296
  if full.strip():
297
  CACHE.set(key, full)
298
  return
 
299
  except Exception as e:
300
+ logger.warning(f"Provider {pname} model {m} failed: {str(e)[:200]}")
301
  continue
302
 
303
+ yield "โŒ ูุดู„ุช ุฌู…ูŠุน ุงู„ู…ุฒูˆุฏุงุช. ุชุฃูƒุฏ ู…ู† ุงุชุตุงู„ ุงู„ุฅู†ุชุฑู†ุช ุฃูˆ ุญุงูˆู„ ู„ุงุญู‚ุงู‹."
304
 
305
  # =====================================================
306
  # FASTAPI
307
  # =====================================================
308
+ app = FastAPI(title="G4F Smart Router", description="AI Gateway - ู…ุชุนุฏุฏ ุงู„ู…ุฒูˆุฏุงุช")
309
 
310
  API_KEY = os.getenv("API_KEY", "mysecretkey123")
311
 
312
  class ChatRequest(BaseModel):
313
  message: str
314
+ provider: str = "Blackbox"
315
+ model: str = "gpt-4o"
316
  history: List[Any] = []
317
 
318
  # =====================================================
319
+ # ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ู…ูุชุงุญ
320
  # =====================================================
 
321
  def verify_api_key(request: Request):
322
  auth = request.headers.get("Authorization", "").strip()
323
  x_key = request.headers.get("X-API-Key", "").strip()
 
335
  raise HTTPException(status_code=401, detail="Invalid API key. Use 'Authorization: Bearer KEY' or 'X-API-Key: KEY'")
336
 
337
  # =====================================================
338
+ # ุฏุนู… HEAD (ู„ุฅุตู„ุงุญ 405)
339
  # =====================================================
 
340
  @app.head("/")
341
  async def head_root():
342
  return Response(status_code=200)
 
350
  return Response(status_code=200)
351
 
352
  # =====================================================
353
+ # ู†ู‚ุงุท ู†ู‡ุงูŠุฉ ู…ุชูˆุงูู‚ุฉ ู…ุน Claude Desktop
354
  # =====================================================
 
355
  @app.get("/v1/models")
356
  async def v1_models(request: Request):
357
+ # ู„ุง ุชุญุชุงุฌ ู…ูุชุงุญ ู„ู‡ุฐู‡ ุงู„ู†ู‚ุทุฉ (Claude Desktop ูŠุทู„ุจู‡ุง ุฃูˆู„ุงู‹)
 
358
  models = []
359
  for pname, pobj in REAL_PROVIDERS.items():
360
+ models_list = discover_provider_models(pobj, pname)
361
+ for model in models_list[:5]: # ู†ุนุฑุถ ุฃูˆู„ 5 ู†ู…ุงุฐุฌ ู„ูƒู„ ู…ุฒูˆุฏ
 
362
  models.append({
363
  "id": model,
364
  "type": "model",
365
  "display_name": f"{pname} - {model}"
366
  })
 
367
  if not models:
368
+ models = [{"id": "gpt-4o", "type": "model", "display_name": "Default"}]
 
 
 
 
 
 
 
369
  return {"data": models}
370
 
371
  @app.post("/v1/messages")
372
  async def v1_messages(request: Request):
 
373
  verify_api_key(request)
 
374
  body = await request.json()
 
375
  messages = body.get("messages", [])
376
  if not messages:
377
  raise HTTPException(status_code=400, detail="No messages provided")
 
378
  last_message = messages[-1]
379
  user_message = last_message.get("content", "")
380
+ model = body.get("model", "gpt-4o")
 
381
  system_prompt = body.get("system", "")
 
382
  history = []
383
  for msg in messages[:-1]:
384
  role = msg.get("role", "user")
385
  content = msg.get("content", "")
386
  history.append({"role": role, "content": content})
 
387
  full_message = user_message
388
  if system_prompt:
389
  full_message = f"[System: {system_prompt}]\n\n{user_message}"
390
 
391
  full_response = ""
392
+ for chunk in ask(full_message, history, "Blackbox", model):
393
  full_response = chunk
394
 
395
  return {
 
400
  "model": model,
401
  "stop_reason": "end_turn",
402
  "stop_sequence": None,
403
+ "usage": {"input_tokens": len(user_message)//4, "output_tokens": len(full_response)//4}
 
 
 
404
  }
405
 
406
  @app.post("/v1/messages/stream")
407
  async def v1_messages_stream(request: Request):
 
408
  verify_api_key(request)
 
409
  body = await request.json()
410
  messages = body.get("messages", [])
411
  if not messages:
412
  raise HTTPException(status_code=400, detail="No messages provided")
 
413
  last_message = messages[-1]
414
  user_message = last_message.get("content", "")
415
+ model = body.get("model", "gpt-4o")
416
  system_prompt = body.get("system", "")
 
417
  full_message = user_message
418
  if system_prompt:
419
  full_message = f"[System: {system_prompt}]\n\n{user_message}"
420
 
421
  async def generate_stream():
422
  message_id = f"msg_{int(time.time())}_{os.urandom(4).hex()}"
 
423
  yield f"event: message_start\ndata: {{\"message\": {{\"id\": \"{message_id}\", \"type\": \"message\", \"role\": \"assistant\", \"content\": [], \"model\": \"{model}\", \"stop_reason\": null, \"stop_sequence\": null, \"usage\": {{\"input_tokens\": 0, \"output_tokens\": 0}}}}}}\n\n"
 
424
  yield f"event: content_block_start\ndata: {{\"type\": \"content_block_start\", \"index\": 0, \"content_block\": {{\"type\": \"text\", \"text\": \"\"}}}}\n\n"
425
+ for chunk in ask(full_message, [], "Blackbox", model):
 
426
  yield f"event: content_block_delta\ndata: {{\"type\": \"content_block_delta\", \"index\": 0, \"delta\": {{\"type\": \"text_delta\", \"text\": {json.dumps(chunk, ensure_ascii=False)}}}}}\n\n"
 
427
  yield f"event: message_delta\ndata: {{\"type\": \"message_delta\", \"delta\": {{\"stop_reason\": \"end_turn\", \"stop_sequence\": null}}, \"usage\": {{\"output_tokens\": 100}}}}\n\n"
428
  yield f"event: message_stop\ndata: {{}}\n\n"
429
 
430
+ return StreamingResponse(generate_stream(), media_type="text/event-stream", headers={"Cache-Control": "no-cache", "Connection": "keep-alive"})
 
 
 
 
 
 
 
431
 
432
  # =====================================================
433
+ # ู†ู‚ุงุท ู†ู‡ุงูŠุฉ ุฅุถุงููŠุฉ ู„ู„ุชูˆุงูู‚ ุงู„ู‚ุฏูŠู…
434
  # =====================================================
 
435
  @app.get("/")
436
  async def root():
437
  return {
438
+ "message": "G4F Smart Router is running (Multi-Provider)",
439
+ "providers": list(REAL_PROVIDERS.keys()),
440
  "endpoints": {
441
+ "GET /": "Home",
442
+ "GET /health": "Health",
443
  "GET /v1/models": "List models (NO AUTH)",
444
+ "POST /v1/messages": "Send message (AUTH)",
445
+ "POST /v1/messages/stream": "Stream (AUTH)",
446
+ "GET /providers": "Providers list (AUTH)",
 
 
447
  },
 
448
  "cookies": COOKIE_STATUS,
 
449
  "status": "โœ… Server is working"
450
  }
451
 
 
453
  async def health():
454
  return {"status": "ok", "cookies": COOKIE_STATUS, "providers": list(REAL_PROVIDERS.keys())}
455
 
456
+ @app.get("/providers")
457
+ async def get_providers(request: Request):
458
+ verify_api_key(request)
459
+ return {"providers": list(REAL_PROVIDERS.keys())}
460
+
461
  @app.post("/chat")
462
  async def chat(request: Request, chat_req: ChatRequest):
463
  verify_api_key(request)
 
464
  result = ""
465
  for chunk in ask(chat_req.message, chat_req.history, chat_req.provider, chat_req.model):
466
  result = chunk
 
467
  return JSONResponse({"response": result})
468
 
469
  @app.post("/chat/stream")
470
  async def chat_stream(request: Request, chat_req: ChatRequest):
471
  verify_api_key(request)
 
472
  async def generate():
473
  for chunk in ask(chat_req.message, chat_req.history, chat_req.provider, chat_req.model):
474
  yield f"data: {json.dumps({'delta': chunk}, ensure_ascii=False)}\n\n"
475
  yield "data: [DONE]\n\n"
 
476
  return StreamingResponse(generate(), media_type="text/event-stream")
477
 
 
 
 
 
 
 
 
 
 
 
 
 
478
  # =====================================================
479
+ # ุงู„ุชุดุบูŠู„
480
  # =====================================================
481
  if __name__ == "__main__":
482
  import uvicorn
483
  port = int(os.getenv("PORT", 7860))
484
+ uvicorn.run("app:app", host="0.0.0.0", port=port, reload=False)