Spaces:
Sleeping
Sleeping
File size: 4,576 Bytes
f0103c9 8566f99 f0103c9 8566f99 f0103c9 8566f99 f0103c9 8566f99 f0103c9 8566f99 f0103c9 8566f99 f0103c9 8566f99 f0103c9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | import os
import time
import httpx
import json
import re
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import StreamingResponse, HTMLResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI(title="KAI STUDIO ROUTER")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["*"]
)
BACKENDS = {
"qwen": {
"url": os.environ.get("KAI_QWEN_URL", "https://your-qwen-space.hf.space"),
"key": os.environ.get("KEY_QWEN"),
"models": ["Qwen2.5-7B-Instruct"]
},
"mistral": {
"url": os.environ.get("KAI_MISTRAL_URL", "https://your-mistral-space.hf.space"),
"key": os.environ.get("KEY_MISTRAL"),
"models": ["Mistral-7B-v0.3"]
}
}
class Message(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
messages: List[Message]
max_tokens: Optional[int] = 400
temperature: Optional[float] = 0.7
stream: bool = True
model: Optional[str] = None
CODE_KEYWORDS = re.compile(r'\b(def|class|import|function|const|let|var|public|private|return|python|javascript|java|c\+\+|sql|html|css|json|api|docker|git|kod|funkcja|program|skrypt|algorytm)\b', re.I)
MATH_KEYWORDS = re.compile(r'\b(oblicz|policz|r贸wnanie|ca艂ka|pochodna|macierz|prawdopodobie艅stwo|logarytm|pierwiastek|ile to|=\?|\d+\s*[\+\-\*\/]\s*\d+)\b', re.I)
CREATIVE_KEYWORDS = re.compile(r'\b(napisz|wiersz|opowiadanie|historia|bajka|rap|tekst|piosenka|kreatywny|stw贸rz|wymy艣l|zr贸b post|dialog)\b', re.I)
def route_model(messages: List[Message], forced_model: Optional[str] = None) -> str:
if forced_model and forced_model in BACKENDS:
return forced_model
user_text = " ".join([m.content for m in messages if m.role == "user"]).lower()
if CODE_KEYWORDS.search(user_text) or MATH_KEYWORDS.search(user_text):
return "qwen"
if CREATIVE_KEYWORDS.search(user_text):
return "mistral"
pl_chars = len(re.findall(r'[膮膰臋艂艅贸艣藕偶]', user_text))
if pl_chars > 2:
return "mistral"
return "qwen"
# --- 2 oddzielne funkcje: stream i non-stream ---
async def proxy_stream(backend_name: str, request_data: dict):
backend = BACKENDS[backend_name]
url = f"{backend['url']}/v1/chat/completions"
headers = {
"Authorization": f"Bearer {backend['key']}",
"Content-Type": "application/json"
}
async with httpx.AsyncClient(timeout=180.0) as client:
async with client.stream("POST", url, json=request_data, headers=headers) as r:
if r.status_code!= 200:
err = await r.aread()
raise HTTPException(r.status_code, f"Backend {backend_name} error: {err.decode()}")
yield f": x-kai-model-used {backend_name}\n\n"
yield f": connected\n\n"
async for chunk in r.aiter_bytes():
yield chunk
async def proxy_non_stream(backend_name: str, request_data: dict):
backend = BACKENDS[backend_name]
url = f"{backend['url']}/v1/chat/completions"
headers = {
"Authorization": f"Bearer {backend['key']}",
"Content-Type": "application/json"
}
async with httpx.AsyncClient(timeout=180.0) as client:
r = await client.post(url, json=request_data, headers=headers)
if r.status_code!= 200:
raise HTTPException(r.status_code, f"Backend {backend_name} error: {r.text}")
data = r.json()
data["kai_model_used"] = backend_name
return data
@app.get("/", response_class=HTMLResponse)
async def serve_frontend():
with open("static/index.html", "r", encoding="utf-8") as f:
return HTMLResponse(content=f.read())
@app.post("/v1/chat/completions")
async def chat_completions(data: ChatRequest, request: Request):
chosen_backend = route_model(data.messages, data.model)
request_data = data.dict()
request_data.pop("model", None)
if data.stream:
generator = proxy_stream(chosen_backend, request_data)
return StreamingResponse(generator, media_type="text/event-stream")
else:
result = await proxy_non_stream(chosen_backend, request_data)
return JSONResponse(content=result)
@app.get("/health")
async def health():
return {"status": "ok", "backends": list(BACKENDS.keys())}
app.mount("/static", StaticFiles(directory="static"), name="static") |