bahi-bh commited on
Commit
a50fddc
·
verified ·
1 Parent(s): abd7707

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +260 -211
app.py CHANGED
@@ -83,11 +83,9 @@ def verify_api_key(req: Request):
83
 
84
  token = None
85
 
86
- # دعم x-api-key
87
  if x_api_key:
88
  token = x_api_key.strip()
89
 
90
- # دعم Authorization Bearer
91
  elif auth:
92
 
93
  parts = auth.split(" ", 1)
@@ -95,11 +93,9 @@ def verify_api_key(req: Request):
95
  if len(parts) == 2:
96
  token = parts[1].strip()
97
 
98
- # لو لم يتم استخراج توكن صالح
99
  if not token:
100
  return True
101
 
102
- # تحقق اختياري فقط
103
  if API_KEY and token != API_KEY:
104
  raise HTTPException(
105
  status_code=403,
@@ -125,7 +121,69 @@ async def root():
125
 
126
  @app.head("/")
127
  async def root_head():
128
- return JSONResponse(status_code=200, content={})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
 
131
  # =====================================================
@@ -137,24 +195,37 @@ async def get_models():
137
 
138
  models_data = []
139
 
140
- try:
141
 
142
- if hasattr(g4f.models, "_all_models"):
 
 
 
 
143
 
144
- all_models = list(g4f.models._all_models)
 
 
145
 
146
- for model in all_models:
 
147
 
148
- models_data.append({
149
- "id": str(model),
150
- "object": "model",
151
- "created": int(time.time()),
152
- "owned_by": "g4f"
153
- })
154
 
155
- except Exception as e:
 
 
 
156
 
157
- logger.error(f"Models error: {e}")
 
 
 
 
 
 
 
158
 
159
  return {
160
  "object": "list",
@@ -162,46 +233,6 @@ async def get_models():
162
  }
163
 
164
 
165
- # =====================================================
166
- # MODEL NORMALIZER
167
- # =====================================================
168
-
169
- def normalize_model(model: str):
170
-
171
- if not model:
172
- return model
173
-
174
- model_map = {
175
-
176
- # Claude
177
- "claude-sonnet-4-6": "gpt-4o",
178
- "claude-sonnet-4": "gpt-4o",
179
- "claude-4-sonnet": "gpt-4o",
180
- "claude-3-7-sonnet": "gpt-4o",
181
- "claude-3.7-sonnet": "gpt-4o",
182
- "claude-opus-4": "gpt-4o",
183
- "claude-opus-4-6": "gpt-4o",
184
- "claude-opus-4-7": "gpt-4o",
185
- "claude-haiku-4": "gpt-4o-mini",
186
-
187
- # GPT
188
- "gpt-5": "gpt-4o",
189
- "gpt-5-mini": "gpt-4o-mini",
190
- "gpt-4.1": "gpt-4o",
191
- "gpt-4.1-mini": "gpt-4o-mini",
192
-
193
- # Gemini
194
- "gemini-2.5-pro": "gemini",
195
- "gemini-2.5-flash": "gemini",
196
-
197
- # Grok
198
- "grok-4": "gpt-4o",
199
- "grok-4-fast": "gpt-4o-mini",
200
- }
201
-
202
- return model_map.get(model, model)
203
-
204
-
205
  # =====================================================
206
  # CHAT COMPLETIONS
207
  # =====================================================
@@ -214,7 +245,9 @@ async def chat_completions(
214
 
215
  verify_api_key(req)
216
 
217
- model = normalize_model(body.model)
 
 
218
 
219
  messages = [
220
  {
@@ -240,7 +273,7 @@ async def chat_completions(
240
 
241
  try:
242
 
243
- client = Client()
244
 
245
  response = client.chat.completions.create(
246
  model=model,
@@ -248,8 +281,6 @@ async def chat_completions(
248
  stream=True
249
  )
250
 
251
- has_content = False
252
-
253
  for chunk in response:
254
 
255
  try:
@@ -264,13 +295,15 @@ async def chat_completions(
264
  and chunk.choices[0].delta
265
  and chunk.choices[0].delta.content
266
  ):
267
- content = chunk.choices[0].delta.content
 
 
 
 
268
 
269
  if not content:
270
  continue
271
 
272
- has_content = True
273
-
274
  payload = {
275
  "id": chunk_id,
276
  "object": "chat.completion.chunk",
@@ -302,21 +335,6 @@ async def chat_completions(
302
  f"Chunk error: {chunk_error}"
303
  )
304
 
305
- if not has_content:
306
-
307
- error_payload = {
308
- "error": {
309
- "message": "Provider returned empty stream",
310
- "type": "empty_stream"
311
- }
312
- }
313
-
314
- yield (
315
- "data: "
316
- + json.dumps(error_payload)
317
- + "\n\n"
318
- )
319
-
320
  final_payload = {
321
  "id": chunk_id,
322
  "object": "chat.completion.chunk",
@@ -345,16 +363,13 @@ async def chat_completions(
345
  f"Streaming error: {e}"
346
  )
347
 
348
- error_payload = {
349
- "error": {
350
- "message": str(e),
351
- "type": "server_error"
352
- }
353
- }
354
-
355
  yield (
356
  "data: "
357
- + json.dumps(error_payload)
 
 
 
 
358
  + "\n\n"
359
  )
360
 
@@ -371,12 +386,12 @@ async def chat_completions(
371
  )
372
 
373
  # =================================================
374
- # NORMAL RESPONSE
375
  # =================================================
376
 
377
  try:
378
 
379
- client = Client()
380
 
381
  response = client.chat.completions.create(
382
  model=model,
@@ -452,16 +467,16 @@ async def anthropic_messages(
452
 
453
  verify_api_key(req)
454
 
 
 
455
  model = normalize_model(
456
- body.get("model")
457
  )
458
 
459
  messages = body.get("messages", [])
460
 
461
- stream = True
462
-
463
  logger.info(
464
- f"Anthropic request model={model} stream={stream}"
465
  )
466
 
467
  converted_messages = []
@@ -493,153 +508,187 @@ async def anthropic_messages(
493
  "content": content
494
  })
495
 
496
- # =============================================
497
- # STREAM
498
- # =============================================
499
-
500
- if stream:
501
-
502
- def generate_stream():
503
-
504
- try:
505
-
506
- client = Client()
507
 
508
- response = client.chat.completions.create(
509
- model=model,
510
- messages=converted_messages,
511
- stream=True
512
- )
513
 
514
- for chunk in response:
515
-
516
- try:
517
 
518
- content = ""
519
 
520
- if (
521
- hasattr(chunk, "choices")
522
- and chunk.choices
523
- and len(chunk.choices) > 0
524
- and hasattr(chunk.choices[0], "delta")
525
- and chunk.choices[0].delta
526
- and chunk.choices[0].delta.content
527
- ):
528
- content = chunk.choices[0].delta.content
529
 
530
- if not content:
531
- continue
 
532
 
533
- payload = {
534
- "type": "content_block_delta",
535
- "delta": {
536
- "type": "text_delta",
537
- "text": content
538
- }
 
 
 
 
 
 
 
 
 
 
539
  }
 
 
 
 
540
 
541
- yield (
542
- "data: "
543
- + json.dumps(
544
- payload,
545
- ensure_ascii=False
546
- )
547
- + "\n\n"
548
- )
549
-
550
- except Exception as e:
551
-
552
- logger.error(
553
- f"Anthropic chunk error: {e}"
554
- )
555
 
556
- yield (
557
- "data: "
558
- + json.dumps({
559
- "type": "message_stop"
560
- })
561
- + "\n\n"
562
- )
 
 
 
 
 
 
563
 
564
- except Exception as e:
565
 
566
- logger.error(
567
- f"Anthropic stream error: {e}"
568
- )
569
 
570
- yield (
571
- "data: "
572
- + json.dumps({
573
- "error": str(e)
574
- })
575
- + "\n\n"
576
- )
577
 
578
- return StreamingResponse(
579
- generate_stream(),
580
- media_type="text/event-stream"
581
- )
 
 
 
 
 
 
 
 
 
582
 
583
- # =============================================
584
- # NORMAL
585
- # =============================================
586
 
587
- try:
 
 
 
 
 
 
 
588
 
589
- client = Client()
 
 
 
 
 
 
 
 
590
 
591
- response = client.chat.completions.create(
592
- model=model,
593
- messages=converted_messages,
594
- stream=False
595
- )
596
 
597
- text = ""
 
 
598
 
599
- try:
 
 
600
 
601
- text = (
602
- response
603
- .choices[0]
604
- .message
605
- .content
 
 
 
606
  )
607
 
608
- except Exception:
609
-
610
- text = str(response)
611
-
612
- return {
613
-
614
- "id": f"msg_{uuid.uuid4().hex}",
 
 
 
 
 
 
 
 
 
 
 
 
615
 
616
- "type": "message",
 
 
617
 
618
- "role": "assistant",
 
 
 
 
 
 
 
619
 
620
- "model": model,
621
 
622
- "content": [
623
- {
624
- "type": "text",
625
- "text": text
626
- }
627
- ],
628
 
629
- "stop_reason": "end_turn"
 
 
 
 
 
 
 
 
 
 
 
630
 
 
 
 
 
 
 
 
631
  }
632
-
633
- except Exception as e:
634
-
635
- logger.error(
636
- f"Anthropic error: {e}"
637
- )
638
-
639
- raise HTTPException(
640
- status_code=500,
641
- detail=str(e)
642
- )
643
 
644
 
645
  # =====================================================
 
83
 
84
  token = None
85
 
 
86
  if x_api_key:
87
  token = x_api_key.strip()
88
 
 
89
  elif auth:
90
 
91
  parts = auth.split(" ", 1)
 
93
  if len(parts) == 2:
94
  token = parts[1].strip()
95
 
 
96
  if not token:
97
  return True
98
 
 
99
  if API_KEY and token != API_KEY:
100
  raise HTTPException(
101
  status_code=403,
 
121
 
122
  @app.head("/")
123
  async def root_head():
124
+ return JSONResponse(
125
+ status_code=200,
126
+ content={}
127
+ )
128
+
129
+
130
+ # =====================================================
131
+ # MODEL NORMALIZER
132
+ # =====================================================
133
+
134
+ def normalize_model(model: str):
135
+
136
+ if not model:
137
+ return "gpt-4o-mini"
138
+
139
+ model_lower = model.lower()
140
+
141
+ # GPT
142
+ if "gpt-4" in model_lower:
143
+ return "gpt-4"
144
+
145
+ if "gpt-3.5" in model_lower:
146
+ return "gpt-3.5-turbo"
147
+
148
+ # Claude
149
+ if "claude" in model_lower:
150
+ return "gpt-4"
151
+
152
+ # Gemini
153
+ if "gemini" in model_lower:
154
+ return "gemini"
155
+
156
+ # Cohere / Command
157
+ if "command-r-plus" in model_lower:
158
+ return "command-r-plus"
159
+
160
+ if "command-r" in model_lower:
161
+ return "command-r"
162
+
163
+ # Grok
164
+ if "grok" in model_lower:
165
+ return "gpt-4"
166
+
167
+ # Llama
168
+ if "llama" in model_lower:
169
+ return "llama-3-70b"
170
+
171
+ # Mistral
172
+ if "mistral" in model_lower:
173
+ return "mistral-7b"
174
+
175
+ return model
176
+
177
+
178
+ # =====================================================
179
+ # SAFE CLIENT
180
+ # =====================================================
181
+
182
+ def create_client():
183
+
184
+ return Client(
185
+ provider=g4f.Provider.Blackbox
186
+ )
187
 
188
 
189
  # =====================================================
 
195
 
196
  models_data = []
197
 
198
+ visible_models = [
199
 
200
+ "gpt-4",
201
+ "gpt-4-turbo",
202
+ "gpt-4o",
203
+ "gpt-4o-mini",
204
+ "gpt-3.5-turbo",
205
 
206
+ "claude-sonnet-4-6",
207
+ "claude-opus-4",
208
+ "claude-3-7-sonnet",
209
 
210
+ "gemini-2.5-pro",
211
+ "gemini-2.5-flash",
212
 
213
+ "command-r",
214
+ "command-r-plus",
 
 
 
 
215
 
216
+ "grok-4",
217
+ "llama-3-70b",
218
+ "mistral-7b"
219
+ ]
220
 
221
+ for model in visible_models:
222
+
223
+ models_data.append({
224
+ "id": model,
225
+ "object": "model",
226
+ "created": int(time.time()),
227
+ "owned_by": "g4f"
228
+ })
229
 
230
  return {
231
  "object": "list",
 
233
  }
234
 
235
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  # =====================================================
237
  # CHAT COMPLETIONS
238
  # =====================================================
 
245
 
246
  verify_api_key(req)
247
 
248
+ model = normalize_model(
249
+ body.model
250
+ )
251
 
252
  messages = [
253
  {
 
273
 
274
  try:
275
 
276
+ client = create_client()
277
 
278
  response = client.chat.completions.create(
279
  model=model,
 
281
  stream=True
282
  )
283
 
 
 
284
  for chunk in response:
285
 
286
  try:
 
295
  and chunk.choices[0].delta
296
  and chunk.choices[0].delta.content
297
  ):
298
+ content = (
299
+ chunk
300
+ .choices[0]
301
+ .delta.content
302
+ )
303
 
304
  if not content:
305
  continue
306
 
 
 
307
  payload = {
308
  "id": chunk_id,
309
  "object": "chat.completion.chunk",
 
335
  f"Chunk error: {chunk_error}"
336
  )
337
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  final_payload = {
339
  "id": chunk_id,
340
  "object": "chat.completion.chunk",
 
363
  f"Streaming error: {e}"
364
  )
365
 
 
 
 
 
 
 
 
366
  yield (
367
  "data: "
368
+ + json.dumps({
369
+ "error": {
370
+ "message": str(e)
371
+ }
372
+ })
373
  + "\n\n"
374
  )
375
 
 
386
  )
387
 
388
  # =================================================
389
+ # NORMAL
390
  # =================================================
391
 
392
  try:
393
 
394
+ client = create_client()
395
 
396
  response = client.chat.completions.create(
397
  model=model,
 
467
 
468
  verify_api_key(req)
469
 
470
+ requested_model = body.get("model")
471
+
472
  model = normalize_model(
473
+ requested_model
474
  )
475
 
476
  messages = body.get("messages", [])
477
 
 
 
478
  logger.info(
479
+ f"Anthropic request model={model} stream=True"
480
  )
481
 
482
  converted_messages = []
 
508
  "content": content
509
  })
510
 
511
+ def generate_stream():
 
 
 
 
 
 
 
 
 
 
512
 
513
+ message_id = f"msg_{uuid.uuid4().hex}"
 
 
 
 
514
 
515
+ try:
 
 
516
 
517
+ client = create_client()
518
 
519
+ response = client.chat.completions.create(
520
+ model=model,
521
+ messages=converted_messages,
522
+ stream=True
523
+ )
 
 
 
 
524
 
525
+ # =========================================
526
+ # message_start
527
+ # =========================================
528
 
529
+ yield (
530
+ "event: message_start\n"
531
+ "data: "
532
+ + json.dumps({
533
+ "type": "message_start",
534
+ "message": {
535
+ "id": message_id,
536
+ "type": "message",
537
+ "role": "assistant",
538
+ "model": requested_model,
539
+ "content": [],
540
+ "stop_reason": None,
541
+ "stop_sequence": None,
542
+ "usage": {
543
+ "input_tokens": 0,
544
+ "output_tokens": 0
545
  }
546
+ }
547
+ })
548
+ + "\n\n"
549
+ )
550
 
551
+ # =========================================
552
+ # content_block_start
553
+ # =========================================
 
 
 
 
 
 
 
 
 
 
 
554
 
555
+ yield (
556
+ "event: content_block_start\n"
557
+ "data: "
558
+ + json.dumps({
559
+ "type": "content_block_start",
560
+ "index": 0,
561
+ "content_block": {
562
+ "type": "text",
563
+ "text": ""
564
+ }
565
+ })
566
+ + "\n\n"
567
+ )
568
 
569
+ for chunk in response:
570
 
571
+ try:
 
 
572
 
573
+ content = ""
 
 
 
 
 
 
574
 
575
+ if (
576
+ hasattr(chunk, "choices")
577
+ and chunk.choices
578
+ and len(chunk.choices) > 0
579
+ and hasattr(chunk.choices[0], "delta")
580
+ and chunk.choices[0].delta
581
+ and chunk.choices[0].delta.content
582
+ ):
583
+ content = (
584
+ chunk
585
+ .choices[0]
586
+ .delta.content
587
+ )
588
 
589
+ if not content:
590
+ continue
 
591
 
592
+ payload = {
593
+ "type": "content_block_delta",
594
+ "index": 0,
595
+ "delta": {
596
+ "type": "text_delta",
597
+ "text": content
598
+ }
599
+ }
600
 
601
+ yield (
602
+ "event: content_block_delta\n"
603
+ "data: "
604
+ + json.dumps(
605
+ payload,
606
+ ensure_ascii=False
607
+ )
608
+ + "\n\n"
609
+ )
610
 
611
+ except Exception as e:
 
 
 
 
612
 
613
+ logger.error(
614
+ f"Anthropic chunk error: {e}"
615
+ )
616
 
617
+ # =========================================
618
+ # content_block_stop
619
+ # =========================================
620
 
621
+ yield (
622
+ "event: content_block_stop\n"
623
+ "data: "
624
+ + json.dumps({
625
+ "type": "content_block_stop",
626
+ "index": 0
627
+ })
628
+ + "\n\n"
629
  )
630
 
631
+ # =========================================
632
+ # message_delta
633
+ # =========================================
634
+
635
+ yield (
636
+ "event: message_delta\n"
637
+ "data: "
638
+ + json.dumps({
639
+ "type": "message_delta",
640
+ "delta": {
641
+ "stop_reason": "end_turn",
642
+ "stop_sequence": None
643
+ },
644
+ "usage": {
645
+ "output_tokens": 0
646
+ }
647
+ })
648
+ + "\n\n"
649
+ )
650
 
651
+ # =========================================
652
+ # message_stop
653
+ # =========================================
654
 
655
+ yield (
656
+ "event: message_stop\n"
657
+ "data: "
658
+ + json.dumps({
659
+ "type": "message_stop"
660
+ })
661
+ + "\n\n"
662
+ )
663
 
664
+ except Exception as e:
665
 
666
+ logger.error(
667
+ f"Anthropic stream error: {e}"
668
+ )
 
 
 
669
 
670
+ yield (
671
+ "event: error\n"
672
+ "data: "
673
+ + json.dumps({
674
+ "type": "error",
675
+ "error": {
676
+ "type": "api_error",
677
+ "message": str(e)
678
+ }
679
+ })
680
+ + "\n\n"
681
+ )
682
 
683
+ return StreamingResponse(
684
+ generate_stream(),
685
+ media_type="text/event-stream",
686
+ headers={
687
+ "Cache-Control": "no-cache",
688
+ "Connection": "keep-alive",
689
+ "X-Accel-Buffering": "no"
690
  }
691
+ )
 
 
 
 
 
 
 
 
 
 
692
 
693
 
694
  # =====================================================