bahi-bh commited on
Commit
17aa345
·
verified ·
1 Parent(s): f535544

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +251 -255
main.py CHANGED
@@ -1,9 +1,9 @@
1
  import os
2
  import json
3
  import time
4
- import logging
5
  import asyncio
6
- import g4f
 
7
 
8
  from fastapi import FastAPI, HTTPException, Header
9
  from fastapi.responses import StreamingResponse
@@ -11,449 +11,445 @@ from fastapi.middleware.cors import CORSMiddleware
11
  from pydantic import BaseModel
12
  from typing import List, Optional
13
 
 
 
 
14
 
15
- # ================= Logging =================
16
-
17
- logging.basicConfig(
18
- level=logging.INFO,
19
- format="%(asctime)s %(levelname)s %(message)s"
20
- )
21
 
 
22
  logger=logging.getLogger(__name__)
23
 
24
-
25
- # ================= App =================
 
26
 
27
  app=FastAPI(
28
- title="G4F Smart Gateway",
29
  version="2.0"
30
  )
31
 
32
  app.add_middleware(
33
  CORSMiddleware,
34
  allow_origins=["*"],
35
- allow_credentials=False,
36
  allow_methods=["*"],
37
  allow_headers=["*"]
38
  )
39
 
40
  API_KEY=os.getenv(
41
  "API_KEY",
42
- "your_fallback_secret"
43
  )
44
 
 
 
 
 
45
 
46
- # ================= Models =================
 
 
47
 
48
- class ChatMessage(BaseModel):
49
  role:str
50
  content:str
51
 
52
 
53
  class ChatRequest(BaseModel):
54
- model:str="gpt-4o-mini"
55
- messages:List[ChatMessage]
 
 
 
56
  stream:bool=False
 
57
  provider:Optional[str]=None
58
- temperature:Optional[float]=0.7
59
- max_tokens:Optional[int]=2048
60
 
61
 
62
- # ================= Auth =================
 
 
63
 
64
- def verify_key(auth:str):
65
 
66
  if not auth:
67
- raise HTTPException(
68
- status_code=401,
69
- detail="Missing Authorization"
70
- )
71
-
72
- parts=auth.split()
73
 
74
- if len(parts)!=2:
75
  raise HTTPException(
76
  status_code=401,
77
- detail="Malformed Authorization"
78
  )
79
 
80
- if parts[0]!="Bearer":
81
- raise HTTPException(
82
- status_code=401,
83
- detail="Invalid Authorization"
84
- )
85
 
86
- if parts[1]!=API_KEY:
87
  raise HTTPException(
88
  status_code=401,
89
- detail="Invalid API Key"
90
  )
91
 
92
 
93
- # ================= Provider Selection =================
 
 
94
 
95
- def choose_provider(model:str):
96
 
97
- model=model.lower()
 
 
98
 
99
- try:
100
 
101
- if "qwen" in model:
102
 
103
- return getattr(
104
- g4f.Provider,
105
- "DeepInfra",
106
- None
107
- )
108
 
109
- elif "perplexity" in model:
110
 
111
- return getattr(
112
- g4f.Provider,
113
- "PerplexityLabs",
114
- None
115
- )
116
 
117
- elif "llama" in model:
 
 
118
 
119
- return getattr(
120
- g4f.Provider,
121
- "DeepInfra",
122
- None
123
- )
124
 
125
- elif "claude" in model:
 
 
 
 
126
 
127
- return getattr(
128
- g4f.Provider,
129
- "OpenaiChat",
130
- None
131
- )
132
 
133
- elif "gemini" in model:
134
 
135
- return getattr(
136
- g4f.Provider,
137
- "OpenaiChat",
138
- None
139
- )
140
 
141
- elif "gpt" in model:
 
 
 
142
 
143
- return getattr(
144
- g4f.Provider,
145
- "OpenaiChat",
146
- None
147
  )
148
 
149
- except:
150
- pass
151
-
152
- return None
153
 
154
 
