import asyncio import os import logging import random import time import re from app.config import GEMINI_API_KEY, GEMINI_MODEL try: import google.genai as genai try: from google.genai import errors as genai_errors except Exception: genai_errors = None except Exception: genai = None genai_errors = None logging.warning("[summary_service] google.genai module not found; summary generation disabled") try: from google.api_core.exceptions import GoogleAPIError except Exception: GoogleAPIError = Exception gemini_client = None if not genai: logging.warning("[summary_service] google.genai not available, summary generation will be disabled") elif not GEMINI_API_KEY: logging.warning("[summary_service] GEMINI_API_KEY is not set, summary generation will be disabled") else: try: gemini_client = genai.Client(api_key=GEMINI_API_KEY) logging.info(f"[summary_service] Initialized google.genai client with model={GEMINI_MODEL}") except Exception as e: logging.exception(f"[summary_service] Failed to init google.genai client: {e}") gemini_client = None async def generate_summary(text: str) -> str: if not gemini_client: return "" if not text: return "" prompt = f""" Bạn là chuyên gia tóm tắt. Hãy tóm tắt văn bản sau thành một đoạn văn duy nhất. Yêu cầu: 1. Viết khoảng 3-5 câu, tổng hợp đầy đủ chủ đề và các ý chính. 2. Viết liền mạch, KHÔNG xuống dòng, KHÔNG dùng gạch đầu dòng hay đánh số. 3. Chỉ dựa trên thông tin được cung cấp, tuyệt đối KHÔNG tự thêm thông tin bên ngoài. 4. Trả về VĂN BẢN THUẦN (plain text), không bọc trong ``` hoặc JSON. Văn bản: \"\"\"{text}\"\"\" """ loop = asyncio.get_event_loop() MAX_RETRIES = 3 BASE_DELAY = 1.0 def call(): last_exc = None for attempt in range(1, MAX_RETRIES + 1): try: resp = gemini_client.models.generate_content( model=GEMINI_MODEL, contents=prompt, ) return (resp.text or "").strip() except Exception as e: last_exc = e is_server_error = False try: if genai_errors and isinstance(e, genai_errors.ServerError): is_server_error = True except Exception: is_server_error = False msg = str(e) if "503" in msg or "UNAVAILABLE" in msg or is_server_error: if attempt < MAX_RETRIES: delay = BASE_DELAY * (2 ** (attempt - 1)) # add jitter delay = delay + random.uniform(0, 0.5 * delay) logging.warning(f"[summary_service] model overloaded (attempt {attempt}/{MAX_RETRIES}), retrying after {delay:.2f}s") time.sleep(delay) continue # non-retryable or out of retries logging.exception(f"[summary_service] generate_summary call failed on attempt {attempt}: {e}") break # propagate last exception to outer handler if last_exc: raise last_exc return "" try: result = await loop.run_in_executor(None, call) result = result.replace("```", "").strip() if result: return result except GoogleAPIError as e: logging.error(f"[summary_service] Gemini API error: {e}") except Exception as e: logging.exception(f"[summary_service] generate_summary failed: {e}") # fallback: return a very small extracted summary (first 1-2 sentences) or empty try: sentences = re.split(r"(?<=[.!?])\s+", text.strip()) if not sentences: return "" fallback = " ".join(sentences[:2]).strip() # keep fallback reasonably short if len(fallback) > 400: fallback = fallback[:400].rsplit(" ", 1)[0] + "..." logging.info("[summary_service] Returning fallback summary after errors") return fallback except Exception: return ""