import asyncio import json import logging import firebase_admin import httpx from fastapi import Depends, FastAPI, File, HTTPException, Request, UploadFile from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from firebase_admin import auth, credentials from string import Template # Importazioni dai moduli locali import config import prompts # --- INIT --- logging.basicConfig(level=logging.INFO) logger = logging.getLogger("monito_gateway") # Inizializzazione Firebase try: cred = credentials.Certificate(json.loads(config.FIREBASE_SERVICE_ACCOUNT_JSON)) firebase_admin.initialize_app(cred) except Exception as e: logger.error(f"Errore inizializzazione Firebase: {e}") app = FastAPI() security = HTTPBearer() client = OpenAI( api_key=config.HF_TOKEN, base_url="https://s5jfvadty4tkdlkr.eu-west-1.aws.endpoints.huggingface.cloud/v1" ) # --- UTILITY --- async def send_to_telegram(message: str): url = f"https://api.telegram.org/bot{config.TELEGRAM_BOT_TOKEN}/sendMessage" payload = {"chat_id": config.TELEGRAM_CHAT_ID, "text": f"🖥️ [Gateway]: {message}"} async with httpx.AsyncClient() as client: try: await client.post(url, json=payload, timeout=5.0) except Exception as e: logger.error(f"Telegram Log Fallito: {e}") async def verify_firebase_token(creds: HTTPAuthorizationCredentials = Depends(security)): try: return auth.verify_id_token(creds.credentials)["uid"] except Exception: raise HTTPException(status_code=401, detail="Token non valido") async def call_deepseek_with_retry(messages: list, max_retries=3): async with httpx.AsyncClient(timeout=180.0) as client: for attempt in range(max_retries): try: response = await client.post( f"{config.DEEPSEEK_BASE_URL}/chat/completions", headers={"Authorization": f"Bearer {config.DEEPSEEK_API_KEY}"}, json={ "model": "deepseek-chat", "messages": messages, "temperature": 0.1, "response_format": {"type": "json_object"} } ) response.raise_for_status() content = response.json()['choices'][0]['message']['content'] # Pulizia output cleaned = content.replace("```json", "").replace("```", "").strip() return json.loads(cleaned) except Exception as e: logger.warning(f"Tentativo {attempt+1} fallito: {e}") if attempt == max_retries - 1: raise e await asyncio.sleep(2 ** attempt) return None # --- ENDPOINTS --- @app.get("/") async def root(): return {"status": "ok", "message": "Gateway AI Attivo"} @app.post("/extract-image") async def extract_image(uid: str = Depends(verify_firebase_token), file: UploadFile = File(...)): await send_to_telegram(f"Nuova richiesta VLM da {uid}") # Leggi l'immagine e convertila in base64 image_bytes = await file.read() encoded_image = base64.b64encode(image_bytes).decode('utf-8') try: # La chiamata all'API tramite il client OpenAI response = client.chat.completions.create( model="ggml-org/Qwen2.5-VL-7B-Instruct-GGUF", messages=[ { "role": "user", "content": [ { "type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{encoded_image}"} }, { "type": "text", "text": "Describe this image in one sentence." } ] } ], max_tokens=100 ) # Ritorna il risultato return response.choices[0].message.content except Exception as e: await send_to_telegram(f"🚨 Errore VLM: {str(e)}") raise HTTPException(status_code=502, detail=str(e)) @app.post("/analyze-document") async def analyze_document(request: Request, uid: str = Depends(verify_firebase_token)): body = await request.json() messages = [{"role": "system", "content": prompts.PROMPT_ANALISI}, {"role": "user", "content": f"Analizza:\n{body['text']}"}] return await call_deepseek_with_retry(messages) @app.post("/validate-document") async def validate_document(request: Request, uid: str = Depends(verify_firebase_token)): body = await request.json() messages = [{"role": "system", "content": prompts.PROMPT_VALIDAZIONE}, {"role": "user", "content": f"Valida:\n{body['text']}"}] return await call_deepseek_with_retry(messages) @app.post("/generate-quiz") async def generate_quiz(request: Request, uid: str = Depends(verify_firebase_token)): body = await request.json() # Utilizzo di string.Template per popolare i placeholder del prompt t = Template(prompts.PROMPT_QUIZ) prompt_finale = t.substitute( targetLanguage=body.get("language", "Italiano"), count=body.get("count", 3), macro=body.get("macro", "Generico"), argomento=body.get("argomento", "Generico") ) messages = [{"role": "system", "content": prompt_finale}, {"role": "user", "content": f"Genera:\n{body['content']}"}] return await call_deepseek_with_retry(messages) @app.post("/generate-flashcards") async def generate_flashcards(request: Request, uid: str = Depends(verify_firebase_token)): body = await request.json() t = Template(prompts.PROMPT_FLASHCARD) prompt_finale = t.substitute( targetLanguage=body.get("language", "Italiano"), tag=body.get("tag", "Studio") ) messages = [{"role": "system", "content": prompt_finale}, {"role": "user", "content": f"Genera:\n{body['content']}"}] return await call_deepseek_with_retry(messages)