Spaces:
Sleeping
Sleeping
| """ | |
| SixFingerDev Arena - Multi-Model Agentic Backend | |
| Supports: GPT-5.1-Codex, Claude Opus 4.5, o3, o3-mini | |
| Hugging Face Spaces üzerinde çalışır | |
| """ | |
| from fastapi import FastAPI, HTTPException, Header, Request | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import StreamingResponse, JSONResponse | |
| from pydantic import BaseModel | |
| from typing import List, Optional, Dict, Any, AsyncGenerator | |
| import requests | |
| import os | |
| import secrets | |
| from datetime import datetime | |
| import time | |
| import json | |
| import asyncio | |
| from collections import defaultdict | |
| app = FastAPI( | |
| title="SixFingerDev Arena Backend", | |
| description="Multi-Model AI Backend with Task Routing", | |
| version="2.0.0" | |
| ) | |
| # CORS | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ============================================ | |
| # CONFIGURATION | |
| # ============================================ | |
| # Model configurations | |
| MODEL_CONFIGS = { | |
| "gpt-5.1-codex": { | |
| "provider": "pollinations", | |
| "api_name": "openai", | |
| "capabilities": ["code", "debug", "test"], | |
| "max_tokens": 200000, | |
| "temperature": 0.7 | |
| }, | |
| "claude-opus-4.5": { | |
| "provider": "pollinations", | |
| "api_name": "claude-opus-4", | |
| "capabilities": ["plan", "modify", "test"], | |
| "max_tokens": 200000, | |
| "temperature": 0.7 | |
| }, | |
| "o3": { | |
| "provider": "pollinations", | |
| "api_name": "openai", | |
| "capabilities": ["debug", "test"], | |
| "max_tokens": 4096, | |
| "temperature": 0.7 | |
| }, | |
| "o3-mini": { | |
| "provider": "pollinations", | |
| "api_name": "openai", | |
| "capabilities": ["test"], | |
| "max_tokens": 2048, | |
| "temperature": 0.7 | |
| } | |
| } | |
| # Task to Model mapping (Optimized Elon Musk config) | |
| TASK_MODELS = { | |
| "Planner": "claude-opus-4.5", # Mimari vizyon + roadmap | |
| "Coder": "gpt-5.1-codex", # State-of-the-art kod üretimi | |
| "Tester": "claude-opus-4.5", # Farklı perspektif, edge case coverage | |
| "Debugger": "gpt-5.1-codex", # Cybersecurity + vulnerability detection | |
| "Modifier": "claude-opus-4.5" # Safe refactoring | |
| } | |
| # API Keys | |
| API_KEYS_RAW = os.getenv('AI_API_KEYS', '') | |
| BACKEND_API_KEY = os.getenv('BACKEND_API_KEY', secrets.token_urlsafe(32)) | |
| def parse_api_keys(): | |
| if not API_KEYS_RAW: | |
| return [] | |
| if API_KEYS_RAW.startswith('['): | |
| try: | |
| return json.loads(API_KEYS_RAW) | |
| except: | |
| pass | |
| return [k.strip() for k in API_KEYS_RAW.split(',') if k.strip()] | |
| API_KEYS = parse_api_keys() | |
| # Pollinations URL | |
| POLLINATIONS_URL = "https://gen.pollinations.ai/v1/chat/completions" | |
| # Rate limiting | |
| REQUEST_COUNTS = defaultdict(list) | |
| MAX_REQUESTS_PER_MINUTE = 60 | |
| # Session storage (in-memory) | |
| SESSIONS = {} | |
| # ============================================ | |
| # MODELS | |
| # ============================================ | |
| class Message(BaseModel): | |
| role: str | |
| content: str | |
| class ChatRequest(BaseModel): | |
| session_id: str | |
| message: str | |
| model: Optional[str] = None # Auto-detect if None | |
| task_type: Optional[str] = None # Planner, Coder, Tester, Debugger, Modifier | |
| stream: Optional[bool] = True | |
| temperature: Optional[float] = None | |
| max_tokens: Optional[int] = None | |
| class TaskDetectionRequest(BaseModel): | |
| message: str | |
| context: Optional[List[Message]] = [] | |
| class HealthResponse(BaseModel): | |
| status: str | |
| timestamp: str | |
| available_models: List[str] | |
| task_mappings: Dict[str, str] | |
| version: str | |
| # ============================================ | |
| # KEY MANAGER | |
| # ============================================ | |
| class APIKeyManager: | |
| def __init__(self, keys: List[str]): | |
| self.keys = keys if keys else [None] | |
| self.failed_keys = {} | |
| self.current_index = 0 | |
| self.cooldown = 60 | |
| def get_working_key(self) -> Optional[str]: | |
| if not self.keys or self.keys[0] is None: | |
| return None | |
| now = time.time() | |
| attempts = 0 | |
| while attempts < len(self.keys): | |
| key = self.keys[self.current_index] | |
| if key in self.failed_keys: | |
| if now - self.failed_keys[key] > self.cooldown: | |
| del self.failed_keys[key] | |
| else: | |
| self.current_index = (self.current_index + 1) % len(self.keys) | |
| attempts += 1 | |
| continue | |
| return key | |
| if self.failed_keys: | |
| oldest_key = min(self.failed_keys, key=self.failed_keys.get) | |
| del self.failed_keys[oldest_key] | |
| return oldest_key | |
| return self.keys[0] if self.keys else None | |
| def mark_failed(self, key: Optional[str]): | |
| if key: | |
| self.failed_keys[key] = time.time() | |
| self.current_index = (self.current_index + 1) % len(self.keys) | |
| def mark_success(self, key: Optional[str]): | |
| if key and key in self.failed_keys: | |
| del self.failed_keys[key] | |
| key_manager = APIKeyManager(API_KEYS) | |
| # ============================================ | |
| # TASK DETECTION | |
| # ============================================ | |
| def detect_task_type(message: str) -> str: | |
| """Mesajdan task type'ı otomatik tespit et""" | |
| message_lower = message.lower() | |
| # Keyword-based detection | |
| if any(word in message_lower for word in ['plan', 'tasarla', 'mimari', 'architecture', 'design', 'roadmap']): | |
| return "Planner" | |
| elif any(word in message_lower for word in ['kod yaz', 'implement', 'create', 'build', 'develop', 'function']): | |
| return "Coder" | |
| elif any(word in message_lower for word in ['test', 'kontrol et', 'check', 'verify', 'validate']): | |
| return "Tester" | |
| elif any(word in message_lower for word in ['hata', 'bug', 'debug', 'fix', 'error', 'düzelt']): | |
| return "Debugger" | |
| elif any(word in message_lower for word in ['değiştir', 'modify', 'update', 'refactor', 'optimize']): | |
| return "Modifier" | |
| # Default: Genel sohbet için Coder | |
| return "Coder" | |
| # ============================================ | |
| # SESSION MANAGEMENT | |
| # ============================================ | |
| def get_or_create_session(session_id: str) -> Dict: | |
| """Session al veya oluştur""" | |
| if session_id not in SESSIONS: | |
| SESSIONS[session_id] = { | |
| "messages": [], | |
| "created_at": datetime.utcnow().isoformat(), | |
| "last_activity": datetime.utcnow().isoformat(), | |
| "metadata": {} | |
| } | |
| SESSIONS[session_id]["last_activity"] = datetime.utcnow().isoformat() | |
| return SESSIONS[session_id] | |
| def add_message_to_session(session_id: str, role: str, content: str): | |
| """Session'a mesaj ekle""" | |
| session = get_or_create_session(session_id) | |
| session["messages"].append({ | |
| "role": role, | |
| "content": content, | |
| "timestamp": datetime.utcnow().isoformat() | |
| }) | |
| # ============================================ | |
| # MIDDLEWARE | |
| # ============================================ | |
| def verify_api_key(authorization: Optional[str] = Header(None)): | |
| """Backend API key doğrulama""" | |
| if not authorization: | |
| raise HTTPException(status_code=401, detail="Missing authorization header") | |
| if not authorization.startswith("Bearer "): | |
| raise HTTPException(status_code=401, detail="Invalid authorization format") | |
| token = authorization.replace("Bearer ", "") | |
| if token != BACKEND_API_KEY: | |
| raise HTTPException(status_code=401, detail="Invalid API key") | |
| return token | |
| def rate_limit_check(client_id: str): | |
| """Rate limiting""" | |
| now = time.time() | |
| minute_ago = now - 60 | |
| REQUEST_COUNTS[client_id] = [ | |
| req_time for req_time in REQUEST_COUNTS[client_id] | |
| if req_time > minute_ago | |
| ] | |
| if len(REQUEST_COUNTS[client_id]) >= MAX_REQUESTS_PER_MINUTE: | |
| raise HTTPException(status_code=429, detail="Rate limit exceeded") | |
| REQUEST_COUNTS[client_id].append(now) | |
| # ============================================ | |
| # CORE API FUNCTIONS | |
| # ============================================ | |
| async def call_model_api( | |
| model: str, | |
| messages: List[Dict], | |
| stream: bool = False, | |
| temperature: Optional[float] = None, | |
| max_tokens: Optional[int] = None | |
| ) -> Any: | |
| """Model API'sini çağır""" | |
| if model not in MODEL_CONFIGS: | |
| raise HTTPException(status_code=400, detail=f"Unknown model: {model}") | |
| config = MODEL_CONFIGS[model] | |
| api_key = key_manager.get_working_key() | |
| headers = {"Content-Type": "application/json"} | |
| if api_key: | |
| headers["Authorization"] = f"Bearer {api_key}" | |
| payload = { | |
| "model": config["api_name"], | |
| "messages": messages, | |
| "stream": stream, | |
| "temperature": temperature or config["temperature"], | |
| "max_tokens": max_tokens or config["max_tokens"] | |
| } | |
| max_retries = 3 | |
| last_error = None | |
| for attempt in range(max_retries): | |
| try: | |
| response = requests.post( | |
| POLLINATIONS_URL, | |
| json=payload, | |
| headers=headers, | |
| stream=stream, | |
| timeout=120 | |
| ) | |
| if response.status_code == 200: | |
| key_manager.mark_success(api_key) | |
| if stream: | |
| return response | |
| else: | |
| return response.json() | |
| elif response.status_code in [403, 429]: | |
| key_manager.mark_failed(api_key) | |
| api_key = key_manager.get_working_key() | |
| if api_key: | |
| headers["Authorization"] = f"Bearer {api_key}" | |
| await asyncio.sleep(1) | |
| continue | |
| elif response.status_code == 401: | |
| key_manager.mark_failed(api_key) | |
| api_key = key_manager.get_working_key() | |
| if api_key: | |
| headers["Authorization"] = f"Bearer {api_key}" | |
| continue | |
| else: | |
| last_error = f"API error: {response.status_code}" | |
| if attempt < max_retries - 1: | |
| await asyncio.sleep(2) | |
| continue | |
| raise HTTPException(status_code=response.status_code, detail=last_error) | |
| except requests.exceptions.Timeout: | |
| if attempt < max_retries - 1: | |
| await asyncio.sleep(2) | |
| continue | |
| raise HTTPException(status_code=504, detail="Request timeout") | |
| except requests.exceptions.RequestException as e: | |
| if attempt < max_retries - 1: | |
| await asyncio.sleep(1) | |
| continue | |
| raise HTTPException(status_code=500, detail=f"Request failed: {str(e)}") | |
| raise HTTPException(status_code=503, detail="All retries failed") | |
| async def stream_generator(response) -> AsyncGenerator[str, None]: | |
| """Stream response'u SSE formatına çevir""" | |
| try: | |
| for line in response.iter_lines(): | |
| if line: | |
| line_decoded = line.decode('utf-8') | |
| if line_decoded.startswith("data: "): | |
| data_str = line_decoded[6:] | |
| if data_str.strip() == "[DONE]": | |
| yield f"data: {json.dumps({'done': True})}\n\n" | |
| break | |
| try: | |
| data = json.loads(data_str) | |
| if "choices" in data and len(data["choices"]) > 0: | |
| delta = data["choices"][0].get("delta", {}) | |
| content = delta.get("content", "") | |
| if content: | |
| yield f"data: {json.dumps({'chunk': content})}\n\n" | |
| except json.JSONDecodeError: | |
| continue | |
| yield f"data: {json.dumps({'done': True})}\n\n" | |
| finally: | |
| response.close() | |
| # ============================================ | |
| # ROUTES | |
| # ============================================ | |
| async def health_check(): | |
| """Health check""" | |
| return { | |
| "status": "healthy", | |
| "timestamp": datetime.utcnow().isoformat(), | |
| "available_models": list(MODEL_CONFIGS.keys()), | |
| "task_mappings": TASK_MODELS, | |
| "version": "2.0.0" | |
| } | |
| async def chat(request: ChatRequest): | |
| """ | |
| Ana chat endpoint - Task-based routing ile | |
| Body: | |
| { | |
| "session_id": "sf_xxx", | |
| "message": "Kayra için tokenizer yaz", | |
| "model": "gpt-5.1-codex", // optional | |
| "task_type": "Coder", // optional | |
| "stream": true | |
| } | |
| """ | |
| # Rate limiting | |
| rate_limit_check(request.session_id) | |
| # Session'ı al/oluştur | |
| session = get_or_create_session(request.session_id) | |
| # User mesajını ekle | |
| add_message_to_session(request.session_id, "user", request.message) | |
| # Task type'ı belirle | |
| task_type = request.task_type or detect_task_type(request.message) | |
| # Model'i belirle | |
| model = request.model or TASK_MODELS.get(task_type, "gpt-5.1-codex") | |
| # Messages'ı hazırla | |
| messages = [ | |
| {"role": msg["role"], "content": msg["content"]} | |
| for msg in session["messages"] | |
| ] | |
| # System prompt ekle (task-specific) | |
| system_prompts = { | |
| "Planner": "Sen deneyimli bir yazılım mimarısın. Detaylı planlama ve tasarım yap.", | |
| "Coder": "Sen expert bir kod geliştiricisisin. Temiz, okunabilir ve performanslı kod yaz.", | |
| "Tester": "Sen titiz bir test mühendisisin. Comprehensive test senaryoları oluştur.", | |
| "Debugger": "Sen yetenekli bir debugger'sın. Hataları kök nedenine inip çöz.", | |
| "Modifier": "Sen dikkatli bir refactoring uzmanısın. Mevcut kodu bozmadan değiştir." | |
| } | |
| if task_type in system_prompts: | |
| messages.insert(0, {"role": "system", "content": system_prompts[task_type]}) | |
| # API'yi çağır | |
| if request.stream: | |
| response = await call_model_api( | |
| model=model, | |
| messages=messages, | |
| stream=True, | |
| temperature=request.temperature, | |
| max_tokens=request.max_tokens | |
| ) | |
| return StreamingResponse( | |
| stream_generator(response), | |
| media_type="text/event-stream" | |
| ) | |
| else: | |
| result = await call_model_api( | |
| model=model, | |
| messages=messages, | |
| stream=False, | |
| temperature=request.temperature, | |
| max_tokens=request.max_tokens | |
| ) | |
| # Assistant yanıtını session'a ekle | |
| assistant_message = result["choices"][0]["message"]["content"] | |
| add_message_to_session(request.session_id, "assistant", assistant_message) | |
| return result | |
| async def detect_task(request: TaskDetectionRequest): | |
| """Task type'ı tespit et""" | |
| task_type = detect_task_type(request.message) | |
| recommended_model = TASK_MODELS.get(task_type) | |
| return { | |
| "task_type": task_type, | |
| "recommended_model": recommended_model, | |
| "model_config": MODEL_CONFIGS.get(recommended_model, {}) | |
| } | |
| async def get_session(session_id: str): | |
| """Session bilgilerini getir""" | |
| if session_id not in SESSIONS: | |
| raise HTTPException(status_code=404, detail="Session not found") | |
| return SESSIONS[session_id] | |
| async def delete_session(session_id: str): | |
| """Session'ı sil""" | |
| if session_id in SESSIONS: | |
| del SESSIONS[session_id] | |
| return {"status": "deleted"} | |
| raise HTTPException(status_code=404, detail="Session not found") | |
| async def list_models(): | |
| """Kullanılabilir modelleri listele""" | |
| return { | |
| "models": MODEL_CONFIGS, | |
| "task_mappings": TASK_MODELS | |
| } | |
| async def get_stats(): | |
| """İstatistikler""" | |
| return { | |
| "total_sessions": len(SESSIONS), | |
| "total_keys": len(API_KEYS) if API_KEYS and API_KEYS[0] is not None else 0, | |
| "failed_keys": len(key_manager.failed_keys), | |
| "active_clients": len(REQUEST_COUNTS), | |
| "requests_last_minute": sum(len(v) for v in REQUEST_COUNTS.values()), | |
| "available_models": list(MODEL_CONFIGS.keys()) | |
| } | |
| # ============================================ | |
| # ERROR HANDLERS | |
| # ============================================ | |
| async def http_exception_handler(request, exc): | |
| return JSONResponse( | |
| status_code=exc.status_code, | |
| content={ | |
| "error": { | |
| "message": exc.detail, | |
| "type": "api_error", | |
| "code": exc.status_code | |
| } | |
| } | |
| ) | |
| async def general_exception_handler(request, exc): | |
| print(f"Unhandled exception: {exc}") | |
| return JSONResponse( | |
| status_code=500, | |
| content={ | |
| "error": { | |
| "message": "Internal server error", | |
| "type": "internal_error", | |
| "code": 500 | |
| } | |
| } | |
| ) | |
| # ============================================ | |
| # STARTUP | |
| # ============================================ | |
| async def startup_event(): | |
| print("=" * 60) | |
| print("🚀 SixFingerDev Arena Starting...") | |
| print("=" * 60) | |
| print(f"📦 Available Models: {', '.join(MODEL_CONFIGS.keys())}") | |
| print(f"🎯 Task Mappings:") | |
| for task, model in TASK_MODELS.items(): | |
| print(f" {task}: {model}") | |
| print(f"🔑 API Keys: {len(API_KEYS) if API_KEYS and API_KEYS[0] is not None else 0}") | |
| print(f"🔐 Backend Key: {BACKEND_API_KEY[:10]}...") | |
| print(f"⏱️ Rate Limit: {MAX_REQUESTS_PER_MINUTE} req/min") | |
| print("=" * 60) | |
| print("✅ Arena Ready!") | |
| print("=" * 60) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=7860) |