| import os |
| import base64 |
| from fastapi import FastAPI |
| from fastapi.responses import StreamingResponse |
| from fastapi.staticfiles import StaticFiles |
| from pydantic import BaseModel |
| from google import genai |
| from google.genai import types |
|
|
| app = FastAPI() |
|
|
| |
| API_KEYS_RAW = os.environ.get("API_KEYS", os.environ.get("GEMINI_API_KEY", "")) |
| API_KEYS =[k.strip() for k in API_KEYS_RAW.split(",") if k.strip()] |
| CURRENT_KEY_INDEX = 0 |
|
|
| clients =[] |
| for key in API_KEYS: |
| clients.append(genai.Client(api_key=key)) |
|
|
| |
| class FileData(BaseModel): |
| mime_type: str |
| data: str |
| name: str |
|
|
| class ChatRequest(BaseModel): |
| message: str |
| history: list |
| files: list[FileData] =[] |
|
|
| def get_client(): |
| global CURRENT_KEY_INDEX |
| if not clients: |
| raise ValueError("لم يتم العثور على مفاتيح API (API_KEYS) صالحة.") |
| return clients[CURRENT_KEY_INDEX] |
|
|
| @app.post("/chat") |
| async def chat_endpoint(request: ChatRequest): |
| global CURRENT_KEY_INDEX |
| |
| attempts = 0 |
| while attempts < len(clients): |
| try: |
| client = get_client() |
| |
| |
| contents =[] |
| for entry in request.history: |
| if entry.get('user'): |
| contents.append(types.Content(role="user", parts=[types.Part.from_text(text=entry['user'])])) |
| if entry.get('bot'): |
| contents.append(types.Content(role="model", parts=[types.Part.from_text(text=entry['bot'])])) |
| |
| |
| user_parts =[] |
| |
| |
| if request.files: |
| for f in request.files: |
| try: |
| file_bytes = base64.b64decode(f.data) |
| user_parts.append(types.Part.from_bytes(data=file_bytes, mime_type=f.mime_type)) |
| except Exception as e: |
| print(f"Error decoding file {f.name}: {e}") |
| |
| |
| user_parts.append(types.Part.from_text(text=request.message)) |
| |
| contents.append(types.Content(role="user", parts=user_parts)) |
| |
| |
| tools =[types.Tool(googleSearch=types.GoogleSearch())] |
| system_instruction = """انت Genisi نموذج ذكاء اصطناعي متطور من قبل AnesNT |
| AnesNT مبادرة جزائرية من ولاية باتنة صاحبها انس كامش باللاتينية Anes Kameche وهو حاليا العضو الوحيد في هذي المبادرة وهي مبادرة تكنولوجية |
| كن مفيدا وصديقا للمستخدم تحدث اللفة التي تحدث بها المستخدم""" |
|
|
| config = types.GenerateContentConfig( |
| temperature=2, |
| thinking_config=types.ThinkingConfig(thinking_level="MINIMAL"), |
| media_resolution="MEDIA_RESOLUTION_HIGH", |
| tools=tools, |
| system_instruction=[types.Part.from_text(text=system_instruction)] |
| ) |
|
|
| stream = client.models.generate_content_stream( |
| model="gemma-4-31b-it", |
| contents=contents, |
| config=config |
| ) |
|
|
| def stream_generator(): |
| try: |
| for chunk in stream: |
| if chunk.text: |
| yield chunk.text |
| except Exception as stream_err: |
| yield f"\n\n⚠️ **خطأ أثناء توليد الرد:**\n`{str(stream_err)}`" |
|
|
| return StreamingResponse(stream_generator(), media_type="text/plain") |
|
|
| except Exception as e: |
| error_msg = str(e).lower() |
| if "429" in error_msg or "quota" in error_msg: |
| CURRENT_KEY_INDEX = (CURRENT_KEY_INDEX + 1) % len(clients) |
| attempts += 1 |
| else: |
| def err_gen(): yield f"⚠️ **خطأ في السيرفر:** {str(e)}" |
| return StreamingResponse(err_gen(), media_type="text/plain") |
|
|
| def limit_gen(): yield "⚠️ تم الوصول للحد الأقصى لجميع المفاتيح المتاحة." |
| return StreamingResponse(limit_gen(), media_type="text/plain") |
|
|
| app.mount("/", StaticFiles(directory=".", html=True), name="static") |