""" Sixfinger Backend API - FRONTEND UYUMLU VERSİYON Ultra-fast AI Chat Backend with Multi-Model Support Supports: Groq, DeepInfra, LLM7.io """ import os import time import json import logging import requests from typing import Optional, Dict, Any, Generator from datetime import datetime from fastapi import FastAPI, HTTPException, Header, Request from fastapi.responses import StreamingResponse, JSONResponse from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel, Field from groq import Groq from openai import OpenAI # ========== CONFIGURATION ========== API_VERSION = "1.1.0" GROQ_API_KEY = os.getenv("GROQ_API_KEY", "") # ========== API PROVIDERS ========== PROVIDERS = { "groq": { "name": "Groq", "type": "groq", "requires_key": True }, "deepinfra": { "name": "DeepInfra", "type": "deepinfra", "base_url": "https://api.deepinfra.com/v1/openai/chat/completions", "requires_key": False }, "llm7": { "name": "LLM7.io", "type": "openai_compatible", "base_url": "https://api.llm7.io/v1", "api_key": "unused", "requires_key": False } } # ========== MODEL MAPPING ========== MODELS = { # ============ FREE PLAN MODELS ============ # Groq Models (Free) "llama-8b-instant": { "provider": "groq", "model_id": "llama-3.1-8b-instant", "display_name": "Llama 3.1 8B Instant", "size": "8B", "language": "Multilingual", "speed": "⚡⚡⚡", "description": "Hızlı ve hafif genel amaçlı model", "plans": ["free", "starter", "pro", "plus"], "daily_limit": 14400 }, "allam-2-7b": { "provider": "groq", "model_id": "llama-3.1-8b-instant", "display_name": "Allam 2 7B", "size": "7B", "language": "Turkish/Arabic", "speed": "⚡⚡", "description": "Türkçe ve Arapça optimizeli model", "plans": ["free", "starter", "pro", "plus"], "daily_limit": 300 }, # DeepInfra Models (Free) "llama4-maverick": { "provider": "deepinfra", "model_id": "meta-llama/Llama-4-Maverick-17B-128E-Instruct-Turbo", "display_name": "Llama 4 Maverick 17B", "size": "17B", "language": "Multilingual", "speed": "⚡⚡", "description": "Meta'nın en yeni hızlı ve yetenekli modeli", "plans": ["free", "starter", "pro", "plus"], "daily_limit": 1000 }, "qwen3-coder": { "provider": "deepinfra", "model_id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo", "display_name": "Qwen3 Coder 480B", "size": "480B", "language": "Multilingual", "speed": "⚡", "description": "Kod yazma uzmanı dev model", "plans": ["free", "starter", "pro", "plus"], "daily_limit": 500 }, "deepseek-r1": { "provider": "deepinfra", "model_id": "deepseek-ai/DeepSeek-R1-0528-Turbo", "display_name": "DeepSeek R1 Turbo", "size": "Unknown", "language": "Multilingual", "speed": "⚡", "description": "Muhakeme ve zeka odaklı model", "plans": ["free", "starter", "pro", "plus"], "daily_limit": 500 }, # ============ STARTER PLAN MODELS ============ # LLM7.io Models (Starter+) "gpt4-nano": { "provider": "llm7", "model_id": "gpt-4.1-nano-2025-04-14", "display_name": "GPT-4.1 Nano", "size": "Nano", "language": "Multilingual", "speed": "⚡⚡⚡", "description": "OpenAI GPT-4 tabanlı hızlı model", "plans": ["starter", "pro", "plus"], "daily_limit": 1000 }, # Groq Models (Starter+) "qwen3-32b": { "provider": "groq", "model_id": "llama-3.3-70b-versatile", "display_name": "Qwen3 32B", "size": "32B", "language": "Turkish/Chinese", "speed": "⚡⚡", "description": "Türkçe ve Çince optimize edilmiş model", "plans": ["starter", "pro", "plus"], "daily_limit": 1000 }, "llama-70b": { "provider": "groq", "model_id": "llama-3.3-70b-versatile", "display_name": "Llama 3.3 70B", "size": "70B", "language": "Multilingual", "speed": "⚡⚡", "description": "Güçlü ve çok yönlü büyük model", "plans": ["starter", "pro", "plus"], "daily_limit": 1000 }, "llama-maverick-17b": { "provider": "groq", "model_id": "llama-3.1-8b-instant", "display_name": "Llama Maverick 17B", "size": "17B", "language": "Multilingual", "speed": "⚡⚡", "description": "Deneysel maverick model", "plans": ["starter", "pro", "plus"], "daily_limit": 1000 }, "llama-scout-17b": { "provider": "groq", "model_id": "llama-3.1-8b-instant", "display_name": "Llama Scout 17B", "size": "17B", "language": "Multilingual", "speed": "⚡⚡⚡", "description": "Keşif odaklı hızlı model", "plans": ["starter", "pro", "plus"], "daily_limit": 1000 }, "gpt-oss-20b": { "provider": "groq", "model_id": "llama-3.1-8b-instant", "display_name": "GPT-OSS 20B", "size": "20B", "language": "Multilingual", "speed": "⚡⚡", "description": "Açık kaynak GPT alternatifleri", "plans": ["starter", "pro", "plus"], "daily_limit": 1000 }, # ============ PRO PLAN MODELS ============ "gpt-oss-120b": { "provider": "groq", "model_id": "llama-3.3-70b-versatile", "display_name": "GPT-OSS 120B", "size": "120B", "language": "Multilingual", "speed": "⚡⚡", "description": "En büyük açık kaynak model", "plans": ["pro", "plus"], "daily_limit": 1000 }, "kimi-k2": { "provider": "groq", "model_id": "llama-3.3-70b-versatile", "display_name": "Kimi K2", "size": "Unknown", "language": "Chinese", "speed": "⚡⚡", "description": "Çince uzmanı güçlü model", "plans": ["pro", "plus"], "daily_limit": 1000 } } # Plan bazlı otomatik model seçimi DEFAULT_MODELS = { "free": "llama4-maverick", "starter": "gpt4-nano", "pro": "llama-70b", "plus": "gpt-oss-120b" } # ========== LOGGING ========== logging.basicConfig( level=logging.INFO, format='[%(asctime)s] %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) logger = logging.getLogger(__name__) # ========== FASTAPI APP ========== app = FastAPI( title="Sixfinger Backend API", version=API_VERSION, description="Ultra-fast AI Chat Backend with Multi-Provider Support", docs_url="/docs", redoc_url="/redoc" ) # CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ========== API CLIENTS ========== # Groq Client groq_client = Groq(api_key=GROQ_API_KEY) if GROQ_API_KEY else None # LLM7 Client llm7_client = OpenAI( base_url=PROVIDERS["llm7"]["base_url"], api_key=PROVIDERS["llm7"]["api_key"] ) # ========== PYDANTIC MODELS ========== class ChatRequest(BaseModel): prompt: str = Field(..., description="User's message") max_tokens: int = Field(default=300, ge=1, le=4000) temperature: float = Field(default=0.7, ge=0, le=2) top_p: float = Field(default=0.9, ge=0, le=1) system_prompt: Optional[str] = None history: Optional[list] = None class ChatResponse(BaseModel): response: str model: str model_key: str model_size: str model_language: str provider: str attempts: int usage: Dict[str, int] parameters: Dict[str, Any] # ========== HELPER FUNCTIONS ========== def get_allowed_models(plan: str) -> list: """Plan'a göre izin verilen modelleri döndür""" return [k for k, v in MODELS.items() if plan in v["plans"]] def select_model(plan: str, preferred_model: Optional[str] = None) -> str: """Model seçimi yap""" allowed_models = get_allowed_models(plan) if preferred_model and preferred_model in allowed_models: return preferred_model default = DEFAULT_MODELS.get(plan, "llama-8b-instant") return default if default in allowed_models else allowed_models[0] def build_messages(prompt: str, system_prompt: Optional[str], history: Optional[list]) -> list: """Chat messages listesi oluştur""" messages = [] if system_prompt: messages.append({"role": "system", "content": system_prompt}) else: messages.append({"role": "system", "content": "Sen yardımcı bir asistansın. Adın SixFinger."}) if history: for msg in history: if "role" in msg and "content" in msg: messages.append(msg) messages.append({"role": "user", "content": prompt}) return messages # ========== PROVIDER-SPECIFIC API CALLS ========== def call_groq_api( model_id: str, messages: list, max_tokens: int, temperature: float, top_p: float, stream: bool = False ): """Groq API'ye istek at""" if not groq_client: raise HTTPException(status_code=500, detail="Groq API key not configured") try: response = groq_client.chat.completions.create( model=model_id, messages=messages, max_tokens=max_tokens, temperature=temperature, top_p=top_p, stream=stream ) return response except Exception as e: logger.error(f"Groq API error: {e}") raise HTTPException(status_code=500, detail=f"Groq API error: {str(e)}") def call_deepinfra_api( model_id: str, messages: list, max_tokens: int, temperature: float, top_p: float, stream: bool = False ) -> Dict[str, Any]: """DeepInfra API'ye istek at (non-streaming)""" url = PROVIDERS["deepinfra"]["base_url"] headers = { "Content-Type": "application/json", "X-Deepinfra-Source": "web-page" } data = { "model": model_id, "messages": messages, "max_tokens": max_tokens, "temperature": temperature, "top_p": top_p, "stream": stream } try: if stream: return requests.post(url, headers=headers, json=data, stream=True) else: response = requests.post(url, headers=headers, json=data) response.raise_for_status() return response.json() except Exception as e: logger.error(f"DeepInfra API error: {e}") raise HTTPException(status_code=500, detail=f"DeepInfra API error: {str(e)}") def call_llm7_api( model_id: str, messages: list, max_tokens: int, temperature: float, top_p: float, stream: bool = False ): """LLM7.io API'ye istek at""" try: response = llm7_client.chat.completions.create( model=model_id, messages=messages, max_tokens=max_tokens, temperature=temperature, top_p=top_p, stream=stream ) return response except Exception as e: logger.error(f"LLM7 API error: {e}") raise HTTPException(status_code=500, detail=f"LLM7 API error: {str(e)}") def call_api( provider: str, model_id: str, messages: list, max_tokens: int, temperature: float, top_p: float, stream: bool = False ): """Universal API caller - provider'a göre yönlendir""" if provider == "groq": return call_groq_api(model_id, messages, max_tokens, temperature, top_p, stream) elif provider == "deepinfra": return call_deepinfra_api(model_id, messages, max_tokens, temperature, top_p, stream) elif provider == "llm7": return call_llm7_api(model_id, messages, max_tokens, temperature, top_p, stream) else: raise HTTPException(status_code=400, detail=f"Unknown provider: {provider}") # ========== ENDPOINTS ========== @app.get("/health") def health_check(): """Health check endpoint""" return { "status": "healthy", "version": API_VERSION, "timestamp": datetime.now().isoformat(), "providers": { "groq": bool(GROQ_API_KEY), "deepinfra": True, "llm7": True } } @app.post("/api/chat") def chat( request: ChatRequest, x_user_plan: str = Header(default="free", alias="X-User-Plan"), x_model: Optional[str] = Header(default=None, alias="X-Model") ): """ Normal chat endpoint (JSON response) Frontend'e TAM UYUMLU format """ start_time = time.time() # Model seçimi model_key = select_model(x_user_plan, x_model) model_config = MODELS[model_key] provider = model_config["provider"] model_id = model_config["model_id"] logger.info(f"Chat request: plan={x_user_plan}, model={model_key}, provider={provider}") # Messages messages = build_messages( request.prompt, request.system_prompt, request.history ) try: # Provider'a göre API call if provider == "deepinfra": # DeepInfra non-streaming response response_data = call_api( provider=provider, model_id=model_id, messages=messages, max_tokens=request.max_tokens, temperature=request.temperature, top_p=request.top_p, stream=False ) content = response_data["choices"][0]["message"]["content"] usage = response_data.get("usage", {}) usage = { "prompt_tokens": usage.get("prompt_tokens", 0), "completion_tokens": usage.get("completion_tokens", 0), "total_tokens": usage.get("total_tokens", 0) } else: # Groq veya LLM7 response response = call_api( provider=provider, model_id=model_id, messages=messages, max_tokens=request.max_tokens, temperature=request.temperature, top_p=request.top_p, stream=False ) content = response.choices[0].message.content usage = { "prompt_tokens": getattr(response.usage, 'prompt_tokens', 0), "completion_tokens": getattr(response.usage, 'completion_tokens', 0), "total_tokens": getattr(response.usage, 'total_tokens', 0) } elapsed = time.time() - start_time logger.info(f"Chat completed: provider={provider}, tokens={usage['total_tokens']}, time={elapsed:.2f}s") return { "response": content, "model": model_id, "model_key": model_key, "model_size": model_config["size"], "model_language": model_config["language"], "provider": provider, "attempts": 1, "usage": usage, "parameters": { "max_tokens": request.max_tokens, "temperature": request.temperature, "top_p": request.top_p } } except HTTPException: raise except Exception as e: logger.error(f"Chat error: {e}") raise HTTPException(status_code=500, detail=str(e)) @app.post("/api/chat/stream") def chat_stream( request: ChatRequest, x_user_plan: str = Header(default="free", alias="X-User-Plan"), x_model: Optional[str] = Header(default=None, alias="X-Model") ): """ Streaming chat endpoint (SSE) Tüm provider'ları destekler """ model_key = select_model(x_user_plan, x_model) model_config = MODELS[model_key] provider = model_config["provider"] model_id = model_config["model_id"] logger.info(f"Stream request: plan={x_user_plan}, model={model_key}, provider={provider}") messages = build_messages( request.prompt, request.system_prompt, request.history ) def generate_groq(): """Groq streaming generator""" try: yield f"data: {json.dumps({'info': f'Using {model_key} via Groq'})}\n\n" response = call_groq_api( model_id=model_id, messages=messages, max_tokens=request.max_tokens, temperature=request.temperature, top_p=request.top_p, stream=True ) total_tokens = 0 prompt_tokens = 0 completion_tokens = 0 for chunk in response: if chunk.choices[0].delta.content: text = chunk.choices[0].delta.content yield f"data: {json.dumps({'text': text})}\n\n" if hasattr(chunk, 'x_groq') and hasattr(chunk.x_groq, 'usage'): usage_data = chunk.x_groq.usage prompt_tokens = getattr(usage_data, 'prompt_tokens', 0) completion_tokens = getattr(usage_data, 'completion_tokens', 0) total_tokens = getattr(usage_data, 'total_tokens', 0) yield f"data: {json.dumps({'done': True, 'model_key': model_key, 'provider': 'groq', 'attempts': 1, 'usage': {'prompt_tokens': prompt_tokens, 'completion_tokens': completion_tokens, 'total_tokens': total_tokens}})}\n\n" except Exception as e: logger.error(f"Groq stream error: {e}") yield f"data: {json.dumps({'error': str(e)})}\n\n" def generate_deepinfra(): """DeepInfra streaming generator""" try: yield f"data: {json.dumps({'info': f'Using {model_key} via DeepInfra'})}\n\n" url = PROVIDERS["deepinfra"]["base_url"] headers = { "Content-Type": "application/json", "X-Deepinfra-Source": "web-page" } data = { "model": model_id, "messages": messages, "max_tokens": request.max_tokens, "temperature": request.temperature, "top_p": request.top_p, "stream": True } response = requests.post(url, headers=headers, json=data, stream=True) total_completion_tokens = 0 for line in response.iter_lines(): if line: decoded = line.decode('utf-8') if decoded.startswith("data: "): content = decoded[6:] if content == "[DONE]": break try: json_data = json.loads(content) delta = json_data.get("choices", [{}])[0].get("delta", {}) if "content" in delta: token = delta["content"] yield f"data: {json.dumps({'text': token})}\n\n" total_completion_tokens += 1 except json.JSONDecodeError: continue yield f"data: {json.dumps({'done': True, 'model_key': model_key, 'provider': 'deepinfra', 'attempts': 1, 'usage': {'prompt_tokens': 0, 'completion_tokens': total_completion_tokens, 'total_tokens': total_completion_tokens}})}\n\n" except Exception as e: logger.error(f"DeepInfra stream error: {e}") yield f"data: {json.dumps({'error': str(e)})}\n\n" def generate_llm7(): """LLM7.io streaming generator""" try: yield f"data: {json.dumps({'info': f'Using {model_key} via LLM7.io'})}\n\n" stream = llm7_client.chat.completions.create( model=model_id, messages=messages, max_tokens=request.max_tokens, temperature=request.temperature, top_p=request.top_p, stream=True ) total_completion_tokens = 0 for chunk in stream: if chunk.choices[0].delta.content: text = chunk.choices[0].delta.content yield f"data: {json.dumps({'text': text})}\n\n" total_completion_tokens += 1 yield f"data: {json.dumps({'done': True, 'model_key': model_key, 'provider': 'llm7', 'attempts': 1, 'usage': {'prompt_tokens': 0, 'completion_tokens': total_completion_tokens, 'total_tokens': total_completion_tokens}})}\n\n" except Exception as e: logger.error(f"LLM7 stream error: {e}") yield f"data: {json.dumps({'error': str(e)})}\n\n" # Provider'a göre generator seç if provider == "groq": generator = generate_groq() elif provider == "deepinfra": generator = generate_deepinfra() elif provider == "llm7": generator = generate_llm7() else: raise HTTPException(status_code=400, detail=f"Unknown provider: {provider}") return StreamingResponse( generator, media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "X-Accel-Buffering": "no", "Connection": "keep-alive" } ) @app.get("/api/models") def list_models(x_user_plan: str = Header(default="free", alias="X-User-Plan")): """ Kullanıcının erişebileceği modelleri listele """ allowed_models = get_allowed_models(x_user_plan) models_info = [] for model_key in allowed_models: config = MODELS[model_key] models_info.append({ "key": model_key, "display_name": config.get("display_name", model_key), "size": config["size"], "language": config["language"], "speed": config["speed"], "description": config.get("description", ""), "provider": config["provider"], "daily_limit": config["daily_limit"] }) # Provider'a göre grupla grouped = {} for model in models_info: provider = model["provider"] if provider not in grouped: grouped[provider] = [] grouped[provider].append(model) return { "plan": x_user_plan, "total_models": len(models_info), "models": models_info, "models_by_provider": grouped, "default_model": DEFAULT_MODELS.get(x_user_plan, "llama-8b-instant"), "providers": list(grouped.keys()) } @app.get("/api/providers") def list_providers(): """Mevcut API provider'larını listele""" return { "providers": [ { "id": "groq", "name": "Groq", "status": "active" if GROQ_API_KEY else "inactive", "description": "Ultra-fast inference with Groq LPU" }, { "id": "deepinfra", "name": "DeepInfra", "status": "active", "description": "Free tier AI models - Llama 4, Qwen3 Coder, DeepSeek" }, { "id": "llm7", "name": "LLM7.io", "status": "active", "description": "GPT-4 based models - Free tier available" } ] } @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException): """Custom HTTP exception handler""" return JSONResponse( status_code=exc.status_code, content={ "error": exc.detail, "status_code": exc.status_code } ) @app.exception_handler(Exception) async def general_exception_handler(request: Request, exc: Exception): """General exception handler""" logger.error(f"Unhandled exception: {exc}") return JSONResponse( status_code=500, content={ "error": "Internal server error", "detail": str(exc) } ) # ========== STARTUP/SHUTDOWN ========== @app.on_event("startup") async def startup_event(): logger.info("🚀 Sixfinger Backend API started") logger.info(f"📦 Version: {API_VERSION}") logger.info(f"🔑 Groq API: {'✅ Configured' if GROQ_API_KEY else '❌ Not configured'}") logger.info(f"🌐 DeepInfra: ✅ Active (Free tier)") logger.info(f"🌐 LLM7.io: ✅ Active (Free tier)") logger.info(f"🤖 Total Models: {len(MODELS)}") # Plan başına model sayısı for plan in ["free", "starter", "pro", "plus"]: count = len(get_allowed_models(plan)) logger.info(f" └─ {plan.upper()} plan: {count} models") @app.on_event("shutdown") async def shutdown_event(): logger.info("👋 Sixfinger Backend API shutting down") if __name__ == "__main__": import uvicorn uvicorn.run( "main:app", host="0.0.0.0", port=8000, reload=True, log_level="info" )