StrawberryJelly commited on
Commit
f0103c9
verified
1 Parent(s): 26175ec

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +130 -0
app.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import httpx
4
+ import json
5
+ import re
6
+ from fastapi import FastAPI, Request, HTTPException
7
+ from fastapi.responses import StreamingResponse, HTMLResponse, JSONResponse
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from fastapi.staticfiles import StaticFiles
10
+ from pydantic import BaseModel
11
+ from typing import List, Optional
12
+
13
+ app = FastAPI(title="KAI STUDIO ROUTER")
14
+ app.add_middleware(
15
+ CORSMiddleware,
16
+ allow_origins=["*"],
17
+ allow_credentials=True,
18
+ allow_methods=["*"],
19
+ allow_headers=["*"],
20
+ expose_headers=["*"]
21
+ )
22
+
23
+ # --- Konfiguracja backend贸w ---
24
+ BACKENDS = {
25
+ "qwen": {
26
+ "url": os.environ.get("KAI_QWEN_URL", "https://your-qwen-space.hf.space"),
27
+ "key": os.environ.get("KEY_QWEN"),
28
+ "models": ["Qwen2.5-7B-Instruct"]
29
+ },
30
+ "mistral": {
31
+ "url": os.environ.get("KAI_MISTRAL_URL", "https://your-mistral-space.hf.space"),
32
+ "key": os.environ.get("KEY_MISTRAL"),
33
+ "models": ["Mistral-7B-v0.3"]
34
+ }
35
+ }
36
+
37
+ class Message(BaseModel):
38
+ role: str
39
+ content: str
40
+
41
+ class ChatRequest(BaseModel):
42
+ messages: List[Message]
43
+ max_tokens: Optional[int] = 400
44
+ temperature: Optional[float] = 0.7
45
+ stream: bool = True
46
+ model: Optional[str] = None
47
+
48
+ # --- Heurystyka wyboru modelu ---
49
+ 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)
50
+ 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)
51
+ 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)
52
+
53
+ def route_model(messages: List[Message], forced_model: Optional[str] = None) -> str:
54
+ if forced_model and forced_model in BACKENDS:
55
+ return forced_model
56
+
57
+ user_text = " ".join([m.content for m in messages if m.role == "user"]).lower()
58
+
59
+ # Priorytet 1: Kod lub matma -> Qwen
60
+ if CODE_KEYWORDS.search(user_text) or MATH_KEYWORDS.search(user_text):
61
+ return "qwen"
62
+
63
+ # Priorytet 2: Kreatywne PL -> Mistral
64
+ if CREATIVE_KEYWORDS.search(user_text):
65
+ return "mistral"
66
+
67
+ # Domy艣lnie: Mistral dla PL, Qwen dla reszty
68
+ # Prosta heurystyka PL
69
+ pl_chars = len(re.findall(r'[膮膰臋艂艅贸艣藕偶]', user_text))
70
+ if pl_chars > 2:
71
+ return "mistral"
72
+
73
+ return "qwen" # domy艣lnie Qwen bo szybszy
74
+
75
+ # --- Proxy do backendu ---
76
+ async def proxy_to_backend(backend_name: str, request_data: dict, stream: bool):
77
+ backend = BACKENDS[backend_name]
78
+ url = f"{backend['url']}/v1/chat/completions"
79
+ headers = {
80
+ "Authorization": f"Bearer {backend['key']}",
81
+ "Content-Type": "application/json"
82
+ }
83
+
84
+ async with httpx.AsyncClient(timeout=180.0) as client:
85
+ if stream:
86
+ async with client.stream("POST", url, json=request_data, headers=headers) as r:
87
+ if r.status_code!= 200:
88
+ err = await r.aread()
89
+ raise HTTPException(r.status_code, f"Backend {backend_name} error: {err.decode()}")
90
+
91
+ # Wysy艂amy info kt贸ry model zosta艂 u偶yty
92
+ yield f": x-kai-model-used {backend_name}\n\n"
93
+ yield f": connected\n\n"
94
+
95
+ async for chunk in r.aiter_bytes():
96
+ yield chunk
97
+ else:
98
+ r = await client.post(url, json=request_data, headers=headers)
99
+ if r.status_code!= 200:
100
+ raise HTTPException(r.status_code, f"Backend {backend_name} error: {r.text}")
101
+ data = r.json()
102
+ data["kai_model_used"] = backend_name
103
+ return data
104
+
105
+ # --- Endpointy ---
106
+ @app.get("/", response_class=HTMLResponse)
107
+ async def serve_frontend():
108
+ with open("static/index.html", "r", encoding="utf-8") as f:
109
+ return HTMLResponse(content=f.read())
110
+
111
+ @app.post("/v1/chat/completions")
112
+ async def chat_completions(data: ChatRequest, request: Request):
113
+ chosen_backend = route_model(data.messages, data.model)
114
+
115
+ request_data = data.dict()
116
+ request_data.pop("model", None) # usuwamy bo backendy nie potrzebuj膮
117
+
118
+ if data.stream:
119
+ generator = proxy_to_backend(chosen_backend, request_data, True)
120
+ return StreamingResponse(generator, media_type="text/event-stream")
121
+ else:
122
+ result = await proxy_to_backend(chosen_backend, request_data, False)
123
+ return JSONResponse(content=result)
124
+
125
+ @app.get("/health")
126
+ async def health():
127
+ return {"status": "ok", "backends": list(BACKENDS.keys())}
128
+
129
+ # Serwuj pliki statyczne
130
+ app.mount("/static", StaticFiles(directory="static"), name="static")