""" Human+ AI Client Singleton Gemini client dengan typed config & error handling terpusat. """ import os import streamlit as st from google import genai from google.genai import types # ── Model config ───────────────────────────────────────────────────── MODEL_NAME = "gemini-2.5-flash-lite" TEMP_EXTRACTION = 0.1 # PDF extraction — deterministik TEMP_PROTOCOL = 0.4 # Protocol generation — sedikit kreatif # PENTING: 4096 terlalu kecil untuk protocol lengkap (bisa 6000-10000 token). # Naikkan ke 16000 agar semua section tidak terpotong. MAX_TOKENS_EXTRACT = 10000 MAX_TOKENS_PROTOCOL = 65536 class GeminiClientError(Exception): pass @st.cache_resource(show_spinner=False) def get_client() -> genai.Client: api_key = os.getenv("GEMINI_API_KEY") if not api_key: raise GeminiClientError( "GEMINI_API_KEY tidak ditemukan. " "Tambahkan di HuggingFace Settings → Repository Secrets." ) try: return genai.Client(api_key=api_key) except Exception as e: raise GeminiClientError(f"Gagal menginisialisasi Gemini client: {e}") from e def call_gemini( contents: list, temperature: float = TEMP_EXTRACTION, max_tokens: int = MAX_TOKENS_EXTRACT, ) -> str: try: client = get_client() response = client.models.generate_content( model=MODEL_NAME, contents=contents, config=types.GenerateContentConfig( temperature=temperature, max_output_tokens=max_tokens, ), ) return response.text except GeminiClientError: raise except Exception as e: raise GeminiClientError(f"Gemini API call gagal: {e}") from e def pdf_bytes_to_part(pdf_bytes: bytes) -> types.Part: return types.Part.from_bytes(data=pdf_bytes, mime_type="application/pdf") def check_client_health() -> tuple[bool, str]: try: get_client() return True, "✅ Gemini client siap" except GeminiClientError as e: return False, str(e)