sixfingerdev commited on
Commit
bbde514
·
verified ·
1 Parent(s): e86f718

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +342 -127
app.py CHANGED
@@ -1,28 +1,33 @@
1
  """
2
- SixFinger Code - Pollinations API Proxy Backend
 
3
  Hugging Face Spaces üzerinde çalışır
4
  """
5
 
6
- from fastapi import FastAPI, HTTPException, Header
7
  from fastapi.middleware.cors import CORSMiddleware
 
8
  from pydantic import BaseModel
9
- from typing import List, Optional, Dict, Any
10
  import requests
11
  import os
12
  import secrets
13
  from datetime import datetime
14
  import time
 
 
 
15
 
16
  app = FastAPI(
17
- title="SixFinger AI Backend",
18
- description="Pollinations API Proxy for PythonAnywhere",
19
- version="1.0.0"
20
  )
21
 
22
- # CORS - tüm originlere izin ver (production'da domain belirt)
23
  app.add_middleware(
24
  CORSMiddleware,
25
- allow_origins=["*"], # Production'da: ["https://yourdomain.pythonanywhere.com"]
26
  allow_credentials=True,
27
  allow_methods=["*"],
28
  allow_headers=["*"],
@@ -32,36 +37,73 @@ app.add_middleware(
32
  # CONFIGURATION
33
  # ============================================
34
 
35
- # API Keys - Space Secrets'tan al
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  API_KEYS_RAW = os.getenv('AI_API_KEYS', '')
 
37
 
38
  def parse_api_keys():
39
- """AI_API_KEYS'i parse et"""
40
  if not API_KEYS_RAW:
41
- # Fallback - public Pollinations (sınırlı)
42
  return []
43
-
44
  if API_KEYS_RAW.startswith('['):
45
- import json
46
  try:
47
  return json.loads(API_KEYS_RAW)
48
  except:
49
  pass
50
-
51
  return [k.strip() for k in API_KEYS_RAW.split(',') if k.strip()]
52
 
53
  API_KEYS = parse_api_keys()
54
 
55
- # Backend API Key (güvenlik için)
56
- BACKEND_API_KEY = os.getenv('BACKEND_API_KEY', secrets.token_urlsafe(32))
57
-
58
  # Pollinations URL
59
  POLLINATIONS_URL = "https://gen.pollinations.ai/v1/chat/completions"
60
 
61
  # Rate limiting
62
- REQUEST_COUNTS = {}
63
  MAX_REQUESTS_PER_MINUTE = 60
64
 
 
 
 
65
  # ============================================
66
  # MODELS
67
  # ============================================
@@ -71,24 +113,23 @@ class Message(BaseModel):
71
  content: str
72
 
73
  class ChatRequest(BaseModel):
74
- model: str
75
- messages: List[Message]
76
- stream: Optional[bool] = False
77
- temperature: Optional[float] = 0.7
78
- max_tokens: Optional[int] = 2000
79
-
80
- class ChatResponse(BaseModel):
81
- id: str
82
- object: str
83
- created: int
84
- model: str
85
- choices: List[Dict[str, Any]]
86
- usage: Dict[str, int]
87
 
88
  class HealthResponse(BaseModel):
89
  status: str
90
  timestamp: str
91
- api_keys_count: int
 
92
  version: str
93
 
94
  # ============================================
@@ -97,14 +138,14 @@ class HealthResponse(BaseModel):
97
 
98
  class APIKeyManager:
99
  def __init__(self, keys: List[str]):
100
- self.keys = keys if keys else [None] # None = keyless mode
101
  self.failed_keys = {}
102
  self.current_index = 0
103
  self.cooldown = 60
104
 
105
  def get_working_key(self) -> Optional[str]:
106
  if not self.keys or self.keys[0] is None:
107
- return None # Keyless mode
108
 
109
  now = time.time()
110
  attempts = 0
@@ -122,7 +163,6 @@ class APIKeyManager:
122
 
123
  return key
124
 
125
- # All keys failed - use oldest
126
  if self.failed_keys:
127
  oldest_key = min(self.failed_keys, key=self.failed_keys.get)
128
  del self.failed_keys[oldest_key]
@@ -141,6 +181,59 @@ class APIKeyManager:
141
 
142
  key_manager = APIKeyManager(API_KEYS)
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  # ============================================
145
  # MIDDLEWARE
146
  # ============================================
@@ -161,14 +254,10 @@ def verify_api_key(authorization: Optional[str] = Header(None)):
161
  return token
162
 
163
  def rate_limit_check(client_id: str):
164
- """Basit rate limiting"""
165
  now = time.time()
166
  minute_ago = now - 60
167
 
168
- if client_id not in REQUEST_COUNTS:
169
- REQUEST_COUNTS[client_id] = []
170
-
171
- # Eski istekleri temizle
172
  REQUEST_COUNTS[client_id] = [
173
  req_time for req_time in REQUEST_COUNTS[client_id]
174
  if req_time > minute_ago
@@ -180,72 +269,37 @@ def rate_limit_check(client_id: str):
180
  REQUEST_COUNTS[client_id].append(now)
181
 
182
  # ============================================
183
- # ROUTES
184
  # ============================================
185
 
186
- @app.get("/", response_model=HealthResponse)
187
- async def health_check():
188
- """Health check endpoint"""
189
- return {
190
- "status": "healthy",
191
- "timestamp": datetime.utcnow().isoformat(),
192
- "api_keys_count": len(API_KEYS) if API_KEYS and API_KEYS[0] is not None else 0,
193
- "version": "1.0.0"
194
- }
195
-
196
- @app.get("/health", response_model=HealthResponse)
197
- async def health():
198
- """Alias for health check"""
199
- return await health_check()
200
-
201
- @app.post("/v1/chat/completions")
202
- async def chat_completion(
203
- request: ChatRequest,
204
- authorization: str = Header(None)
205
- ):
206
- """
207
- Pollinations API proxy endpoint
208
-
209
- Headers:
210
- Authorization: Bearer YOUR_BACKEND_API_KEY
211
-
212
- Body:
213
- {
214
- "model": "openai",
215
- "messages": [{"role": "user", "content": "Hello"}],
216
- "stream": false
217
- }
218
- """
219
-
220
- # Verify backend API key
221
- verify_api_key(authorization)
222
 
223
- # Rate limiting (IP bazlı olabilir, şimdilik basit)
224
- client_id = authorization # veya request.client.host
225
- rate_limit_check(client_id)
226
 
227
- # Get working API key
228
  api_key = key_manager.get_working_key()
229
 
230
- # Prepare headers
231
- headers = {
232
- "Content-Type": "application/json"
233
- }
234
-
235
  if api_key:
236
  headers["Authorization"] = f"Bearer {api_key}"
237
 
238
- # Prepare payload
239
  payload = {
240
- "model": request.model,
241
- "messages": [{"role": m.role, "content": m.content} for m in request.messages],
242
- "stream": request.stream,
243
- "temperature": request.temperature,
244
- "max_tokens": request.max_tokens
245
  }
246
 
247
- # Call Pollinations API
248
- max_retries = len(API_KEYS) if API_KEYS and API_KEYS[0] is not None else 3
249
  last_error = None
250
 
251
  for attempt in range(max_retries):
@@ -254,24 +308,27 @@ async def chat_completion(
254
  POLLINATIONS_URL,
255
  json=payload,
256
  headers=headers,
 
257
  timeout=120
258
  )
259
 
260
  if response.status_code == 200:
261
  key_manager.mark_success(api_key)
262
- return response.json()
 
 
 
 
263
 
264
- elif response.status_code == 403:
265
- # Rate limit - try next key
266
  key_manager.mark_failed(api_key)
267
  api_key = key_manager.get_working_key()
268
  if api_key:
269
  headers["Authorization"] = f"Bearer {api_key}"
270
- time.sleep(1)
271
  continue
272
 
273
  elif response.status_code == 401:
274
- # Invalid key - try next
275
  key_manager.mark_failed(api_key)
276
  api_key = key_manager.get_working_key()
277
  if api_key:
@@ -279,44 +336,199 @@ async def chat_completion(
279
  continue
280
 
281
  else:
282
- # Other error
283
- last_error = f"API error: {response.status_code} - {response.text[:200]}"
 
 
284
  raise HTTPException(status_code=response.status_code, detail=last_error)
285
 
286
  except requests.exceptions.Timeout:
287
- last_error = "Request timeout"
288
  if attempt < max_retries - 1:
289
- time.sleep(2)
290
  continue
291
- raise HTTPException(status_code=504, detail=last_error)
292
 
293
  except requests.exceptions.RequestException as e:
294
- last_error = str(e)
295
  if attempt < max_retries - 1:
296
- time.sleep(1)
297
  continue
298
- raise HTTPException(status_code=500, detail=f"Request failed: {last_error}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
- # All retries failed
301
- raise HTTPException(status_code=503, detail=f"All API keys failed: {last_error}")
 
 
 
302
 
303
- @app.get("/stats")
304
- async def get_stats(authorization: str = Header(None)):
305
- """Backend istatistikleri (admin only)"""
306
- verify_api_key(authorization)
 
 
 
 
 
 
 
 
 
 
307
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  return {
 
309
  "total_keys": len(API_KEYS) if API_KEYS and API_KEYS[0] is not None else 0,
310
  "failed_keys": len(key_manager.failed_keys),
311
  "active_clients": len(REQUEST_COUNTS),
312
- "requests_last_minute": sum(len(v) for v in REQUEST_COUNTS.values())
 
313
  }
314
 
315
- # ============================================
316
- # ERROR HANDLERS
317
- # ============================================
318
- from starlette.responses import JSONResponse # En üste ekle, zaten FastAPI'den geliyor ama emin ol
319
-
320
  # ============================================
321
  # ERROR HANDLERS
322
  # ============================================
@@ -336,9 +548,7 @@ async def http_exception_handler(request, exc):
336
 
337
  @app.exception_handler(Exception)
338
  async def general_exception_handler(request, exc):
339
- # Logla hatayı (production'da önemli)
340
  print(f"Unhandled exception: {exc}")
341
-
342
  return JSONResponse(
343
  status_code=500,
344
  content={
@@ -348,21 +558,26 @@ async def general_exception_handler(request, exc):
348
  "code": 500
349
  }
350
  }
351
- )# ============================================
 
 
352
  # STARTUP
353
  # ============================================
354
 
355
  @app.on_event("startup")
356
  async def startup_event():
357
  print("=" * 60)
358
- print("🚀 SixFinger AI Backend Starting...")
359
  print("=" * 60)
360
- print(f"📦 API Keys: {len(API_KEYS) if API_KEYS and API_KEYS[0] is not None else 0}")
361
- print(f"🔑 Backend API Key: {BACKEND_API_KEY[:10]}...")
362
- print(f"🌐 Pollinations URL: {POLLINATIONS_URL}")
 
 
 
363
  print(f"⏱️ Rate Limit: {MAX_REQUESTS_PER_MINUTE} req/min")
364
  print("=" * 60)
365
- print("✅ Ready to serve!")
366
  print("=" * 60)
367
 
368
  if __name__ == "__main__":
 
1
  """
2
+ SixFingerDev Arena - Multi-Model Agentic Backend
3
+ Supports: GPT-5.1-Codex, Claude Opus 4.5, o3, o3-mini
4
  Hugging Face Spaces üzerinde çalışır
5
  """
6
 
7
+ from fastapi import FastAPI, HTTPException, Header, Request
8
  from fastapi.middleware.cors import CORSMiddleware
9
+ from fastapi.responses import StreamingResponse, JSONResponse
10
  from pydantic import BaseModel
11
+ from typing import List, Optional, Dict, Any, AsyncGenerator
12
  import requests
13
  import os
14
  import secrets
15
  from datetime import datetime
16
  import time
17
+ import json
18
+ import asyncio
19
+ from collections import defaultdict
20
 
21
  app = FastAPI(
22
+ title="SixFingerDev Arena Backend",
23
+ description="Multi-Model AI Backend with Task Routing",
24
+ version="2.0.0"
25
  )
26
 
27
+ # CORS
28
  app.add_middleware(
29
  CORSMiddleware,
30
+ allow_origins=["*"],
31
  allow_credentials=True,
32
  allow_methods=["*"],
33
  allow_headers=["*"],
 
37
  # CONFIGURATION
38
  # ============================================
39
 
40
+ # Model configurations
41
+ MODEL_CONFIGS = {
42
+ "gpt-5.1-codex": {
43
+ "provider": "pollinations",
44
+ "api_name": "openai",
45
+ "capabilities": ["code", "debug", "test"],
46
+ "max_tokens": 4096,
47
+ "temperature": 0.7
48
+ },
49
+ "claude-opus-4.5": {
50
+ "provider": "pollinations",
51
+ "api_name": "claude-opus-4",
52
+ "capabilities": ["plan", "modify", "test"],
53
+ "max_tokens": 4096,
54
+ "temperature": 0.7
55
+ },
56
+ "o3": {
57
+ "provider": "pollinations",
58
+ "api_name": "openai",
59
+ "capabilities": ["debug", "test"],
60
+ "max_tokens": 4096,
61
+ "temperature": 0.7
62
+ },
63
+ "o3-mini": {
64
+ "provider": "pollinations",
65
+ "api_name": "openai",
66
+ "capabilities": ["test"],
67
+ "max_tokens": 2048,
68
+ "temperature": 0.7
69
+ }
70
+ }
71
+
72
+ # Task to Model mapping (Elon Musk config)
73
+ TASK_MODELS = {
74
+ "Planner": "claude-opus-4.5",
75
+ "Coder": "gpt-5.1-codex",
76
+ "Tester": "claude-opus-4.5",
77
+ "Debugger": "gpt-5.1-codex",
78
+ "Modifier": "claude-opus-4.5"
79
+ }
80
+
81
+ # API Keys
82
  API_KEYS_RAW = os.getenv('AI_API_KEYS', '')
83
+ BACKEND_API_KEY = os.getenv('BACKEND_API_KEY', secrets.token_urlsafe(32))
84
 
85
  def parse_api_keys():
 
86
  if not API_KEYS_RAW:
 
87
  return []
 
88
  if API_KEYS_RAW.startswith('['):
 
89
  try:
90
  return json.loads(API_KEYS_RAW)
91
  except:
92
  pass
 
93
  return [k.strip() for k in API_KEYS_RAW.split(',') if k.strip()]
94
 
95
  API_KEYS = parse_api_keys()
96
 
 
 
 
97
  # Pollinations URL
98
  POLLINATIONS_URL = "https://gen.pollinations.ai/v1/chat/completions"
99
 
100
  # Rate limiting
101
+ REQUEST_COUNTS = defaultdict(list)
102
  MAX_REQUESTS_PER_MINUTE = 60
103
 
104
+ # Session storage (in-memory)
105
+ SESSIONS = {}
106
+
107
  # ============================================
108
  # MODELS
109
  # ============================================
 
113
  content: str
114
 
115
  class ChatRequest(BaseModel):
116
+ session_id: str
117
+ message: str
118
+ model: Optional[str] = None # Auto-detect if None
119
+ task_type: Optional[str] = None # Planner, Coder, Tester, Debugger, Modifier
120
+ stream: Optional[bool] = True
121
+ temperature: Optional[float] = None
122
+ max_tokens: Optional[int] = None
123
+
124
+ class TaskDetectionRequest(BaseModel):
125
+ message: str
126
+ context: Optional[List[Message]] = []
 
 
127
 
128
  class HealthResponse(BaseModel):
129
  status: str
130
  timestamp: str
131
+ available_models: List[str]
132
+ task_mappings: Dict[str, str]
133
  version: str
134
 
135
  # ============================================
 
138
 
139
  class APIKeyManager:
140
  def __init__(self, keys: List[str]):
141
+ self.keys = keys if keys else [None]
142
  self.failed_keys = {}
143
  self.current_index = 0
144
  self.cooldown = 60
145
 
146
  def get_working_key(self) -> Optional[str]:
147
  if not self.keys or self.keys[0] is None:
148
+ return None
149
 
150
  now = time.time()
151
  attempts = 0
 
163
 
164
  return key
165
 
 
166
  if self.failed_keys:
167
  oldest_key = min(self.failed_keys, key=self.failed_keys.get)
168
  del self.failed_keys[oldest_key]
 
181
 
182
  key_manager = APIKeyManager(API_KEYS)
183
 
184
+ # ============================================
185
+ # TASK DETECTION
186
+ # ============================================
187
+
188
+ def detect_task_type(message: str) -> str:
189
+ """Mesajdan task type'ı otomatik tespit et"""
190
+ message_lower = message.lower()
191
+
192
+ # Keyword-based detection
193
+ if any(word in message_lower for word in ['plan', 'tasarla', 'mimari', 'architecture', 'design', 'roadmap']):
194
+ return "Planner"
195
+
196
+ elif any(word in message_lower for word in ['kod yaz', 'implement', 'create', 'build', 'develop', 'function']):
197
+ return "Coder"
198
+
199
+ elif any(word in message_lower for word in ['test', 'kontrol et', 'check', 'verify', 'validate']):
200
+ return "Tester"
201
+
202
+ elif any(word in message_lower for word in ['hata', 'bug', 'debug', 'fix', 'error', 'düzelt']):
203
+ return "Debugger"
204
+
205
+ elif any(word in message_lower for word in ['değiştir', 'modify', 'update', 'refactor', 'optimize']):
206
+ return "Modifier"
207
+
208
+ # Default: Genel sohbet için Coder
209
+ return "Coder"
210
+
211
+ # ============================================
212
+ # SESSION MANAGEMENT
213
+ # ============================================
214
+
215
+ def get_or_create_session(session_id: str) -> Dict:
216
+ """Session al veya oluştur"""
217
+ if session_id not in SESSIONS:
218
+ SESSIONS[session_id] = {
219
+ "messages": [],
220
+ "created_at": datetime.utcnow().isoformat(),
221
+ "last_activity": datetime.utcnow().isoformat(),
222
+ "metadata": {}
223
+ }
224
+
225
+ SESSIONS[session_id]["last_activity"] = datetime.utcnow().isoformat()
226
+ return SESSIONS[session_id]
227
+
228
+ def add_message_to_session(session_id: str, role: str, content: str):
229
+ """Session'a mesaj ekle"""
230
+ session = get_or_create_session(session_id)
231
+ session["messages"].append({
232
+ "role": role,
233
+ "content": content,
234
+ "timestamp": datetime.utcnow().isoformat()
235
+ })
236
+
237
  # ============================================
238
  # MIDDLEWARE
239
  # ============================================
 
254
  return token
255
 
256
  def rate_limit_check(client_id: str):
257
+ """Rate limiting"""
258
  now = time.time()
259
  minute_ago = now - 60
260
 
 
 
 
 
261
  REQUEST_COUNTS[client_id] = [
262
  req_time for req_time in REQUEST_COUNTS[client_id]
263
  if req_time > minute_ago
 
269
  REQUEST_COUNTS[client_id].append(now)
270
 
271
  # ============================================
272
+ # CORE API FUNCTIONS
273
  # ============================================
274
 
275
+ async def call_model_api(
276
+ model: str,
277
+ messages: List[Dict],
278
+ stream: bool = False,
279
+ temperature: Optional[float] = None,
280
+ max_tokens: Optional[int] = None
281
+ ) -> Any:
282
+ """Model API'sini çağır"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
 
284
+ if model not in MODEL_CONFIGS:
285
+ raise HTTPException(status_code=400, detail=f"Unknown model: {model}")
 
286
 
287
+ config = MODEL_CONFIGS[model]
288
  api_key = key_manager.get_working_key()
289
 
290
+ headers = {"Content-Type": "application/json"}
 
 
 
 
291
  if api_key:
292
  headers["Authorization"] = f"Bearer {api_key}"
293
 
 
294
  payload = {
295
+ "model": config["api_name"],
296
+ "messages": messages,
297
+ "stream": stream,
298
+ "temperature": temperature or config["temperature"],
299
+ "max_tokens": max_tokens or config["max_tokens"]
300
  }
301
 
302
+ max_retries = 3
 
303
  last_error = None
304
 
305
  for attempt in range(max_retries):
 
308
  POLLINATIONS_URL,
309
  json=payload,
310
  headers=headers,
311
+ stream=stream,
312
  timeout=120
313
  )
314
 
315
  if response.status_code == 200:
316
  key_manager.mark_success(api_key)
317
+
318
+ if stream:
319
+ return response
320
+ else:
321
+ return response.json()
322
 
323
+ elif response.status_code in [403, 429]:
 
324
  key_manager.mark_failed(api_key)
325
  api_key = key_manager.get_working_key()
326
  if api_key:
327
  headers["Authorization"] = f"Bearer {api_key}"
328
+ await asyncio.sleep(1)
329
  continue
330
 
331
  elif response.status_code == 401:
 
332
  key_manager.mark_failed(api_key)
333
  api_key = key_manager.get_working_key()
334
  if api_key:
 
336
  continue
337
 
338
  else:
339
+ last_error = f"API error: {response.status_code}"
340
+ if attempt < max_retries - 1:
341
+ await asyncio.sleep(2)
342
+ continue
343
  raise HTTPException(status_code=response.status_code, detail=last_error)
344
 
345
  except requests.exceptions.Timeout:
 
346
  if attempt < max_retries - 1:
347
+ await asyncio.sleep(2)
348
  continue
349
+ raise HTTPException(status_code=504, detail="Request timeout")
350
 
351
  except requests.exceptions.RequestException as e:
 
352
  if attempt < max_retries - 1:
353
+ await asyncio.sleep(1)
354
  continue
355
+ raise HTTPException(status_code=500, detail=f"Request failed: {str(e)}")
356
+
357
+ raise HTTPException(status_code=503, detail="All retries failed")
358
+
359
+ async def stream_generator(response) -> AsyncGenerator[str, None]:
360
+ """Stream response'u SSE formatına çevir"""
361
+ try:
362
+ for line in response.iter_lines():
363
+ if line:
364
+ line_decoded = line.decode('utf-8')
365
+
366
+ if line_decoded.startswith("data: "):
367
+ data_str = line_decoded[6:]
368
+
369
+ if data_str.strip() == "[DONE]":
370
+ yield f"data: {json.dumps({'done': True})}\n\n"
371
+ break
372
+
373
+ try:
374
+ data = json.loads(data_str)
375
+
376
+ if "choices" in data and len(data["choices"]) > 0:
377
+ delta = data["choices"][0].get("delta", {})
378
+ content = delta.get("content", "")
379
+
380
+ if content:
381
+ yield f"data: {json.dumps({'chunk': content})}\n\n"
382
+
383
+ except json.JSONDecodeError:
384
+ continue
385
+
386
+ yield f"data: {json.dumps({'done': True})}\n\n"
387
+
388
+ finally:
389
+ response.close()
390
+
391
+ # ============================================
392
+ # ROUTES
393
+ # ============================================
394
+
395
+ @app.get("/", response_model=HealthResponse)
396
+ async def health_check():
397
+ """Health check"""
398
+ return {
399
+ "status": "healthy",
400
+ "timestamp": datetime.utcnow().isoformat(),
401
+ "available_models": list(MODEL_CONFIGS.keys()),
402
+ "task_mappings": TASK_MODELS,
403
+ "version": "2.0.0"
404
+ }
405
+
406
+ @app.post("/api/chat")
407
+ async def chat(request: ChatRequest):
408
+ """
409
+ Ana chat endpoint - Task-based routing ile
410
+
411
+ Body:
412
+ {
413
+ "session_id": "sf_xxx",
414
+ "message": "Kayra için tokenizer yaz",
415
+ "model": "gpt-5.1-codex", // optional
416
+ "task_type": "Coder", // optional
417
+ "stream": true
418
+ }
419
+ """
420
+
421
+ # Rate limiting
422
+ rate_limit_check(request.session_id)
423
+
424
+ # Session'ı al/oluştur
425
+ session = get_or_create_session(request.session_id)
426
+
427
+ # User mesajını ekle
428
+ add_message_to_session(request.session_id, "user", request.message)
429
+
430
+ # Task type'ı belirle
431
+ task_type = request.task_type or detect_task_type(request.message)
432
+
433
+ # Model'i belirle
434
+ model = request.model or TASK_MODELS.get(task_type, "gpt-5.1-codex")
435
+
436
+ # Messages'ı hazırla
437
+ messages = [
438
+ {"role": msg["role"], "content": msg["content"]}
439
+ for msg in session["messages"]
440
+ ]
441
+
442
+ # System prompt ekle (task-specific)
443
+ system_prompts = {
444
+ "Planner": "Sen deneyimli bir yazılım mimarısın. Detaylı planlama ve tasarım yap.",
445
+ "Coder": "Sen expert bir kod geliştiricisisin. Temiz, okunabilir ve performanslı kod yaz.",
446
+ "Tester": "Sen titiz bir test mühendisisin. Comprehensive test senaryoları oluştur.",
447
+ "Debugger": "Sen yetenekli bir debugger'sın. Hataları kök nedenine inip çöz.",
448
+ "Modifier": "Sen dikkatli bir refactoring uzmanısın. Mevcut kodu bozmadan değiştir."
449
+ }
450
+
451
+ if task_type in system_prompts:
452
+ messages.insert(0, {"role": "system", "content": system_prompts[task_type]})
453
+
454
+ # API'yi çağır
455
+ if request.stream:
456
+ response = await call_model_api(
457
+ model=model,
458
+ messages=messages,
459
+ stream=True,
460
+ temperature=request.temperature,
461
+ max_tokens=request.max_tokens
462
+ )
463
+
464
+ return StreamingResponse(
465
+ stream_generator(response),
466
+ media_type="text/event-stream"
467
+ )
468
+ else:
469
+ result = await call_model_api(
470
+ model=model,
471
+ messages=messages,
472
+ stream=False,
473
+ temperature=request.temperature,
474
+ max_tokens=request.max_tokens
475
+ )
476
+
477
+ # Assistant yanıtını session'a ekle
478
+ assistant_message = result["choices"][0]["message"]["content"]
479
+ add_message_to_session(request.session_id, "assistant", assistant_message)
480
+
481
+ return result
482
+
483
+ @app.post("/api/detect-task")
484
+ async def detect_task(request: TaskDetectionRequest):
485
+ """Task type'ı tespit et"""
486
+ task_type = detect_task_type(request.message)
487
+ recommended_model = TASK_MODELS.get(task_type)
488
 
489
+ return {
490
+ "task_type": task_type,
491
+ "recommended_model": recommended_model,
492
+ "model_config": MODEL_CONFIGS.get(recommended_model, {})
493
+ }
494
 
495
+ @app.get("/api/session/{session_id}")
496
+ async def get_session(session_id: str):
497
+ """Session bilgilerini getir"""
498
+ if session_id not in SESSIONS:
499
+ raise HTTPException(status_code=404, detail="Session not found")
500
+
501
+ return SESSIONS[session_id]
502
+
503
+ @app.delete("/api/session/{session_id}")
504
+ async def delete_session(session_id: str):
505
+ """Session'ı sil"""
506
+ if session_id in SESSIONS:
507
+ del SESSIONS[session_id]
508
+ return {"status": "deleted"}
509
 
510
+ raise HTTPException(status_code=404, detail="Session not found")
511
+
512
+ @app.get("/api/models")
513
+ async def list_models():
514
+ """Kullanılabilir modelleri listele"""
515
+ return {
516
+ "models": MODEL_CONFIGS,
517
+ "task_mappings": TASK_MODELS
518
+ }
519
+
520
+ @app.get("/api/stats")
521
+ async def get_stats():
522
+ """İstatistikler"""
523
  return {
524
+ "total_sessions": len(SESSIONS),
525
  "total_keys": len(API_KEYS) if API_KEYS and API_KEYS[0] is not None else 0,
526
  "failed_keys": len(key_manager.failed_keys),
527
  "active_clients": len(REQUEST_COUNTS),
528
+ "requests_last_minute": sum(len(v) for v in REQUEST_COUNTS.values()),
529
+ "available_models": list(MODEL_CONFIGS.keys())
530
  }
531
 
 
 
 
 
 
532
  # ============================================
533
  # ERROR HANDLERS
534
  # ============================================
 
548
 
549
  @app.exception_handler(Exception)
550
  async def general_exception_handler(request, exc):
 
551
  print(f"Unhandled exception: {exc}")
 
552
  return JSONResponse(
553
  status_code=500,
554
  content={
 
558
  "code": 500
559
  }
560
  }
561
+ )
562
+
563
+ # ============================================
564
  # STARTUP
565
  # ============================================
566
 
567
  @app.on_event("startup")
568
  async def startup_event():
569
  print("=" * 60)
570
+ print("🚀 SixFingerDev Arena Starting...")
571
  print("=" * 60)
572
+ print(f"📦 Available Models: {', '.join(MODEL_CONFIGS.keys())}")
573
+ print(f"🎯 Task Mappings:")
574
+ for task, model in TASK_MODELS.items():
575
+ print(f" {task}: {model}")
576
+ print(f"🔑 API Keys: {len(API_KEYS) if API_KEYS and API_KEYS[0] is not None else 0}")
577
+ print(f"🔐 Backend Key: {BACKEND_API_KEY[:10]}...")
578
  print(f"⏱️ Rate Limit: {MAX_REQUESTS_PER_MINUTE} req/min")
579
  print("=" * 60)
580
+ print("✅ Arena Ready!")
581
  print("=" * 60)
582
 
583
  if __name__ == "__main__":