g4ff / server.py
ayb-bh1146's picture
Update server.py
9ecbdf5 verified
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
G4F Chat API Server โ€” FastAPI + g4f + Streaming SSE
"""
import g4f
import asyncio
import json
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from pathlib import Path
import uvicorn
import logging
# ุฅุนุฏุงุฏ ุงู„ุณุฌู„ุงุช
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="G4F Chat API", version="2.1")
# โœ… ุชูุนูŠู„ CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โœ… ุฏุงู„ุฉ ุฌู„ุจ ุงู„ู…ุฒูˆุฏูŠู† ูˆุงู„ู†ู…ุงุฐุฌ (ุขู…ู†ุฉ)
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def get_providers_and_models():
"""ุฌู„ุจ ู‚ุงุฆู…ุฉ ุงู„ู…ุฒูˆุฏูŠู† ุงู„ุนุงู…ู„ูŠู† ู…ุน ู†ู…ุงุฐุฌู‡ู…"""
providers_data = {}
try:
# ู…ุญุงูˆู„ุฉ ุฌู„ุจ ุงู„ู…ุฒูˆุฏูŠู† ู…ู† g4f
provider_list = getattr(g4f.Provider, '__providers__', [])
for provider in provider_list:
try:
name = getattr(provider, '__name__', None)
if not name or not getattr(provider, 'working', False):
continue
# ุฌู„ุจ ุงู„ู†ู…ุงุฐุฌ ุงู„ู…ุฏุนูˆู…ุฉ
models = []
if hasattr(provider, 'models') and provider.models:
models = list(provider.models) if isinstance(provider.models, (list, tuple)) else [provider.models]
elif hasattr(provider, 'model') and provider.model:
models = [provider.model] if isinstance(provider.model, str) else list(provider.model)
else:
models = ['default']
providers_data[name] = {
'models': models,
'supports_stream': getattr(provider, 'supports_stream', True),
'needs_auth': getattr(provider, 'needs_auth', False),
'url': getattr(provider, 'url', ''),
}
except Exception as e:
logger.warning(f"ุชุฎุทูŠ ุงู„ู…ุฒูˆู‘ุฏ: {e}")
continue
except Exception as e:
logger.error(f"ุฎุทุฃ ููŠ ุฌู„ุจ ุงู„ู…ุฒูˆุฏูŠู†: {e}")
# ู‚ุงุฆู…ุฉ ุงุญุชูŠุงุทูŠุฉ ููŠ ุญุงู„ ุงู„ูุดู„
return {
'g4f': {
'models': ['gpt-4o', 'gpt-4o-mini', 'claude-3-5-sonnet', 'llama-3.1-70b'],
'supports_stream': True,
'needs_auth': False,
'url': '',
}
}
return providers_data
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โœ… Endpoint: ู‚ุงุฆู…ุฉ ุงู„ู…ุฒูˆุฏูŠู† ูˆุงู„ู†ู…ุงุฐุฌ
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@app.get("/api/providers")
async def list_providers():
"""ุฅุฑุฌุงุน ุงู„ู…ุฒูˆุฏูŠู† ูˆุงู„ู†ู…ุงุฐุฌ ุงู„ู…ุชุงุญุฉ ุจุตูŠุบุฉ JSON"""
try:
data = get_providers_and_models()
return JSONResponse(content=data)
except Exception as e:
logger.error(f"Error in /api/providers: {e}")
raise HTTPException(status_code=500, detail=str(e))
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โœ… Endpoint: ุงู„ุฏุฑุฏุดุฉ ุงู„ุนุงุฏูŠุฉ (Non-streaming)
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@app.post("/api/chat")
async def chat(request: Request):
"""ู…ุนุงู„ุฌุฉ ุทู„ุจ ุงู„ุฏุฑุฏุดุฉ ุงู„ุนุงุฏูŠ"""
try:
body = await request.json()
messages = body.get("messages", [])
model = body.get("model", "gpt-4o-mini")
provider_name = body.get("provider", None)
web_search = body.get("web_search", False)
if not messages:
raise HTTPException(status_code=400, detail="messages list is empty")
# ุชุญุถูŠุฑ ู…ุนุงู…ู„ุงุช ุงู„ุงุณุชุฏุนุงุก
kwargs = {
"model": model,
"messages": messages,
"stream": False,
}
# ุฅุถุงูุฉ ุงู„ู…ุฒูˆู‘ุฏ ุฅุฐุง ุญูุฏู‘ุฏ
if provider_name and provider_name != "Auto":
try:
provider_cls = getattr(g4f.Provider, provider_name, None)
if provider_cls:
kwargs["provider"] = provider_cls
except AttributeError:
pass
# ุฅุถุงูุฉ ุฃุฏุงุฉ ุงู„ุจุญุซ ุฅุฐุง ููุนู‘ู„ุช
if web_search and hasattr(g4f, 'tools') and hasattr(g4f.tools, 'search'):
kwargs["tool_calls"] = [g4f.tools.search]
# ุชู†ููŠุฐ ุงู„ุทู„ุจ ููŠ thread ู…ู†ูุตู„ ู„ุชุฌู†ุจ ุญุฌุจ ุงู„ู€ event loop
loop = asyncio.get_running_loop()
response = await loop.run_in_executor(
None,
lambda: g4f.ChatCompletion.create(**kwargs)
)
# ู…ุนุงู„ุฌุฉ ุงู„ุฑุฏ (ู‚ุฏ ูŠูƒูˆู† ู†ุตุงู‹ ุฃูˆ ู…ูˆู„ู‘ุฏุงู‹)
if hasattr(response, '__iter__') and not isinstance(response, (str, bytes)):
response = "".join(str(chunk) for chunk in response if chunk)
return JSONResponse({
"response": str(response),
"model": model,
"provider": provider_name or "Auto"
})
except HTTPException:
raise
except Exception as e:
logger.error(f"Chat error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"Server error: {str(e)[:200]}")
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โœ… Endpoint: ุงู„ุฏุฑุฏุดุฉ ุงู„ู…ุชุฏูู‚ุฉ (Streaming SSE)
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@app.post("/api/chat/stream")
async def chat_stream(request: Request):
"""ู…ุนุงู„ุฌุฉ ุทู„ุจ ุงู„ุฏุฑุฏุดุฉ ู…ุน ุงู„ุจุซ ุงู„ู…ุจุงุดุฑ (Server-Sent Events)"""
try:
body = await request.json()
messages = body.get("messages", [])
model = body.get("model", "gpt-4o-mini")
provider_name = body.get("provider", None)
web_search = body.get("web_search", False)
if not messages:
raise HTTPException(status_code=400, detail="messages list is empty")
# ุชุญุถูŠุฑ ู…ุนุงู…ู„ุงุช ุงู„ุงุณุชุฏุนุงุก ู„ู„ุจุซ
kwargs = {
"model": model,
"messages": messages,
"stream": True,
}
if provider_name and provider_name != "Auto":
try:
provider_cls = getattr(g4f.Provider, provider_name, None)
if provider_cls:
kwargs["provider"] = provider_cls
except AttributeError:
pass
if web_search and hasattr(g4f, 'tools') and hasattr(g4f.tools, 'search'):
kwargs["tool_calls"] = [g4f.tools.search]
async def event_generator():
"""ู…ูˆู„ู‘ุฏ ุฃุญุฏุงุซ SSE"""
try:
loop = asyncio.get_running_loop()
# ุชู†ููŠุฐ g4f ููŠ thread ู…ู†ูุตู„
response = await loop.run_in_executor(
None,
lambda: g4f.ChatCompletion.create(**kwargs)
)
# ู…ุนุงู„ุฌุฉ ุงู„ุฑุฏ ุญุณุจ ู†ูˆุนู‡
if isinstance(response, str):
# ุฑุฏ ู†ุตูŠ ู…ุจุงุดุฑ
yield f" {json.dumps({'chunk': response, 'type': 'text'})}\n\n"
elif hasattr(response, '__iter__'):
# ุฑุฏ ู…ุชุฏูู‚ (generator)
for chunk in response:
if chunk:
chunk_str = str(chunk).strip()
if chunk_str:
yield f" {json.dumps({'chunk': chunk_str, 'type': 'chunk'})}\n\n"
await asyncio.sleep(0) # Yield control to event loop
else:
# ู…ุนุงู„ุฌุฉ ุบูŠุฑ ู…ุชูˆู‚ุนุฉ
yield f" {json.dumps({'chunk': str(response), 'type': 'text'})}\n\n"
except Exception as e:
logger.error(f"Stream error: {e}", exc_info=True)
yield f" {json.dumps({'error': str(e)[:300], 'type': 'error'})}\n\n"
finally:
# ุฅุดุงุฑุฉ ู†ู‡ุงูŠุฉ ุงู„ุจุซ
yield " [DONE]\n\n"
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache, no-transform",
"X-Accel-Buffering": "no",
"Connection": "keep-alive",
},
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Stream endpoint error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"Streaming error: {str(e)[:200]}")
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โœ… ุชู‚ุฏูŠู… ุงู„ูˆุงุฌู‡ุฉ ุงู„ุฃู…ุงู…ูŠุฉ
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@app.get("/", response_class=HTMLResponse)
async def root():
"""ุชู‚ุฏูŠู… ู…ู„ู index.html"""
html_path = Path(__file__).parent / "index.html"
if html_path.exists():
content = html_path.read_text(encoding="utf-8")
return HTMLResponse(content=content)
return HTMLResponse(
content="<h1 style='color:#fff;background:#111;padding:20px'>โŒ index.html not found</h1>",
status_code=404
)
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โœ… ู†ู‚ุทุฉ ุงู„ุฏุฎูˆู„
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
if __name__ == "__main__":
import sys
port = int(sys.argv[1]) if len(sys.argv) > 1 else 7860
host = "0.0.0.0"
print(f"\n๐Ÿš€ G4F Chat Server starting...")
print(f"๐Ÿ“ URL: http://{host if host != '0.0.0.0' else 'localhost'}:{port}")
print(f"๐Ÿ“ฆ g4f version: {getattr(g4f, '__version__', 'unknown')}")
print(f"โšก Press Ctrl+C to stop\n")
uvicorn.run(
app,
host=host,
port=port,
log_level="info",
access_log=True,
)