155
- # ================= Health =================
 
 
156
 
157
  @app.get("/")
 
158
  async def root():
159
 
160
  return {
161
- "status":"online"
 
 
 
 
162
  }
163
 
164
 
165
- # ================= Models =================
 
 
166
 
167
  @app.get("/v1/models")
 
168
  async def models(
169
- authorization:str=Header(None)
170
  ):
171
 
172
- verify_key(
173
- authorization
174
- )
175
 
176
  try:
177
 
178
- found=[]
179
 
180
- if hasattr(
181
- g4f.models,
182
- "ModelUtils"
183
- ):
184
 
185
- found=list(
186
- g4f.models.ModelUtils.convert.keys()
187
- )
188
 
189
- found=sorted(
190
- list(set(found))
191
- )
 
 
192
 
193
- if not found:
194
 
195
- found=[
196
 
197
- "gpt-4o-mini",
198
- "gpt-4",
199
- "gpt-3.5-turbo",
200
- "qwen-2.5-72b",
201
- "llama-3-70b",
202
- "perplexity"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
- ]
 
205
 
206
- return {
 
 
207
 
208
- "object":"list",
209
 
210
- "data":[
211
 
212
- {
213
  "id":m,
 
214
  "object":"model",
215
- "owned_by":"g4f"
216
- }
217
 
218
- for m in found
219
 
220
- ]
221
- }
222
 
223
  except Exception as e:
224
 
225
- logger.exception(e)
226
-
227
- return {
228
 
229
- "object":"list",
230
 
231
- "data":[
232
 
233
- {
234
- "id":"gpt-4o-mini",
235
- "object":"model"
236
- }
237
 
238
- ]
239
- }
240
 
241
 
242
- # ================= Chat =================
 
 
243
 
244
  @app.post("/v1/chat/completions")
 
245
  async def chat(
246
- body:ChatRequest,
247
- authorization:str=Header(None)
 
 
 
248
  ):
249
 
250
- verify_key(
251
  authorization
252
  )
253
 
254
- try:
255
 
256
- messages=[
257
 
258
- {
259
- "role":m.role,
260
- "content":m.content
261
- }
262
 
263
- for m in body.messages
264
- ]
265
 
 
266
 
267
- provider=None
 
268
 
269
- if body.provider:
270
 
271
- provider=getattr(
272
- g4f.Provider,
273
- body.provider,
274
- None
275
- )
276
 
277
- else:
278
 
279
- provider=choose_provider(
280
- body.model
281
- )
282
 
 
283
 
284
- logger.info(
285
- f"Model={body.model} Provider={provider}"
286
- )
287
 
 
288
 
289
- # ========= STREAM =========
290
 
291
- if body.stream:
292
 
293
- def generate():
294
 
295
- try:
296
 
297
- response=g4f.ChatCompletion.create(
298
 
299
- model=body.model,
300
- messages=messages,
301
- provider=provider,
302
- stream=True
303
 
304
- )
 
 
305
 
306
- for chunk in response:
307
 
308
  payload={
309
 
310
  "id":"chatcmpl",
311
 
312
- "object":
313
- "chat.completion.chunk",
314
 
315
- "created":
316
- int(time.time()),
317
 
318
- "model":
319
- body.model,
320
 
321
- "choices":[
322
- {
323
- "delta":{
324
- "content":
325
- str(chunk)
326
- },
327
- "index":0
328
- }
329
- ]
330
- }
331
 
332
- yield (
333
- f"data: {json.dumps(payload)}\n\n"
334
- )
335
 
336
- yield "data: [DONE]\n\n"
337
 
338
- except Exception as e:
339
 
340
- logger.exception(e)
341
 
342
- yield (
343
- f"data:{json.dumps({'error':str(e)})}\n\n"
344
- )
345
 
 
346
 
347
- return StreamingResponse(
348
- generate(),
349
- media_type="text/event-stream"
350
- )
351
 
 
 
 
352
 
353
- # ========= NORMAL =========
354
 
