| import os |
| import time |
| import base64 |
| import io |
| import threading |
| from PIL import Image |
| from fastapi import FastAPI, HTTPException |
| from fastapi.middleware.cors import CORSMiddleware |
| from pydantic import BaseModel |
| from typing import Optional, List |
| from google import genai |
|
|
| app = FastAPI() |
|
|
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| raw_keys = os.getenv("GEMINI_KEYS", "") |
| KEY_POOL = [k.strip() for k in raw_keys.split(",") if k.strip()] |
|
|
| current_key_index = 0 |
| index_lock = threading.Lock() |
|
|
| def get_next_key(): |
| global current_key_index |
| with index_lock: |
| if not KEY_POOL: |
| return None, -1 |
| idx = current_key_index |
| current_key_index = (current_key_index + 1) % len(KEY_POOL) |
| return KEY_POOL[idx], idx |
|
|
| class AIRequest(BaseModel): |
| prompt: str |
| model: str = "gemini-2.5-flash" |
| image_b64: Optional[str] = None |
| images_b64: List[str] = [] |
|
|
| @app.get("/") |
| def home(): |
| return {"status": "M2W AI Hub is Online (Round-Robin + Multi-Image)", "total_keys_loaded": len(KEY_POOL)} |
|
|
| @app.post("/ask-ai") |
| def ask_gemini(req: AIRequest): |
| if not KEY_POOL: |
| raise HTTPException(status_code=500, detail="Server chưa được nạp GEMINI_KEYS!") |
|
|
| contents = [req.prompt] |
| |
| |
| if req.image_b64: |
| try: |
| contents.append(Image.open(io.BytesIO(base64.b64decode(req.image_b64)))) |
| except Exception as e: |
| return {"success": False, "error": f"Lỗi giải mã ảnh: {str(e)}"} |
| |
| |
| if req.images_b64: |
| try: |
| for b64_str in req.images_b64: |
| contents.append(Image.open(io.BytesIO(base64.b64decode(b64_str)))) |
| except Exception as e: |
| return {"success": False, "error": f"Lỗi giải mã mảng ảnh: {str(e)}"} |
|
|
| max_retries = len(KEY_POOL) + 2 |
| last_error = "" |
|
|
| for attempt in range(max_retries): |
| selected_key, key_idx = get_next_key() |
| try: |
| client = genai.Client(api_key=selected_key) |
| response = client.models.generate_content(model=req.model, contents=contents) |
| return {"success": True, "text": response.text, "key_used": f"Key_Index_{key_idx}"} |
| except Exception as e: |
| last_error = str(e) |
| err_str = last_error.lower() |
| if "400" in err_str or "invalid" in err_str or "quota" in err_str or "exhausted" in err_str: |
| continue |
| elif "429" in err_str or "overload" in err_str: |
| time.sleep(1) |
| continue |
| else: |
| continue |
|
|
| return {"success": False, "error": f"Toàn bộ Key trên Trạm trộn đều lỗi. Lỗi cuối: {last_error}"} |