Update main.py
Browse files
main.py
CHANGED
|
@@ -1,102 +1,224 @@
|
|
| 1 |
-
import os
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
# ---------------- Logging ----------------
|
| 13 |
-
logging.basicConfig(level=logging.INFO)
|
| 14 |
-
logger = logging.getLogger(__name__)
|
| 15 |
-
|
| 16 |
-
app = FastAPI(title="G4F Dynamic API")
|
| 17 |
-
|
| 18 |
-
app.add_middleware(
|
| 19 |
-
CORSMiddleware,
|
| 20 |
-
allow_origins=["*"],
|
| 21 |
-
allow_credentials=False,
|
| 22 |
-
allow_methods=["*"],
|
| 23 |
-
allow_headers=["*"]
|
| 24 |
-
)
|
| 25 |
|
| 26 |
API_KEY = os.getenv("API_KEY", "your_fallback_secret")
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
|
| 32 |
-
class ChatRequest(BaseModel):
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
stream: bool = False
|
| 36 |
-
provider: Optional[str] = None
|
| 37 |
|
| 38 |
def verify_key(auth: str):
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
return {
|
| 51 |
-
"object":
|
| 52 |
-
"data":
|
|
|
|
|
|
|
|
|
|
| 53 |
}
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
model=body.model,
|
| 67 |
messages=messages,
|
| 68 |
provider=provider,
|
| 69 |
stream=True
|
| 70 |
)
|
|
|
|
| 71 |
for chunk in response:
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
yield "data: [DONE]\n\n"
|
| 82 |
|
| 83 |
-
|
|
|
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
messages=messages,
|
| 89 |
-
provider=provider
|
| 90 |
)
|
| 91 |
-
|
| 92 |
-
return {
|
| 93 |
-
"id": "chatcmpl-g4f",
|
| 94 |
-
"object": "chat.completion",
|
| 95 |
-
"created": int(time.time()),
|
| 96 |
-
"model": body.model,
|
| 97 |
-
"choices": [{"index": 0, "message": {"role": "assistant", "content": response}, "finish_reason": "stop"}]
|
| 98 |
-
}
|
| 99 |
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os import json import time import logging import g4f from fastapi import FastAPI, HTTPException, Header from fastapi.responses import StreamingResponse from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import List, Optional
|
| 2 |
+
|
| 3 |
+
---------------- Logging ----------------
|
| 4 |
+
|
| 5 |
+
logging.basicConfig(level=logging.INFO) logger = logging.getLogger(name)
|
| 6 |
+
|
| 7 |
+
---------------- App ----------------
|
| 8 |
+
|
| 9 |
+
app = FastAPI( title="G4F OpenAI Compatible API", version="1.0" )
|
| 10 |
+
|
| 11 |
+
app.add_middleware( CORSMiddleware, allow_origins=[""], allow_credentials=False, allow_methods=[""], allow_headers=["*"] )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
API_KEY = os.getenv("API_KEY", "your_fallback_secret")
|
| 14 |
|
| 15 |
+
---------------- Models ----------------
|
| 16 |
+
|
| 17 |
+
class ChatMessage(BaseModel): role: str content: str
|
| 18 |
|
| 19 |
+
class ChatRequest(BaseModel): model: str = "gpt-4o-mini" messages: List[ChatMessage] stream: bool = False provider: Optional[str] = None max_tokens: Optional[int] = 2048 temperature: Optional[float] = 0.7
|
| 20 |
+
|
| 21 |
+
---------------- Auth ----------------
|
|
|
|
|
|
|
| 22 |
|
| 23 |
def verify_key(auth: str):
|
| 24 |
+
|
| 25 |
+
if not auth:
|
| 26 |
+
raise HTTPException(
|
| 27 |
+
status_code=401,
|
| 28 |
+
detail="Missing Authorization"
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
parts = auth.split()
|
| 32 |
+
|
| 33 |
+
if len(parts) != 2 or parts[0] != "Bearer":
|
| 34 |
+
raise HTTPException(
|
| 35 |
+
status_code=401,
|
| 36 |
+
detail="Malformed Authorization"
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
if parts[1] != API_KEY:
|
| 40 |
+
raise HTTPException(
|
| 41 |
+
status_code=401,
|
| 42 |
+
detail="Invalid API Key"
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
---------------- Health ----------------
|
| 46 |
+
|
| 47 |
+
@app.get("/") async def health(): return { "status": "online" }
|
| 48 |
+
|
| 49 |
+
---------------- Dynamic Models ----------------
|
| 50 |
+
|
| 51 |
+
@app.get("/v1/models") async def models( authorization: str = Header(None) ):
|
| 52 |
+
|
| 53 |
+
verify_key(authorization)
|
| 54 |
+
|
| 55 |
+
try:
|
| 56 |
+
|
| 57 |
+
model_data=[]
|
| 58 |
+
|
| 59 |
+
# الطريقة الأكثر استقرارًا مع g4f
|
| 60 |
+
if hasattr(g4f,"ModelUtils"):
|
| 61 |
+
|
| 62 |
+
for model_name in g4f.ModelUtils.convert.keys():
|
| 63 |
+
|
| 64 |
+
model_data.append({
|
| 65 |
+
"id":str(model_name),
|
| 66 |
+
"object":"model"
|
| 67 |
+
})
|
| 68 |
+
|
| 69 |
+
# fallback
|
| 70 |
+
if not model_data:
|
| 71 |
+
|
| 72 |
+
for name in dir(g4f.models):
|
| 73 |
+
|
| 74 |
+
if name.startswith("_"):
|
| 75 |
+
continue
|
| 76 |
+
|
| 77 |
+
value=getattr(
|
| 78 |
+
g4f.models,
|
| 79 |
+
name,
|
| 80 |
+
None
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
if isinstance(value,str):
|
| 84 |
+
|
| 85 |
+
model_data.append({
|
| 86 |
+
"id":value,
|
| 87 |
+
"object":"model"
|
| 88 |
+
})
|
| 89 |
+
|
| 90 |
+
unique={}
|
| 91 |
+
|
| 92 |
+
for m in model_data:
|
| 93 |
+
unique[m["id"]]=m
|
| 94 |
+
|
| 95 |
return {
|
| 96 |
+
"object":"list",
|
| 97 |
+
"data":sorted(
|
| 98 |
+
unique.values(),
|
| 99 |
+
key=lambda x:x["id"]
|
| 100 |
+
)
|
| 101 |
}
|
| 102 |
|
| 103 |
+
except Exception as e:
|
| 104 |
+
|
| 105 |
+
logger.exception(e)
|
| 106 |
+
|
| 107 |
+
return {
|
| 108 |
+
"object":"list",
|
| 109 |
+
"data":[
|
| 110 |
+
{
|
| 111 |
+
"id":"gpt-4o-mini",
|
| 112 |
+
"object":"model"
|
| 113 |
+
}
|
| 114 |
+
]
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
---------------- Chat ----------------
|
| 118 |
+
|
| 119 |
+
@app.post("/v1/chat/completions") async def chat( body: ChatRequest, authorization: str = Header(None) ):
|
| 120 |
+
|
| 121 |
+
verify_key(authorization)
|
| 122 |
+
|
| 123 |
+
try:
|
| 124 |
+
|
| 125 |
+
provider=None
|
| 126 |
+
|
| 127 |
+
if body.provider:
|
| 128 |
+
|
| 129 |
+
provider=getattr(
|
| 130 |
+
g4f.Provider,
|
| 131 |
+
body.provider,
|
| 132 |
+
None
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
if provider is None:
|
| 136 |
+
raise HTTPException(
|
| 137 |
+
status_code=400,
|
| 138 |
+
detail="Invalid provider"
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
messages=[
|
| 142 |
+
{
|
| 143 |
+
"role":m.role,
|
| 144 |
+
"content":m.content
|
| 145 |
+
}
|
| 146 |
+
for m in body.messages
|
| 147 |
+
]
|
| 148 |
+
|
| 149 |
+
# -------- Streaming --------
|
| 150 |
+
if body.stream:
|
| 151 |
+
|
| 152 |
+
def generate():
|
| 153 |
+
|
| 154 |
+
try:
|
| 155 |
+
|
| 156 |
+
response=g4f.ChatCompletion.create(
|
| 157 |
model=body.model,
|
| 158 |
messages=messages,
|
| 159 |
provider=provider,
|
| 160 |
stream=True
|
| 161 |
)
|
| 162 |
+
|
| 163 |
for chunk in response:
|
| 164 |
+
|
| 165 |
+
payload={
|
| 166 |
+
"id":f"chatcmpl-{int(time.time())}",
|
| 167 |
+
"object":"chat.completion.chunk",
|
| 168 |
+
"created":int(time.time()),
|
| 169 |
+
"model":body.model,
|
| 170 |
+
"choices":[
|
| 171 |
+
{
|
| 172 |
+
"delta":{
|
| 173 |
+
"content":str(chunk)
|
| 174 |
+
},
|
| 175 |
+
"index":0,
|
| 176 |
+
"finish_reason":None
|
| 177 |
+
}
|
| 178 |
+
]
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
yield f"data: {json.dumps(payload)}\n\n"
|
| 182 |
+
|
| 183 |
yield "data: [DONE]\n\n"
|
| 184 |
|
| 185 |
+
except Exception as e:
|
| 186 |
+
logger.exception(e)
|
| 187 |
|
| 188 |
+
return StreamingResponse(
|
| 189 |
+
generate(),
|
| 190 |
+
media_type="text/event-stream"
|
|
|
|
|
|
|
| 191 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
|
| 193 |
+
# -------- Non-stream --------
|
| 194 |
+
response=await g4f.ChatCompletion.create_async(
|
| 195 |
+
model=body.model,
|
| 196 |
+
messages=messages,
|
| 197 |
+
provider=provider
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
return {
|
| 201 |
+
"id":f"chatcmpl-{int(time.time())}",
|
| 202 |
+
"object":"chat.completion",
|
| 203 |
+
"created":int(time.time()),
|
| 204 |
+
"model":body.model,
|
| 205 |
+
"choices":[
|
| 206 |
+
{
|
| 207 |
+
"index":0,
|
| 208 |
+
"message":{
|
| 209 |
+
"role":"assistant",
|
| 210 |
+
"content":str(response)
|
| 211 |
+
},
|
| 212 |
+
"finish_reason":"stop"
|
| 213 |
+
}
|
| 214 |
+
]
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
except Exception as e:
|
| 218 |
+
|
| 219 |
+
logger.exception(e)
|
| 220 |
+
|
| 221 |
+
raise HTTPException(
|
| 222 |
+
status_code=503,
|
| 223 |
+
detail=f"Provider error: {str(e)}"
|
| 224 |
+
)
|