355
- response=await asyncio.to_thread(
356
 
357
- g4f.ChatCompletion.create,
358
 
359
- model=body.model,
360
- messages=messages,
361
- provider=provider
362
 
363
- )
364
 
 
365
 
366
- return {
367
 
368
- "id":"chatcmpl",
369
 
370
- "object":"chat.completion",
371
 
372
- "created":
373
- int(time.time()),
 
374
 
375
- "model":
376
- body.model,
377
 
378
- "choices":[
379
 
380
- {
381
 
382
- "index":0,
383
 
384
- "message":{
 
385
 
386
- "role":
387
- "assistant",
388
 
389
- "content":
390
- str(response)
391
 
392
- },
 
 
393
 
394
- "finish_reason":
395
- "stop"
396
 
397
- }
398
 
399
- ]
400
- }
401
 
402
- except Exception as e:
403
 
404
- logger.exception(e)
405
 
406
- raise HTTPException(
407
- status_code=500,
408
- detail=str(e)
409
  )
410
 
 
411
 
412
- # ================= Test =================
413
 
414
- @app.get("/test/{model}")
415
- async def test_model(
416
- model:str
417
- ):
418
-
419
- try:
420
 
421
- provider=choose_provider(
422
- model
423
  )
424
 
425
- result=await asyncio.to_thread(
426
 
427
  g4f.ChatCompletion.create,
428
 
429
- model=model,
430
 
431
- provider=provider,
 
432
 
433
- messages=[
434
 
435
- {
436
- "role":"user",
437
- "content":"hello"
438
- }
439
 
440
- ]
441
- )
442
 
443
- return {
444
 
445
- "model":model,
446
- "provider":str(provider),
447
- "working":True
448
 
449
- }
450
 
451
- except Exception as e:
 
 
 
 
 
 
452
 
453
- return {
454
 
455
- "model":model,
456
- "working":False,
457
- "error":str(e)
 
 
 
 
 
 
 
 
458
 
459
- }
 
1
  import os
2
  import json
3
  import time
 
4
  import asyncio
5
+ import logging
6
+ import inspect
7
 
8
  from fastapi import FastAPI, HTTPException, Header
9
  from fastapi.responses import StreamingResponse
 
11
  from pydantic import BaseModel
12
  from typing import List, Optional
13
 
14
+ import g4f
15
+ import g4f.Provider as Provider
16
+ import litellm
17
 
18
+ # ----------------------------
19
+ # Logging
20
+ # ----------------------------
 
 
 
21
 
22
+ logging.basicConfig(level=logging.INFO)
23
  logger=logging.getLogger(__name__)
24
 
25
+ # ----------------------------
26
+ # App
27
+ # ----------------------------
28
 
29
  app=FastAPI(
30
+ title="AI Gateway",
31
  version="2.0"
32
  )
33
 
34
  app.add_middleware(
35
  CORSMiddleware,
36
  allow_origins=["*"],
 
37
  allow_methods=["*"],
38
  allow_headers=["*"]
39
  )
40
 
41
  API_KEY=os.getenv(
42
  "API_KEY",
43
+ "your_secret"
44
  )
45
 
46
+ DEFAULT_MODEL=os.getenv(
47
+ "DEFAULT_MODEL",
48
+ "groq/llama-3.3-70b-versatile"
49
+ )
50
 
51
+ # ----------------------------
52
+ # Models
53
+ # ----------------------------
54
 
55
+ class Message(BaseModel):
56
  role:str
57
  content:str
58
 
59
 
60
  class ChatRequest(BaseModel):
61
+
62
+ model:str=DEFAULT_MODEL
63
+
64
+ messages:List[Message]
65
+
66
  stream:bool=False
67
+
68
  provider:Optional[str]=None
 
 
69
 
70
 
71
+ # ----------------------------
72
+ # Auth
73
+ # ----------------------------
74
 
75
+ def verify(auth):
76
 
77
  if not auth:
 
 
 
 
 
 
