sixfingerdev commited on
Commit
2ab3ff7
·
verified ·
1 Parent(s): ca4a657

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +207 -43
app.py CHANGED
@@ -1,7 +1,7 @@
1
  """
2
  Sixfinger Backend API - FRONTEND UYUMLU VERSİYON
3
  Ultra-fast AI Chat Backend with Multi-Model Support
4
- Supports: Groq, LLM7.io
5
  """
6
 
7
  import os
@@ -21,6 +21,9 @@ from openai import OpenAI
21
  # ========== CONFIGURATION ==========
22
  API_VERSION = "1.1.0"
23
  GROQ_API_KEY = os.getenv("GROQ_API_KEY", "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")
 
 
 
24
 
25
  # ========== API PROVIDERS ==========
26
  PROVIDERS = {
@@ -35,6 +38,13 @@ PROVIDERS = {
35
  "base_url": "https://api.llm7.io/v1",
36
  "api_key": "unused",
37
  "requires_key": False
 
 
 
 
 
 
 
38
  }
39
  }
40
 
@@ -65,6 +75,28 @@ MODELS = {
65
  "plans": ["free", "starter", "pro", "plus"],
66
  "daily_limit": 300
67
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
  # ============ STARTER PLAN MODELS ============
70
 
@@ -137,6 +169,39 @@ MODELS = {
137
  "plans": ["starter", "pro", "plus"],
138
  "daily_limit": 1000
139
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
  # ============ PRO PLAN MODELS ============
142
 
@@ -172,6 +237,14 @@ DEFAULT_MODELS = {
172
  "plus": "gpt-oss-120b"
173
  }
174
 
 
 
 
 
 
 
 
 
175
  # ========== LOGGING ==========
176
  logging.basicConfig(
177
  level=logging.INFO,
@@ -208,6 +281,12 @@ llm7_client = OpenAI(
208
  api_key=PROVIDERS["llm7"]["api_key"]
209
  )
210
 
 
 
 
 
 
 
211
  # ========== PYDANTIC MODELS ==========
212
  class ChatRequest(BaseModel):
213
  prompt: str = Field(..., description="User's message")
@@ -258,9 +337,17 @@ def build_messages(prompt: str, system_prompt: Optional[str], history: Optional[
258
  messages.append(msg)
259
 
260
  messages.append({"role": "user", "content": prompt})
261
-
262
  return messages
263
 
 
 
 
 
 
 
 
 
264
  # ========== PROVIDER-SPECIFIC API CALLS ==========
265
 
266
  def call_groq_api(
@@ -312,6 +399,36 @@ def call_llm7_api(
312
  logger.error(f"LLM7 API error: {e}")
313
  raise HTTPException(status_code=500, detail=f"LLM7 API error: {str(e)}")
314
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  def call_api(
316
  provider: str,
317
  model_id: str,
@@ -326,6 +443,8 @@ def call_api(
326
  return call_groq_api(model_id, messages, max_tokens, temperature, top_p, stream)
327
  elif provider == "llm7":
328
  return call_llm7_api(model_id, messages, max_tokens, temperature, top_p, stream)
 
 
329
  else:
330
  raise HTTPException(status_code=400, detail=f"Unknown provider: {provider}")
331
 
@@ -340,7 +459,8 @@ def health_check():
340
  "timestamp": datetime.now().isoformat(),
341
  "providers": {
342
  "groq": bool(GROQ_API_KEY),
343
- "llm7": True
 
344
  }
345
  }
346
 
@@ -371,48 +491,62 @@ def chat(
371
  request.history
372
  )
373
 
374
- try:
375
- response = call_api(
376
- provider=provider,
377
- model_id=model_id,
378
- messages=messages,
379
- max_tokens=request.max_tokens,
380
- temperature=request.temperature,
381
- top_p=request.top_p,
382
- stream=False
383
- )
384
 
385
- content = response.choices[0].message.content
386
- usage = {
387
- "prompt_tokens": getattr(response.usage, 'prompt_tokens', 0),
388
- "completion_tokens": getattr(response.usage, 'completion_tokens', 0),
389
- "total_tokens": getattr(response.usage, 'total_tokens', 0)
390
- }
391
-
392
- elapsed = time.time() - start_time
393
- logger.info(f"Chat completed: provider={provider}, tokens={usage['total_tokens']}, time={elapsed:.2f}s")
394
-
395
- return {
396
- "response": content,
397
- "model": model_id,
398
- "model_key": model_key,
399
- "model_size": model_config["size"],
400
- "model_language": model_config["language"],
401
- "provider": provider,
402
- "attempts": 1,
403
- "usage": usage,
404
- "parameters": {
405
- "max_tokens": request.max_tokens,
406
- "temperature": request.temperature,
407
- "top_p": request.top_p
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  }
409
- }
410
-
411
- except HTTPException:
412
- raise
413
- except Exception as e:
414
- logger.error(f"Chat error: {e}")
415
- raise HTTPException(status_code=500, detail=str(e))
 
 
416
 
417
  @app.post("/api/chat/stream")
418
  def chat_stream(
@@ -499,12 +633,42 @@ def chat_stream(
499
  except Exception as e:
500
  logger.error(f"LLM7 stream error: {e}")
501
  yield f"data: {json.dumps({'error': str(e)})}\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
 
503
  # Provider'a göre generator seç
504
  if provider == "groq":
505
  generator = generate_groq()
506
  elif provider == "llm7":
507
  generator = generate_llm7()
 
 
508
  else:
509
  raise HTTPException(status_code=400, detail=f"Unknown provider: {provider}")
510
 
 
1
  """
2
  Sixfinger Backend API - FRONTEND UYUMLU VERSİYON
3
  Ultra-fast AI Chat Backend with Multi-Model Support
4
+ Supports: Groq, LLM7.io, OpenRouter
5
  """
6
 
7
  import os
 
21
  # ========== CONFIGURATION ==========
22
  API_VERSION = "1.1.0"
23
  GROQ_API_KEY = os.getenv("GROQ_API_KEY", "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")
24
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY") or os.getenv("openrouter_api_key")
25
+ OPENROUTER_SITE_URL = os.getenv("OPENROUTER_SITE_URL", "https://sfapi.pythonanywhere.com")
26
+ OPENROUTER_SITE_NAME = os.getenv("OPENROUTER_SITE_NAME", "Sixfinger Backend")
27
 
28
  # ========== API PROVIDERS ==========
29
  PROVIDERS = {
 
38
  "base_url": "https://api.llm7.io/v1",
39
  "api_key": "unused",
40
  "requires_key": False
41
+ },
42
+ "openrouter": {
43
+ "name": "OpenRouter",
44
+ "type": "openai_compatible",
45
+ "base_url": "https://openrouter.ai/api/v1",
46
+ "api_key": OPENROUTER_API_KEY,
47
+ "requires_key": True
48
  }
49
  }
50
 
 
75
  "plans": ["free", "starter", "pro", "plus"],
76
  "daily_limit": 300
77
  },
78
+ "stepfun/step-3.5-flash:free": {
79
+ "provider": "openrouter",
80
+ "model_id": "stepfun/step-3.5-flash:free",
81
+ "display_name": "Step 3.5 Flash (Free)",
82
+ "size": "Unknown",
83
+ "language": "Multilingual",
84
+ "speed": "⚡⚡⚡",
85
+ "description": "OpenRouter uzerinden hizli StepFun modeli",
86
+ "plans": ["free", "starter", "pro", "plus"],
87
+ "daily_limit": 1000
88
+ },
89
+ "nvidia/nemotron-3-super-120b-a12b:free": {
90
+ "provider": "openrouter",
91
+ "model_id": "nvidia/nemotron-3-super-120b-a12b:free",
92
+ "display_name": "Nemotron 3 Super 120B A12B (Free)",
93
+ "size": "120B",
94
+ "language": "Multilingual",
95
+ "speed": "⚡⚡",
96
+ "description": "OpenRouter uzerinden NVIDIA Nemotron modeli",
97
+ "plans": ["free", "starter", "pro", "plus"],
98
+ "daily_limit": 1000
99
+ },
100
 
101
  # ============ STARTER PLAN MODELS ============
102
 
 
169
  "plans": ["starter", "pro", "plus"],
170
  "daily_limit": 1000
171
  },
172
+ "z-ai/glm-4.5-air:free": {
173
+ "provider": "openrouter",
174
+ "model_id": "z-ai/glm-4.5-air:free",
175
+ "display_name": "GLM 4.5 Air (Free)",
176
+ "size": "Unknown",
177
+ "language": "Multilingual",
178
+ "speed": "⚡⚡",
179
+ "description": "OpenRouter uzerinden GLM 4.5 Air modeli",
180
+ "plans": ["starter", "pro", "plus"],
181
+ "daily_limit": 1000
182
+ },
183
+ "qwen/qwen3-coder:free": {
184
+ "provider": "openrouter",
185
+ "model_id": "qwen/qwen3-coder:free",
186
+ "display_name": "Qwen3 Coder (Free)",
187
+ "size": "Unknown",
188
+ "language": "Multilingual",
189
+ "speed": "⚡⚡",
190
+ "description": "OpenRouter uzerinden kod odakli Qwen modeli",
191
+ "plans": ["starter", "pro", "plus"],
192
+ "daily_limit": 1000
193
+ },
194
+ "liquid/lfm-2.5-1.2b-thinking:free": {
195
+ "provider": "openrouter",
196
+ "model_id": "liquid/lfm-2.5-1.2b-thinking:free",
197
+ "display_name": "LFM 2.5 1.2B Thinking (Free)",
198
+ "size": "1.2B",
199
+ "language": "Multilingual",
200
+ "speed": "⚡⚡⚡",
201
+ "description": "OpenRouter uzerinden hizli dusunme odakli model",
202
+ "plans": ["starter", "pro", "plus"],
203
+ "daily_limit": 1000
204
+ },
205
 
206
  # ============ PRO PLAN MODELS ============
207
 
 
237
  "plus": "gpt-oss-120b"
238
  }
239
 
240
+ # Primary provider başarısız olursa kullanılacak fallback modeli
241
+ # Key: birincil provider adı, Value: fallback olarak denenecek model key'i
242
+ PROVIDER_FALLBACK = {
243
+ "groq": "gpt4-nano", # Groq hata verirse → LLM7 üzerinden gpt4-nano
244
+ "llm7": "llama-8b-instant", # LLM7 hata verirse → Groq üzerinden llama-8b-instant
245
+ "openrouter": "llama-8b-instant"
246
+ }
247
+
248
  # ========== LOGGING ==========
249
  logging.basicConfig(
250
  level=logging.INFO,
 
281
  api_key=PROVIDERS["llm7"]["api_key"]
282
  )
283
 
284
+ # OpenRouter Client
285
+ openrouter_client = OpenAI(
286
+ base_url=PROVIDERS["openrouter"]["base_url"],
287
+ api_key=PROVIDERS["openrouter"]["api_key"]
288
+ ) if OPENROUTER_API_KEY else None
289
+
290
  # ========== PYDANTIC MODELS ==========
291
  class ChatRequest(BaseModel):
292
  prompt: str = Field(..., description="User's message")
 
337
  messages.append(msg)
338
 
339
  messages.append({"role": "user", "content": prompt})
340
+
341
  return messages
342
 
343
+ def _chat_candidates(primary_key: str, primary_config: dict):
344
+ """Primary provider'ı dene, başarısız olursa fallback'i yield et."""
345
+ yield primary_key, primary_config, False
346
+
347
+ fallback_key = PROVIDER_FALLBACK.get(primary_config["provider"])
348
+ if fallback_key and fallback_key != primary_key and fallback_key in MODELS:
349
+ yield fallback_key, MODELS[fallback_key], True
350
+
351
  # ========== PROVIDER-SPECIFIC API CALLS ==========
352
 
353
  def call_groq_api(
 
399
  logger.error(f"LLM7 API error: {e}")
400
  raise HTTPException(status_code=500, detail=f"LLM7 API error: {str(e)}")
401
 
402
+ def call_openrouter_api(
403
+ model_id: str,
404
+ messages: list,
405
+ max_tokens: int,
406
+ temperature: float,
407
+ top_p: float,
408
+ stream: bool = False
409
+ ):
410
+ """OpenRouter API'ye istek at"""
411
+ if not openrouter_client:
412
+ raise HTTPException(status_code=500, detail="OpenRouter API key not configured")
413
+
414
+ try:
415
+ response = openrouter_client.chat.completions.create(
416
+ model=model_id,
417
+ messages=messages,
418
+ max_tokens=max_tokens,
419
+ temperature=temperature,
420
+ top_p=top_p,
421
+ stream=stream,
422
+ extra_headers={
423
+ "HTTP-Referer": OPENROUTER_SITE_URL,
424
+ "X-OpenRouter-Title": OPENROUTER_SITE_NAME
425
+ }
426
+ )
427
+ return response
428
+ except Exception as e:
429
+ logger.error(f"OpenRouter API error: {e}")
430
+ raise HTTPException(status_code=500, detail=f"OpenRouter API error: {str(e)}")
431
+
432
  def call_api(
433
  provider: str,
434
  model_id: str,
 
443
  return call_groq_api(model_id, messages, max_tokens, temperature, top_p, stream)
444
  elif provider == "llm7":
445
  return call_llm7_api(model_id, messages, max_tokens, temperature, top_p, stream)
446
+ elif provider == "openrouter":
447
+ return call_openrouter_api(model_id, messages, max_tokens, temperature, top_p, stream)
448
  else:
449
  raise HTTPException(status_code=400, detail=f"Unknown provider: {provider}")
450
 
 
459
  "timestamp": datetime.now().isoformat(),
460
  "providers": {
461
  "groq": bool(GROQ_API_KEY),
462
+ "llm7": True,
463
+ "openrouter": bool(OPENROUTER_API_KEY)
464
  }
465
  }
466
 
 
491
  request.history
492
  )
493
 
494
+ attempts = 0
495
+ last_error = None
 
 
 
 
 
 
 
 
496
 
497
+ for attempt_model_key, attempt_model_config, is_fallback in _chat_candidates(model_key, model_config):
498
+ attempts += 1
499
+ attempt_provider = attempt_model_config["provider"]
500
+ attempt_model_id = attempt_model_config["model_id"]
501
+
502
+ if is_fallback:
503
+ logger.warning(f"Primary provider failed, retrying with fallback: {attempt_model_key} via {attempt_provider}")
504
+
505
+ try:
506
+ response = call_api(
507
+ provider=attempt_provider,
508
+ model_id=attempt_model_id,
509
+ messages=messages,
510
+ max_tokens=request.max_tokens,
511
+ temperature=request.temperature,
512
+ top_p=request.top_p,
513
+ stream=False
514
+ )
515
+
516
+ content = response.choices[0].message.content
517
+ usage = {
518
+ "prompt_tokens": getattr(response.usage, 'prompt_tokens', 0),
519
+ "completion_tokens": getattr(response.usage, 'completion_tokens', 0),
520
+ "total_tokens": getattr(response.usage, 'total_tokens', 0)
521
+ }
522
+
523
+ elapsed = time.time() - start_time
524
+ logger.info(f"Chat completed: provider={attempt_provider}, model={attempt_model_key}, tokens={usage['total_tokens']}, time={elapsed:.2f}s, attempts={attempts}")
525
+
526
+ return {
527
+ "response": content,
528
+ "model": attempt_model_id,
529
+ "model_key": attempt_model_key,
530
+ "model_size": attempt_model_config["size"],
531
+ "model_language": attempt_model_config["language"],
532
+ "fallback_used": is_fallback,
533
+ "attempts": attempts,
534
+ "usage": usage,
535
+ "parameters": {
536
+ "max_tokens": request.max_tokens,
537
+ "temperature": request.temperature,
538
+ "top_p": request.top_p
539
+ }
540
  }
541
+
542
+ except HTTPException as e:
543
+ last_error = e
544
+ logger.error(f"Provider {attempt_provider} failed (attempt {attempts}): {e.detail}")
545
+ except Exception as e:
546
+ last_error = HTTPException(status_code=500, detail=str(e))
547
+ logger.error(f"Provider {attempt_provider} error (attempt {attempts}): {e}")
548
+
549
+ raise last_error or HTTPException(status_code=500, detail="All providers failed")
550
 
551
  @app.post("/api/chat/stream")
552
  def chat_stream(
 
633
  except Exception as e:
634
  logger.error(f"LLM7 stream error: {e}")
635
  yield f"data: {json.dumps({'error': str(e)})}\n\n"
636
+
637
+ def generate_openrouter():
638
+ """OpenRouter streaming generator"""
639
+ try:
640
+ yield f"data: {json.dumps({'info': f'Using {model_key} via OpenRouter'})}\n\n"
641
+
642
+ stream = call_openrouter_api(
643
+ model_id=model_id,
644
+ messages=messages,
645
+ max_tokens=request.max_tokens,
646
+ temperature=request.temperature,
647
+ top_p=request.top_p,
648
+ stream=True
649
+ )
650
+
651
+ total_completion_tokens = 0
652
+
653
+ for chunk in stream:
654
+ if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content:
655
+ text = chunk.choices[0].delta.content
656
+ yield f"data: {json.dumps({'text': text})}\n\n"
657
+ total_completion_tokens += 1
658
+
659
+ yield f"data: {json.dumps({'done': True, 'model_key': model_key, 'provider': 'openrouter', 'attempts': 1, 'usage': {'prompt_tokens': 0, 'completion_tokens': total_completion_tokens, 'total_tokens': total_completion_tokens}})}\n\n"
660
+
661
+ except Exception as e:
662
+ logger.error(f"OpenRouter stream error: {e}")
663
+ yield f"data: {json.dumps({'error': str(e)})}\n\n"
664
 
665
  # Provider'a göre generator seç
666
  if provider == "groq":
667
  generator = generate_groq()
668
  elif provider == "llm7":
669
  generator = generate_llm7()
670
+ elif provider == "openrouter":
671
+ generator = generate_openrouter()
672
  else:
673
  raise HTTPException(status_code=400, detail=f"Unknown provider: {provider}")
674