sid / main.py
Niansuh's picture
Update main.py
c7a9f55 verified
import json
import logging
import os
import uuid
from datetime import datetime
from typing import Any, Dict, List, Optional
import httpx
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel
from starlette.middleware.cors import CORSMiddleware
from starlette.responses import StreamingResponse, Response
# Configure Logging
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# Load Environment Variables
load_dotenv()
APP_SECRET = os.getenv("APP_SECRET", "786")
ACCESS_TOKEN = os.getenv("SD_ACCESS_TOKEN", "")
# Initialize FastAPI
app = FastAPI()
# Define Allowed Models
ALLOWED_MODELS = [
{"id": "claude-3.5-sonnet", "name": "Claude 3.5 Sonnet"},
{"id": "claude-3-opus", "name": "Claude 3 Opus"},
{"id": "gemini-1.5-pro", "name": "Gemini 1.5 Pro"},
{"id": "gpt-4o", "name": "GPT-4o"},
{"id": "o1-preview", "name": "O1 Preview"},
{"id": "o1-mini", "name": "O1 Mini"},
{"id": "gpt-4o-mini", "name": "GPT-4o Mini"},
]
# Configure CORS Middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Replace with your trusted domains
allow_credentials=True,
allow_methods=["POST", "OPTIONS"],
allow_headers=["Content-Type", "Authorization"],
)
# Security Scheme
security = HTTPBearer()
# Pydantic Models
class Message(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
model: str
messages: List[Message]
stream: Optional[bool] = False
# Helper Functions
def create_chat_completion_data(content: str, model: str, finish_reason: Optional[str] = None) -> Dict[str, Any]:
return {
"id": f"chatcmpl-{uuid.uuid4()}",
"object": "chat.completion.chunk",
"created": int(datetime.now().timestamp()),
"model": model,
"choices": [
{
"index": 0,
"delta": {"content": content, "role": "assistant"},
"finish_reason": finish_reason,
}
],
"usage": None,
}
def verify_app_secret(credentials: HTTPAuthorizationCredentials = Depends(security)):
if credentials.credentials != APP_SECRET:
raise HTTPException(status_code=403, detail="Invalid APP_SECRET")
return credentials.credentials
# Routes
@app.options("/hf/v1/chat/completions")
async def chat_completions_options():
return Response(
status_code=200,
headers={
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
)
@app.get("/hf/v1/models")
async def list_models():
return {"object": "list", "data": ALLOWED_MODELS}
@app.post("/hf/v1/chat/completions")
async def chat_completions(
request: ChatRequest, app_secret: str = Depends(verify_app_secret)
):
logger.info(f"Received chat completion request for model: {request.model}")
if request.model not in [model['id'] for model in ALLOWED_MODELS]:
allowed = ', '.join(model['id'] for model in ALLOWED_MODELS)
raise HTTPException(
status_code=400,
detail=f"Model '{request.model}' is not allowed. Allowed models are: {allowed}",
)
# Generate a UUID
uuid_str = str(uuid.uuid4()).replace("-", "")
# Prepare Headers (Move sensitive data to environment variables or secure storage)
headers = {
'accept': '*/*',
'accept-language': 'en-US,en;q=0.9',
'authorization': f'Bearer {ACCESS_TOKEN}',
'cache-control': 'no-cache',
'origin': 'chrome-extension://difoiogjjojoaoomphldepapgpbgkhkb',
'pragma': 'no-cache',
'priority': 'u=1, i',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'none',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
}
# Prepare JSON Payload
json_data = {
'prompt': "\n".join(
[
f"{'User' if msg.role == 'user' else 'Assistant'}: {msg.content}"
for msg in request.messages
]
),
'stream': True,
'app_name': 'ChitChat_Edge_Ext',
'app_version': '4.28.0',
'tz_name': 'Asia/Karachi',
'cid': 'C092SEMXM9BJ',
'model': request.model,
'search': False,
'auto_search': False,
'from': 'chat',
'group_id': 'default',
'prompt_template': {
'key': 'artifacts',
'attributes': {
'lang': 'original',
},
},
'tools': {
'auto': ['text_to_image', 'data_analysis'],
},
'extra_info': {
'origin_url': 'chrome-extension://difoiogjjojoaoomphldepapgpbgkhkb/standalone.html?from=sidebar',
'origin_title': 'Sider',
},
}
async def generate():
async with httpx.AsyncClient() as client:
try:
async with client.stream(
'POST',
'https://sider.ai/api/v3/completion/text',
headers=headers,
json=json_data,
timeout=120.0
) as response:
response.raise_for_status()
async for line in response.aiter_lines():
if line and ("[DONE]" not in line):
try:
# Adjust the slicing based on the actual response format
data = json.loads(line[5:]) if len(line) > 5 else None
if data and "data" in data and "text" in data["data"]:
content = data["data"].get("text", "")
yield f"data: {json.dumps(create_chat_completion_data(content, request.model))}\n\n"
else:
logger.warning(f"Unexpected data format: {line}")
except json.JSONDecodeError as e:
logger.error(f"JSON decode error for line: {line} - {e}")
except Exception as e:
logger.error(f"Error processing line: {line} - {e}")
# Indicate the end of the stream
yield f"data: {json.dumps(create_chat_completion_data('', request.model, 'stop'))}\n\n"
yield "data: [DONE]\n\n"
except httpx.HTTPStatusError as e:
logger.error(f"HTTP error occurred: {e}")
raise HTTPException(status_code=e.response.status_code, detail=str(e))
except httpx.RequestError as e:
logger.error(f"An error occurred while requesting: {e}")
raise HTTPException(status_code=500, detail=str(e))
if request.stream:
logger.info("Streaming response")
return StreamingResponse(generate(), media_type="text/event-stream")
else:
logger.info("Non-streaming response")
full_response = ""
async for chunk in generate():
if chunk.startswith("data: ") and not chunk[6:].startswith("[DONE]"):
try:
data = json.loads(chunk[6:])
if data["choices"][0]["delta"].get("content"):
full_response += data["choices"][0]["delta"]["content"]
except json.JSONDecodeError as e:
logger.error(f"JSON decode error in non-streaming response: {chunk} - {e}")
except Exception as e:
logger.error(f"Error processing chunk in non-streaming response: {chunk} - {e}")
return {
"id": f"chatcmpl-{uuid.uuid4()}",
"object": "chat.completion",
"created": int(datetime.now().timestamp()),
"model": request.model,
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": full_response},
"finish_reason": "stop",
}
],
"usage": None,
}