78
 
 
79
  raise HTTPException(
80
  status_code=401,
81
+ detail="Missing token"
82
  )
83
 
84
+ if auth != f"Bearer {API_KEY}":
 
 
 
 
85
 
 
86
  raise HTTPException(
87
  status_code=401,
88
+ detail="Unauthorized"
89
  )
90
 
91
 
92
+ # ----------------------------
93
+ # g4f provider discovery
94
+ # ----------------------------
95
 
96
+ SKIP={
97
 
98
+ "BaseProvider",
99
+ "RetryProvider",
100
+ "AsyncProvider"
101
 
102
+ }
103
 
 
104
 
105
+ def collect_models(cls):
 
 
 
 
106
 
107
+ result=[]
108
 
109
+ for attr in [
 
 
 
 
110
 
111
+ "default_model",
112
+ "models",
113
+ "model"
114
 
115
+ ]:
 
 
 
 
116
 
117
+ v=getattr(
118
+ cls,
119
+ attr,
120
+ None
121
+ )
122
 
123
+ if not v:
124
+ continue
 
 
 
125
 
126
+ if isinstance(v,str):
127
 
128
+ result.append(v)
 
 
 
 
129
 
130
+ elif isinstance(
131
+ v,
132
+ (list,tuple)
133
+ ):
134
 
135
+ result.extend(
136
+ [str(x) for x in v]
 
 
137
  )
138
 
139
+ return list(
140
+ set(result)
141
+ )
 
142
 
143
 
144
+ # ----------------------------
145
+ # health
146
+ # ----------------------------
147
 
148
  @app.get("/")
149
+
150
  async def root():
151
 
152
  return {
153
+
154
+ "status":"online",
155
+
156
+ "default":DEFAULT_MODEL
157
+
158
  }
159
 
160
 
161
+ # ----------------------------
162
+ # models
163
+ # ----------------------------
164
 
165
  @app.get("/v1/models")
166
+
167
  async def models(
168
+ authorization:str=Header(None)
169
  ):
170
 
171
+ verify(authorization)
172
+
173
+ data=[]
174
 
175
  try:
176
 
177
+ # LiteLLM models
178
 
179
+ ll_models=[
 
 
 
180
 
181
+ "groq/llama-3.3-70b-versatile",
 
 
182
 
183
+ "groq/llama-3.1-8b-instant",
184
+
185
+ "openrouter/qwen/qwen-2.5-72b-instruct",
186
+
187
+ "huggingface/Qwen/Qwen2.5-72B-Instruct",
188
 
189
+ "openrouter/deepseek/deepseek-chat",
190
 
191
+ "openai/gpt-4o",
192
 
193
+ "openai/gpt-4o-mini"
194
+
195
+ ]
196
+
197
+ for m in ll_models:
198
+
199
+ data.append({
200
+
201
+ "id":m,
202
+
203
+ "object":"model",
204
+
205
+ "owned_by":"litellm"
206
+
207
+ })
208
+
209
+ # g4f dynamic providers
210
+
211
+ for name in dir(Provider):
212
+
213
+ if name.startswith("_"):
214
+ continue
215
+
216
+ if name in SKIP:
217
+ continue
218
+
219
+ cls=getattr(
220
+ Provider,
221
+ name
222
+ )
223
 
224
+ if not inspect.isclass(cls):
225
+ continue
226
 
227
+ models=collect_models(
228
+ cls
229
+ )
230
 
231
+ for m in models:
232
 
233
+ data.append({
234
 
 
235
  "id":m,
236
+
237
  "object":"model",
 
 
238
 
239
+ "owned_by":name
240
 
241
+ })
 
242
 
243
  except Exception as e:
244
 
245
+ logger.error(e)
 
 
246
 
247
+ return {
248
 
249
+ "object":"list",
250
 
251
+ "data":data
 
 
 
252
 
253
+ }
 
254
 
255
 
256
+ # ----------------------------
257
+ # Chat
258
+ # ----------------------------
259
 
260
  @app.post("/v1/chat/completions")
261
+
262
  async def chat(
263
+
264
+ body:ChatRequest,
265
+
266
+ authorization:str=Header(None)
267
+
268
  ):
269
 
270
+ verify(
271
  authorization
272
  )
273
 
274
+ messages=[
275
 
276
+ {
277
 
278
+ "role":m.role,
 
 
 
279
 
280
+ "content":m.content
 
281
 
282
+ }
283
 
284
+ for m in body.messages
285
+ ]
286
 
 
287
 
288
+ # =====================
289
+ # Streaming
290
+ # =====================
 
 
291
 
292
+ if body.stream:
293
 
294
+ async def generate():
 
 
295
 
296
+ try:
297
 
298
+ # LiteLLM first
299
+
300
+ response=litellm.completion(
301
 
302
+ model=body.model,
303
 
304
+ messages=messages,
305
 
306
+ stream=True
307
 
308
+ )
309
 
310
+ for chunk in response:
311
 
312
+ content=""
313
 
314
+ try:
 
 
 
315
 
316
+ content=chunk.choices[0].delta.content
317
+ except:
318
+ pass
319
 
320
+ if content:
321
 
322
  payload={
323
 
324
  "id":"chatcmpl",
325
 
326
+ "object":"chat.completion.chunk",
 
327
 
328
+ "created":int(time.time()),
 
329
 
330
+ "model":body.model,
 
331
 
332
+ "choices":[{
 
 
 
 
 
 
 
 
 
333
 
334
+ "delta":{
 
 
335
 
336
+ "content":content
337
 
338
+ },
339
 
340
+ "index":0
341
 
342
+ }]
343
+ }
 
344
 
345
+ yield f"data:{json.dumps(payload)}\n\n"
346
 
347
+ except:
 
 
 
348
 
349
+ logger.info(
350
+ "Fallback g4f"
351
+ )
352
 
353
+ response=g4f.ChatCompletion.create(
354
 
355
+ model=body.model,
356
 
357
+ messages=messages,
358
 
359
+ stream=True
360
+ )
 
361
 
362
+ for chunk in response:
363
 
364
+ payload={
365
 
366
+ "choices":[{
367
 
368
+ "delta":{
369
 
370
+ "content":str(chunk)
371
 
372
+ }
373
+ }]
374
+ }
375
 
376
+ yield f"data:{json.dumps(payload)}\n\n"
 
377
 
378
+ yield "data:[DONE]\n\n"
379
 
380
+ return StreamingResponse(
381
 
382
+ generate(),
383
 
384
+ media_type="text/event-stream"
385
+ )
386
 
 
 
387
 
 
 
388
 
389
+ # =====================
390
+ # Normal
391
+ # =====================
392
 
393
+ try:
 
394
 
395
+ response=await asyncio.to_thread(
396
 
397
+ litellm.completion,
 
398
 
399
+ model=body.model,
400
 
401
+ messages=messages
402
 
 
 
 
403
  )
404
 
405
+ content=response.choices[0].message.content
406
 
 
407
 
408
+ except Exception:
 
 
 
 
 
409
 
410
+ logger.info(
411
+ "Using g4f fallback"
412
  )
413
 
414
+ content=await asyncio.to_thread(
415
 
416
  g4f.ChatCompletion.create,
417
 
418
+ model=body.model,
419
 
420
+ messages=messages
421
+ )
422
 
 
423
 
 
 
 
 
424
 
425
+ return {
 
426
 
427
+ "id":"chatcmpl",
428
 
429
+ "object":"chat.completion",
 
 
430
 
431
+ "created":int(time.time()),
432
 
433
+ "model":body.model,
434
+
435
+ "choices":[
436
+
437
+ {
438
+
439
+ "index":0,
440
 
441
+ "message":{
442
 
443
+ "role":"assistant",
444
+
445
+ "content":str(content)
446
+
447
+ },
448
+
449
+ "finish_reason":"stop"
450
+
451
+ }
452
+
453
+ ]
454
 
455
+ }